Skip to main content

Global search

Global search allows for searching through all searchable resources in your application.

Global Search

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.

danger

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:

frontend/src/path/to/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)
info

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;
}

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.

info

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
*
!../../.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;

// ...
}
info

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(', ')
];
}
info

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;
}