whitecube / nova-flexible-content

Flexible Content & Repeater Fields for Laravel Nova
MIT License
780 stars 229 forks source link

Unwrap attributes at rest and hydrate with generated group key #436

Closed jfdialogs closed 1 year ago

jfdialogs commented 1 year ago

We are implementing several fields which are lists of objects, like this:

[
    {"foo":"bar"},
    {"foo":"baz"},
]

To do so, we need to not hold onto the generated flexible content blocks, e.g. approximately

[
    {
       "layout": "foo-bar",
       "key": "zrWtojMlY4z6Pz0t",
       "attributes": {"foo":"bar"}
    },
    {
       "layout": "foo-bar",
       "key": "c9olhyTXNwbz39av",
       "attributes": {"foo":"baz"}
    }
]

Above, we would only store the attributes at rest.

I can do this somewhat on the model hydrate:

    public function myField(): Attribute
    {
        return Attribute::make(
            get: static function($value) {
                $items = json_decode($value ?: '[]');

                if ($items) {
                    $items = array_map(fn(object $item) => [
                        'layout' => 'foo-bar',
                        'key' => uniqid(),                                          // <<< Here
                        'attributes' => $item,
                    ], $items);
                }

                return $items;
            }
        );
    }

What I don't know is:

  1. If I generate a new group key each time, will that cause issues?
  2. I could do this potentially with a resolver, however I still end up with the same group/key issue.

Is this possible/advisable?

jfdialogs commented 1 year ago

This is what I got to work, passing null as the key. Seems to work.

Flexible::make('Education', 'education')
    ->addLayout('School Graduation', 'graduation', [
        Text::make('School', 'school'),
        Select::make('Graduated', 'graduated')
            ->options(array_reverse(range(1920, date('Y'))))
    ])
    ->button('Add School Graduation')
    ->confirmRemove('Remove school graduation?')
    ->resolver(new class() implements ResolverInterface {
        public function get($resource, $attribute, $layouts) {
            $items = collect($resource[$attribute] ?: []);

            return $items->map(fn($item) => ($layout = $layouts->find('graduation'))
                ? $layout->duplicateAndHydrate(null, $item)
                : null
            )->filter();
        }

        public function set($model, $attribute, $groups)
        {
            return $model->$attribute = $groups
                ->map(fn($group) => $group->getAttributes());
        }
    }),