Laravel Nova Flexible Content

repository·master·Indexed 21 days ago

https://github.com/whitecube/nova-flexible-content

A Laravel Nova package providing a 'Flexible Field' for managing repeatable and orderable groups of fields. It allows developers to define multiple layouts (sets of fields) that can be added dynamically to a single attribute. The package includes support for custom Layout classes, Preset classes for reusable configurations, and Resolver classes for non-JSON storage. It also provides the FlexibleCast class and HasFlexible trait for parsing JSON content into usable objects in Eloquent models.

Tokens
6.6K
Snippets
30
Records
35
Agent score
73%

What's inside nova-flexible-content

  1. Extend Flexible Content with Custom Classes

    master

    The package is designed to be extended for advanced use cases:

    • Custom Layout Classes: Extract long addLayout definitions into reusable classes.
    • Predefined Preset Classes: Create Preset classes to reuse entire Flexible field configurations across different resources or to make them dynamic.
    • Custom Resolver Classes: If you don't want to use a JSON column (e.g., you want to store data in a separate table), implement a Resolver class with get() and set() methods to control storage and retrieval.
  2. How Layout instances and Collections work

    master

    When you retrieve Flexible content, you interact with two main objects:

    1. Layouts Collection: Extends Illuminate\Support\Collection. It includes a find(string $name) method to retrieve a specific layout by its name.
    2. Layout instance: These act like 'fake models' using Laravel's HasAttributes trait. You can define accessors and mutators on them.

    Key methods on a Layout instance:

    • name(): Returns the layout's name.
    • title(): Returns the layout's display title.
    • key(): Returns the layout's unique identifier.

    Layouts also implement HasFlexible, so you can call $layout->flexible('sub-layout-name') to parse nested content.

  3. Create Custom Layout Classes

    master

    To keep your Nova resources clean or share layouts across different fields, you can extract layouts into dedicated classes that extend Whitecube\NovaFlexibleContent\Layouts\Layout.

    Each class must define a $name (unique identifier) and a $title (display name), and implement the fields() method.

    You can also limit how many times a specific layout type can be added by setting the $limit property within the class.

    Use the Artisan command to scaffold a new layout class: php artisan flexible:layout {classname?} {name?}

    namespace App\Nova\Flexible\Layouts;
    
    use Laravel\Nova\Fields\Text;
    use Laravel\Nova\Fields\Markdown;
    use Whitecube\NovaFlexibleContent\Layouts\Layout;
    
    class SimpleWysiwygLayout extends Layout
    {
        protected $name = 'wysiwyg';
        protected $title = 'Simple content section';
        protected $limit = 1; // Limit this specific layout type
    
        public function fields()
        {
            return [
                Text::make('Title'),
                Markdown::make('Content')
            ];
        }
    }
    
    // Usage in Nova Resource:
    Flexible::make('Content')
        ->addLayout(\App\Nova\Flexible\Layouts\SimpleWysiwygLayout::class);
  4. Integrate with ebess/advanced-nova-media-library

    master

    You can use ebess/advanced-nova-media-library fields within your Flexible layouts by following these requirements:

    1. Use a custom layout class (extending Whitecube\NovaFlexibleContent\Layouts\Layout).
    2. The custom layout class must implement Spatie\MediaLibrary\HasMedia and use the Whitecube\NovaFlexibleContent\Concerns\HasMediaLibrary trait.
    3. The parent model must implement Spatie\MediaLibrary\HasMedia and use the Spatie\MediaLibrary\InteractsWithMedia trait.

    Once configured, you can access media on your layout instance using getMedia('attribute_name').

    // 1. Configure the Parent Model
    class Post extends Model implements HasMedia
    {
        use HasFlexible;
        use InteractsWithMedia;
    }
    
    // 2. Configure the Custom Layout
    class SliderLayout extends Layout implements HasMedia
    {
        use HasMediaLibrary;
    
        protected $name = 'sliderlayout';
        protected $title = 'SliderLayout';
    
        public function fields()
        {
            return [
                Images::make('Images', 'images')
            ];
        }
    }
  5. Integrate with nova-page

    master

    To use Flexible Content within nova-page templates, implement the Whitecube\NovaFlexibleContent\Concerns\HasFlexible trait on your Template class. This enables the Page::flexible('attribute') facade method, which correctly transforms the raw JSON content into usable layout objects.

    namespace App\Nova\Templates;
    
    use Whitecube\NovaFlexibleContent\Concerns\HasFlexible;
    
    class Home extends Template
    {
        use HasFlexible;
    
        // ...
    }
  6. Create Custom Resolver Classes

    master

    By default, Flexible fields expect to be stored in a JSON column. If you need to store data in a different way (e.g., in a separate database table using a HasMany or BelongsToMany relationship), you must implement a custom Resolver.

    To create a resolver, use the Artisan command:

    php artisan flexible:resolver {classname?}

    Each resolver must implement Whitecube\NovaFlexibleContent\Value\ResolverInterface, which requires two methods:

    1. get($resource, $attribute, $layouts): Responsible for retrieving the content and returning a collection of hydrated Layout instances.
    2. set($model, $attribute, $groups): Responsible for saving the field's content to your storage mechanism.

    Resolvers are typically placed in app/Nova/Flexible/Resolvers.

    use Whitecube\NovaFlexibleContent\Value\ResolverInterface;
    use Whitecube\NovaFlexibleContent\Layouts\Collection;
    
    class WysiwygPageResolver implements ResolverInterface
    {
        public function get($resource, $attribute, $layouts)
        {
            // Logic to retrieve data and return hydrated layouts
        }
    
        public function set($model, $attribute, $groups)
        {
            // Logic to save the $groups to your database
        }
    }
  7. Parse Flexible values using the HasFlexible trait

    master

    Alternatively, you can use the HasFlexible trait on your models. This allows you to call $model->flexible('attribute_name') to get a Whitecube\NovaFlexibleContent\Layouts\Collection.

    You can pass a mapping array to the flexible() method to transform layouts into custom Layout instances.

    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    use Whitecube\NovaFlexibleContent\Concerns\HasFlexible;
    
    class MyModel extends Model
    {
        use HasFlexible;
    
        public function getFlexibleContentAttribute()
        {
            return $this->flexible('flexible-content', [
                'wysiwyg' => \App\Nova\Flexible\Layouts\WysiwygLayout::class,
                'video' => \App\Nova\Flexible\Layouts\VideoLayout::class,
            ]);
        }
    }
  8. Use Flexible values in views with FlexibleCast

    master

    Since Flexible fields store data as a JSON string, you should use the FlexibleCast class in your Eloquent model to automatically parse the JSON into a collection of Layout instances.

    For Laravel 7+ applications, add it to your $casts array:

    namespace App;
    
    use Illuminate\Database\Eloquent\Model;
    use Whitecube\NovaFlexibleContent\Value\FlexibleCast;
    
    class MyModel extends Model
    {
        protected $casts = [
            'flexible-content' => FlexibleCast::class
        ];
    }
  9. Map layouts to Custom Layout classes

    master

    To map specific layout keys to custom PHP classes, create a custom cast using the Artisan command:

    php artisan flexible:cast MyFlexibleCast

    This creates a file in App\Casts. Extend FlexibleCast and define the $layouts property to map your layout names to their respective class names.

    namespace App\Casts;
    
    class MyFlexibleCast extends FlexibleCast
    {
        protected $layouts = [
            'wysiwyg' => \App\Nova\Flexible\Layouts\WysiwygLayout::class,
            'video' => \App\Nova\Flexible\Layouts\VideoLayout::class,
        ]
    }
  10. Create Predefined Preset Classes

    master

    Preset classes allow you to bundle a complete Flexible field configuration (including layouts, resolvers, and help text) into a reusable class. This cleans up your Nova Resource classes and makes it easy to apply the same field configuration in multiple places or make it dynamic.

    To create a preset, use the Artisan command:

    php artisan flexible:preset {classname?}

    Your preset class should extend Whitecube\NovaFlexibleContent\Layouts\Preset and implement a handle(Flexible $field) method. Because presets are resolved via Laravel's Container, you can type-hint dependencies in the constructor.

    To apply a preset to a field, use the preset method on the Flexible field instance.

    namespace App\Nova\Flexible\Presets;
    
    use Whitecube\NovaFlexibleContent\Flexible;
    use Whitecube\NovaFlexibleContent\Layouts\Preset;
    
    class WysiwygPagePreset extends Preset
    {
        public function handle(Flexible $field)
        {
            $field->button('Add new block');
            $field->resolver(\App\Nova\Flexible\Resolvers\WysiwygPageResolver::class);
            $field->addLayout('Title', 'title_layout', [...]);
        }
    }
    
    // In your Nova Resource:
    Flexible::make('Content')
        ->preset(\App\Nova\Flexible\Presets\WysiwygPagePreset::class);