Completed
Pull Request — master (#4171)
by Muhlis
09:37
created

Select::load()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 57

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 5
dl 0
loc 57
rs 8.9381
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
     * @var array
22
     */
23
    protected static $js = [
24
        '/vendor/laravel-admin/AdminLTE/plugins/select2/select2.full.min.js',
25
    ];
26
    /**
27
     * @var array
28
     */
29
    protected $groups = [];
30
    /**
31
     * @var array
32
     */
33
    protected $config = [];
34
35
    /**
36
     * Set options.
37
     *
38
     * @param array|callable|string $options
39
     *
40
     * @return $this|mixed
41
     */
42
    public function options($options = [])
43
    {
44
        // remote options
45
        if (is_string($options)) {
46
            // reload selected
47
            if (class_exists($options) && in_array(Model::class, class_parents($options))) {
48
                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...
49
            }
50
51
            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...
52
        }
53
        if ($options instanceof Arrayable) {
54
            $options = $options->toArray();
55
        }
56
        if (is_callable($options)) {
57
            $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...
58
        } else {
59
            $this->options = (array) $options;
60
        }
61
62
        return $this;
63
    }
64
65
    /**
66
     * @param array $groups
67
     */
68
69
    /**
70
     * Set option groups.
71
     *
72
     * eg: $group = [
73
     *        [
74
     *        'label' => 'xxxx',
75
     *        'options' => [
76
     *            1 => 'foo',
77
     *            2 => 'bar',
78
     *            ...
79
     *        ],
80
     *        ...
81
     *     ]
82
     *
83
     * @param array $groups
84
     *
85
     * @return $this
86
     */
87
    public function groups(array $groups)
88
    {
89
        $this->groups = $groups;
90
91
        return $this;
92
    }
93
94
    /**
95
     * Load options for other select on change.
96
     *
97
     * @param string $field
98
     * @param string $sourceUrl
99
     * @param string $idField
100
     * @param string $textField
101
     *
102
     * @return $this
103
     */
104
    public function load($field, $sourceUrl, $idField = 'id', $textField = 'text', bool $allowClear = true)
105
    {
106
        if (Str::contains($field, '.')) {
107
            $field = $this->formatName($field);
108
            $class = str_replace(['[', ']'], '_', $field);
109
        } else {
110
            $class = $field;
111
        }
112
        $placeholder = json_encode([
113
            'id'   => '',
114
            'text' => trans('admin.choose'),
115
        ]);
116
        $script = <<<JS
117
$(document).off('change', "{$this->getElementClassSelector()}");
118
$(document).on('change', "{$this->getElementClassSelector()}", function () {
119
    var target = $(this).closest('.fields-group').find(".$class");
120
    var targetValue = $(target).attr('data-value');
121
    var query=this.value;
122
    $.ajax({
123
        url:"$sourceUrl",
124
        dataType:'json',
125
        type:'GET',
126
        data:{
127
            q:query
128
        },
129
        success:function(data) {
130
          target.find("option").remove();
131
          target.append('<option value=""></option>');
132
          var select2 = $(target).select2({
133
                placeholder: $placeholder,
134
                allowClear: $allowClear,               
135
                data: $.map(data, function (d) {
136
                    d.id = d.{$idField};
137
                    d.text = d.{$textField};
138
                    return d;
139
                })
140
            })
141
        var isInDataList = data.filter(function(d) {
142
            return d.id == targetValue;
143
        }).length > 0;
144
        if(targetValue && isInDataList ) {
145
            select2.val(targetValue);
146
        }
147
        select2.trigger('change');
148
        }
149
    });
150
});
151
$("{$this->getElementClassSelector()}").each(function(){
152
     if ($(this).data('value')) {
153
         $(this).trigger('change');
154
     }
155
});
156
JS;
157
        Admin::script($script);
158
159
        return $this;
160
    }
161
162
    /**
163
     * Load options for other selects on change.
164
     *
165
     * @param array  $fields
166
     * @param array  $sourceUrls
167
     * @param string $idField
168
     * @param string $textField
169
     *
170
     * @return $this
171
     */
172
    public function loads($fields = [], $sourceUrls = [], $idField = 'id', $textField = 'text', bool $allowClear = true)
173
    {
174
        $fieldsStr = implode('.', $fields);
175
        $urlsStr = implode('^', $sourceUrls);
176
        $placeholder = json_encode([
177
            'id'   => '',
178
            'text' => trans('admin.choose'),
179
        ]);
180
        $script = <<<JS
181
var fields = '$fieldsStr'.split('.');
182
var urls = '$urlsStr'.split('^');
183
var refreshOptions = function(url, target) {
184
    $.ajax({
185
        url:url,
186
        dataType:'json',
187
        success:function(data) {
188
          target.find("option").remove();
189
          target.append('<option value=""></option>');
190
            $(target).select2({
191
                placeholder: $placeholder,
192
                allowClear: $allowClear,               
193
                data: $.map(data, function (d) {
194
                    d.id = d.{$idField};
195
                    d.text = d.{$textField};
196
                    return d;
197
                })
198
            }).trigger('change');
199
        }
200
    });
201
};
202
$(document).off('change', "{$this->getElementClassSelector()}");
203
$(document).on('change', "{$this->getElementClassSelector()}", function () {
204
    var _this = this;
205
    var promises = [];
206
    fields.forEach(function(field, index){
207
        var target = $(_this).closest('.fields-group').find('.' + fields[index]);
208
        promises.push(refreshOptions(urls[index] + "?q="+ _this.value, target));
209
    });
210
});
211
JS;
212
        Admin::script($script);
213
214
        return $this;
215
    }
216
217
    /**
218
     * Load options from current selected resource(s).
219
     *
220
     * @param string $model
221
     * @param string $idField
222
     * @param string $textField
223
     *
224
     * @return $this
225
     */
226 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...
227
    {
228
        if (
229
            !class_exists($model)
230
            || !in_array(Model::class, class_parents($model))
231
        ) {
232
            throw new \InvalidArgumentException("[$model] must be a valid model class");
233
        }
234
        $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...
235
            if (empty($value)) {
236
                return [];
237
            }
238
            $resources = [];
239
            if (is_array($value)) {
240
                if (Arr::isAssoc($value)) {
241
                    $resources[] = Arr::get($value, $idField);
242
                } else {
243
                    $resources = array_column($value, $idField);
244
                }
245
            } else {
246
                $resources[] = $value;
247
            }
248
249
            return $model::find($resources)->pluck($textField, $idField)->toArray();
250
        };
251
252
        return $this;
253
    }
