Completed
Push — master ( ef0ca4...a4bcce )
by Song
02:56
created

Select   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 371
Duplicated Lines 26.42 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 98
loc 371
rs 10
c 0
b 0
f 0
wmc 23
lcom 1
cbo 5

9 Methods

Rating   Name   Duplication   Size   Complexity  
B options() 0 24 6
A groups() 0 6 1
A load() 0 30 2
A loads() 0 41 1
B model() 30 30 6
A loadRemoteOptions() 18 28 1
A ajax() 50 50 1
A config() 0 6 1
A render() 0 32 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
namespace Encore\Admin\Form\Field;
4
5
use Encore\Admin\Facades\Admin;
6
use Encore\Admin\Form\Field;
7
use Illuminate\Contracts\Support\Arrayable;
8
use Illuminate\Database\Eloquent\Model;
9
use Illuminate\Support\Arr;
10
use Illuminate\Support\Str;
11
12
class Select extends Field
13
{
14
    /**
15
     * @var array
16
     */
17
    protected static $css = [
18
        '/vendor/laravel-admin/AdminLTE/plugins/select2/select2.min.css',
19
    ];
20
21
    /**
22
     * @var array
23
     */
24
    protected static $js = [
25
        '/vendor/laravel-admin/AdminLTE/plugins/select2/select2.full.min.js',
26
    ];
27
28
    /**
29
     * @var array
30
     */
31
    protected $groups = [];
32
33
    /**
34
     * @var array
35
     */
36
    protected $config = [];
37
38
    /**
39
     * Set options.
40
     *
41
     * @param array|callable|string $options
42
     *
43
     * @return $this|mixed
44
     */
45
    public function options($options = [])
46
    {
47
        // remote options
48
        if (is_string($options)) {
49
            // reload selected
50
            if (class_exists($options) && in_array(Model::class, class_parents($options))) {
51
                return $this->model(...func_get_args());
0 ignored issues
show
Documentation introduced by
func_get_args() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
52
            }
53
54
            return $this->loadRemoteOptions(...func_get_args());
0 ignored issues
show
Documentation introduced by
func_get_args() is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
55
        }
56
57
        if ($options instanceof Arrayable) {
58
            $options = $options->toArray();
59
        }
60
61
        if (is_callable($options)) {
62
            $this->options = $options;
0 ignored issues
show
Documentation Bug introduced by
It seems like $options of type callable is incompatible with the declared type array of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
63
        } else {
64
            $this->options = (array) $options;
65
        }
66
67
        return $this;
68
    }
69
70
    /**
71
     * @param array $groups
72
     */
73
74
    /**
75
     * Set option groups.
76
     *
77
     * eg: $group = [
78
     *        [
79
     *        'label' => 'xxxx',
80
     *        'options' => [
81
     *            1 => 'foo',
82
     *            2 => 'bar',
83
     *            ...
84
     *        ],
85
     *        ...
86
     *     ]
87
     *
88
     * @param array $groups
89
     *
90
     * @return $this
91
     */
92
    public function groups(array $groups)
93
    {
94
        $this->groups = $groups;
95
96
        return $this;
97
    }
98
99
    /**
100
     * Load options for other select on change.
101
     *
102
     * @param string $field
103
     * @param string $sourceUrl
104
     * @param string $idField
105
     * @param string $textField
106
     *
107
     * @return $this
108
     */
109
    public function load($field, $sourceUrl, $idField = 'id', $textField = 'text')
110
    {
111
        if (Str::contains($field, '.')) {
112
            $field = $this->formatName($field);
113
            $class = str_replace(['[', ']'], '_', $field);
114
        } else {
115
            $class = $field;
116
        }
117
118
        $script = <<<EOT
119
$(document).off('change', "{$this->getElementClassSelector()}");
120
$(document).on('change', "{$this->getElementClassSelector()}", function () {
121
    var target = $(this).closest('.fields-group').find(".$class");
122
    $.get("$sourceUrl?q="+this.value, function (data) {
123
        target.find("option").remove();
124
        $(target).select2({
125
            data: $.map(data, function (d) {
126
                d.id = d.$idField;
127
                d.text = d.$textField;
128
                return d;
129
            })
130
        }).trigger('change');
131
    });
132
});
133
EOT;
134
135
        Admin::script($script);
136
137
        return $this;
138
    }
139
140
    /**
141
     * Load options for other selects on change.
142
     *
143
     * @param string $fields
144
     * @param string $sourceUrls
145
     * @param string $idField
146
     * @param string $textField
147
     *
148
     * @return $this
149
     */
150
    public function loads($fields = [], $sourceUrls = [], $idField = 'id', $textField = 'text')
151
    {
152
        $fieldsStr = implode('.', $fields);
153
        $urlsStr = implode('^', $sourceUrls);
154
        $script = <<<EOT
155
var fields = '$fieldsStr'.split('.');
156
var urls = '$urlsStr'.split('^');
157
158
var refreshOptions = function(url, target) {
159
    $.get(url).then(function(data) {
160
        target.find("option").remove();
161
        $(target).select2({
162
            data: $.map(data, function (d) {
163
                d.id = d.$idField;
164
                d.text = d.$textField;
165
                return d;
166
            })
167
        }).trigger('change');
168
    });
169
};
170
171
$(document).off('change', "{$this->getElementClassSelector()}");
172
$(document).on('change', "{$this->getElementClassSelector()}", function () {
173
    var _this = this;
174
    var promises = [];
175
176
    fields.forEach(function(field, index){
177
        var target = $(_this).closest('.fields-group').find('.' + fields[index]);
178
        promises.push(refreshOptions(urls[index] + "?q="+ _this.value, target));
179
    });
180
181
    $.when(promises).then(function() {
182
        console.log('开始更新其它select的选择options');
183
    });
184
});
185
EOT;
186
187
        Admin::script($script);
188
189
        return $this;
190
    }
191
192
    /**
193
     * Load options from current selected resource(s).
194
     *
195
     * @param string $model
196
     * @param string $idField
197
     * @param string $textField
198
     *
199
     * @return $this
200
     */
201 View Code Duplication
    public function model($model, $idField = 'id', $textField = 'name')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
    {
203
        if (!class_exists($model)
204
            || !in_array(Model::class, class_parents($model))
205
        ) {
206
            throw new \InvalidArgumentException("[$model] must be a valid model class");
207
        }
208
209
        $this->options = function ($value) use ($model, $idField, $textField) {
0 ignored issues
show
Documentation Bug introduced by
It seems like function ($value) use($m...$idField)->toArray(); } of type object<Closure> is incompatible with the declared type array of property $options.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
210
            if (empty($value)) {
211
                return [];
212
            }
213
214
            $resources = [];
215
216
            if (is_array($value)) {
217
                if (Arr::isAssoc($value)) {
218
                    $resources[] = array_get($value, $idField);
219
                } else {
220
                    $resources = array_column($value, $idField);
221
                }
222
            } else {
223
                $resources[] = $value;
224
            }
225
226
            return $model::find($resources)->pluck($textField, $idField)->toArray();
227
        };
228
229
        return $this;
230
    }
