Laravel: Dynamic Array Input Form Field Example
Leonel Elimpe
by Leonel Elimpe
4 min read

Tags

  • Laravel

In my app, I have a promotion’s table which has a field called phone_restrictions, which is an array of phone numbers a promotion is restricted to.

From my backend admin panel, I’d like to populate this array, edit already existing values, or add new ones. Essentially, make phone_restrictions an array input field and add Javascript functionality to dynamically add new inputs to the array, then save to the database. Let’s dive in.

Laravel dynamic array iput fields demo

The migration and model setup

phone_restrictions is set up as a json column on the promotions table.

<?php

// ...

class CreatePromotionsTable extends Migration
{

    public function up()
    {
        Schema::create('promotions', function (Blueprint $table) {
            
            // ...

            // Restrict the promotion to an array of phone numbers
            $table->json( 'phone_restrictions' )->nullable();
            
        });
    }

    // ...
}

Here’s what the Promotion model looks like, only the sections important to this blog post.

<?php

namespace App;

use App\Rules\IsValidCameroonianPhoneNumber;
use Eloquent as Model;

/**
 * Class Promotion
 * @package App
 *
 * @property array $phone_restrictions
 * // ...
 */
class Promotion extends Model
{
    public $fillable = [
        'phone_restrictions',
        
        // ...
    ];

    protected $casts = [
        'phone_restrictions' => 'array',
        
        // ...
    ];

    public static function rules() {
        return [
            
            // ...
            
            'phone_restrictions' => [
                'nullable',
                'array',
            ],
            'phone_restrictions.*' => [
                'nullable',
                new IsValidCameroonianPhoneNumber,
            ],
        ];
    }
}

phone_restrictions is cast to an array - the json value pulled from the database is cast to an array, and there are Request validation rules to make sure if phone_restrictions is not null, it should be an array. There’s equally validation for each value in the array, if the value is not null, it should be a valid phone number.

The view setup

P.S., my project uses the Laravel Collective package.

I have a views/promotions/fields.blade.php partial that contains the core promotion creation and editing form fields as seen below (only the parts related to the phone_restrictions input).

<!-- Phone Restrictions [] Field -->
<div class="col-sm-12">
    <br>
    <fieldset>
        <legend>Phone Restrictions</legend>
        <div class="row">
            <div class="input_fields_wrap">
                <!--  @see https://gist.github.com/whoisryosuke/04731177e772fefe08929e809f4fef05#gistcomment-3256172 -->
                @foreach(old('phone_restrictions', isset($promotion) ? $promotion->phone_restrictions : []) as $key => $item)
                    <div class="form-group col-sm-2">
                        <input class="form-control" name="phone_restrictions[]" type="text" value="{{$item}}">
                    </div>
                @endforeach

            </div>

            <div class="col-sm-12">
                <button class="btn btn-link btn-sm add_field_button" type="button">+ Add</button>
            </div>

        </div>
    </fieldset>
    <br>
</div>

@push('scripts')
    <script type="text/javascript">
        
        <!--    @see https://stackoverflow.com/a/36973538/6924437    -->
          
        $(document).ready(function() {
            var wrapper         = $(".input_fields_wrap"); //Fields wrapper
            var add_button      = $(".add_field_button"); //Add button ID

            $(add_button).click(function(e){ //on add input button click
                e.preventDefault();
                //add input box
                var template = `<div class="form-group col-sm-2"> <input class="form-control" name="phone_restrictions[]" type="text"></div>`;
                
                $(wrapper).append(template);
            });
        });
    </script>
@endpush


<!-- Submit Field -->
<div class="form-group col-sm-12">
    {!! Form::submit('Save', ['class' => 'btn btn-primary']) !!}
</div>

I believe the line @foreach(old('phone_restrictions', isset($promotion) ? $promotion->phone_restrictions : []) as $key => $item) deserves to be explained.