254
255
    /**
256
     * Load options from remote.
257
     *
258
     * @param string $url
259
     * @param array  $parameters
260
     * @param array  $options
261
     *
262
     * @return $this
263
     */
264
    protected function loadRemoteOptions($url, $parameters = [], $options = [])
265
    {
266
        $ajaxOptions = [
267
            'url' => $url.'?'.http_build_query($parameters),
268
        ];
269
        $configs = array_merge([
270
            'allowClear'         => true,
271
            'placeholder'        => [
272
                'id'        => '',
273
                'text'      => trans('admin.choose'),
274
            ],
275
        ], $this->config);
276
        $configs = json_encode($configs);
277
        $configs = substr($configs, 1, strlen($configs) - 2);
278
        $ajaxOptions = json_encode(array_merge($ajaxOptions, $options));
279
        $this->script = <<<EOT
280
$.ajax($ajaxOptions).done(function(data) {
281
  $("{$this->getElementClassSelector()}").each(function(index, element) {
282
      $(element).select2({
283
        data: data,
284
        $configs
285
      });
286
      var value = $(element).data('value') + '';
287
      if (value) {
288
        value = value.split(',');
289
        $(element).select2('val', value);
290
      }
291
  });
292
});
293
EOT;
294
295
        return $this;
296
    }