231
232
    /**
233
     * Load options from remote.
234
     *
235
     * @param string $url
236
     * @param array  $parameters
237
     * @param array  $options
238
     *
239
     * @return $this
240
     */
241 View Code Duplication
    protected function loadRemoteOptions($url, $parameters = [], $options = [])
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
242
    {
243
        $ajaxOptions = [
244
            'url' => $url.'?'.http_build_query($parameters),
245
        ];
246
247
        $ajaxOptions = json_encode(array_merge($ajaxOptions, $options));
248
249
        $this->script = <<<EOT
250
251
$.ajax($ajaxOptions).done(function(data) {
252
253
  var select = $("{$this->getElementClassSelector()}");
254
255
  select.select2({data: data});
256
  
257
  var value = select.data('value') + '';
258
  
259
  if (value) {
260
    value = value.split(',');
261
    select.select2('val', value);
262
  }
263
});
264
265
EOT;
266
267
        return $this;
268
    }
269
270
    /**
271
     * Load options from ajax results.
272
     *
273
     * @param string $url
274
     * @param $idField
275
     * @param $textField
276
     *
277
     * @return $this
278
     */
279 View Code Duplication
    public function ajax($url, $idField = 'id', $textField = 'text')
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
280
    {
281
        $configs = array_merge([
282
            'allowClear'         => true,
283
            'placeholder'        => $this->label,
284
            'minimumInputLength' => 1,
285
        ], $this->config);
286
287
        $configs = json_encode($configs);
288
        $configs = substr($configs, 1, strlen($configs) - 2);
289
290
        $this->script = <<<EOT
291
292
$("{$this->getElementClassSelector()}").select2({
293
  ajax: {
294
    url: "$url",
295
    dataType: 'json',
296
    delay: 250,
297
    data: function (params) {
298
      return {
299
        q: params.term,
300
        page: params.page
301
      };
302
    },
303
    processResults: function (data, params) {
304
      params.page = params.page || 1;
305
306
      return {
307
        results: $.map(data.data, function (d) {
308
                   d.id = d.$idField;
309
                   d.text = d.$textField;
310
                   return d;
311
                }),
312
        pagination: {
313
          more: data.next_page_url
314
        }
315
      };
316
    },
317
    cache: true
318
  },
319
  $configs,
320
  escapeMarkup: function (markup) {
321
      return markup;
322
  }
323
});
324
325
EOT;
326
327
        return $this;
328
    }
329
330
    /**
331
     * Set config for select2.
332
     *
333
     * all configurations see https://select2.org/configuration/options-api
334
     *
335
     * @param string $key
336
     * @param mixed  $val
337
     *
338
     * @return $this
339
     */
340
    public function config($key, $val)
341
    {
342
        $this->config[$key] = $val;
343
344
        return $this;
345
    }
346
347
    /**
348
     * {@inheritdoc}
349
     */
350
    public function render()
351
    {
352
        $configs = array_merge([
353
            'allowClear'  => true,
354
            'placeholder' => $this->label,
355
        ], $this->config);
356
357
        $configs = json_encode($configs);
358
359
        if (empty($this->script)) {
360
            $this->script = "$(\"{$this->getElementClassSelector()}\").select2($configs);";
361
        }
362
363
        if ($this->options instanceof \Closure) {
364
            if ($this->form) {
365
                $this->options = $this->options->bindTo($this->form->model());
366
            }
367
368
            $this->options(call_user_func($this->options, $this->value));
369
        }
370
371
        $this->options = array_filter($this->options, 'strlen');
372
373
        $this->addVariables([
374
            'options' => $this->options,
375
            'groups'  => $this->groups,
376
        ]);
377
378
        $this->attribute('data-value', implode(',', (array) $this->value()));
379
380
        return parent::render();
0 ignored issues
show
Bug Compatibility introduced by
The expression parent::render(); of type string|Illuminate\View\V...\Contracts\View\Factory adds the type Illuminate\Contracts\View\Factory to the return on line 380 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
381
    }
382
}
383