I am trying to understand how sorting works in GridView by means of generating a default CRUD application. Sorting happens after clicking the respective attribute which is the table header. The column name is attached to the url with the variable sort and on click the action method is invoked, but what I am wondering is that the action method which is mentioned in the url with the actual variable $sort is not present in the controller.
Below is a example
The url looks like the below,
/advanced/frontend/web/index.php?r=site%2Findex&sort=customer_user_name2
But there is no corresponding action method in the site controller as
function actionIndex($sort);
Consider following example
We have Passenger CRUD
Consider Passenger - a model and PassengerSearch - its corresponding search model
Passenger attribute are id, name and country_name
In PassengerController.php
<?php
...
public function actionIndex()
{
$searchModel = new PassengerSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
return $this->render('index', [
'searchModel' => $searchModel,
'dataProvider' => $dataProvider,
]);
}
...
?>
Search string a in name column
and sort id column in desc order
Look at the url generated, it would be like
`http://localhost/YII2proj/passenger/index?PassengerSearch%5Bid%5D=&PassengerSearch%5Bname%5D=a&PassengerSearch%5Bcountry_name%5D=&sort=-id`
In human readable format
http://localhost/YII2proj/passenger/index?
PassengerSearch[id]=&
PassengerSearch[name]=a&
PassengerSearch[country_name]=&
sort=-id
Observe PassengerSearch is a array. Please refer - How to send a array in url request?
Focus on the parameter to search() method of object PassengerSearch class it is Yii::$app->request->queryParams
Before
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
line, insert following code
echo '<pre>';
print_r(Yii::$app->request->queryParams);
echo '</pre>';
die;
and see the result
Array
(
[PassengerSearch] => Array
(
[id] =>
[name] => a
[country_name] =>
)
[sort] => -id
)
If no searching sorting done, then this array will be empty
So this line does your code like following
<?php
if(isset($_GET['sort']) || isset($_GET['PassengerSearch']['id'] ...)
{
// validate it because what if I changed url like
// `http://localhost/YII2proj/passenger/index?PassengerSearcasdh[dfsid]=&PassengerSearch[name]=a&PassengerSearch[country_name]=&sorsfdft=-idadlashdfhls`
// ie some scrap value
// If error returned from validate function, default sorting is done, and malformed search param is not set
// if success, fill up that array
}
?>
Then in search Model load() is the hero.
Where and how above code is done? - Refer Model and load()
Searching is done via andFilterWhere after loading ie load() and that you can see in search() method
Hope you got the idea behind searching and sorting.
Related
In my app, after a jobseeker reviews for an exam, a new jobseekerExamReview instance is created and stored to the $examReview variable to store their score. They are then redirected to a new page that displays the results of said exam review, so I passed $examReview as a parameter to the results route. The id gets displayed in the link and works, but the content of the exam won't display as values are apparently null. I checked all the columns of the new record and they all have data. When I finally performed dd on $examReview it returns empty.
Value of $examReview (Controller):
// create new Exam review session
$examReview = jobseekerExamReview::create([
'jobseeker_id' => $jobseeker->id,
'exam_id' => $exam_id
//upon creation 'results' are set to 0 at default and updated later after score calculation (see full code)
]);
Redirect in Controller:
// return view to review results with answered and correct
return redirect()->route('showResults', [$examReview]);
web.php
Route::get('/exam/reviewExam/results/{jobseekerExamReview:id}', [reviewExamController::class, 'showResults'])
->middleware(['auth','verified'])
->name('showResults');
Full code in controller:
public function calculateResult()
{
// get needed details
$jobseeker = Jobseeker::findorFail(auth()->user()->id);
$exam_id = request()->input('exam_id');
$jobApplication = jobApplication::find(session('jobApplicationId'));
$correctAnswers = 0;
// create new Exam review session
$examReview = jobseekerExamReview::create([
'jobseeker_id' => $jobseeker->id,
'exam_id' => $exam_id
]);
// loop through each question taken by user
foreach(request()->input('taken_questions') as $key => $question_id){
// is answer correct or not
// echo "<br>Question ID: ".$question_id;
$status = 0;
//answer[questionID] => answer_id
// if the answer[questionID] is not emptu and matched answer ID is correct
// echo "<br>Answer ID:".request()->input('answer.'.$question_id);
if((request()->input('answer.'.$question_id) != null) && (examAnswer::find(request()->input('answer.'.$question_id))->isCorrect == true)){
// answer is correct
// echo "is Correct";
$status = 1;
$correctAnswers++;
// create review Exam Answers
jobseekerExamReviewAnswer::create([
'exam_review_id' => $examReview->id,
'question_id' => $question_id,
'answer_id' => request()->input('answer.'.$question_id),
'isCorrect' => $status
]);
}
}
// calculate score
$percentScore = $correctAnswers/count(request()->input('taken_questions'))*100;
// update score in review
$examReview->update(['result' => $percentScore]);
// return view to review results with answered and correct
return redirect()->route('showResults',[$examReview]);
}
public function showResults(jobseekerExamReview $examReview)
{
return view('exams.exam-review-results',[
'examReview' => $examReview
])->with('reviewExamAnswers');
}
You have to have the name of the typehinted variable in your method signature (route action) matching the name of the route parameter in the route definition. Your route parameter is named jobseekerExamReview but the parameter to your Controller method is named jobseeker, which does not match. Because of this you are getting a new non existing instance of jobseekerExamReview injected (Dependency Injection) instead of Route Model Binding. You should adjust your route parameter to match:
Route::get('/exam/reviewExam/results/{examReview:id}', ...);
This would cause Implicit Route Model Binding to happen instead of Dependency Injection.
"Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name."
Laravel 8.x Docs - Routing - Route Model Binding - Implicit Binding
As a side note, you should get in the habit of using an associative array when passing parameters to the URL helpers, like route, so it knows exactly what parameters you are passing:
return redirect()->route('showResults', ['examReview' => $examReview]);
Hi I got trouble in retrieve URL segment CAkephp3 in view. I want to get the ID from current URL.
Lets say my URL is http://localhost/admin/financial_agreements/edit/50
and I want redirect to http://localhost/admin/financial_agreements/do_print/50
simply :
var urlPrint = "<?=$this->Url->build(['controller' => 'financial_agreements', 'action' => 'do_print', 'I NEED ID FROM CURRENT'], true)?>";
I try debug
<?=debug($this->Url->build()); die();?>
But its produce : admin/financial_agreements/edit/50
whats called in 50 ? I need that 50 inside my "url->build" urlPrint
sorry for bad english.
Anyhelp will appreciate.
thanks.
You can use the Request object to get request data (including url parameters) within views.
Try this in your view:
$this->request->getParam('pass') //CakePHP 3.4+
$this->request->params['pass'] // CakePHP 3.3
That will return an array of all non-named parameters that were passed after the action's name in the URL. Example: /mycontroller/myaction/param1/param2. So in this example, $this->request->getParam('pass') will produce an array like: [0 => 'param1', 1 => 'param2'].
Bonus answer: you can also 'name' parameters in the URL, like: /mycontroller/myaction/some_name:some_value. To retrieve this kind of named parameters, you would do the same trick but using: $this->request->getParam('named') (Use the argument 'named' instead of 'pass').
More info:
https://book.cakephp.org/3.0/en/controllers/request-response.html
https://book.cakephp.org/3.0/en/development/routing.html#passed-arguments
Assuming that your edit function follows standard practices, you'll have something like this:
public function edit($id) {
$financialAgreement = $this->FinancialAgreements->get($id);
...
$this->set(compact('financialAgreement'));
}
Then in edit.ctp, you can get the id of the current record very simply as $financialAgreement->id, so your URL will be generated with
$this->Url->build(['controller' => 'financial_agreements', 'action' => 'do_print', $financialAgreement->id], true)
I've got problem with field validation.
I would like to validate form through model. I want to check if field with some value exists.
I would like to block using some titles more than once.
For example
if field "Site" with title "Main" exists in database, you can't validate form.
If it doesn't exist, you can pass it.
I would like to allow user to add just one "Site" with title "Main", but he can add "Site" with any other title in any case.
Have you got some idea how to solve it?
I think you have two options.
(1) Setup an Ajax request to the server.
To do so:
Create a function, that responds to an Ajax request, in your SiteController named checkName()
public function checkName($name) {
// allow ajax requests
$this->request->allowMethod(['ajax']);
// perform your check within the db
$isExistent = [...];
// prepare the response
$response = ['name' => $name, 'isExistent' => $isExistent];
if ($this->request->isAjax()){
$this->autoRender = false;
$this->response->disableCache();
$this->response->type(['json' => 'application/json']);
$this->response->body(json_encode($response));
}
}
Add the route to your routes file with the option '_ext' => 'json'
Prepare your Javascript Ajax function that call the route you have defined and attach it on the onchange attribute of your input field. (see this link for a simple example: http://www.w3schools.com/jquery/ajax_ajax.asp)
(2) Make the 'name' field of the Site table unique.
To do so you could add the following function to your SiteTable class
public function buildRules(
RulesChecker $rules
) {
$rules->add($rules->isUnique(['name']));
return $rules;
}
In my form, I created the value by populating the dropbox from values from a table.
<?php echo $form->dropDownList($model,'status', CHtml::listData(Statusprospect::model()->findAll(), 'id', 'status'),array('prompt' => 'Select')); ?>
When I view the record it has a 1, as it should for status. How do I make it display the value when the record is viewed, instead of the 1.
The view file code that currently displays the field is this:
<?php echo CHtml::encode($data->status); ?>
The Model does have the relationship defined:
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'status0' => array(self::BELONGS_TO, 'Statusprospect', 'status'),
);
}
How would I accomplish showing the value instead of the number?
Right now this should work $data->status0->status.
Take care that $data->status0->status might not be set if $data->status can be null so make a check beforehand if that is the case. You can use
CHtml::encode(isset($data->status0->status) ? $data->status0->status : '-');
I have the following link structure for my portfolio:
<?php echo $this->Html->link($post['Portfolio']['title'], array('controller' => 'portfolio', 'action' => 'view', Inflector::slug($post['Portfolio']['title'])), array('title' => $post['Portfolio']['title'])); ?>
Which gives urls like: http://driz.co.uk/portfolio/view/Paperview_Magazine
However how do I get my controller to show the item based on the title?
So far I have this but have not been able to get it to work and just get a blank page (so I ALSO need to check the format is correct and that their is a relevant item)
function view ( $title )
{
$posts = $this->Portfolio->find('first', array('conditions' => array('Portfolio.title' => $title)
));
if (empty($title))
{
$this->cakeError('error404');
}
$this->set(compact('posts'));
}
#Ross suggested that you search using Portfolio.slug so here's how you could do this:
Add a field to your database table called slug. You'll most likely want a VARCHAR with sufficient length to accommodate the slug.
When you create or update a "Portfolio" record, use the Inflector::slug method to generate a slug and save it to your database. You could always do this in the model's beforeSave event or if you prefer, in the controller before saving the data.
Update the find call to look for Portfolio.slug instead of Portfolio.title.
Unfortunately, there's no way to reverse the Inflector::Slug function as it removes certain characters like apostrophes, quotes, parentheses, etc. which is why you need to save the slug to your database if you want to search for it.
Here's how you could use the beforeSave event in your model:
public function beforeSave(array $options = array())
{
// If the title is not empty, create/update the slug.
if ( ! empty($this->data[$this->alias]['title'] )
$this->data[$this->alias]['slug'] = Inflector::slug($this->data[$this->alias]['title']);
// Returning true is important otherwise the save or saveAll call will fail.
return true;
}