1

To satisfy the requirement (Add support for custom form fields without any plugin or library), I developed a module which allows admin to perform below actions.

  1. Admin can create custom form fields.
  2. Fields data should be encrypted when stored into database.
  3. It can be mapped with multiple countries so if user belongs to that country, selected custom form fields will appear in profile.

Below is my code. Kindly suggest if there are any improvements needed.

Migration file to create custom form fields

public function up()
{
    Schema::create('custom_forms', function (Blueprint $table) {
        $table->id();
        $table->string('title', 250);
        $table->string('field_title', 250);
        $table->string('field_name', 250);
        $table->string('field_input_name', 250);
        $table->enum(
            'field_type',
            [
                'text',
                'textarea',
                'number',
                'email',
                'date',
                'time',
                'select',
                'radio',
                'checkbox',
                'tel',
                'url'
            ]
        )->default('text');
        $table->string('field_min_value', 10)->nullable();
        $table->string('field_max_value', 10)->nullable();
        $table->text('field_option_values')->nullable();
        $table->string('field_validation_pattern', 250)->nullable();
        $table->string('field_validation_message', 250)->nullable();
        $table->string('field_placeholder', 250)->nullable();
        $table->tinyInteger('is_required')->default(0);
        $table->integer('country_id');
        $table->tinyInteger('is_active')->default(1);
        $table->timestamps();
    });
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::dropIfExists('custom_forms');
}

Migration file to store custom form field values

public function up()
{
    Schema::create('custom_form_values', function (Blueprint $table) {
        $table->id();
        $table->foreignId('form_id')->references('id')->on('custom_forms');
        $table->foreignId('user_id')->references('id')->on('users');
        $table->mediumText('value')->nullable();
        $table->timestamps();
    });
}

/**
 * Reverse the migrations.
 *
 * @return void
 */
public function down()
{
    Schema::dropIfExists('custom_form_values');
}

White creating a custom form field, admin can select multiple countries for single. So, I've created a loop for all selected countries and stored the data.

CustomFormController.php

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Http\Response
 */
public function store(Request $request)
{

    foreach ($request->country_id as $country) {
        $custom_form = new CustomForm();
        $custom_form->title = $request->name;
        $custom_form->field_title = $request->name . '-' . Str::random(10);
        $custom_form->field_name = $request->field_name;
        $custom_form->field_input_name  = preg_replace('/\s+/', '', $request->name) . '-' . Str::random(10);
        $custom_form->field_type = $request->field_type;
        $custom_form->field_min_value = $request->field_min_value;
        $custom_form->field_max_value = $request->field_max_value;
        $custom_form->field_option_values = $request->field_option_values;
        $custom_form->field_validation_pattern = $request->field_validation_pattern;
        $custom_form->field_validation_message = $request->field_validation_message;
        $custom_form->field_placeholder = $request->field_placeholder;
        $custom_form->is_required = $request->field_is_required;
        $custom_form->country_id = $country;
        $custom_form->save();
    }

    return redirect()->route('admin.custom-forms.index');
}

In CustomForm.php model, added relation to the values to get data for selected custom form field.

public function values()
{
    return $this->belongsTo(CustomFormValue::class, 'id', 'form_id')->select(['id', 'value','form_id']);
}

In CustomFormValue.php model, defined encrypted field to match requirement no. 2.

protected $casts = [
    'value' => 'encrypted',
];

Below code is from edit_profile.blade.php to display all custom form fields.

