Twill: Is it possible to use repeaters outside Block Editor?

Created on 26 Dec 2018  路  16Comments  路  Source: area17/twill

I'm a little confused about the usage of repeaters.
In the docs under the form fields section I see the following example:

<a17-fieldset title="Videos" id="videos" :open="true">
    @formField('repeater', ['type' => 'video'])
</a17-fieldset>

This seems to use the repeaters without the Block Editor, but I can't figure out how to make it work, where should video template stuff go? And would the content be saved on a specific column on the model's table?

Any help appreciated, Thanks.

Most helpful comment

@yanhao-li I was just a bit confused about settings for this repeater being under "block editor" as the field itself is used outside of it. Maybe it would make sense to move config for all repeaters outside of "block_editor" (this are already semantics)?

All 16 comments

Hi @amrnn,

Yes, the repeater could be used outside of the Block Editor. However, different from using it as a block, we have to create a module for it in this case.

Take the code snippet in your comment as example, let's assume you are working on an module named "Post", after declaring the formField in view, you will want to create a corresponding "video" module using the command php artisan twill:module videos, following the command line instructions to define the module routes and migrate the databases.

Next, what we are going to do is to define the one-to-many relationships between the original module[ex. Post] and repeater module [ex. Video]. Make a new migration to create an new column post_id in videos table, migrate it, declare the hasMany relationship in App\Models\Post:

    public function video()
    {
        return $this->hasMany(\App\Models\Video::class);
    }

Now, go to the repository of the module which you are working on, in this case, maybe PostRepository.php, include the HanldeRepeater trait from A17\Twill\Repositories\Behaviors\HandleRepeaters, and override the afterSave and getFormField method with following:

    public function afterSave($object, $fields)
    {
        $this->updateRepeater($object, $fields, 'video', 'Video');
        parent::afterSave($object, $fields);
    }
    public function getFormFields($object)
    {
        $fields = parent::getFormFields($object);
        $fields = $this->getFormFieldsForRepeater($object, $fields, 'video', 'Video');
        return $fields;
    }

Checkout more details about the custom form method in documentation

There still have some tricky parts I didn't addressed out here, but the repeater as static content should be able to work. I would suggest to read the source code if you met problems while doing this, but let me know if you have further questions, I'm always happy to help!

Hey @yanhao-li, thanks for the instructions.

I was going to try digging into the source code since the docs seem to be missing some details but I wanted to make sure it was possible to achieve what I'm asking for first.

I will try to get this working now and see what I get, thanks again.

It might also be valuable to mention that this type of repeater should still be defined in 'block_editor' config section as with repeaters in Block Editor.

