Nova Ajax Select. 2.0

gabrielesbaiz/nova-ajax-select · 2.0

Seven thousand options. Twelve sent.

A Laravel Nova select field whose options are resolved on the server — scoped to whatever the user picked a moment ago, searched in the database rather than the browser, and validated on the way back in.

0routes this package registers
7,896rows it will not send you
103tests, including every doc sample
11kBcompiled field

01 — The payload

A plain select ships the whole table, opened or not.

Every option a Nova Select knows about is serialized into the form on render. For a status list that costs nothing. For a world cities table it is a third of a megabyte of JSON sent on the chance somebody opens the dropdown.

Drawn to scale. The accent bar is three pixels tall because 0.9 kB against 341 kB is what three pixels looks like next to a seventeen‑rem column.

And the weight is only the visible half. The browser still has to parse it, hold it, and filter it on every keystroke — work the database was built to do and does against an index.

02 — The filter

Pick a country. Everything else is struck before the response is written.

The scope lives in the query, not in the browser. Choose the country and the field resolves only the cities that belong to it — ordered, capped by limit(), and checked again when the form comes back so a value from another country is refused even though the id exists.

Note the near miss: the table holds two cities called Vancouver, and only one of them is in Canada. A filter by name in the browser would have let the other through.

03 — How it works

One line per relationship. Not one route per relationship.

Country to city to postal code is three fields and no routes at all. The endpoints you hand-wrote — query, map to value and display, cache, repeat in every tenant’s route file — stop existing.

01

Declare the parent

parent() registers a Nova dependency. The change travels the field event bus — no crawling the component tree.

02

Nova asks the server

On its own sync endpoint, already authorized for this resource, this mode and this user before your closure runs.

03

The source resolves

A closure, a model, a relation, a backed enum, a cached call to a geo service. Scoped, searched in SQL, capped.

04

The chain moves on

A value the new parent doesn’t own is cleared — and that clearing is what cascades to the next field down.

Before — a route per relationship

// routes/nova.php, once per tenant
Route::get('api/cities/{country}', function ($id) {
    return Cache::rememberForever(
        "cities.$id",
        fn () => GeoService::citiesByCountry($id)
    )->map(fn ($c) => [
        'value'   => $c['id'],
        'display' => $c['name'],
    ]);
});

After — the field knows

AjaxSelect::make('City', 'city_id')
    ->parent('country_id')
    ->optionsFromModel(City::class, query:
        fn ($q, $c) => $q->where(
            'country_id', $c->parent()
        ))
    ->labelFrom('city.name')
    ->cacheFor(3600);

04 — Versus dependsOn

If your options fit in the page, use dependsOn.

Nova already re-renders a field when another one changes, and you can set options() inside that callback. For a status list or twenty categories that is the right tool and this package is overhead. Here is the line where it stops being.

 Select + dependsOnAjaxSelect
Small, in-memory option setsIdealUnnecessary
7,896 rows in the tableAll of them, in the payloadScoped and capped
SearchIn the browser, over what it was sentIn SQL, across your columns
Options from an HTTP serviceYour problemA closure
Label on index and detailA second fieldlabelFrom()
Rejecting a forged valueHand-written ruleOne membership query

Support this package

Free forever.
Not free to maintain.

A field like this one is invisible when it works. Nobody opens a ticket about the 341 kB the form did not send, or the forged city id the validator quietly refused on the way in.

Sponsorship is what keeps it current with the things it sits on — a Nova licence to test against, the version matrix, and the upgrade written the week a new major lands rather than the month somebody complains. It stays MIT either way.

Free

Star the repo

Thirty seconds, and it is the first signal other developers look at when deciding whether to trust a package.

Free

Open a good issue

A clear reproduction is worth more than you think. Two of the bugs fixed in 2.0 were found writing the test for somebody else’s report.

Sponsor from $5

Goes towards the maintenance nobody sees: framework upgrades, the Nova licence, and keeping 103 tests green.

Company tier

Your logo here and in the README. If this field sits in an admin panel your team uses daily, it costs less than the afternoon it saves.

Why ask at all? This package is MIT and always will be. Nothing is paywalled, nothing phones home, and no feature is held back for sponsors. Sponsorship buys maintenance time, not features — and if you cannot sponsor, the star and the bug report genuinely help.