Skip to content

Record Finder ​

The recordfinder field shows the picked record by its name and description, and opens a searchable, paginated list in a popup to pick another. It suits relations with too many records for a dropdown.

Picking a record ​

By default the field name is a relation on the model, here the reviewer relation to the User model. Select opens the popup, clicking a row picks that record, and Clear empties the field. The field posts the record's key, and saving the record sets the relation.

yaml
fields:
    reviewer:
        label: Reviewer
        type: recordfinder
        title: Pick a reviewer
        list:
            columns:
                name:
                    label: Name
                    searchable: true
                email:
                    label: Email
                    searchable: true
        nameFrom: name
        descriptionFrom: email
        recordsPerPage: 5
        comment: Select opens a searchable list of users, five to a page.
php
/**
 * @var array<string, mixed> belongsTo relations, the owner, reviewer, group and category picked by the relation widgets.
 */
public $belongsTo = [
    'user' => User::class,
    'reviewer' => User::class,
    'primary_group' => UserGroup::class,
    'primary_category' => Category::class,
];

list defines the popup's columns, inline or as the path to a columns YAML file, and the columns marked searchable feed the search box. nameFrom and descriptionFrom name the columns that describe the picked record. title heads the popup, and recordsPerPage sets how many rows each page lists.

On a plain Eloquent model the posted key needs mapping onto the relation when you save, as the relation field shows.

Scopes ​

scope narrows the popup list. It takes the name of a model scope on the related model, or a static Class::method, and either is called with the query and the record being edited. Here a static method on Sample lists verified users only.

yaml
fields:
    user:
        label: Owner
        type: recordfinder
        title: Pick an owner
        list:
            columns:
                name:
                    label: Name
                    searchable: true
                email:
                    label: Email
                    searchable: true
        nameFrom: name
        descriptionFrom: email
        recordsPerPage: 5
        scope: App\Models\Sample::applyOwnerScope
        searchScope: searchNameOrEmail
        comment: Lists verified users only, and the search matches the start of a name or email.
php
/**
 * applyOwnerScope narrows the owner record finder to verified users, as a static scope method.
 */
public static function applyOwnerScope(Builder $query, self $sample): void
{
    $query->whereNotNull('email_verified_at');
}

searchScope replaces the built-in search with a scope on the related model, called with the search term and the searchable columns. This one matches the start of a name or an email, where the built-in search matches words anywhere in the columns. The search box still needs at least one searchable column.

php
/**
 * Search users by the start of their name or email, used as a record finder's searchScope on the Record Finder reference page.
 *
 * @param  array<int, string>  $columns
 */
public function scopeSearchNameOrEmail(\Illuminate\Database\Eloquent\Builder $query, string $term, array $columns = []): \Illuminate\Database\Eloquent\Builder
{
    return $query->where(function ($query) use ($term) {
        $query->where('name', 'like', $term.'%')->orWhere('email', 'like', $term.'%');
    });
}

filter adds filter scopes above the list, inline or as the path to a scopes YAML file, with the same scope types as any filter. defaultSort orders the list by a column and a direction. showSetup adds the list setup button, where the user picks the visible columns and the page size. popupSize sets the popup width: tiny, small, large, huge (the default), giant or adaptive.

yaml
fields:
    primary_group:
        label: Primary Group
        type: recordfinder
        title: Pick a group
        list:
            columns:
                name:
                    label: Name
                    searchable: true
                code:
                    label: Code
                description:
                    label: Description
        filter:
            scopes:
                hide_guest:
                    label: Hide the guest group
                    type: checkbox
                    conditions: code <> 'guest'
        nameFrom: name
        descriptionFrom: code
        defaultSort:
            column: name
            direction: asc
        showSetup: true
        popupSize: large
        comment: The popup sorts by name, adds a filter and a list setup button, and opens at the large size.

Trees and the stacked layout ​

When the related model is a tree, using the NestedTree or SimpleTree trait, the popup lists the records as a tree, with children under their parent and no reordering. structure: false turns the tree off, and structure also takes list structure options.

