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
|
|||
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 |
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.