file: config/twill.php
```
return [
...
'block_editor' => [
...
'repeaters' => [
'video' => [
'title' => 'Video',
'trigger' => 'Add video',
'component' => 'a17-block-video_item'
]
]
]
]

@zipavlin You are right! All repeaters have to be defined first in config.

@yanhao-li I was just a bit confused about settings for this repeater being under "block editor" as the field itself is used outside of it. Maybe it would make sense to move config for all repeaters outside of "block_editor" (this are already semantics)?

Hi I'm trying to create a repeater field as above, but store the data in a serialized field, rather than a related model?
I have the need for a repeater field (address lines), however the use case doesn't require a seperate table for the related data. I've managed to save the serialized data correctly in the DB using the prepareFieldsBeforeSave function in my repository, but I can't seem to format it correctly when loading the fields again to edit after save.

I'm currently using:
$fields['repeaters']['addressline'] = unserialize($object->address);
in the getFormFields function, but it doesnt work

Hi I'm trying to create a repeater field as above, but store the data in a serialized field, rather than a related model?

Any chance you can share this code @Riaan-ZA ?

Also @yanhao-li , its very unclear to me still in what view file the fields that appear within the repeater go. Can you clarify ? Right now I have a repeater field visible and I can add/remove rows but it has no fields within it.

Hi @arkid I have it working as follows:
First set up casting for the field in your model which allows easily saving the array as json:

//Return JSON data as an array
protected $casts = [
    'address' => 'array'
];

Then in your module repository to format the data for the form:

public function getFormFields($object) {
    if (isset($fields['address']) && !empty($fields['address'])) {
    $fields = $this->getJsonRepeater($fields, 'addressline', 'address');
    }
}

this is the getJsonRepeater function:

public function getJsonRepeater($fields, $repeaterName, $serializedData) {
        $repeater_items = $fields[$serializedData]; //JSON converted to array using $casts in Model
        $repeatersConfig = config('twill.block_editor.repeaters');

        foreach($repeater_items as $index => $repeater_item) {
            $id = $repeater_item['id'] ?? $index;

            $repeaters[] = [
                              'id' => $id,
                              'type' => $repeatersConfig[$repeaterName]['component'],
                              'title' => $repeatersConfig[$repeaterName]['title'],
            ];

            $repeater_fields = array_except($repeater_item, ['id', 'medias', 'browsers', 'blocks']);

            foreach($repeater_fields as $index => $repeater_field) {
                $repeater_data[] = [
                    'name' => 'blocks[' . $id . '][' . $index . ']',
                    'value' => $repeater_field
                ];
            }
        }   

        $fields['repeaters'][$repeaterName] = $repeaters;
                 $fields['repeaterFields'][$repeaterName] = $repeater_data;
        return $fields;
}

Then when saving the data, use this function to assign the data from the repeater to the db field which gets json encoded by the casts functionality above:

public function prepareFieldsBeforeSave($object, $fields) {
        if (isset($fields['repeaters']['addressline'])) {
        $fields['address'] = $fields['repeaters']['addressline'];
    }

        return parent::prepareFieldsBeforeSave($object, $fields);
}

This might not work 100% for your use case but should point you in the right direction

Thanks for this @Riaan-ZA , that is amazing and just what I wanted as the earlier example of saving the results into a seperate table were overkill for my scenario.

The problem I now have is confusion on how views need to be configured. I have this setup in the view of the master module...

@section('fieldsets')
    <a17-fieldset title="Room Configuration" id="rates" :open="true">
        @formField('repeater', ['type' => 'rate'])
    </a17-fieldset>
@stop

And that makes the rates field appear fine, but when I click the add new row button it appears but its empty, just a blank space with no field inputs.

Where is the direction of the fields that appear inside the repeater? In another view file and what is the naming/location convention for this?

That sounds like you missed something in the process of creating the repeater field.

Have you created the block blade template for you repeater field containing the fields you want and have you run npm run twill-build to generate the Vue file from that template?

Yes exactly @Riaan-ZA thanks for your time. I found what I needed to run and wasn't was the php artisan twill:blocks command.

I'm new to vue and I'm not really sure what this command and the npm one are doing so my running of these commands has been somewhat arbitrary. That mixed with having the name of the component in the twill settings not matching the blocks view file caused the issue.

Thanks again

@yanhao-li do you know if running the npm twill build command is actually required if you are only using a repeater outside of the full block infrastructure in your twill project ? I'm somewhat confused about the requirement for this step. It's clear I need to run the twill:blocks command after any changes to my blocks but on this npm command I'm not sure. Do you have a second to clarify ?

Hey @arkid ,
That's correct, you have to run npm run twill-build after every time you made change to the Twill's admin UI assets, or you could easily using npm run twill-dev to watch the file changes and hot reload code automatically.


Regards to your questions,

I'm new to vue and I'm not really sure what this command and the npm one are doing so my running of these commands has been somewhat arbitrary.

php artisan twill:blocks is used for converting the blade block to Vue component.
npm run twill-build is used for compiling the Vue components we just generated.


As for the problem that you met, could you please check out the common error in documentation and make sure you did everything right?

Additional to that, I'm not sure which version of Twill are you using, but there is a known issue in 1.2.0 which may cause the same problem as you met, in that case, you will need to update the twill-copy-blocks in package.json to the following:

 "twill-copy-blocks": "npm run twill-clean-blocks && mkdir -p resources/assets/js/blocks/ && mkdir -p vendor/area17/twill/frontend/js/components/blocks/customs/ && cp -R resources/assets/js/blocks/* vendor/area17/twill/frontend/js/components/blocks/customs/."

Checkout the discussion here: https://github.com/area17/twill/issues/151

Thanks so much for taking the time to clarify this clearly @yanhao-li ;) I have this working now, it was a combination of not running the two console commands and also not knowing where the view file needs to go (ie in the /views/admin/blocks folder path).

How does the foreign key column get populated? I'm getting this error on save Field 'resource_container_id' doesn't have a default value not sure what I'm missing, followed all the instructions here.

just incase anyone out there is as much of a goof as me, I forgot to add the foreignkey to my fillables on the repeater module, problem solved

Was this page helpful?
0 / 5 - 0 ratings

Related issues

newvladimirov picture newvladimirov  路  5Comments

zipavlin picture zipavlin  路  3Comments

noxify picture noxify  路  4Comments

jayhaluska picture jayhaluska  路  4Comments

bastienrobert picture bastienrobert  路  7Comments