yaml
fields:
    primary_category:
        label: Primary Category
        type: recordfinder
        title: Pick a category
        list:
            columns:
                name:
                    label: Name
                    searchable: true
                slug:
                    label: Slug
        nameFrom: name
        descriptionFrom: description
        inlineOptions: false
        comment: Categories are a nested tree, so the popup lists them as one.
php
/**
 * Category is a post category stored as a nested set tree, so the list structure can display, nest and reorder it.
 */
#[Fillable(['name', 'slug', 'description', 'parent'])]
class Category extends Model
{
    /** @use HasFactory<CategoryFactory> */
    use HasFactory;
    use NestedTree;
    use Validation;

inlineOptions: false stacks the picked record above the Replace and Clear buttons, instead of beside them, which leaves room for a longer description or a narrow column.

Without a relation ​

useRelation: false saves a plain value instead of setting a relation. modelClass names the model the popup lists, and keyFrom names the column whose value the field saves, id by default. Here the field saves a group's code in the group_code column.

yaml
fields:
    group_code:
        label: Group Code
        type: recordfinder
        title: Pick a group
        useRelation: false
        modelClass: App\Models\UserGroup
        keyFrom: code
        conditions: code <> 'guest'
        list:
            columns:
                name:
                    label: Name
                    searchable: true
                code:
                    label: Code
        nameFrom: name
        descriptionFrom: code
        comment: Saves the picked group's code in a plain column, with no relation behind it.

conditions filters the list with a raw SQL where clause. It takes the place of scope, so when both are set only conditions applies. With neither, a relation's own constraints filter the list.

Read-only ​

readOnly shows the record without its buttons, and still posts its key from a hidden input. This copy reads the reviewer through valueFrom, and its name starts with an underscore, which keeps it out of the saved data.

yaml
fields:
    _reviewer_readonly:
        label: Reviewer
        type: recordfinder
        valueFrom: reviewer
        readOnly: true
        list:
            columns:
                name:
                    label: Name
        nameFrom: name
        descriptionFrom: email
        comment: Shows the reviewer without the Replace and Clear buttons, and still posts its key.

Preview ​

disabled shows the record in preview mode, and the field is left out of the save. With no record, the field says so.

yaml
fields:
    owner_preview:
        label: Owner
        type: recordfinder
        valueFrom: user
        disabled: true
        list:
            columns:
                name:
                    label: Name
        nameFrom: name
        descriptionFrom: email
        comment: Shows the owner as a preview, which is never saved.

Options ​

OptionDescription
listthe popup list's columns, inline or as the path to a columns YAML file.
titlethe popup title. Default: Find Record.
nameFromthe column shown as the picked record's name. Default: name.
descriptionFromthe column shown beside the name. Default: none.
keyFromthe column whose value the field saves. Default: the relation's key, or id without a relation.
recordsPerPagethe rows on each page of the popup list. Default: 10.
scopea model scope on the related model, or a static Class::method, that narrows the list. Also accepted as modelScope.
conditionsa raw SQL where clause that narrows the list, used in place of scope.
searchModehow the search box matches words: all (every word), any (any word) or exact (the whole phrase). Default: all.
searchScopea model scope on the related model that performs the search instead.
filterfilter scopes shown above the list, inline or as the path to a scopes YAML file.
defaultSortthe list order, as a column and a direction.
showSetupadds the list setup button to the popup. Default: false.
structurefalse turns off the tree for a tree model, true forces it, or list structure options. Default: a tree when the related model is a tree.
inlineOptionsshows the buttons beside the picked record. false stacks them below it. Default: true.
popupSizethe popup width: tiny, small, large, huge, giant or adaptive. Default: huge.
useRelationtreats the field name as a relation. false saves a plain value from modelClass records. Default: true.
modelClassthe model the popup lists when useRelation is false.

The field also takes the common field properties, such as readOnly, disabled and valueFrom.