Imagine you have a form where dropdowns depend on each other. Common example is a city select, where you need to choose a country first, and then cities list is refreshed. How to do it in Laravel?

To be honest, this topic is as old as jQuery, and there are tons of examples online, but since it was one of the most popular questions from our audience, we decided to write our own example within QuickAdminPanel.

Step 1. Generated form code

Here’s the form generated by QuickAdminPanel. We will input offices with their cities, and city depends on country.

We’re mostly interested in Blade code from resources/views/admin/offices/create.blade.php:

<form action="{{ route("admin.offices.store") }}" method="POST" enctype="multipart/form-data">
    @csrf
    <div class="form-group">
        <label for="country">{{ trans('global.office.fields.country') }}</label>
        <select name="country_id" id="country" class="form-control">
            @foreach($countries as $id => $country)
                <option value="{{ $id }}">
                    {{ $country }}
                </option>
            @endforeach
        </select>
    </div>
    <div class="form-group {{ $errors->has('city_id') ? 'has-error' : '' }}">
        <label for="city">{{ trans('global.office.fields.city') }}</label>
        <select name="city_id" id="city" class="form-control">
            <option value="">{{ trans('global.pleaseSelect') }}</option>
        </select>
        @if($errors->has('city_id'))
            <p class="help-block">
                {{ $errors->first('city_id') }}
            </p>
        @endif
    </div>

As you can see, we’re listing only the Countries options, cities are empty at first.
Here’s the code for app/Http/Controllers/Admin/OfficesController.php:

public function create()
{
    abort_unless(\Gate::allows('office_create'), 401);
    $countries = Country::all()->pluck('name', 'id')->prepend(trans('global.pleaseSelect'), '');

    return view('admin.offices.create', compact('countries'));
}

Step 2. jQuery code for AJAX call

In our Blade resources/views/admin/offices/create.blade.php file we have this:

@extends('layouts.admin')

In that “parent” Blade file resources/views/layouts/admin.blade.php we have this at the bottom:

// ... Loading jQuery from CDN
// ... Some other global JavaScript
@yield('scripts')

</body>
</html>

This means that in every child Blade file we can implement @section(‘scripts’) with custom JavaScript code. So we will do exactly that in our offices/create.blade.php at the bottom:

@section('scripts')
    <script type="text/javascript">
        $("#country").change(function(){
            $.ajax({
                url: "{{ route('admin.cities.get_by_country') }}?country_id=" + $(this).val(),
                method: 'GET',
                success: function(data) {
                    $('#city').html(data.html);
                }
            });
        });
    </script>
@endsection

Now, you see a new Laravel route here: {{ route(‘admin.cities.get_by_country’) }}. The idea here is that would return full HTML for select input to us.

Let’s build the response for this AJAX request:

routes/web.php:

Route::get('cities/get_by_country', 'CitiesController@get_by_country')->name('admin.cities.get_by_country');

app/Http/Controllers/Admin/CitiesController.php:

public function get_by_country(Request $request)
{
    abort_unless(\Gate::allows('city_access'), 401);

    if (!$request->country_id) {
        $html = '<option value="">'.trans('global.pleaseSelect').'</option>';
    } else {
        $html = '';
        $cities = City::where('country_id', $request->country_id)->get();
        foreach ($cities as $city) {
            $html .= '<option value="'.$city->id.'">'.$city->name.'</option>';
        }
    }

    return response()->json(['html' => $html]);
}

That’s it! AJAX call response would fill in the city dropdown with new HTML by country.