表单构造器 - 字段
Builder
概述
类似于 Repeater,Builder 组件允许你输出重复的表单组件的 JSON 数组。不像 Repeater 那样只定义一个表单 Schema,Builder 允许你定义不同的 Schema 块,你可以使用任何顺序重复。这对于创建更高级的数组结构非常有用。
Builder 主要用于通过预定义块创建网页内容。这可能是营销网站的内容,甚至可能是在线表单中的字段。下面的示例在页面内容中为不同元素定义了多个块。在网站的前端,你可以循环 JSON 中的每个块,并根据自己的意愿对其进行格式化。
use Filament\Forms\Components\Builder;use Filament\Forms\Components\FileUpload;use Filament\Forms\Components\Select;use Filament\Forms\Components\Textarea;use Filament\Forms\Components\TextInput; Builder::make('content')    ->blocks([        Builder\Block::make('heading')            ->schema([                TextInput::make('content')                    ->label('Heading')                    ->required(),                Select::make('level')                    ->options([                        'h1' => 'Heading 1',                        'h2' => 'Heading 2',                        'h3' => 'Heading 3',                        'h4' => 'Heading 4',                        'h5' => 'Heading 5',                        'h6' => 'Heading 6',                    ])                    ->required(),            ])            ->columns(2),        Builder\Block::make('paragraph')            ->schema([                Textarea::make('content')                    ->label('Paragraph')                    ->required(),            ]),        Builder\Block::make('image')            ->schema([                FileUpload::make('url')                    ->label('Image')                    ->image()                    ->required(),                TextInput::make('alt')                    ->label('Alt text')                    ->required(),            ]),    ]) 
  
