Completed
Push — master ( f2fcb0...d4b67c )
by Song
03:48 queued 51s
created

CanCascadeFields::fill()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
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
use function MongoDB\BSON\toJSON;
9
10
/**
11
 * @property Form $form
12
 */
13
trait CanCascadeFields
14
{
15
    /**
16
     * @var array
17
     */
18
    protected $conditions = [];
19
20
    /**
21
     * @param $operator
22
     * @param $value
23
     * @param $closure
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(),
0 ignored issues
show
Bug introduced by
It seems like column() 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...
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
     * @return string
87
     */
88
    protected function getCascadeClass($value)
89
    {
90
        if (is_array($value)) {
91
            $value = implode('-', $value);
92
        }
93
94
        return sprintf('cascade-%s-%s', $this->getElementClassString(), $value);
0 ignored issues
show
Bug introduced by
It seems like getElementClassString() 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...
95
    }
96
97
    /**
98
     * Apply conditions to dependents fields.
99
     *
100
     * @return void
101
     */
102
    protected function applyCascadeConditions()
103
    {
104
        $this->form->fields()
105
            ->filter(function (Form\Field $field) {
106
                return $field instanceof CascadeGroup
107
                    && $field->dependsOn($this)
108
                    && $this->hitsCondition($field);
109
            })->each->visiable();
110
    }
111
112
    /**
113
     * @param CascadeGroup $group
114
     * @return bool
115
     * @throws \Exception
116
     */
117
    protected function hitsCondition(CascadeGroup $group)
118
    {
119
        $condition = $this->conditions[$group->index()];
120
121
        extract($condition);
122
123
        $old = old($this->column(), $this->value());
0 ignored issues
show
Bug introduced by
It seems like column() 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...
Bug introduced by
It seems like value() 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...
124
125
        switch ($operator) {
126
            case '=' :
127
                return $old == $value;
128
            case '>' :
129
                return $old > $value;
130
            case '<' :
131
                return $old < $value;
132
            case '>=' :
133
                return $old >= $value;
134
            case '<=' :
135
                return $old <= $value;
136
            case '!=' :
137
                return $old != $value;
138
            case 'in' :
139
                return in_array($old, $value);
140
            case 'notIn' :
141
                return !in_array($old, $value);
142
            case 'has' :
143
                return in_array($value, $old);
144
            default:
145
                throw new \Exception("Operator [$operator] not support.");
146
        }
147
    }
148
149
    /**
150
     * Add cascade scripts to contents.
151
     *
152
     * @return void
153
     */
154
    protected function addCascadeScript()
155
    {
156
        if (empty($this->conditions)) {
157
            return;
158
        }
159
160
        $cascadeGroups = collect($this->conditions)->map(function ($condition) {
161
             return [
162
                 'class'    => $this->getCascadeClass($condition['value']),
163
                 'operator' => $condition['operator'],
164
                 'value'    => $condition['value']
165
             ];
166
        })->toJson();
167
168
        $script = <<<SCRIPT
169
(function () {
170
    var operator_table = {
171
        '=': function(a, b) {
172
            if ($.isArray(a) && $.isArray(b)) {
173
                return $(a).not(b).length === 0 && $(b).not(a).length === 0;
174
            }
175
176
            return a == b;
177
        },
178
        '>': function(a, b) { return a > b; },
179
        '<': function(a, b) { return a < b; },
180
        '>=': function(a, b) { return a >= b; },
181
        '<=': function(a, b) { return a <= b; },
182
        '!=': function(a, b) {
183
             if ($.isArray(a) && $.isArray(b)) {
184
                return !($(a).not(b).length === 0 && $(b).not(a).length === 0);
185
             }
186
187
             return a != b;
188
        },
189
        'in': function(a, b) { return $.inArray(a, b) != -1; },
190
        'notIn': function(a, b) { return $.inArray(a, b) == -1; },
191
        'has': function(a, b) { return $.inArray(b, a) != -1; },
192
    };
193
    var cascade_groups = {$cascadeGroups};
194
    $('{$this->getElementClassSelector()}').on('{$this->cascadeEvent}', function (e) {
0 ignored issues
show
Bug introduced by
The property cascadeEvent does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
Bug introduced by
It seems like getElementClassSelector() 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...
195
196
        {$this->getFormFrontValue()}
197
198
        cascade_groups.forEach(function (event) {
199
            var group = $('div.cascade-group.'+event.class);
200
            if( operator_table[event.operator](checked, event.value) ) {
201
                group.removeClass('hide');
202
            } else {
203
                group.addClass('hide');
204
            }
205
        });
206
    })
207
})();
208
SCRIPT;
209
210
        Admin::script($script);
211
    }
212
213
    /**
214
     * @return string
215
     */
216
    protected function getFormFrontValue()
217
    {
218
        switch (get_class($this)) {
219
            case Radio::class:
220
            case RadioButton::class:
221
            case RadioCard::class:
222
            case Select::class:
223
            case BelongsTo::class:
224
            case BelongsToMany::class:
225
            case MultipleSelect::class:
226
                return "var checked = $(this).val();";
227
            case Checkbox::class:
228
            case CheckboxButton::class:
229
            case CheckboxCard::class:
230
                return <<<SCRIPT
231
var checked = $('{$this->getElementClassSelector()}:checked').map(function(){
0 ignored issues
show
Bug introduced by
It seems like getElementClassSelector() 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...
232
  return $(this).val();
233
}).get();
234
SCRIPT;
235
            default:
236
                throw new \InvalidArgumentException('Invalid form field type');
237
        }
238
    }
239
}
240