Completed
Push — master ( 82be35...1537c0 )
by Song
02:28
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).select2('val', value);
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')
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').first().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();
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 450 which is incompatible with the return type declared by the interface Illuminate\Contracts\Support\Renderable::render of type string.
Loading history...
451
    }
452
}
453