我们建议你在数据库中使用 JSON 类型存储 Builder 数据。此外,如果使用 Eloquent,请确保该列有 array casts。
如上例所示,块(Block)在组件的 blocks() 方法中定义。块是 Builder\Block 对象,需要传入一个唯一名称和一个组件 Schema:
use Filament\Forms\Components\Builder;use Filament\Forms\Components\TextInput; Builder::make('content')    ->blocks([        Builder\Block::make('heading')            ->schema([                TextInput::make('content')->required(),                // ...            ]),        // ...    ])设置块标签
默认情况下,块的标签是基于块名自动生成。要重写块标签,请使用 label() 方法。如果你想使用本地化翻译字符用这种方法自定义标签很有用。
use Filament\Forms\Components\Builder; Builder\Block::make('heading')    ->label(__('blocks.heading'))基于内容创建项目标签
使用同一个 label() 方法, 可以为 Builder 项目添加标签。该方法接受一个闭包,闭包以 $state 变量接收项目数据。如果 $state 为空,你应该返回在块选择器中显示的块标签。否则,请返回用作项目标签的字符串:
use Filament\Forms\Components\Builder;use Filament\Forms\Components\TextInput; Builder\Block::make('heading')    ->schema([        TextInput::make('content')            ->live(onBlur: true)            ->required(),        // ...    ])    ->label(function (?array $state): string {        if ($state === null) {            return 'Heading';        }         return $state['content'] ?? 'Untitled heading';    })如果你想在使用表单时,实时更新项目标签,那么任何 $state 用到的字段都应该是 live() 的。
 
  
Builder 项目编号
默认情况下,Builder 中的项目在其标签旁有一个数字。你可以使用 blockNumbers(false) 方法,禁用此编号:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->blockNumbers(false)设置块图标
块也可以有图标,显示在图标旁边。你可以将图标名传入到 icon() 方法中,添加图标:
use Filament\Forms\Components\Builder; Builder\Block::make('paragraph')    ->icon('heroicon-m-bars-3-bottom-left') 
  
在 Block 头部添加图标
默认情况下,Builder 中的 Block 在其头部的标签旁没有图标,只在下拉菜单用以添加新的 Block。你可以使用 blockIcons() 方法启用它:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->blockIcons()添加项目
Builder 下面有一个 Action 按钮,用以允许用户添加新项目。
设置添加按钮标签
使用 addActionLabel() 方法,你可以自定义添加 Builder 项目的按钮的标签:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->addActionLabel('Add a new block')对齐添加 Action 按钮
默认情况下,添加 Action 剧中对齐。你可以使用 addActionAlignment() 方法,传递一个 Alignment 选项如 Alignment::Start 或 Alignment::End,对其进行调整:
use Filament\Forms\Components\Builder;use Filament\Support\Enums\Alignment; Builder::make('content')    ->schema([        // ...    ])    ->addActionAlignment(Alignment::Start)防止用户添加新项目
使用 addable(false) 方法,你可以阻止用户添加新项目到 Builder 中:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->addable(false)删除项目
在每个项目中都有一个 action 按钮用于允许用户删除项目。
防止用户删除项目
使用 deletable(false) 方法,可以阻止用户从 Builder 中删除项目:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->deletable(false)项目重新排序
每个项目中都有一个按钮,用于允许用户拖拽对其重新排序。
防止用户重新排序
使用 reorderable(false) 方法,可以阻止用户对 Builder 项目重新排序:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->reorderable(false)使用按钮重新排序项目
使用 reorderableWithButtons() 方法,你可以启用通过按钮将项目上下移动进行排序:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->reorderableWithButtons() 
  
阻止用户使用拖拽排序
使用 reorderableWithDragAndDrop(false) 方法,你可以阻止项目通过拖拽进行排序:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->reorderableWithDragAndDrop(false)折叠项目
Builder 可以设为可折叠的 collapsible(),以在长表单中隐藏内容:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->collapsible()你也可以默认折叠所有项目:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->collapsed() 
  
克隆项目
使用 cloneable() 方法,你可以允许复制 Builder 项目:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->cloneable() 
  
自定义块选择器(block picker)
修改块选择器中的列数
块选择器默认只有 1 列。你可以将列数传递给 blockPickerColumns() 对其进行自定义:
use Filament\Forms\Components\Builder; Builder::make()    ->blockPickerColumns(2)    ->blocks([        // ...    ])这个方法可以通过几种不同的方式使用:
- 你可以传递整数,如 blockPickerColumns(2)。该整数是lg临界点及其以上的列数。所有比之小的设备只有 1 列。
- 你可以传递数组,其中键为临界点、值为列数。比如 blockPickerColumns(['md' => 2, 'xl' => 4])将在中等设备中创建 2 列布局,在超大设备中创建 4 列布局。较小(smaller)设备的临界点使用 1 列布局,除非你使用default数组键对其进行设置。
临界点(sm、md、lg、xl、2xl)由 Tailwind 定义,可以在 Tailwind 文档中找到。
增加块选择器的宽度
当你增加列数时,下拉列表的宽度应该会递增以处理额外的列。如果你想要更多的控制,可以使用 blockPickerWidth() 方法手动设置下拉列表的最大宽度。选项对应于 Tailwind 的最大宽度比例。选项为 xs、sm、md、lg、xl、2xl、3xl、4xl、5xl、6xl、7xl:
use Filament\Forms\Components\Builder; Builder::make()    ->blockPickerColumns(3)    ->blockPickerWidth('2xl')    ->blocks([        // ...    ])限制块可被使用的次数
默认情况下,每个块都可以在 Builder 中使用无限多次。你可以在块上使用 maxItems() 方法限制块的使用次数:
use Filament\Forms\Components\Builder; Builder\Block::make('heading')    ->schema([        // ...    ])    ->maxItems(1)Builder 验证
除了验证页面中列出的所有规则之外,还有一些其他专用于 Builder 的验证规则。
项目数量验证
使用 minFiles() 和 maxFiles() 方法,你可以验证 Builder 中项目的最小和最大数量:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->minItems(1)    ->maxItems(5)使用 $get() 访问父级字段值
所有表单组件都能使用 $get() 和 $set() 来访问另一个字段的值。不过,在 Builder schema 内使用时,你可能会遇到非预期的行为。
这是因为 $get() 和 $set() 默认限定在当前 Builder 项目中。这意味着你无需知道当前表单组件属于哪个 Builder 项目,就可以在 Builder 项目内与其他字段交互。
这样的后果是,当无法与 Builder 之外的字段交互时,你会产生疑惑。我们可以使用 ../ 语法来解决该问题 - $get('../../parent_field_name')。
假设表单有这样的数据结构:
[    'client_id' => 1,     'builder' => [        'item1' => [            'service_id' => 2,        ],    ],]你要在 Builder 项内检索 client_id 的值。
$get() 与当前的 Builder 项目相关,因此 $get('client_id') 是在查找 $get('builder.item1.client_id')。
你可以使用 ../ 去到该数据结构的上一级,因此 $get('../client_id') 是 $get('builder.client_id'),而 $get('../../client_id') 是 $get('client_id').
$get() 不带参数或者 $get('')或者 $get('./')的特例, 将总是返回当前 Builder 项目的所有数据的数组。
自定义 Builder 项 Action
该字段使用 action 对象,在其中对按钮进行自定义。通过传递一个函数到 action 注册方法中,可以自定义这些按钮。该函数可以访问 $action 对象,用于自定义。下面是一些可以用于自定义 action 的方法:
- addAction()
- addBetweenAction()
- cloneAction()
- collapseAction()
- collapseAllAction()
- deleteAction()
- expandAction()
- expandAllAction()
- moveDownAction()
- moveUpAction()
- reorderAction()
下面是一个如何自定义 Action 的示例:
use Filament\Forms\Components\Actions\Action;use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->collapseAllAction(        fn (Action $action) => $action->label('Collapse all content'),    )使用模态框确认 Builder Action
在 action 对象中使用 requiresConfirmation() 方法,你可以使用模态框确认 action。你可以使用所有模态框自定义方法来修改其内容和行为:
use Filament\Forms\Components\Actions\Action;use Filament\Forms\Components\Builder; Builder::make('content')    ->blocks([        // ...    ])    ->deleteAction(        fn (Action $action) => $action->requiresConfirmation(),    )
addAction()、addBetweenAction()、collapseAction()、collapseAllAction()、expandAction()、expandAllAction()andreorderAction()方法不支持确认模态框,因为点击这些按钮不会发起要求显示模态框的网络请求。
将额外的项目 Action 添加到 Builder 中
通过将 Action 对象传入到 extraItemActions() 方法,你可以将新的 Action 按钮添加到每个 Builder 项目中:
use Filament\Forms\Components\Actions\Action;use Filament\Forms\Components\Builder;use Filament\Forms\Components\TextInput;use Illuminate\Support\Facades\Mail; Builder::make('content')    ->blocks([        Builder\Block::make('contactDetails')            ->schema([                TextInput::make('email')                    ->label('Email address')                    ->email()                    ->required(),                // ...            ]),        // ...    ])    ->extraItemActions([        Action::make('sendEmail')            ->icon('heroicon-m-square-2-stack')            ->action(function (array $arguments, Builder $component): void {                $itemData = $component->getItemState($arguments['item']);                                Mail::to($itemData['email'])                    ->send(                        // ...                    );            }),    ])本例中,$arguments['item'] 提供了当前 Builder 项目的 ID。你可以在 Builder 组件中使用 getItemState() 方法验证该 Builder 项目中的数据。该方法返回该项目已验证的数据。如果数据无效,它会取消该操作并在表单中显示该项目的错误消息。
如果你想获取当前项目的原始数据,而不对其进行验证,你可以使用 $component->getRawItemState($arguments['item'])。
如果你想操作整个 Builder 的原始数据,比如,添加、删除或修改项目,你可以使用 $component->getState() 获取数据,并 $component->state($state) 对其重新设置:
use Illuminate\Support\Str; // Get the raw data for the entire builder$state = $component->getState(); // Add an item, with a random UUID as the key$state[Str::uuid()] = [    'type' => 'contactDetails',    'data' => [        'email' => auth()->user()->email,    ],]; // Set the new data for the builder$component->state($state);预览 Block
如果你倾向于在 Builder 渲染只读预览而非 Block 表单,可以使用 blockPreviews() 方法。这只会渲染每个 Block 的预览(preview())而非表单。Block 数据会以一个同名变量传递给预览 Blade 视图:
use Filament\Forms\Components\Builder;use Filament\Forms\Components\Builder\Block;use Filament\Forms\Components\TextInput; Builder::make('content')    ->blockPreviews()    ->blocks([        Block::make('heading')            ->schema([                TextInput::make('text')                    ->placeholder('Default heading'),            ])            ->preview('filament.content.block-previews.heading'),    ])在 /resources/views/filament/content/block-previews/heading.blade.php 中,你可以像这样访问 Block 数据:
<h1>    {{ $text ?? 'Default heading' }}</h1>交互式 Block 预览
默认情况下,预览内容不是交互式的,单击它将打开该 Block 的"编辑"莫天空以管理其设置。如果你有想在 Block 预览中保持交互式的链接和按钮,可以使用 blockPreviews() 方法的 areInteractive: true 参数:
use Filament\Forms\Components\Builder; Builder::make('content')    ->blockPreviews(areInteractive: true)    ->blocks([        //    ])测试 builder
在内部,Builer 为项目生成 UUID,以便更容易地在 Livewire HTML 中跟踪它们。这意味着,当测试带有 Builder 的表单时,需要确保表单和测试之间的 UUID 一致。这可能很棘手,如果操作不正确,测试可能会失败,因为测试需要的是 UUID,而不是数字键。
但是,由于 Livewire 不需要跟踪测试中的 UUID,你可以在测试开始时使用 Builder::fake() 方法禁用 UUID 生成并用数字键替换它们:
use Filament\Forms\Components\Builder;use function Pest\Livewire\livewire; $undoBuilderFake = Builder::fake(); livewire(EditPost::class, ['record' => $post])    ->assertFormSet([        'content' => [            [                'type' => 'heading',                'data' => [                    'content' => 'Hello, world!',                    'level' => 'h1',                ],            ],            [                'type' => 'paragraph',                'data' => [                    'content' => 'This is a test post.',                ],            ],        ],        // ...    ]); $undoBuilderFake();你可能还发现,通过将函数传递到 assertFormSet() 方法来访问测试子项数量也是很有用的:
use Filament\Forms\Components\Builder;use function Pest\Livewire\livewire; $undoBuilderFake = Builder::fake(); livewire(EditPost::class, ['record' => $post])    ->assertFormSet(function (array $state) {        expect($state['content'])            ->toHaveCount(2);    }); $undoBuilderFake();Still need help? Join our Discord community or open a GitHub discussion
 
 
 
 
 
