reedware / nova-select-toggle-field

添加一个Laravel Nova选择字段,其值将根据另一个选择字段的内容而变化。

v1.0.0 2020-05-19 13:01 UTC

This package is auto-updated.

Last update: 2024-09-07 20:57:58 UTC


README

Latest Stable Version Total Downloads

此包提供了一个Laravel Nova选择字段,其值将根据另一个选择字段的内容而变化。

简介

虽然Laravel Nova提供了选择下拉框,甚至可以在其中搜索,但有时我发现不同的用户体验更适合我的需求。选择框有时可能非常长,如果你不熟悉你正在处理的资源,你可能会不知道从哪里开始搜索。

在过去,我还需要采取一些更高级的方法,这些方法涉及动态生成下拉选项。当列表变长时,计算时间开始影响表单的性能。

这两个问题的解决方案之一就是这个包试图提供的“选择切换”。一个“选择切换”实际上是一个下拉选择框,其选项可以根据另一个下拉选择框的内容而变化。

以下是实际操作效果

Example

安装

使用Composer在Laravel Nova应用程序中安装此包

composer require reedware/nova-select-toggle-field

你需要在任何计划使用该字段的资源中包含以下字段

Reedware\NovaSelectToggleField\SelectToggle

或者,你可以安装我的Field Manager Package,该包旨在帮助减少每个资源文件顶部的大量字段包含。

使用方法

由于选择切换字段依赖于另一个字段,因此你需要定义至少两个字段(一个为“目标”字段,另一个为“切换”字段)。

抽象示例

以下是通用设置

public function fields(Request $request)
{
  return [
        Select::make('Target Field', 'target_field')
            ->options([
                /* ... values => labels ... */
            ]),

        SelectToggle::make('Toggle Field', 'toggle_field')
            ->target('target_field')
            ->options(function($targetValue) {
              /**
               * $targetValue is the in-flight form value from the "target_field" field.
               * Use this value to return your dynamically generated list. The value
               * will be the value from the target, not the label within the UI.
               */
              return [
                  /* ... values => labels ... */
              ];
            })
  ];
}

具体示例

以下是您如何在项目中重新创建介绍示例的方法

public function fields(Request $request)
{
  return [
        Select::make('Group', 'group_name')
            ->help('The group containing the resource.')
            ->options(
                collect(Nova::$resources)->mapWithKeys(function($resource) {
                    return [$resource::$group => str_replace('.', ' > ', $resource::$group)];
                })->unique()->sort()
            ),

        SelectToggle::make('Resource', 'resource_name')
            ->help('The resource within the group.')
            ->target('group_name')
            ->options(function($targetValue) {
                return collect(Nova::$resources)->filter(function($resource) use ($targetValue) {
                    return $resource::$group == $targetValue;
                })->mapWithKeys(function($resource) {
                    return [$resource => $resource::label()];
                })->sort()->toArray();
            })
  ];
}

复杂示例

本节包含一个复杂示例,这是我在实际项目中使用的一个示例。这是我的“权限”资源的一部分,用户可以创建新的权限,并将其与策略方法关联起来。目标下拉框包含我应用程序中的资源列表,并且这些资源已被按其资源组分组。切换下拉框包含策略中可许可的方法列表(例如“查看所有”、“创建”等),并且它只显示与“目标”下拉框中指定的资源相关的选项。

在此处,我使用了两个其他包,这些包对于此示例是可选的

  • 我的Field Manager Package,它允许我使用Field::select(...)而不是Select::make(...)(这是为了在我的资源中只有一个Field包含)。
  • 我的Value Toggle Field,它允许我根据其他字段的内容仅显示某些字段。我使用此功能在指定目标选项之前隐藏选择切换字段。

以下是代码

/**
 * Returns the fields displayed by the resource.
 *
 * @param  \Illuminate\Http\Request  $request
 *
 * @return array
 */
public function fields(Request $request)
{
  return [
  
        // "Resource" field
        Field::select(__('Resource'), 'resource_name')
            ->help('The resource tied to this permission.')
            ->required()
            ->options($this->getPermissionResourceOptions())
            ->displayUsingLabels(),

        // "Ability" (on Create form)
        Field::selectToggle(__('Ability'), 'ability_name')
            ->onlyOnForms()
            ->hideWhenUpdating()
            ->help('The ability being granted to the resource.')
            ->target('resource_name')
            ->options(function($targetValue) {
                return $this->getPolicyMethodOptions($targetValue);
            })
            ->displayUsing(function($value) {
                return static::getLabelForAbility($value);
            })
            ->valueToggle(function($toggle) {
                return $toggle->whereNotNull('resource_name');
            }),

        // "Ability" (on Update form)
        Field::text(__('Ability'), 'ability_name')
            ->onlyOnForms()
            ->hideWhenCreating()
            ->help('The ability name of this permission.')
            ->readonly()
            ->resolveUsing(function($value) {
                return static::getLabelForAbility($value);
            }),

        // "Ability" (on Display & Index)
        Field::text(__('Ability'), 'ability_name')
            ->exceptOnForms()
            ->displayUsing(function($value) {
                return static::getLabelForAbility($value);
            })

  ];
}

/**
 * Returns the permission resource options.
 *
 * @return array
 */
public function getPermissionResourceOptions()
{
    // Determine all of the resources
    $resources = collect(Nova::$resources);

    // Filter to only resources that have policies
    $resources = $resources->filter(function($resource) {
        return !is_null(Gate::getPolicyFor($resource::$model));
    });

    // Convert the resources into selection options
    $options = $resources->map(function($resource) {

        return [
            'label' => __($resource::label()),
            'value' => $resource,
            'group' => str_replace('.', ' > ', $resource::$group)
        ];

    });

    // Sort the options
    $options = $options->sortBy(function($option) {
        return str_pad($option['group'], 255) . '.' . str_pad($option['label'], 255);
    });

    // Exclude the resources that won't have any selectable abilities
    $options = $options->filter(function($option) {
        return !empty($this->getPolicyMethodOptions($option['value']));
    });

    // Return the options
    return $options->all();
}

/**
 * Returns the policy method options for the specified resource.
 *
 * @param  string  $resource
 *
 * @return array
 */
public function getPolicyMethodOptions($resource)
{
    // Determine the model from the resource
    $model = $resource::$model;

    // Determine the policy for the model
    $policy = Gate::getPolicyFor($model);

    // Determine the policy methods
    $methods = $policy::getPermissableMethods();

    // Determine the existing options
    $existing = static::newModel()->newQuery()->where('resource_name', $resource)->pluck('ability_name')->toArray();

    // Filter out the existing options
    $remaining = array_filter($methods, function($method) use ($existing) {
        return !in_array($method, $existing);
    });

    // Include the current option
    if($this->exists && $resource == $this->resource_name) {
        $options[] = $this->ability_name;
    }

    // Determine the method options
    $options = collect($remaining)->mapWithKeys(function($ability) {
        return [$ability => static::getLabelForAbility($ability)];
    });

    // Return the options
    return $options->all();
}

/**
 * Returns the label for the specified ability.
 *
 * @param  string  $ability
 *
 * @return string
 */
public static function getLabelForAbility($ability)
{
    return Str::title(Str::snake($ability, ' '));
}