297
298
    /**
299
     * Load options from ajax results.
300
     *
301
     * @param string $url
302
     * @param $idField
303
     * @param $textField
304
     *
305
     * @return $this
306
     */
307 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...
308
    {
309
        $configs = array_merge([
310
            'allowClear'         => true,
311
            'placeholder'        => $this->label,
312
            'minimumInputLength' => 1,
313
        ], $this->config);
314
        $configs = json_encode($configs);
315
        $configs = substr($configs, 1, strlen($configs) - 2);
316
        $this->script = <<<EOT
317
$("{$this->getElementClassSelector()}").select2({
318
  ajax: {
319
    url: "$url",
320
    dataType: 'json',
321
    delay: 250,
322
    data: function (params) {
323
      return {
324
        q: params.term,
325
        page: params.page
326
      };
327
    },
328
    processResults: function (data, params) {
329
      params.page = params.page || 1;
330
      return {
331
        results: $.map(data.data, function (d) {
332
                   d.id = d.$idField;
333
                   d.text = d.$textField;
334
                   return d;
335
                }),
336
        pagination: {
337
          more: data.next_page_url
338
        }
339
      };
340
    },
341
    cache: true
342
  },
343
  $configs,
344
  escapeMarkup: function (markup) {
345
      return markup;
346
  }
347
});
348
EOT;
349
350
        return $this;
351
    }
352
353
    /**
354
     * Set config for select2.
355
     *
356
     * all configurations see https://select2.org/configuration/options-api
357
     *
358
     * @param string $key
359
     * @param mixed  $val
360
     *
361
     * @return $this
362
     */
363
    public function config($key, $val)
364
    {
365
        $this->config[$key] = $val;
366
367
        return $this;
368
    }
369
370
    /**
371
     * {@inheritdoc}
372
     */
373
    public function readOnly()
374
    {
375
        //移除特定字段名称,增加MultipleSelect的修订
376
        //没有特定字段名可以使多个readonly的JS代码片段被Admin::script的array_unique精简代码
377
        $script = <<<'EOT'
378
$("form select").on("select2:opening", function (e) {
379
    if($(this).attr('readonly') || $(this).is(':hidden')){
380
    e.preventDefault();
381
    }
382
});
383
$(document).ready(function(){
384
    $('select').each(function(){
385
        if($(this).is('[readonly]')){
386
            $(this).closest('.form-group').find('span.select2-selection__choice__remove').first().remove();
387
            $(this).closest('.form-group').find('li.select2-search').first().remove();
388
            $(this).closest('.form-group').find('span.select2-selection__clear').first().remove();
389
        }
390
    });
391
});
392
EOT;
393
        Admin::script($script);
394
395
        return parent::readOnly();
396
    }
397
398
    /**
399
     * {@inheritdoc}
400
     */
401
    public function render()
402
    {
403
        $configs = array_merge([
404
            'allowClear'  => true,
405
            'placeholder' => [
406
                'id'   => '',
407
                'text' => $this->label,
408
            ],
409
        ], $this->config);
410
        $configs = json_encode($configs);
411
        if (empty($this->script)) {
412
            $this->script = "$(\"{$this->getElementClassSelector()}\").select2($configs);";
413
        }
414
        if ($this->options instanceof \Closure) {
415
            if ($this->form) {
416
                $this->options = $this->options->bindTo($this->form->model());
417
            }
418
            $this->options(call_user_func($this->options, $this->value, $this));
419
        }
420
        $this->options = array_filter($this->options, 'strlen');
421
        $this->addVariables([
422
            'options' => $this->options,
423
            'groups'  => $this->groups,
424
        ]);
425
        $this->attribute('data-value', implode(',', (array) $this->value()));
426
427
        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 427 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
428
    }
429
}
430