lara-zeus / bolt

form builder for your users, with so many use cases
https://larazeus.com/bolt
MIT License
172 stars 31 forks source link

Repeater field type #283

Closed ryanmitchell closed 3 months ago

ryanmitchell commented 4 months ago

Is your feature request related to a problem? Please describe. We are looking to build forms where you can have multiple participants, each with a name, email and other user defined fields. Currently I can't see a way to achieve this.

Describe the solution you'd like I would like to be able to use the Repeater field from core (with min/max options etc) to allow me to add multiple participants to my form submission.

Describe alternatives you've considered An alternative would be to allow sections to be multi-filled, again with min and max options.

atmonshi commented 4 months ago

we do have "Dynamic Textbox" with bolt pro: https://demo.larazeus.com/bolt/bolt-pro

but it only have one field, I may add the repeater later this year to bolt pro

also you can add any field you want: https://larazeus.com/docs/bolt/v3/advanced/add-fields

ryanmitchell commented 4 months ago

Yeah I did see that, but as you said it's only one field, whereas we would want multiple fields to be repeatable.

I looked into making a custom field, but as the options then require sub options I couldn't see a way of doing it.

atmonshi commented 4 months ago

its doable:

<?php

namespace LaraZeus\BoltPro\Fields;

use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\TextInput;
use Filament\Tables\Columns\Column;
use Filament\Tables\Columns\TextColumn;
use LaraZeus\Accordion\Forms\Accordion;
use LaraZeus\Accordion\Forms\Accordions;
use LaraZeus\Bolt\Facades\Bolt;
use LaraZeus\Bolt\Fields\FieldsContract;
use LaraZeus\Bolt\Models\Field;
use LaraZeus\Bolt\Models\FieldResponse;
use LaraZeus\Bolt\Models\Response;

class Repeater extends FieldsContract
{
    public string $renderClass = \Filament\Forms\Components\Repeater::class;

    public int $sort = 103;

    public function title(): string
    {
        return __('Dynamic Textbox');
    }

    public function icon(): string
    {
        return 'tabler-text-wrap';
    }

    public function description(): string
    {
        return __('add multiple text values');
    }

    public static function getOptions(): array
    {
        return [
            Accordions::make('check-list-options')
                ->columns()
                ->accordions([
                    Accordion::make('general-options')
                        ->label(__('General Options'))
                        ->icon('iconpark-checklist-o')
                        ->schema([
                            // add a repeater to defined the fields
                            TextInput::make('options.minItems')->default(1)->numeric(),
                            TextInput::make('options.maxItems')->default(1)->numeric(),
                            TextInput::make('options.defaultItems')->default(1)->numeric(),
                            self::required(),
                            self::htmlID(),
                        ]),
                ]),
        ];
    }

    public static function getOptionsHidden(): array
    {
        return [
            self::hiddenHtmlID(),
            self::hiddenRequired(),
            Hidden::make('options.minItems'),
            Hidden::make('options.maxItems'),
            Hidden::make('options.defaultItems'),
        ];
    }

    // @phpstan-ignore-next-line
    public function appendFilamentComponentsOptions($component, $zeusField, bool $hasVisibility = false)
    {
        parent::appendFilamentComponentsOptions($component, $zeusField, $hasVisibility);

        $fieldOptions = $zeusField['options'];

        if (filled($fieldOptions['minItems'])) {
            $component->minItems($fieldOptions['minItems']);
        }

        // render the repeater here with the schema from the fields in the $fieldOptions 

        if (filled($fieldOptions['maxItems'])) {
            $component->maxItems($fieldOptions['maxItems']);
        }

        if (filled($fieldOptions['defaultItems'])) {
            $component->defaultItems($fieldOptions['defaultItems']);
        }

        $repeatedField = TextInput::make('title');

        if (filled($fieldOptions['is_required'])) {
            $repeatedField->required()->default('');
        }

        $component
            ->simple($repeatedField)
            ->reorderableWithDragAndDrop(false)
            ->reorderableWithButtons()
            ->cloneable()
            ->addActionLabel(__('Add'))
            ->default(['']);

        return $component;
    }

    public function getResponse(Field $field, FieldResponse $resp): string
    {
        $responseValue = (filled($resp->response) && Bolt::isJson($resp->response)) ? json_decode($resp->response) : [$resp->response];

        return view('zeus::filament.fields.repeater')
            ->with('resp', $resp)
            ->with('responseValue', $responseValue)
            ->with('field', $field)
            ->render();
    }

    public function TableColumn(Field $field): ?Column
    {
        return TextColumn::make('zeusData.' . $field->id)
            ->label($field->name)
            ->badge()
            ->getStateUsing(function (Response $record) use ($field) {
                return json_decode($record->fieldsResponses->where('field_id', $field->id)->first()->response);
            })
            ->toggleable();
    }
}

this is the simple repeater code and you can adjust it to be with multiple.

atmonshi commented 4 months ago

remember all the entries of the form (repeater or not) will be stored in one row in the responses table

so keep in mind the performance I recommend not to do it like this and go with separate models you can also use the extensions https://larazeus.com/docs/bolt/v3/advanced/extension

ryanmitchell commented 4 months ago

Thanks for sharing the code. Yes that sets the min/max, but it doesn't let you define the schema of the repeating block per form, which is where I've been struggling.

Using an extension wont work in this instance as the repeating fields need to be user definable.

atmonshi commented 4 months ago

did you see the comment // add a repeater to defined the fields

you can render a repeater here as per filament docs and it will be saved to the db then

// render the repeater here with the schema from the fields in the $fieldOptions

to render the saved items in another repeater read the code and try to understand it how it works, since I dont think I am gonna add the repeater till after the summer/early winter.

ryanmitchell commented 4 months ago

Sorry, yeah I see what you mean now. Ok - I'll see how we get on with that.

atmonshi commented 3 months ago

feel free to comment or reopen if more assistant needed :)