Laravel Nova isn't using the array key as the option value in filters

130 Views Asked by At

I created this filter for Nova v4.32.11:

class AccountFilter extends Filter
{
    public function apply(Request $request, $query, $value)
    {
        return $query->where('account_id', $value);
    }

    public function options(Request $request)
    {
        return Account::all()
            ->sortBy('name')
            ->mapWithKeys(function ($account) {
                return [$account->id => $account->name];
            })
            ->toArray();
    }
}

If I dd() the array being returned in options() it looks like this:

[
    1 => 'Foo',
    2 => 'Bar',
    3 => 'Baz',
]

However, if I inspect the element in Nova I see this for the select options:

<select dusk="Account Filter-select-filter" class="w-full block form-control form-select form-control-sm form-select-bordered">
<option value="">—</option>
<option value="Foo">Foo</option>
<option value="Bar">Bar</option>
<option value="Baz">Baz</option>
</select>

If I dd() the $value in apply() I see that value is indeed the account name instead of the ID as I'd expect from the array returned for the options list.

I decided to manually return exactly what I pasted above as the output...

The code: manually overriding the options code

The output in Chrome: screenshot of Chrome inspection on rendered options

What is wrong here?

1

There are 1 best solutions below

3
Karl Hill On BEST ANSWER

It seems like you need to use the account ID for filtering, but Laravel Nova is using the account name instead. One workaround is to use a custom component for the filter. This way, you can control the select options' value and display text separately.

First, you need to create a new Vue component. This component will be used for the filter. In this component, you can specify the value and the display text of the select options separately.

Here is an example of how you can create this component:

<template>
    <select class="form-control form-select" v-model="value">
        <option v-for="(name, id) in options" :value="id">{{ name }}</option>
    </select>
</template>

<script>
export default {
    props: ['options', 'value'],
}
</script>

Next, you need to register this component in your Nova service provider:

public function boot()
{
    parent::boot();

    Nova::script('custom-filter', __DIR__.'/../dist/js/filter.js');
    Nova::style('custom-filter', __DIR__.'/../dist/css/filter.css');

    Nova::provideToScript([
        'custom-filter' => new CustomFilter,
    ]);
}

Finally, you can use this component in your AccountFilter class:

class AccountFilter extends Filter
{
    public function apply(Request $request, $query, $value)
    {
        return $query->where('account_id', $value);
    }

    public function options(Request $request)
    {
        return Account::all()
            ->sortBy('name')
            ->mapWithKeys(function ($account) {
                return [$account->id => $account->name];
            })
            ->toArray();
    }

    public function components()
    {
        return [
            'custom-filter' => new CustomFilter,
        ];
    }
}

In this modified version, the apply method filters by the account ID, and the options method returns an array where the keys are the IDs and the values are the names. The components method specifies that the custom-filter component should be used for this filter. This way, the value selected in the Nova interface will match the value used in the apply method.