Webbutveckling

Filtrera relationer beroende på valda värden i Laravel Nova

Laravel Nova 4 har lagt till möjligheten till beroende fält. Vi använder dem flitigt för att rensa upp administrationsgränssnitt och endast visa fält som är relevanta utifrån ett befintligt sammanhang.

I ett nyligen genomfört projekt behövde vi ett fält vars val beror på ett tidigare val. Så här är fallet:

En projektmodell har ett fält för vilket team projektet tillhör. Projektet har också en huvudkontakt. Vi vill bara visa primära kontakter (användar-ID) som tillhör det team som valts. Dessutom har vi också flera fält som lagrar användar-ID.

Problemet med en ”relatable query”

Vårt första alternativ är att börja med Relatable Query. Detta är en något dold funktion som dokumenteras i avsnittet Field Authorization i Nova-dokumentationen.

Med en reletable query kan du begränsa vad ett relationsfält returnerar. I teorin är det precis vad vi vill göra.

I vårt fall har vi flera modeller som relaterar till användarmodellen, så vi kan välja något liknande:

public static function relatableUsers(NovaRequest $request, $query, Field $field): Builder
{
    if (in_array($field->attribute, ['project_manager', 'assigned_to'])) {
        return $query->whereHas('teams', function ($q) {
            $q->whereIn('team_id', [ ... ]); // Replacing [...] with a list of team IDs.
        });
    }

    return $query;
}

Tyvärr har den relaterbara frågan en stor nackdel. Den låter oss inte hämta värden från tidigare val i formuläret.

Vi vill filtrera listan över användare utifrån det valda teamet. Det är samma fråga, men vi behöver tillgång till det valda lag-ID:t. Detta kan vi inte få. En relaterbar fråga fungerar bara när filtreringen är fast.

Använda ett select-fält i stället och lite request-magi

Vår lösning var istället att ersätta fältet BelongsTo med ett Select-fält. Detta fungerar i vårt fall endast eftersom vi alltid förväntar oss att listan över användare som returneras är ganska liten. Tänk på potentiella prestandaproblem med större listor.

Vår select-fråga måste ta hänsyn till två fall:

  1. Skapa-skärmen där requesten har det valda Team ID:t.
  2. Uppdaterings-skärmen där det första Team ID:t ges av resursen och uppdateringar av requesten.
// The Nova resource has a BelongsTo field with the name "team" already.

Select::make('Primary Contact', 'primary_contact_id')
    ->options(function () use ($request) {
        return \App\Models\User::whereHas('teams', function ($q) use ($request) {

            // Get the "team" ID from the request, or default to the model attribute if not set.
            $q->where('team_id', $request->get('team', $this->team_id));

        })
            ->get(['first_name', 'last_name', 'id'])
            ->mapWithKeys(fn($user) => [$user->id => $user->name])
            ->toArray();
    })
    ->displayUsingLabels()
    ->nullable()
    ->hideFromIndex()
    ->dependsOn(
        ['team'],
        function (Select $field, NovaRequest $request, FormData $formData) {
            if ($formData->team === null) {
                $field->hide();
            }
        }
    ),

Koden ovan är hämtad från vårt projektexempel och visar både hämtning av alternativen och logiken för beroende av.

Genom att kombinera dessa två kan vi skapa ett dynamiskt och hjälpsamt gränssnitt för användarna, även med mer komplicerade val.

En önskan för en kommande Nova 4-version skulle definitivt vara att utöka den beroende logiken med denna funktionalitet för alla relationsfält.