I have a question about how I can search, for example, by name or surname in the INDEX view.
Is there any plugin or easy-to-use component that does not load the application too much?
Currently, I have a small form in index.ctp with one input field, where I enter what I want to search, and in controller have an if ($ this-> request-> is ('post')) after which to $this->Model->find adds condition WHERE. But I admit that this is not a nice way, it also reload the page.
This is the controller:
public function index()
{
if ($this->request->is('post')) {
$s = '%'.$this->request->getData('Search').'%';
$students = $this->Students->find('all')->where(['OR' => ['name LIKE' => $s, 'lastname LIKE' => $s]]);
$this->set('students', $this->paginate($students));
} else {
$students = $this->paginate($this->Students);
$this->set(compact('students'));
}
}
And this is the index.ctp:
<div class="input-group mb-3">
<?= $this->Form->button(__('<i class="fas fa-search"></i>'), ['escape' => false, 'class' => 'btn btn-primary']) ?>
<?= $this->Form->input('Search', ['type' => 'text', 'class' => 'form-control', 'label' => false]); ?>
<?= $this->Form->end() ?>
</div>
From what I can see you are either after a better solution to handle searching and/or a way of not reloading the page to display your search results.
If this isn't what you are after then please clarify your question to better outline what you would like you solution to look like.
You should be separating your searching out of your index function as that is only to display the content on that page. Adding conditions like you have will end up in you having a really long index function running your whole application based on the differences in the request, which isn't great.
Split out the searching into a separate function which will in turn create a new page for you to display your results.
public function index()
{
$students = $this->paginate($this->Students);
$this->set(compact('students'));
}
public function search()
{
$s = '%' . $this->request->getData('Search') . '%';
$students = $this->Students->find('all')->where(['OR' => ['name LIKE' => $s, 'lastname LIKE' => $s]]);
$this->set('students', $this->paginate($students));
}
This is well described in the tutorial on the CakePHP docs - CMS Tutorial - Creating the Articles Controller and can be related to your application as the index for you contains a form to pass search criteria to your application and the search function/page will then fetch the results and display them for you.
Don't forget to then alter your form to point it to the /search page. - Setting a URL for the Form
You will need to create a search.ctp file in your src/Template/ExampleController folder. Put your html code in there to display the results in a table or however you want to display the search results.
Finally you will need to add a route to your routes.php to allow the /search path in your application.
$routes->connect('/search', ['controller' => 'Example', 'action' => 'search']);
Now if you don't want the page to reload when you use your form then you have to use Ajax and JS/JQuery to make the request to your search method and display the results of the search function on the page dynamically. There are already a lot of good examples of this on stackoverflow and the web for using ajax and jquery for building tables from searches so I won't bother posting them here.
If you want a plugin/library solution then look at this tutorial on having search results displayed in a table using DataTables. - Search Using Datatables
install plugin https://github.com/FriendsOfCake/search
then in search configurations
$this->searchManager()
// Here we will alias the 'q' query param to search the `Students.name`
// field and the `Students.lastname` field, using a LIKE match, with `%`
// both before and after.
->add('q', 'Search.Like', [
'before' => true,
'after' => true,
'fieldMode' => 'OR',
'comparison' => 'LIKE',
'wildcardAny' => '*',
'wildcardOne' => '?',
'field' => ['name', 'lastname']
]);
Related
I have been trying to get cakephp to suggest input from data that is from my tables like autocomplete. I've done some reading about how some other people have done this but still can't figure it out. Currently it seems that every time my controller is waiting for an ajax request and it is always false. No errors come up from the console some i'm not sure what i'm doing wrong. I tried removing the if ($this->request->is('ajax')) statement but then I get a error about it cannot emit headers.
Here is my search function in InvoicesController which I have taken code from someone else example but failed to implement it.
public function search()
{
if ($this->request->is('ajax')) {
$this->autoRender = false;
pr('b');
$name = $this->request->query['term'];
$results = $this->Invoices->find('all', [
'conditions' => [ 'OR' => [
'id LIKE' => $id . '%',
]]
]);
$resultsArr = [];
foreach ($results as $result) {
$resultsArr[] =['label' => $result['full_name'], 'value' => $result['id']];
}
echo json_encode($resultsArr);
}
}
And here is my search.ctp
<?php use Cake\Routing\Router; ?>
<?php echo $this->Form->input('id', ['type' => 'text']);?>
<script>
jQuery('#id').autocomplete({
source:'<?php echo Router::url(array('controller' => 'Invoices', 'action' => 'search')); ?>',
minLength: 1
});
</script>
This is my invoice table and the ids are what I want to be suggested from what users type in.
I may not be seeing your exact problem but let me point out a few things I see that might help this issue.
Remove this line. It is not necessary
$this->autoRender = false;
Instead you should be doing this at the end. See using the RequestHandler
$this->set('resultsArr', $resultsArr);
// This line is what handles converting your array into json
// To get this to work you must load the request handler
$this->set('_serialize', 'resultsArr');
This will return the data without a root key
[
{"label":"Label Value"},
{"label":"Another Label Value"}
]
Or you can do it like this
$this->set('_serialize', ['resultsArr']);
This will return data like
{"resultArr":[
{"label":"Label Value"},
{"label":"Another Value"}
]}
Replace your finder query with this.
$resultArr = $this->Invoices->find('all')
->where(['id LIKE' => $id . '%'])
// If you want to remap your data use map
// All queries are collections
->map(function ($invoice) {
return ['label' => $invoice->full_name, 'id' => $invoice->id];
});
It seems to me you might want to review the new cakephp 3 orm. A lot of hard work went into writing these docs so that they could be easily read and relevant. I'm not one to push docs on people but it will save you hours of frustration.
Cakephp 3 ORM documentation
A few minor things I noticed that are also problems.
You never define $id.
You define $name but never use it.
pr is a debug statement and I am not sure why you have it.
Based on your comment, here is an update on ajax detection.
// By default the ajax detection is limited to the x-request-with header
// I didn't want to have to set that for every ajax request
// So I overrode that with the accepts header.
// Any request where Accept is application/json the system will assume it is an ajax request
$this->request->addDetector('ajax', function ($request) {
$acceptHeaders = explode(',', $request->env('HTTP_ACCEPT'));
return in_array('application/json', $acceptHeaders);
});
I am using cakePHP 2.5 and I want to access $this->Paginator->hasNext() in controller, but it through exception.
$type = array('featured', 'newest', 'trending');
foreach($type as $each)
{
$conditions = array('Product.status'=>'1', "is_$each" => '1');
$this->Product->recursive = 1;
//retrieve and set final result set
$this->paginate = array(
'fields' => array('Product.*'),
'conditions' => $conditions,
'limit' => $page_limit,
'page' => $page,
'order' => "$order_by $order_by_sort",
);
$products[$each] = $this->paginate('Product');
}
On my page, I want to display 3 type of product featured/trending/newest. Initially I load first page by default, then when user scroll down then I will call ajax and append next page like wise. But if last page is reached then I want to stop ajax call (because unknown page through 404 not found error).
To overcome that, I prevent AJAX call for unknown pages. And also make sure that first page isn't last!!
Hope my details make sense!
I could not find any answer on any forum/document. Please guide me or point me if it's duplicate of any.
You don't need to (and cannot*) access a helper in the controller.
Take a look at the source code for hasNext which is just calling the helper function _hasPage, which is simply checking the parameters array.
The paginator parameters are all available in the controller:
$paging = array(
'page' => $page,
'current' => count($results),
'count' => $count,
'prevPage' => ($page > 1),
'nextPage' => ($count > ($page * $limit)),
'pageCount' => $pageCount,
'order' => $order,
'limit' => $limit,
'options' => Hash::diff($options, $defaults),
'paramType' => $options['paramType']
);
So from the code in the question:
$this->request['paging']['Product']['nextPage']
Is the information you're looking for (it will be overwritten each time you call paginate.
Consider your application design.
Unless you have exactly the same number of featured, newest and trending records - it'd be better to have one ajax request for each type. Also see this related question, for more information on implementing infinite-scroll type pages.
* Almost anything is possible, you certainly should not need to use a helper in a controller though.
MVC Violation
$this->Paginator->hasNext()
That's a method of the helper. Helpers are not thought to be used in controllers because this is a violation of the MVC pattern and bad practice. If you think you have to you're wrong: Fix your architecture, it's broken by design.
Proper solution
Return the status of the pagination along with the data in the AJAX response:
$this->set('pagination', $this->request->paging['Product']);
$this->set('products', $result);
$this->set('_serialize', ['products', 'pagination']);
You can change the state of your view depending on the data then:
if (pagination.Product.nextPage == true) { /*... */ }
So I have this part in my View:
<body>
<div id = "content">
<?php echo $catalog ?>
</div>
</body>
There are also other variables in it. Here is the part of my Controller where I send them to the View:
$this->load->view('layout',array(
'categories' => $categories,
'home_menu' => $home_menu,
'information' => $information,
'favourite' => $favourite,
'new_products' => $new_products,
'bestsellers' => $bestsellers,
'login_info' => $login_info,
'catalog' => ''
));
I want to create second controller, which when activated sends a second view to the variable $catalog.
Something like this (similar to Kohana):
$this->layout->catalog = $this->load->view('products/catalog', array(
'name' => $name,
'description' => $description));
But it's not working.
My question is, how can I show this second nested view after clicking on a link that activates the second Controller?
EDIT:
But I want to send the catalog view to $catalog variable after the user has clicked on a link that activates second controller, which look something like this:
$products = $this->Product_model->list_products($category_id);
foreach ($products as $row)
{
$name = $row->name;
$description = $row->description;
}
.. after that I want $name and $description to be passed to:
$this->load->view('products/catalog', array(
'name' => $name,
'description' => $description));
..which itself to be passed to $catalog in the layout view defined in the first controller
You can call a $this->load->view within the view's code but I would not recommend it.
Instead pass true as the 3rd parameter in the load view function and this will return the view rather than echo it straight out. Then you can assign that returned code to your original view.
I'm hoping I'm understanding your question fully, but if not, I apologize.
My guess is that you're loading the page with all of the 'extras' and want to be able to update the 'content' part of your page through a user initiated click.
If you're implementing a javascript based solution, then you just need a controller that will output the html fragment and inject that into the current page via an ajax call.
If you're not implementing javascript, then it would be an entire page refresh, so you would just rebuild the page and pass the selected catalog content to the controller.
UPDATE
To do this without ajax or hmvc, you need to get the contents from another controller into this controller, so you could just make an additional request with php:
$catalog_content = file_get_contents('/url_to_second_controller.html');
$this->load->view('layout',array(
'categories' => $categories,
'home_menu' => $home_menu,
'information' => $information,
'favourite' => $favourite,
'new_products' => $new_products,
'bestsellers' => $bestsellers,
'login_info' => $login_info,
'catalog' => $catalog_content
));
I'm new to cakephp...and I have a page with a url this:
http://localhost/books/filteredByAuthor/John-Doe
so the controller is ´books´, the action is ´filteredByAuthor´ and ´John-Doe´ is a parameter.. but the url looks ugly so i've added a Route like this:
Router::connect('/author/:name', array( 'controller' => 'books','action' => 'filteredByAuthor'), array('pass'=>array('name'),'name'=>".*"));
and now my link is:
http://localhost/author/John-Doe
the problem is that the view has a paginator and when i change the page (by clicking on the next or prev button).. the paginator won't consider my routing... and will change the url to this
http://localhost/books/filteredByAuthor/John-Doe/page:2
the code on my view is just:
<?php echo $this->Paginator->prev('<< ' . __('previous', true), array(), null, array('class'=>'disabled'));?>
the documentation doesn't say anything about avoiding this and i've spent hours reading the paginators source code and api.. and in the end i just want my links to be something like this: (with the sort and direction included on the url)
http://localhost/author/John-Doe/1/name/asc
Is it possible to do that and how?
hate to answer my own question... but this might save some time to another developper =) (is all about getting good karma)
i found out that you can pass an "options" array to the paginator, and inside that array you can specify the url (an array of: controller, action and parameters) that the paginator will use to create the links.. so you have to write all the possible routes in the routes.php file. Basically there are 3 possibilities:
when the "page" is not defined
For example:
http://localhost/author/John-Doe
the paginator will assume that the it's the first page. The corresponding route would be:
Router::connect('/author/:name', array( 'controller' => 'books','action' => 'filteredByAuthor'),array('pass'=>array('name'),'name'=>'[a-zA-Z\-]+'));
when the "page" is defined
For example:
http://localhost/author/John-Doe/3 (page 3)
The route would be:
Router::connect('/author/:name/:page', array( 'controller' => 'books','action' => 'filteredByAuthor'),array('pass'=>array('name','page'),'name'=>'[a-zA-Z\-]+','page'=>'[0-9]+'));
finally when the page and the sort is defined on the url (by clicking on the sort links created by the paginator).
For example:
http://localhost/author/John-Doe/3/title/desc (John Doe's books ordered desc by title)
The route is:
Router::connect('/author/:name/:page/:sort/:direction', array( 'controller' => 'books','action' => 'filteredByAuthor'),
array('pass'=>array('name','page','sort','direction'),
'name'=>"[a-zA-Z\-]+",
'page'=>'[0-9]*',
'sort'=>'[a-zA-Z\.]+',
'direction'=>'[a-z]+',
));
on the view you have to unset the url created by the paginator, cause you'll specify your own url array on the controller:
Controller:
function filteredByAuthor($name = null,$page = null , $sort = null , $direction = null){
$option_url = array('controller'=>'books','action'=>'filteredByAuthor','name'=>$name);
if($sort){
$this->passedArgs['sort'] = $sort;
$options_url['sort'] = $sort;
}
if($direction){
$this->passedArgs['direction'] = $direction;
$options_url['direction'] = $direction;
}
Send the variable $options_url to the view using set()... so in the view you'll need to do this:
View:
unset($this->Paginator->options['url']);
echo $this->Paginator->prev(__('« Précédente', true), array('url'=>$options_url), null, array('class'=>'disabled'));
echo $this->Paginator->numbers(array('separator'=>'','url'=>$options_url));
echo $this->Paginator->next(__('Suivante »', true), array('url'=>$options_url), null, array('class' => 'disabled'));
Now, on the sort links you'll need to unset the variables 'sort' and 'direction'. We already used them to create the links on the paginator, but if we dont delete them, then the sort() function will use them... and we wont be able to sort =)
$options_sort = $options_url;
unset($options_sort['direction']);
unset($options_sort['sort']);
echo $this->Paginator->sort('Produit <span> </span>', 'title',array('escape'=>false,'url'=>$options_sort));
hope this helps =)
I am trying to use a drop down input in my cakephp application, with this I want the drop down on submit to render the url like so:
www.example.com/cake/FILE/VALUE
However the only url i can get the select input to create is the following:
www.example.com/cake/FILE?form_value=VALUE
How do I go about making the URL SEO friendly like the first example without using httaccess because I want the URL to appear seo friendly in the search engines eyes.
Here is the code I am using.
In The VIEW
echo $form->input('form_value', array(
'label' => '',
'type' => 'select',
'options' => $listOfOptions,
'selected' => '0',));
Thank you.
In your controller, get the value of "form_value" through $file = $this->data['FILE']['form_value'] and do a redirect $this->redirect(array('action' => 'download', $file)).
You then create a function called download which should look like this:
<?php
function download($file = null) {
if ($file != null) {
/*make download*/
} else {
$this->Session->setFlash('no file specified')
}
?>
If you don't want the action "download" to appear in the URL you can use Cakes built-in routes in cakephp/app/config/routes.php.
With something like this you could map the index-action to the download-action:
Router::connect('/FILE/*', array('controller' => 'files', 'action' => 'download'));
See http://book.cakephp.org/view/46/Routes-Configuration for better explanation.