Global search
Global search allows for searching through all searchable resources in your application.
Getting started
By default, Skeleton ships with a global search input in MainHeader.vue
.
If you're coming from an older version, add the following to the toolbar (next to the logo):
<global-search
class="q-ml-md"
:style="$q.screen.gt.md ? 'width: 300px' : undefined"
v-if="$q.screen.gt.xs"
/>
By default, resources are not globally searchable. You can make a resource searchable by setting the following property on the resource:
public $globalSearchEnabled = true;
Now when you type into the search input, a search will be performed.
If you have no filterable fields in your resource, an exception will be thrown
Customizing the search query
By default a search is conducted on all Text
fields that are filterable
and shown on the index page.
You can override this behaviour by adding the following method on your resource, for example:
public function globalSearch(string $query): ?Builder
{
return $this->indexQuery()->where('first_name', 'LIKE', "%$query%")
->orWhere('last_name', 'LIKE', "%$query%")
->orWhere('serial_number', 'LIKE', "%$query%")
->orWhereHas('notes', function($q) use ($query) {
return $q->where('message', 'LIKE', "%$query%");
})
}
Customizing the search results
By default, Qore exposes a Vue component for each search result that supports a title, a subtitle and a side.
The title will be the resource modelTitle
by default.
You can add the following method to override a search result:
public function globalSearchResult(Model $model): array
{
return [
'title' => $this->modelTitle($model),
'side' => $model->gender->description,
'subtitle' => "Email address: {$model->email}\n And some other text"
];
}
Defining a custom search result component
You can also define a search result component yourself.
For example, start by creating a MyGlobalSearchResult.vue
:
<template>
<fragment>
<q-item-section avatar>
<q-avatar>
<img src="https://cdn.quasar.dev/img/avatar2.jpg">
</q-avatar>
</q-item-section>
<q-item-section>
<q-item-label lines="1">
<text-highlight
:queries="[query]">{{result.data.title}}
</text-highlight>
</q-item-label>
<q-item-label
caption
lines="2"
>
<text-highlight
:queries="[query]">
Lorem ipsum dolar et
</text-highlight>
</q-item-label>
</q-item-section>
<q-item-section
side
top
>
<q-badge color="grey-9">
<text-highlight
:queries="[query]"> {{result.data.side}}
</text-highlight>
</q-badge>
<q-item-label caption>{{result.data.created_at}}</q-item-label>
</q-item-section>
</fragment>
</template>
<script>
export default {
props: ['query', 'result']
}
</script>
The data you have available in the result.data
prop is defined in your globalSearchResult
method.
Then register this search result globally (for example in your app.js
):
import MyGlobalSearchResult from 'src/path/to/MyGlobalSearchResult'
Vue.component('MyGlobalSearchResult', MyGlobalSearchResult)
The surrounding component for a search result is a Quasar item.
See https://quasar.dev/vue-components/list-and-list-items for more information.
Now you can point to this component in your resource:
public function globalSearchResultComponent()
{
return 'MyGlobalSearchResult';
}
Sort order
By default, the sort order of search results are identical to the order of which Resource is registered (first to last).
In some cases this might not be ideal, you can define a custom sort order (default is 100
). Lower numbers will be prioritized more:
/**
* Lower numbers will be higher in the list (low = more priority)
*
* @return int
*/
public function globalSearchSortOrder(): int
{
return 25;
}
Fuzzy search
Qore uses Laravel Scout
as a supplement to the globalSearch
query in your resource when enabled. It will not replace
it, but will combine search results if data cannot be found with normal queries.
Fuzzy search allows for typo's to usually still find data. For example, a query heuvle
may still find models
containing the text Heuvel
.
Scout drivers usually work with indices. Indices contain and store information about your models, and needs to be generated (either manually, or automatically by model events).
The trait (as mentioned below) will add model event listeners that will re-index automatically.
Installation
Skeleton ships a config/scout.php
by default which uses the TNTSearch
driver. This driver does not require you to
set up additional services (unlike Elastic
for example), but you can still use other drivers.
If you're coming from an older version, here is the content:
<?php
return [
'driver' => env('SCOUT_DRIVER', 'tntsearch'),
'prefix' => env('SCOUT_PREFIX', ''),
'queue' => env('SCOUT_QUEUE', false),
'after_commit' => false,
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
'soft_delete' => false,
'identify' => env('SCOUT_IDENTIFY', false),
'tntsearch' => [
'storage' => base_path('storage/indices'),
'fuzziness' => env('TNTSEARCH_FUZZINESS', true),
'fuzzy' => [
'prefix_length' => 2,
'max_expansions' => 50,
'distance' => 3
],
'asYouType' => false,
'searchBoolean' => env('TNTSEARCH_BOOLEAN', false),
'maxDocs' => env('TNTSEARCH_MAX_DOCS', 500),
'wal' => false,
],
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
],
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY', null),
],
];
Indices will be stored into storage/indices
by default. Make sure this directory exists, and add a .gitignore
inside
this directory:
*
!../../.gitignore
Implementation
To start implementing fuzzy search, add the following trait to your model:
use Qore\System\Search\Searchable;
class User extends QoreUser
{
use Searchable;
// ...
}
The reason why we're not using the Searchable
trait from Laravel Scout
, is because we're dealing with multitenancy.
Now, whenever a model is created/updated a search index will be created.
Defining indices
By default, like normal queries, every Text
field is added as an searchable index.
You can customize your indices with the following method. Every value should be a string.
public function globalSearchIndices(Model $model): array
{
return [
'title' => $this->modelTitle($model),
'projects' => implode(', ', $model->projects->map(function ($project) {
return $project->name;
})->toArray()),
'serial_number' => $model->serial_number,
'notes' => $model->notes->map(function($note) {
return $note->message;
})->implode(', ')
];
}
Make sure you regenerate your indices every time you update this method (see below).
Updating indices
If you added the Searchable
trait to a model which already contains records in the database, or you updated
the globalSearchIndices
method, you can re-index all models with the following command:
php artisan qore:index
Or programmatically:
Artisan::call('qore:index');
Or specific models only:
User::where('id', $someId)->searchable()
Additional configuration
You can define how many search results will be shown per resource.
The default is 8
.
public function globalSearchResultLimit(): int
{
return 15;
}