Given some input fields will be added dynamically using Javascript, should a validation error occur we need to be able to recreate all previous input fields and fill them with their previous values, that’s what the line of code does. We use the old() helper to retrieve the old phone_restrictions value, passing in a second parameter to be returned if there’s no old value. This second parameter, $promotion, is conditional as it’s only available when updating a promotion record. It’s retrieved at the level of the controller and passed to the view. When it’s unavailable, e.g. when creating a promotion, the ternary operator is setup such that an empty array is returned.

The partial fields.blade.php is imported into two views:

  • views/promotions/create.blade.php for creating new promotion records.

    @extends('layouts.app')
    
    @section('content')
        {!! Form::open(['route' => 'promotions.store']) !!}
    
    		@include('promotions.fields')
    
    	{!! Form::close() !!}
    @endsection
    
  • views/promotions/edit.blade.php for updating promotion records.

    @extends('layouts.app')
    
    @section('content')
    	{!! Form::model($promotion, ['route' => ['promotions.update', $promotion->id], 'method' => 'patch']) !!}
    
    		@include('promotions.fields')
    
    	{!! Form::close() !!}
    @endsection
    

These views push the form data to the store and update controller methods on submit.

The controller setup

<?php

namespace App\Http\Controllers;

use App\Http\Requests;
use App\Http\Requests\CreatePromotionRequest;
use App\Http\Requests\UpdatePromotionRequest;
use App\Promotion;
use Flash;
use Response;

class PromotionController extends AppBaseController
{
    /**
     * Show the form for creating a new Promotion.
     *
     * @return Response
     */
    public function create()
    {
        return view('promotions.create');
    }

    /**
     * Store a newly created Promotion in storage.
     *
     * @param CreatePromotionRequest $request
     *
     * @return Response
     */
    public function store(CreatePromotionRequest $request)
    {
        $input = $request->all();

        // Remove empty strings in phone_restrictions array, remove repeated numbers
        if (!empty($input['phone_restrictions'])) {
            $input['phone_restrictions'] = array_filter($input['phone_restrictions'], static function ($item) {
                return !empty($item);
            });

            $input['phone_restrictions'] = array_unique($input['phone_restrictions']);
        }

        /** @var Promotion $promotion */
        $promotion = Promotion::create($input);

        Flash::success('Promotion saved successfully.');

        return redirect(route('promotions.index'));
    }

    /**
     * Show the form for editing the specified Promotion.
     *
     * @param  int $id
     *
     * @return Response
     */
    public function edit($id)
    {
        /** @var Promotion $promotion */
        $promotion = Promotion::find($id);

        if ($promotion === null) {
            Flash::error('Promotion not found');

            return redirect(route('promotions.index'));
        }

        return view('promotions.edit', compact(['promotion']));
    }

    /**
     * Update the specified Promotion in storage.
     *
     * @param  int              $id
     * @param UpdatePromotionRequest $request
     *
     * @return Response
     */
    public function update($id, UpdatePromotionRequest $request)
    {
        /** @var Promotion $promotion */
        $promotion = Promotion::find($id);

        if ($promotion === null) {
            Flash::error('Promotion not found');

            return redirect(route('promotions.index'));
        }

        $input = $request->all();

        // Remove empty strings in phone_restrictions array, remove repeated numbers
        if (!empty($input['phone_restrictions'])) {
            $input['phone_restrictions'] = array_filter($input['phone_restrictions'], static function ($item) {
                return !empty($item);
            });

            $input['phone_restrictions'] = array_unique($input['phone_restrictions']);
        }

        $promotion->fill($input);
        $promotion->save();

        Flash::success('Promotion updated successfully.');

        return redirect(route('promotions.index'));
    }

}

The phone_restrictions array is cleaned up before saving in both the store and update methods. Given the ability to remove empty fields is missing at the view layer, we need to make all empty fields (containing null values) are filtered out. We also make sure no two fields contain the same phone number by passing the array through PHP’s array_unique function.

Here’s what the final result looks like:

Laravel dynamic array iput fields demo

That’s it for this post, if you’ve found a typo, issue, or have any questions, please leave a comment below and I’ll get back to you.