浏览代码

wip cascadeField

jqh 5 年之前
父节点
当前提交
a52c874896

+ 2 - 2
resources/assets/dcat/js/extensions/Helpers.js

@@ -87,11 +87,11 @@ export default class Helpers {
      * @returns {def|boolean}
      */
     has(arr, key) {
-        if (LA.len(arr) < 1) return def;
+        if (this.len(arr) < 1) return def;
         key = String(key).split('.');
 
         for (var i = 0; i < key.length; i++) {
-            if (LA.isset(arr, key[i])) {
+            if (this.isset(arr, key[i])) {
                 arr = arr[key[i]];
             } else {
                 return false;

+ 9 - 0
src/Form.php

@@ -91,6 +91,7 @@ class Form implements Renderable
         Concerns\HasEvents,
         Concerns\HasFiles,
         Concerns\HasSteps,
+        Concerns\HandleCascadeFields,
         Macroable {
             __call as macroCall;
         }
@@ -330,6 +331,14 @@ class Form implements Renderable
         return $this->builder->field($name);
     }
 
+    /**
+     * @return Collection|Field[]
+     */
+    public function fields()
+    {
+        return $this->builder->fields();
+    }
+
     /**
      * @param $column
      *

+ 25 - 0
src/Form/Concerns/HandleCascadeFields.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Dcat\Admin\Form\Concerns;
+
+use Dcat\Admin\Form\Field;
+
+trait HandleCascadeFields
+{
+    /**
+     * @param array    $dependency
+     * @param \Closure $closure
+     *
+     * @return Field\CascadeGroup
+     */
+    public function cascadeGroup(\Closure $closure, array $dependency)
+    {
+        $this->pushField($group = new Field\CascadeGroup($dependency));
+
+        call_user_func($closure, $this);
+
+        $group->end();
+
+        return $group;
+    }
+}

+ 202 - 0
src/Form/Field/CanCascadeFields.php

@@ -0,0 +1,202 @@
+<?php
+
+namespace Dcat\Admin\Form\Field;
+
+use Dcat\Admin\Admin;
+use Dcat\Admin\Form;
+use Illuminate\Support\Arr;
+
+/**
+ * @property Form $form
+ */
+trait CanCascadeFields
+{
+    /**
+     * @var array
+     */
+    protected $conditions = [];
+
+    /**
+     * @var array
+     */
+    protected $cascadeGroups = [];
+
+    /**
+     * @param $operator
+     * @param $value
+     * @param $closure
+     *
+     * @return $this
+     */
+    public function when($operator, $value, $closure = null)
+    {
+        if (func_num_args() == 2) {
+            $closure = $value;
+            $value = $operator;
+            $operator = $this->getDefaultOperator();
+        }
+
+        $this->formatValues($operator, $value);
+
+        $this->addDependents($operator, $value, $closure);
+
+        return $this;
+    }
+
+    protected function getDefaultOperator()
+    {
+        if ($this instanceof MultipleSelect || $this instanceof Checkbox) {
+            return 'in';
+        }
+
+        return '=';
+    }
+
+    /**
+     * @param string $operator
+     * @param mixed  $value
+     */
+    protected function formatValues(string $operator, &$value)
+    {
+        if (in_array($operator, ['in', 'notIn'])) {
+            $value = Arr::wrap($value);
+        }
+
+        if (is_array($value)) {
+            $value = array_map('strval', $value);
+        } else {
+            $value = strval($value);
+        }
+    }
+
+    /**
+     * @param string   $operator
+     * @param mixed    $value
+     * @param \Closure $closure
+     */
+    protected function addDependents(string $operator, $value, \Closure $closure)
+    {
+        $this->conditions[] = compact('operator', 'value', 'closure');
+
+        $this->form->cascadeGroup($closure, [
+            'column' => $this->column(),
+            'index'  => count($this->conditions) - 1,
+            'class'  => $this->getCascadeClass($value),
+        ]);
+    }
+
+    /**
+     * @param mixed $value
+     *
+     * @return string
+     */
+    protected function getCascadeClass($value)
+    {
+        if (is_array($value)) {
+            $value = implode('-', $value);
+        }
+
+        return sprintf('cascade-%s-%s', $this->getElementClassString(), $value);
+    }
+
+    /**
+     * Add cascade scripts to contents.
+     *
+     * @return void
+     */
+    protected function addCascadeScript()
+    {
+        if (empty($this->conditions)) {
+            return;
+        }
+
+        $cascadeGroups = collect($this->conditions)->map(function ($condition) {
+            return [
+                'class'    => $this->getCascadeClass($condition['value']),
+                'operator' => $condition['operator'],
+                'value'    => $condition['value'],
+            ];
+        })->toJson();
+
+        $script = <<<JS
+(function () {
+    var compare = function (a, b, o) {
+        if ($.isArray(b)) {
+            for (var i in b) {
+                if (operator_table[o](a, b[i])) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        
+        return operator_table[o](a, b)
+    };
+    
+    var operator_table = {
+        '=': function(a, b) {
+            if ($.isArray(a) && $.isArray(b)) {
+                return $(a).not(b).length === 0 && $(b).not(a).length === 0;
+            }
+
+            return String(a) === String(b);
+        },
+        '>': function(a, b) {
+            return a > b; 
+        },
+        '<': function(a, b) {
+            return a < b; 
+        },
+        '>=': function(a, b) { return a >= b; },
+        '<=': function(a, b) { return a <= b; },
+        '!=': function(a, b) {
+             return ! operator_table['='](a, b);
+        },
+        'in': function(a, b) { return Dcat.helpers.inObject(a, String(b), true); },
+        'notIn': function(a, b) { return ! Dcat.helpers.inObject(a, String(b), true); },
+        'has': function(a, b) { return Dcat.helpers.inObject(b, String(b), true); },
+    };
+    var cascade_groups = {$cascadeGroups}, event = '{$this->cascadeEvent}';
+
+    $('{$this->getElementClassSelector()}').on(event, function (e) {
+        {$this->getFormFrontValue()}
+
+        cascade_groups.forEach(function (event) {
+            var group = $('div.cascade-group.'+event.class);
+            if (compare(checked, event.value, event.operator)) {
+                group.removeClass('d-none');
+            } else {
+                group.addClass('d-none');
+            }
+        });
+    }).trigger(event);
+})();
+JS;
+
+        Admin::script($script);
+    }
+
+    /**
+     * @return string
+     */
+    protected function getFormFrontValue()
+    {
+        switch (get_class($this)) {
+            case Select::class:
+            case MultipleSelect::class:
+                return 'var checked = $(this).val();';
+            case Radio::class:
+                return <<<JS
+var checked = $('{$this->getElementClassSelector()}:checked').val();
+JS;
+            case Checkbox::class:
+                return <<<JS
+var checked = $('{$this->getElementClassSelector()}:checked').map(function(){
+  return $(this).val();
+}).get();
+JS;
+            default:
+                throw new \InvalidArgumentException('Invalid form field type');
+        }
+    }
+}

+ 72 - 0
src/Form/Field/CascadeGroup.php

@@ -0,0 +1,72 @@
+<?php
+
+namespace Dcat\Admin\Form\Field;
+
+use Dcat\Admin\Form\Field;
+
+class CascadeGroup extends Field
+{
+    /**
+     * @var array
+     */
+    protected $dependency;
+
+    /**
+     * @var string
+     */
+    protected $hide = 'd-none';
+
+    /**
+     * CascadeGroup constructor.
+     *
+     * @param array $dependency
+     */
+    public function __construct(array $dependency)
+    {
+        $this->dependency = $dependency;
+    }
+
+    /**
+     * @param Field $field
+     *
+     * @return bool
+     */
+    public function dependsOn(Field $field)
+    {
+        return $this->dependency['column'] == $field->column();
+    }
+
+    /**
+     * @return int
+     */
+    public function index()
+    {
+        return $this->dependency['index'];
+    }
+
+    /**
+     * @return void
+     */
+    public function visiable()
+    {
+        $this->hide = '';
+    }
+
+    /**
+     * @return string
+     */
+    public function render()
+    {
+        return <<<HTML
+<div class="cascade-group {$this->dependency['class']} {$this->hide}">
+HTML;
+    }
+
+    /**
+     * @return void
+     */
+    public function end()
+    {
+        $this->form->html('</div>')->plain();
+    }
+}

+ 10 - 1
src/Form/Field/Checkbox.php

@@ -7,11 +7,15 @@ use Dcat\Admin\Widgets\Checkbox as WidgetCheckbox;
 
 class Checkbox extends MultipleSelect
 {
+    use CanCascadeFields;
+
     public static $css = [];
     public static $js = [];
 
     protected $style = 'primary';
 
+    protected $cascadeEvent = 'change';
+
     /**
      * @param array|\Closure|string $options
      *
@@ -55,6 +59,8 @@ class Checkbox extends MultipleSelect
             );
         }
 
+        $this->addCascadeScript();
+
         $checkbox = WidgetCheckbox::make(
             $this->getElementName().'[]',
             $this->options,
@@ -65,7 +71,10 @@ class Checkbox extends MultipleSelect
             $checkbox->disable();
         }
 
-        $checkbox->inline()->check(old($this->column, $this->value()));
+        $checkbox
+            ->inline()
+            ->check(old($this->column, $this->value()))
+            ->class($this->getElementClassString());
 
         $this->addVariables([
             'checkbox' => $checkbox,

+ 10 - 1
src/Form/Field/Radio.php

@@ -8,8 +8,12 @@ use Dcat\Admin\Widgets\Radio as WidgetRadio;
 
 class Radio extends Field
 {
+    use CanCascadeFields;
+
     protected $style = 'primary';
 
+    protected $cascadeEvent = 'change';
+
     /**
      * @param array|\Closure|string $options
      *
@@ -53,13 +57,18 @@ class Radio extends Field
             );
         }
 
+        $this->addCascadeScript();
+
         $radio = WidgetRadio::make($this->getElementName(), $this->options, $this->style);
 
         if ($this->attributes['disabled'] ?? false) {
             $radio->disable();
         }
 
-        $radio->inline()->check(old($this->column, $this->value()));
+        $radio
+            ->inline()
+            ->check(old($this->column, $this->value()))
+            ->class($this->getElementClassString());
 
         $this->addVariables([
             'radio' => $radio,

+ 6 - 0
src/Form/Field/Select.php

@@ -11,9 +11,13 @@ use Illuminate\Support\Str;
 
 class Select extends Field
 {
+    use CanCascadeFields;
+
     public static $js = '@select2';
     public static $css = '@select2';
 
+    protected $cascadeEvent = 'change';
+
     /**
      * @var array
      */
@@ -399,6 +403,8 @@ JS;
             $this->script = "$(\"{$this->getElementClassSelector()}\").select2($configs);";
         }
 
+        $this->addCascadeScript();
+
         if ($this->options instanceof \Closure) {
             $this->options = $this->options->bindTo($this->values());
 

+ 2 - 0
src/Widgets/Form.php

@@ -4,6 +4,7 @@ namespace Dcat\Admin\Widgets;
 
 use Closure;
 use Dcat\Admin\Admin;
+use Dcat\Admin\Form\Concerns\HandleCascadeFields;
 use Dcat\Admin\Form\Field;
 use Dcat\Admin\Support\Helper;
 use Dcat\Admin\Traits\HasAuthorization;
@@ -80,6 +81,7 @@ class Form implements Renderable
     use HasHtmlAttributes,
         HasFormResponse,
         HasAuthorization,
+        HandleCascadeFields,
         Macroable {
             __call as macroCall;
         }

+ 1 - 1
src/Widgets/Radio.php

@@ -12,7 +12,7 @@ class Radio extends Widget
 
     protected $style = 'primary';
 
-    protected $right = '14px';
+    protected $right = '16px';
 
     protected $checked;