The problem with your approach is, that it only requests the data once and ProcessWire returns all pages instead of those who match the query string. This could be a problem on very dynamic sites, where the content changes often. Here is my solution which solves this: Modify the standard search.php and add
if ($config->ajax) {
header("Content-type: application/json"); // Set header to JSON
echo $matches->toJSON(); // Output the results as JSON via the toJSON function
}
so the whole file reads
<?php namespace ProcessWire;
// look for a GET variable named 'q' and sanitize it
$q = $sanitizer->text($input->get->q);
// did $q have anything in it?
if ($q) {
// Send our sanitized query 'q' variable to the whitelist where it will be
// picked up and echoed in the search box by _main.php file. Now we could just use
// another variable initialized in _init.php for this, but it's a best practice
// to use this whitelist since it can be read by other modules. That becomes
// valuable when it comes to things like pagination.
$input->whitelist('q', $q);
// Sanitize for placement within a selector string. This is important for any
// values that you plan to bundle in a selector string like we are doing here.
$q = $sanitizer->selectorValue($q);
// Search the title and body fields for our query text.
// Limit the results to 50 pages.
$selector = "title|body%=$q, limit=50";
// If user has access to admin pages, lets exclude them from the search results.
// Note that 2 is the ID of the admin page, so this excludes all results that have
// that page as one of the parents/ancestors. This isn't necessary if the user
// doesn't have access to view admin pages. So it's not technically necessary to
// have this here, but we thought it might be a good way to introduce has_parent.
if ($user->isLoggedin()) $selector .= ", has_parent!=2";
// Find pages that match the selector
$matches = $pages->find($selector);
$cnt = $matches->count;
// did we find any matches?
if ($cnt) {
// yes we did: output a headline indicating how many were found.
// note how we handle singular vs. plural for multi-language, with the _n() function
$content = "<h2>" . sprintf(_n('Found %d page', 'Found %d pages', $cnt), $cnt) . "</h2>";
// we'll use our renderNav function (in _func.php) to render the navigation
$content .= renderNav($matches);
} else {
// we didn't find any
$content = "<h2>" . __('Sorry, no results were found.') . "</h2>";
}
if ($config->ajax) {
header("Content-type: application/json"); // Set header to JSON
echo $matches->toJSON(); // Output the results as JSON via the toJSON function
}
} else {
// no search terms provided
$content = "<h2>" . __('Please enter a search term in the search box (upper right corner)') . "</h2>";
}
Then call typeahead with the following options:
$.typeahead({
input: '#q',
order: 'desc',
hint: false,
minLength: 3,
//cache: false,
accent: true,
display: ['title'], // Search objects by the title-key
backdropOnFocus: true,
dynamic: true,
backdrop: {
"opacity": 1,
"background-color": "#fff"
},
href: "{{url}}",
emptyTemplate: "No results for {{query}}",
searchOnFocus: true,
cancelButton: false,
debug: true,
source: {
//url: actionURL // Ajax request to get JSON from the action url
ajax: {
method: "GET",
url: actionURL,
data: {
q: '{{query}}'
},
}
},
callback: {
onHideLayout: function (node, query) {
$('#searchform').hide();
console.log('hide search');
}
}
});
The important parts are "dynamic:true" and the "source" configuration so the query string is beeing sent. Now you have a nice AJAX search. EDIT: If you also want to find the query string in other fields than the title make sure you add
filter: false
to the config of typeahead.