think.studio/nova-flexible-content

Laravel Nova的灵活内容与重复字段。

4.2.0 2023-10-02 15:10 UTC

README

Packagist License Packagist Version Total Downloads Build Status Code Coverage Scrutinizer Code Quality

这是一个从优秀的包whitecube/nova-flexible-content分叉而来,但重构时没有简单的方法将功能合并到主包中。

一个简单且完整的Laravel Nova灵活字段,非常适合重复和灵活的字段组。

Laravel Nova Flexible Content in action

安装

composer require think.studio/nova-flexible-content
# optional publish configs
php artisan vendor:publish --provider="NovaFlexibleContent\ServiceProvider" --tag="config"

使用方法

灵活字段允许轻松管理可重复和可排序的字段组。该包对这些组内允许使用的字段没有任何限制。这意味着您可以使用所有Laravel Nova字段类型,也可以使用任何社区制作的字段。

布局

布局表示一组可以在灵活字段内部重复的字段。您可以添加任意数量的不同布局。如果只定义了一个布局,字段将像简单的重复器一样表现。通过添加更多布局,您将获得灵活内容。

布局定义

namespace App\Nova\Flexible\Layouts;

use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Markdown;
use NovaFlexibleContent\Layouts\Layout;

class VideoLayout extends Layout
{

    // Optionally you limit count of this layout in flexible groups
    // protected int $limit = 3;
    
     protected function linksPreset()
    {
        return Preset::withLayouts([
            LinkLayout::class,
        ]);
    }

    /**
     * Get the fields displayed by the layout.
     */
    public function fields(): array
    {
        return [
            Text::make('Title', 'title')
                ->help('Optional'),
            FileForFlexible::make('Video', 'video')
                           ->prunable()
                           ->acceptedTypes('video/mp4')
                           ->deletable()
                           ->help('Aspect ratio 16:9. Please do not upload a large file.'),
            ImageForFlexible::make('Poster', 'poster')
                            ->prunable()
                            ->rules(['max:' . 1024 * 10])
                            ->deletable()
                            ->help('Aspect ratio 16:9.'),
            // Recursive flexible
            \NovaFlexibleContent\Flexible::make('Links', 'links')
                        ->preset($this->linksPreset())
                        ->layoutsMenuButton('Add link'),
        ];
    }
}

然后使用此布局

\NovaFlexibleContent\Flexible::make('Content')
    ->useLayout(\App\Nova\Flexible\Layouts\VideoLayout::class)
    ->useLayout(\App\Nova\Flexible\Layouts\FooLayout::class)
    ->useLayout(\App\Nova\Flexible\Layouts\BarLayout::class);

Example of Flexible layout

自定义显示

您可以通过调用方法来更改显示

\NovaFlexibleContent\Flexible::make('Content')
    ->fullWidth()
    ->useSearchableLayoutsMenu()
    ->layoutsMenuButton('Add Video')
    ->limit(3)
    ->withGroupRemovingConfirmation('Are you sure?', 'Yes :)', 'Ahh, no');

Add something amazing

值解析器

默认情况下,字段利用模型表的JSON列。在某些情况下,JSON属性并不是最佳选择。例如,您可能希望将值存储在另一个表中(这意味着您将使用灵活内容字段而不是传统的BelongsToMany或HasMany字段)。不用担心,我们已为您解决问题!

解析器定义

每个解析器必须实现NovaFlexibleContent\Value\Resolver契约,因此至少具有两个方法:setget

namespace App\Nova\Flexible\Resolvers;

use NovaFlexibleContent\Layouts\Collections\GroupsCollection;
use NovaFlexibleContent\Layouts\Collections\LayoutsCollection;
use NovaFlexibleContent\Value\Resolver;

class WysiwygPageResolver implements Resolver
{
    public function get(mixed $resource, string $attribute, LayoutsCollection $groups): GroupsCollection
    {
        return new GroupsCollection();
    }

    public function set(mixed $resource, string $attribute, GroupsCollection $groups): string
    {
        return '';
    }
}

解析字段

用于解析字段内容的get方法。它负责从某处检索内容并返回布局实例(组)的集合。例如,我们可能希望从blocks表中检索值并将其转换为布局实例。

public function get(mixed $resource, string $attribute, LayoutsCollection $groups): GroupsCollection 
{
    $blocks = $resource->blocks()->orderBy('order')->get();

    return $blocks->map(
        fn($block) => $layouts->find($block->name)
            ?->duplicate($block->id, ['value' => $block->value]);
    )->filter();
}

填充字段

负责保存灵活内容的set方法。在我们的示例中,它应在blocks表中存储数据。

public function set(mixed $resource, string $attribute, GroupsCollection $groups): string
{
    if($resource instanceof \Illuminate\Database\Eloquent\Model) {
        $resource::saved(function ($model) use ($groups) {
            // This is a quick & dirty example, syncing the models is probably a better idea.
            $model->blocks()->delete();
            $model->blocks()
                ->createMany($groups->map(function($group, $index) {
                    return [
                        'name' => $group->name(),
                        'value' => $group->toArray(),
                        'order' => $index
                    ];
                }));
        });
    }
    
    return '';
}

预设

除了可重用的布局类之外,您还可以为您的灵活字段创建Preset类。这允许您在需要的地方重复使用整个灵活字段。它们还使您的灵活字段更易于动态化,例如,如果您想有条件地添加布局。最重要的是,它们还有助于清理您的Nova资源类,如果您的灵活字段有很多useLayout定义。

预设定义

namespace App\Nova\Flexible\Presets;

use NovaFlexibleContent\Layouts\Preset;

class WysiwygPagePreset extends Preset
{
    /**
     * @var array
     */
    protected array $usedLayouts = [
        \App\Nova\Flexible\Layouts\SimpleWysiwygLayout::class,
        \App\Nova\Flexible\Layouts\FooLayout::class,
    ];
    
    public function handle(\NovaFlexibleContent\Flexible $field)
    {
        parent::handle($field);
        
        $field->layoutsMenuButton('Add new block')
            ->setResolver(\App\Nova\Flexible\Resolvers\WysiwygPageResolver::class)
            ->help('Example help.');
    }
}
\NovaFlexibleContent\Flexible::make('Content')
    ->preset(\App\Nova\Flexible\Presets\WysiwygPagePreset::class);

显示灵活内容

字段将值存储为单个JSON字符串,这意味着在您的应用程序中使用之前需要解析此字符串。

namespace App;

use Illuminate\Database\Eloquent\Model;
use NovaFlexibleContent\Concerns\HasFlexible;

class Post extends Model
{
    use HasFlexible;

    // Collect basic `Layout` instances
    public function getCollectedFlexibleContentAttribute()
    {
        return $this->flexible('flexible-content');
    }
    
    // Cast to specified classes
    public function getCastedFlexibleContentAttribute()
    {
        return $this->flexible('flexible-content', [
            'wysiwyg' => \App\Nova\Flexible\Layouts\WysiwygLayout::class,
            'video' => \App\Nova\Flexible\Layouts\VideoLayout::class,
        ]);
    }
}

IDE辅助工具

为您的应用程序中的灵活布局创建IDE辅助文件。

php artisan nova-flexible-content:ide-helper:layouts
# or
php artisan  nova-flexible-content:ide-helper:layouts --filename custom-file.php

鸣谢