Completed
Push — master ( 0ba9c9...c4f173 )
by jxlwqq
15s queued 11s
created

src/Form/Field/CanCascadeFields.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace Encore\Admin\Form\Field;
4
5
use Encore\Admin\Admin;
6
use Encore\Admin\Form;
7
use Illuminate\Support\Arr;
8
9
/**
10
 * @property Form $form
11
 */
12
trait CanCascadeFields
13
{
14
    /**
15
     * @var array
16
     */
17
    protected $conditions = [];
18
19
    /**
20
     * @param $operator
21
     * @param $value
22
     * @param $closure
23
     *
24
     * @return $this
25
     */
26
    public function when($operator, $value, $closure = null)
27
    {
28
        if (func_num_args() == 2) {
29
            $closure = $value;
30
            $value = $operator;
31
            $operator = '=';
32
        }
33
34
        $this->formatValues($operator, $value);
35
36
        $this->addDependents($operator, $value, $closure);
37
38
        return $this;
39
    }
40
41
    /**
42
     * @param string $operator
43
     * @param mixed  $value
44
     */
45
    protected function formatValues(string $operator, &$value)
46
    {
47
        if (in_array($operator, ['in', 'notIn'])) {
48
            $value = Arr::wrap($value);
49
        }
50
51
        if (is_array($value)) {
52
            $value = array_map('strval', $value);
53
        } else {
54
            $value = strval($value);
55
        }
56
    }
57
58
    /**
59
     * @param string   $operator
60
     * @param mixed    $value
61
     * @param \Closure $closure
62
     */
63
    protected function addDependents(string $operator, $value, \Closure $closure)
64
    {
65
        $this->conditions[] = compact('operator', 'value', 'closure');
66
67
        $this->form->cascadeGroup($closure, [
68
            'column' => $this->column(),
69
            'index'  => count($this->conditions) - 1,
70
            'class'  => $this->getCascadeClass($value),
71
        ]);
72
    }
73
74
    /**
75
     * {@inheritdoc}
76
     */
77
    public function fill($data)
78
    {
79
        parent::fill($data);
80
81
        $this->applyCascadeConditions();
82
    }
83
84
    /**
85
     * @param mixed $value
86
     *
87
     * @return string
88
     */
89
    protected function getCascadeClass($value)
90
    {
91
        if (is_array($value)) {
92
            $value = implode('-', $value);
93
        }
94
95
        return sprintf('cascade-%s-%s', $this->getElementClassString(), $value);
96
    }
97
98
    /**
99
     * Apply conditions to dependents fields.
100
     *
101
     * @return void
102
     */
103
    protected function applyCascadeConditions()
104
    {
105
        if ($this->form) {
106
            $this->form->fields()
107
                ->filter(function (Form\Field $field) {
108
                    return $field instanceof CascadeGroup
109
                        && $field->dependsOn($this)
110
                        && $this->hitsCondition($field);
111
                })->each->visiable();
112
        }
113
    }
114
115
    /**
116
     * @param CascadeGroup $group
117
     *
118
     * @throws \Exception
119
     *
120
     * @return bool
121
     */
122
    protected function hitsCondition(CascadeGroup $group)
123
    {
124
        $condition = $this->conditions[$group->index()];
125
126
        extract($condition);
127
128
        $old = old($this->column(), $this->value());
129
130
        switch ($operator) {
131
            case '=':
132
                return $old == $value;
133
            case '>':
134
                return $old > $value;
135
            case '<':
136
                return $old < $value;
137
            case '>=':
138
                return $old >= $value;
139
            case '<=':
140
                return $old <= $value;
141
            case '!=':
142
                return $old != $value;
143
            case 'in':
144
                return in_array($old, $value);
145
            case 'notIn':
146
                return !in_array($old, $value);
147
            case 'has':
148
                return in_array($value, $old);
149
            case 'oneIn':
150
                return count(array_intersect($value, $old)) >= 1;
151
            case 'oneNotIn':
152
                return count(array_intersect($value, $old)) == 0;
153
            default:
154
                throw new \Exception("Operator [$operator] not support.");
155
        }
156
    }
157
158
    /**
159
     * Add cascade scripts to contents.
160
     *
161
     * @return void
162
     */
163
    protected function addCascadeScript()
164
    {
165
        if (empty($this->conditions)) {
166
            return;
167
        }
168
169
        $cascadeGroups = collect($this->conditions)->map(function ($condition) {
170
            return [
171
                'class'    => $this->getCascadeClass($condition['value']),
172
                'operator' => $condition['operator'],
173
                'value'    => $condition['value'],
174
            ];
175
        })->toJson();
176
177
        $script = <<<SCRIPT
178
;(function () {
179
    var operator_table = {
180
        '=': function(a, b) {
181
            if ($.isArray(a) && $.isArray(b)) {
182
                return $(a).not(b).length === 0 && $(b).not(a).length === 0;
183
            }
184
185
            return a == b;
186
        },
187
        '>': function(a, b) { return a > b; },
188
        '<': function(a, b) { return a < b; },
189
        '>=': function(a, b) { return a >= b; },
190
        '<=': function(a, b) { return a <= b; },
191
        '!=': function(a, b) {
192
             if ($.isArray(a) && $.isArray(b)) {
193
                return !($(a).not(b).length === 0 && $(b).not(a).length === 0);
194
             }
195
196
             return a != b;
197
        },
198
        'in': function(a, b) { return $.inArray(a, b) != -1; },
199
        'notIn': function(a, b) { return $.inArray(a, b) == -1; },
200
        'has': function(a, b) { return $.inArray(b, a) != -1; },
201
        'oneIn': function(a, b) { return a.filter(v => b.includes(v)).length >= 1; },
202
        'oneNotIn': function(a, b) { return a.filter(v => b.includes(v)).length == 0; },
203
    };
204
    var cascade_groups = {$cascadeGroups};
205
        
206
    cascade_groups.forEach(function (event) {
207
        var default_value = '{$this->getDefault()}' + '';
0 ignored issues
show
It seems like getDefault() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
208
        var class_name = event.class;
209
        if(default_value == event.value) {
210
            $('.'+class_name+'').removeClass('hide');
211
        }
212
    });
213
    
214
    $('{$this->getElementClassSelector()}').on('{$this->cascadeEvent}', function (e) {
215
216
        {$this->getFormFrontValue()}
217
218
        cascade_groups.forEach(function (event) {
219
            var group = $('div.cascade-group.'+event.class);
220
            if( operator_table[event.operator](checked, event.value) ) {
221
                group.removeClass('hide');
222
            } else {
223
                group.addClass('hide');
224
            }
225
        });
226
    })
227
})();
228
SCRIPT;
229
230
        Admin::script($script);
231
    }
232
233
    /**
234
     * @return string
235
     */
236
    protected function getFormFrontValue()
237
    {
238
        switch (get_class($this)) {
239
            case Radio::class:
240
            case RadioButton::class:
241
            case RadioCard::class:
242
            case Select::class:
243
            case BelongsTo::class:
244
            case BelongsToMany::class:
245
            case MultipleSelect::class:
246
                return 'var checked = $(this).val();';
247
            case Checkbox::class:
248
            case CheckboxButton::class:
249
            case CheckboxCard::class:
250
                return <<<SCRIPT
251
var checked = $('{$this->getElementClassSelector()}:checked').map(function(){
252
  return $(this).val();
253
}).get();
254
SCRIPT;
255
            default:
256
                throw new \InvalidArgumentException('Invalid form field type');
257
        }
258
    }
259
}
260