@forelse ($custom_forms  as $form)
    <div class="form-group">
        <label class="required"
            for="{{ $form->field_input_name }}">{{ $form->field_name }}</label>

        {{-- FOr Text area --}}
        @if ($form->field_type === 'textarea')
            <textarea class="form-control" type="{{ $form->field_type }}"
                name="custom_form_{{ $form->id }}" id="{{ $form->field_input_name }}"
                minlength="{{ $form->field_min_value }}"
                maxlength="{{ $form->field_max_value }}"
                @if($form->field_validation_pattern) pattern="{{ $form->field_validation_pattern }}" @endif
                title="{{ $form->field_validation_message }}"
                placeholder="{{ $form->field_placeholder }}"
                {{ $form->is_required == 1 ? 'required' : '' }}>{{ $form->values ? $form->values->value : null }}</textarea>
            <span class="help-block">{{ $form->field_placeholder }}</span>

            {{-- for select option --}}
        @elseif ($form->field_type === 'select')
            @php
                $option_values = explode(',', $form->field_option_values);
                $is_selected = $form->values ? $form->values->value : '';
            @endphp
            <div class="form-group">
                <select class="form-control" type="{{ $form->field_type }}"
                    name="custom_form_{{ $form->id }}"
                    id="{{ $form->field_input_name }}"
                    @if($form->field_validation_pattern) pattern="{{ $form->field_validation_pattern }}" @endif
                    title="{{ $form->field_validation_message }}"
                    placeholder="{{ $form->field_placeholder }}"
                    {{ $form->is_required == 1 ? 'required' : '' }}>
                    @forelse ($option_values as $value)
                        <option @if ($value == $is_selected) selected @endif value="{{ $value }}">
                            {{ $value }}</option>
                    @empty
                    @endforelse
                </select>
                <span class="help-block">{{ $form->field_placeholder }}</span>
            </div>
            {{-- for radio buttons --}}
        @elseif ($form->field_type === 'radio')
            @php
                $option_values = explode(',', $form->field_option_values);
                $is_selected = $form->values ? $form->values->value : '';
            @endphp
            <div class="col-sm-10">
                @forelse ($option_values as $value)
                    <div>
                        <label>
                            <input class=" " type="{{ $form->field_type }}"
                                name="custom_form_{{ $form->id }}"
                                id="{{ $form->field_input_name }}"
                                @if($form->field_validation_pattern) pattern="{{ $form->field_validation_pattern }}" @endif
                                title="{{ $form->field_validation_message }}"
                                placeholder="{{ $form->field_placeholder }}"
                                value="{{ $value }}" @if ($value == $is_selected) checked @endif
                                {{ $form->is_required == 1 ? 'required' : '' }}>{{ $value }}
                        </label>
                    </div>
                @empty
                @endforelse
            </div>
        {{-- for checkbox --}}
        @elseif ($form->field_type === 'checkbox')
            @php
                $option_values = explode(',', $form->field_option_values);
                $is_selected = $form->values ? $form->values->value : '';
            @endphp
            <div class="col-sm-10">
                @forelse ($option_values as $value)
                    <div>
                        <label>
                            <input class=" " type="{{ $form->field_type }}"
                                name="custom_form_{{ $form->id }}[]"
                                id="{{ $form->field_input_name }}"
                                @if($form->field_validation_pattern) pattern="{{ $form->field_validation_pattern }}" @endif
                                title="{{ $form->field_validation_message }}"
                                placeholder="{{ $form->field_placeholder }}"
                                value="{{ $value }}" @if (str_contains($is_selected,$value)) checked @endif
                                >{{ $value }}
                        </label>
                    </div>
                @empty
                @endforelse
            </div>
        @else
            <input class="form-control " type="{{ $form->field_type }}"
                name="custom_form_{{ $form->id }}" id="{{ $form->field_input_name }}"
                minlength="{{ $form->field_min_value }}"
                maxlength="{{ $form->field_max_value }}" {{-- pattern="{{ $form->field_validation_pattern }}" --}}
                title="{{ $form->field_validation_message }}"
                placeholder="{{ $form->field_placeholder }}"
                value="{{ $form->values ? $form->values->value : null }}"
                @if($form->field_validation_pattern) pattern="{{ $form->field_validation_pattern }}" @endif
                {{ $form->is_required == 1 ? 'required' : '' }}>
            <span class="help-block">{{ $form->field_placeholder }}</span>
        @endif
    </div>
@empty
    {{-- Message to display if there is no custom field added--}}
@endforelse

To store user entered custom form field values, in UserController.php

foreach ($custom_values as $key => $value) {
    $form_value = Str::contains($key, 'custom_form_');
    if ($form_value == 1) {
        $form_id = str_replace('custom_form_', '', $key);
        $custom_form_value = CustomFormValue::updateOrCreate([
            'form_id' => $form_id,
            'user_id' => Auth::user()->id
        ], [
            'form_id' => $form_id,
            'user_id' => Auth::user()->id,
            'value' => is_array($value) ? implode(', ', $value) : $value
        ]);
    }
}

One user can have many custom form field values. So, in User.php model,

public function customFormValue()
{
    return $this->hasMany(CustomFormValue::class, 'user_id');
}

And finally, to display custom form values, in profile.blade.php

 @forelse ($custom_forms  as $form)
    <tr>
        <td>
            {{ $form->field_name }}: <strong class="pull-right">{{ $form->values ? $form->values->value : '' }}</strong>
        </td>
    </tr>                                    
@empty
    {{-- Message if any --}}
@endforelse

So, this is what I have done so far to add support for custom form field without any plugin or library. Let me know if there is any room for improvement.

I hope it may help others who are looking for something similar.

Shivam Pandya
  • 1,061
  • 3
  • 29
  • 64

1 Answers1

2

Here are some points that you can implement for a better scalable system.

1) custom_forms table

  • We can store the nullable fields in JSON. So we can avoid the multiple nullable fields because it can be possible to have 50% nullable values. It is effective for storage purposes and will make a more efficient and scalable DB design.

2) custom_form_values table

  • $table->mediumText('value')->nullable() TO $table->json('value')->nullable()

  • NOTE - The JSON column type is recommended as it allows you to do SQL queries on JSON data. Here is a quick doc link https://dev.mysql.com/doc/refman/8.0/en/json.html

3) CustomFormController.php

4) edit_profile.blade.php

  • AVOID USING MULTIPLE IF/ELSE. IT WILL CREATES ISSUES AT TIME OF SCALIBILITY. Use components instead so it will make code more readable and scalable.
  • Here is the laravel components doc https://laravel.com/docs/9.x/blade#components

5) General Tips

  • Try to use the Null Coalescing operator instead ternary.

Hope this will help you.

Vivek Pawar
  • 664
  • 5
  • 11