Completed
Push — master ( b72548...496db0 )
by Song
03:52 queued 01:34
created

Select::readOnly()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
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
    protected function loadRemoteOptions($url, $parameters = [], $options = [])
242
    {
243
        $ajaxOptions = [
244
            'url' => $url.'?'.http_build_query($parameters),
245
        ];
246
        $configs = array_merge([
247
            'allowClear'         => true,
248
            'placeholder'        => [
249
                'id'        => '',
250
                'text'      => trans('admin.choose'),
251
            ],
252
        ], $this->config);
253
254
        $configs = json_encode($configs);
255
        $configs = substr($configs, 1, strlen($configs) - 2);
256
257
        $ajaxOptions = json_encode(array_merge($ajaxOptions, $options));
258
259
        $this->script = <<<EOT
260
261
$.ajax($ajaxOptions).done(function(data) {
262
263
  var select = $("{$this->getElementClassSelector()}");
264
265
  select.select2({
266
    data: data,
267
    $configs
268
  });
269
  
270
  var value = select.data('value') + '';
271
  
272
  if (value) {
273
    value = value.split(',');
274
    select.select2('val', value);
275
  }
276
});
277
278
EOT;
279
280
        return $this;
281
    }
282
283
    /**
284
     * Load options from ajax results.
285
     *
286
     * @param string $url
287
     * @param $idField
288
     * @param $textField
289
     *
290
     * @return $this
291
     */
292 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...
293
    {
294
        $configs = array_merge([
295
            'allowClear'         => true,
296
            'placeholder'        => $this->label,
297
            'minimumInputLength' => 1,
298
        ], $this->config);
299
300
        $configs = json_encode($configs);
301
        $configs = substr($configs, 1, strlen($configs) - 2);
302
303
        $this->script = <<<EOT
304
305
$("{$this->getElementClassSelector()}").select2({
306
  ajax: {
307
    url: "$url",
308
    dataType: 'json',
309
    delay: 250,
310
    data: function (params) {
311
      return {
312
        q: params.term,
313
        page: params.page
314
      };
315
    },
316
    processResults: function (data, params) {
317
      params.page = params.page || 1;
318
319
      return {
320
        results: $.map(data.data, function (d) {
321
                   d.id = d.$idField;
322
                   d.text = d.$textField;
323
                   return d;
324
                }),
325
        pagination: {
326
          more: data.next_page_url
327
        }
328
      };
329
    },
330
    cache: true
331
  },
332
  $configs,
333
  escapeMarkup: function (markup) {
334
      return markup;
335
  }
336
});
337
338
EOT;
339
340
        return $this;
341
    }
342
343
    /**
344
     * Set config for select2.
345
     *
346
     * all configurations see https://select2.org/configuration/options-api
347
     *
348
     * @param string $key
349
     * @param mixed  $val
350
     *
351
     * @return $this
352
     */
353
    public function config($key, $val)
354
    {
355
        $this->config[$key] = $val;
356
357
        return $this;
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function readOnly()
364
    {
365
        $script = <<<EOT
366
$("{$this->getElementClassSelector()}").on("select2:opening", function (e) {
367
    if($(this).attr('readonly') || $(this).is(':hidden')){
368
        e.preventDefault();
369
    }
370
});
371
EOT;
372
        Admin::script($script);
373
374
        return parent::readOnly();
375
    }
376
377
    /**
378
     * {@inheritdoc}
379
     */
380
    public function render()
381
    {
382
        $configs = array_merge([
383
            'allowClear'  => true,
384
            'placeholder' => [
385
                'id'   => '',
386
                'text' => $this->label,
387
            ],
388
        ], $this->config);
389
390
        $configs = json_encode($configs);
391
392
        if (empty($this->script)) {
393
            $this->script = "$(\"{$this->getElementClassSelector()}\").select2($configs);";
394
        }
395
396
        if ($this->options instanceof \Closure) {
397
            if ($this->form) {
398
                $this->options = $this->options->bindTo($this->form->model());
399
            }
400
401
            $this->options(call_user_func($this->options, $this->value, $this));
402
        }
403
404
        $this->options = array_filter($this->options, 'strlen');
405
406
        $this->addVariables([
407
            'options' => $this->options,
408
            'groups'  => $this->groups,
409
        ]);
410
411
        $this->attribute('data-value', implode(',', (array) $this->value()));
412
413
        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 413 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
414
    }
415
}
416