yougotnet's avatar

How to allows user to build custom forms

I have an application where I need to allow the user to build a custom Survey forms and they design their own questions.

I've done this before but wanted to see how others have accomplished this.

The process involves the user building the custom form, then display the form for user input, storing the form input and generating a report based on the custom form data.

Any input is greatly appreciated!

0 likes
3 replies
LaryAI's avatar
Level 58

Certainly! Allowing users to build custom forms is a common requirement, and it involves creating a flexible, dynamic system. Here’s how you can approach it:

1. Database Design

You’ll need tables to store form structures and user responses. For example:

forms

  • id
  • name
  • created_by

form_fields

  • id
  • form_id
  • label
  • field_type (text, textarea, select, radio, checkbox, etc.)
  • options (nullable; JSON for select, radio, checkbox choices)
  • required (boolean)

form_responses

  • id
  • form_id
  • submitted_by
  • submitted_at

form_response_values

  • id
  • form_response_id
  • form_field_id
  • value (string)

2. UI to Build the Form

Use a front-end framework (like Vue or React) or regular Blade templates with some JS to allow users to:

  • Add/remove fields
  • Choose field type
  • Specify label and options

Save the structure to the database.


3. Display Form for Input

When someone needs to fill out the form:

  • Load the form fields from the database.
  • Loop through them to generate the inputs dynamically.

Example in Blade:

@foreach ($form->fields as $field)
    <label>{{ $field->label }}</label>
    @if ($field->field_type == 'text')
        <input type="text" name="fields[{{ $field->id }}]" />
    @elseif ($field->field_type == 'select')
        <select name="fields[{{ $field->id }}]">
            @foreach (json_decode($field->options) as $option)
                <option value="{{ $option }}">{{ $option }}</option>
            @endforeach
        </select>
    @endif
@endforeach

4. Store Form Responses

In your controller:

public function store(Request $request, Form $form)
{
    $response = $form->responses()->create([
        'submitted_by' => auth()->id(),
        'submitted_at' => now(),
    ]);

    foreach ($request->input('fields') as $field_id => $value) {
        $response->values()->create([
            'form_field_id' => $field_id,
            'value' => is_array($value) ? json_encode($value) : $value,
        ]);
    }

    // Redirect or return response
}

5. Generate Reports

Query form_responses and form_response_values, then transform the data for reporting.

Example:

$results = FormResponse::where('form_id', $formId)
    ->with('values.field')
    ->get();

You can then aggregate or export this data as needed.


Summary

  • Store forms and fields as dynamic records
  • Build a front-end to let users design forms
  • Render forms dynamically for input
  • Store responses tied to the form/fields
  • Query responses for reporting

This approach is highly flexible and is how most "form builder" tools work. Consider using packages like Laravel-form-builder or Nova Flexible Content as a starting point if you need more.

Let me know if you need concrete examples for any part!

martinbean's avatar

@yougotnet Depends on how far you want to go with it.

You can be lazy and just put the form definition in a JSON column in some sort of forms table. But this means that if you need to change the structure of anything at all, your stored definitions are then going to become out of date and lead to bugs where your front-end may be expecting something different to what “shape” of data is stored in the database.

Alternatively, you can then model your forms with a has-many relation to some sort of fields table. Your fields model could have name and label columns, and then for things like selects and radio groups, its own optional has-many relation for allowed values. So your schema would look something like this:

Schema::create('forms', function (Blueprint $table): void {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('name');
    $table->timestamps();
});
Schema::create('form_fields', function (Blueprint $table): void {
    $table->id();
    $table->foreignId('form_id')->constrained()->cascadeOnDelete();
    $table->string('type'); // text, select, etc.
    $table->string('label');
    $table->string('name');
    $table->string('default_value')->nullable();
    $table->boolean('is_required');
    $table->unsignedInteger('minimum_length')->nullable();
    $table->unsignedInteger('maximum_length')->nullable();
    $table->timestamps();

    $table->unique(['form_id', 'name']);
});
Schema::create('form_field_options', function (Blueprint $table): void {
    $table->id();
    $table->foreignId('form_field_id')->constrained()->cascadeOnDelete();
    $table->string('label');
    $table->string('value');
    $table->timestamps();

    $table->unique(['form_field_id', 'value']);
});

You can use the minimum_length and maximum_length for text-type fields (i.e. <input type="text" maxlength="{{ $field->maximum_length }}" minlength="{{ $field->minimum_length }}">) but you can also re-use it for say, multiple selects and checkbox groups where you want a minimum and/or maximum number of options selected.

One thing you will need to think about is form versioning. If a user changes a form’s fields after submissions come in and say, changes the name of some fields, then your responses aren’t going to match up any more. So maybe you “lock” forms from being edited once they’ve been published or have received their first submission.

raobilal4822's avatar

i think just create only two table one is for user and 2nd is for form with a field fields(jsonb) and store field title type and etc in that jsonb field named fields.

Please or to participate in this conversation.