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

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