Completed
Push — master ( db07c2...1d91ec )
by Dmitry
03:12
created

Combo   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 23.39%

Importance

Changes 0
Metric Value
wmc 38
lcom 1
cbo 9
dl 0
loc 387
ccs 29
cts 124
cp 0.2339
rs 9.36
c 0
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A getReturn() 0 4 1
A getRename() 0 4 1
A getFilter() 0 4 1
A getPrimaryFilter() 0 4 2
A getFormIsBulk() 0 4 1
A getHasId() 0 4 2
A setHasId() 0 4 1
C init() 0 33 10
A run() 0 6 1
A renderInput() 0 15 2
A registerClientConfig() 0 9 1
A registerClientScript() 0 7 1
A setFilter() 0 4 1
A setRename() 0 4 1
A setReturn() 0 4 1
A setPrimaryFilter() 0 4 1
A getPluginOptions() 0 39 2
A setPluginOptions() 0 4 1
B getCurrentOptions() 0 27 7
1
<?php
2
/**
3
 * Combo widget for Yii2
4
 *
5
 * @link      https://github.com/hiqdev/yii2-combo
6
 * @package   yii2-combo
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2015-2017, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\combo;
12
13
use hiqdev\yii2\assets\select2\Select2SelectAllAsset;
14
use Yii;
15
use yii\helpers\ArrayHelper;
16
use yii\helpers\Html;
17
use yii\helpers\Json;
18
use yii\helpers\Url;
19
use yii\web\JsExpression;
20
use yii\web\View;
21
use yii\widgets\InputWidget;
22
23
/**
24
 * Widget Combo.
25
 *
26
 * @property mixed $return see [[_return]]
27
 * @property mixed $rename see [[_rename]]
28
 * @property mixed $filter see [[_filter]]
29
 * @property mixed $pluginOptions see [[_pluginOptions]]
30
 * @property mixed $primaryFilter see [[_primaryFilter]]
31
 * @property mixed hasId
32
 */
33
class Combo extends InputWidget
34
{
35
    /**
36
     * @var array the url that will be passed to [[Url::to()]] method to create the request URL
37
     */
38
    public $url;
39
40
    /**
41
     * @var string the type of the field.
42
     * Usual should be module/comboName.
43
     * For example: client/client, hosting/account, domain/domain.
44
     * In case of the combo overriding with some specific filters,
45
     * the type should represent the filter.
46
     * For example: if the hosting/service combo is extended with filter
47
     * to show only DB services, the type should be hosting/service/db or hosting/dbService.
48
     * The decision of the style depends on overall code style and readability
49
     */
50
    public $type;
51
52
    /**
53
     * @var string the name of the representative field in the model.
54
     * Used by [[getPrimaryFilter]] to create the name of the filtering field
55
     * @see getPrimaryFilter()
56
     */
57
    public $name;
58
59
    /**
60
     * @var string md5 of the configuration.
61
     * Appears only after the combo registration in [[register]]
62
     * @see register()
63
     */
64
    public $configId;
65
66
    /**
67
     * @var array the HTML options for the input element
68
     */
69
    public $inputOptions = [];
70
71
    /**
72
     * @var string the outer element selector, that holds all of related Combos
73
     */
74
    public $formElementSelector = 'form';
75
76
    /**
77
     * @var string the language. Default is application language
78
     */
79
    public $language;
80
81
    /**
82
     * @var bool allow multiple selection
83
     */
84
    public $multiple;
85
86
    /**
87
     * @var array
88
     */
89
    public $current;
90
91
    /**
92
     * @var mixed returning arguments
93
     * Example:
94
     *
95
     * ```
96
     *  ['id', 'password', 'another_column']
97
     * ```
98
     *
99
     * @see getReturn()
100
     * @see setReturn()
101
     */
102
    protected $_return;
103
104
    /**
105
     * @var array renamed arguments
106
     * Example:
107
     *
108
     * ```
109
     *  [
110
     *      'new_col_name' => 'old_col_name',
111
     *      'text' => 'login',
112
     *      'deep' => 'array.subarray.value' // can extract some value from an array
113
     *  ]
114
     * ```
115
     *
116
     * @see getName()
117
     * @see setName()
118
     */
119
    protected $_rename;
120
121
    /**
122
     * @var array the static filters
123
     * Example:
124
     *
125
     * ```
126
     * [
127
     *      'someStaticValue' => ['format' => 'the_value'],
128
     *      'type'            => ['format' => 'seller'],
129
     *      'is_active'       => [
130
     *          'field'  => 'server',
131
     *          'format' => new JsExpression('function (id, text, field) {
132
     *              if (field.isSet()) {
133
     *                  return 1;
134
     *              }
135
     *          }'),
136
     *      ]
137
     * ]
138
     * @see setFilter()
139
     * @see getFilter()
140
     */
141
    protected $_filter = [];
142
143
    /**
144
     * @var string the name of the primary filter. Default: [[name]]_like
145
     * @see getPrimaryFilter
146
     * @see setPrimaryFilter
147
     */
148
    protected $_primaryFilter;
149
150
    /**
151
     * @var boolean|string whether the combo has a primary key
152
     *   null (default) - decision will be taken automatically.
153
     *                    In case when [[attribute]] has the `_id` postfix, this property will be treated as `true`
154
     *            false - the combo does not have an id. Meaning the value of the attribute will be used as the ID
155
     *           string - the explicit name of the ID attribute
156
     */
157
    public $_hasId;
158
159
    /**
160
     * Options that will be passed to the plugin.
161
     *
162
     * @var array
163
     * @see getPluginOptions()
164
     */
165
    public $_pluginOptions = [];
166
167
    /** {@inheritdoc} */
168 2
    public function init()
169
    {
170 2
        parent::init();
171
172
        // Set language
173 2
        if ($this->language === null && ($language = Yii::$app->language) !== 'en-US') {
174 2
            $this->language = substr($language, 0, 2);
175 2
        }
176 2
        if (!$this->_return) {
177 2
            $this->return = ['id'];
178 2
        }
179 2
        if (!$this->rename) {
180 2
            $this->rename = ['text' => $this->name];
181 2
        }
182 2
        if (!$this->inputOptions['id']) {
183
            $this->inputOptions['id'] = Html::getInputId($this->model, $this->attribute);
184
        }
185 2
        if ($this->multiple) {
186
            $this->inputOptions['multiple'] = true;
187
        }
188 2
        if ($this->inputOptions['readonly']) {
189
            // According to the HTML specification, the `select` element does not support
190
            // property `readonly`. Solution: render `readonly` field as disabled and prepend hidden
191
            // input to submit the attribute value.
192
            $this->inputOptions['disabled'] = true;
193
        }
194 2
        if (!$this->inputOptions['data-combo-field']) {
195 2
            $this->inputOptions['data-combo-field'] = $this->name;
196 2
        }
197 2
        if (!isset($this->inputOptions['unselect'])) {
198 2
            $this->inputOptions['unselect'] = null;
199 2
        }
200 2
    }
201
202
    public function run()
203
    {
204
        $this->registerClientConfig();
205
        $this->registerClientScript();
206
        return $this->renderInput();
207
    }
208
209
    /**
210
     * Renders text input that will be used by the plugin.
211
     * Must apply [[inputOptions]] to the HTML element.
212
     *
213
     * @param string $type for compatible reasons. Should not be used
214
     * @return string
215
     */
216
    protected function renderInput($type = null)
0 ignored issues
show
Unused Code introduced by
The parameter $type is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
217
    {
218
        $html = [];
219
        if ($this->inputOptions['readonly']) {
220
            // As it was said in comments of `init` method, the `select` element does not support property `readonly`.
221
            // However, disabled select will not be submitted.
222
            // Solution: render hidden input to submit the attribue value.
223
            $html[] = Html::activeHiddenInput($this->model, $this->attribute, [
224
                'id' => $this->inputOptions['id'] . '-hidden',
225
            ]);
226
        }
227
        $html[] = Html::activeDropDownList($this->model, $this->attribute, $this->getCurrentOptions(), $this->inputOptions);
228
229
        return implode('', $html);
230
    }
231
232
    public function registerClientConfig()
233
    {
234
        $view = $this->view;
235
        ComboAsset::register($view);
236
237
        $pluginOptions = Json::encode($this->pluginOptions);
238
        $this->configId = md5($this->type . $pluginOptions);
239
        $view->registerJs("$.comboConfig().add('{$this->configId}', $pluginOptions);", View::POS_READY, 'combo_' . $this->configId);
240
    }
241
242
    public function registerClientScript()
243
    {
244
        $selector = $this->inputOptions['id'];
245
        $js = "if ($('#$selector').length > 0) $('#$selector').closest('{$this->formElementSelector}').combo().register('#$selector', '$this->configId');";
246
247
        $this->view->registerJs($js);
248
    }
249
250
    public function getReturn()
251
    {
252
        return $this->_return;
253
    }
254
255
    /**
256
     * @return mixed
257
     */
258
    public function getRename()
259 2
    {
260
        return $this->_rename;
261 2
    }
262
263
    /**
264
     * @return mixed
265
     */
266
    public function getFilter()
267
    {
268
        return $this->_filter;
269
    }
270
271
    /**
272
     * @param mixed $filter
273
     */
274
    public function setFilter($filter)
275
    {
276
        $this->_filter = $filter;
0 ignored issues
show
Documentation Bug introduced by
It seems like $filter of type * is incompatible with the declared type array of property $_filter.

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...
277
    }
278
279
    /**
280
     * @param mixed $rename
281
     */
282
    public function setRename($rename)
283 2
    {
284
        $this->_rename = $rename;
0 ignored issues
show
Documentation Bug introduced by
It seems like $rename of type * is incompatible with the declared type array of property $_rename.

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...
285 2
    }
286 2
287
    /**
288
     * @param mixed $return
289
     */
290
    public function setReturn($return)
291 2
    {
292
        $this->_return = $return;
293 2
    }
294 2
295
    /**
296
     * @return string
297
     * @see _primaryFilter
298
     */
299
    public function getPrimaryFilter()
300
    {
301
        return $this->_primaryFilter ?: $this->name . '_like';
302
    }
303
304
    /**
305
     * @param $primaryFilter
306
     * @see _primaryFilter
307
     */
308
    public function setPrimaryFilter($primaryFilter)
309
    {
310
        $this->_primaryFilter = $primaryFilter;
311
    }
312
313
    /**
314
     * Returns the config of the Combo, merges with the passed $config.
315
     *
316
     * @param array $options
317
     * @return array
318
     */
319
    public function getPluginOptions($options = [])
320
    {
321
        $defaultOptions = [
322
            'name' => $this->name,
323
            'type' => $this->type,
324
            'hasId' => $this->hasId,
325
            'select2Options' => [
326
                'width'       => '100%',
327
                'placeholder' => '----------',
328
                'minimumInputLength' => '0',
329
                'ajax'        => [
330
                    'url'    => Url::toRoute($this->url),
331
                    'type'   => 'post',
332
                    'return' => $this->return,
333
                    'rename' => $this->rename,
334
                    'filter' => $this->filter,
335
                    'data' => new JsExpression("
336
                        function (event) {
337
                            return $(this).data('field').createFilter($.extend(true, {
338
                                '{$this->primaryFilter}': {format: event.term}
339
                            }, event.filters || {}));
340
                        }
341
                    "),
342
                ],
343
            ],
344
        ];
345
        if ($this->multiple) {
346
            Select2SelectAllAsset::register($this->view);
347
            $defaultOptions = ArrayHelper::merge($defaultOptions, [
348
                'select2Options' => [
349
                    'tags' => false,
350
                    'tokenSeparators' => [',', ', ', ' '],
351
                    'dropdownAdapter' => new JsExpression('$.fn.select2.amd.require("select2/custom/dropdown-adapter/select-all")'),
352
                ],
353
            ]);
354
        }
355
356
        return ArrayHelper::merge($defaultOptions, $this->_pluginOptions, $options);
357
    }
358
359
    public function getFormIsBulk()
360
    {
361
        return preg_match("/^\[.*\].+$/", $this->attribute);
362
    }
363
364
    /**
365
     * @param array $pluginOptions
366
     */
367
    public function setPluginOptions($pluginOptions)
368
    {
369
        $this->_pluginOptions = $pluginOptions;
370
    }
371
372
    /**
373
     * @return bool|string
374
     */
375
    public function getHasId()
376
    {
377
        return $this->_hasId === null ? (substr($this->attribute, -3) === '_id') : $this->_hasId;
378
    }
379
380
    /**
381
     * @param bool|string $hasId
382
     */
383
    public function setHasId($hasId)
384
    {
385
        $this->_hasId = $hasId;
386
    }
387
388
    /**
389
     * Method collects list of options that will be rendered inside the `select` tag.
390
     * @return array
391
     */
392
    protected function getCurrentOptions()
393
    {
394
        $value = Html::getAttributeValue($this->model, $this->attribute);
395
396
        if (!isset($value) || empty($value)) {
397
            return [];
398
        }
399
400
        if (!empty($this->current)) {
401
            return $this->current;
402
        }
403
404
        if ($this->getHasId()) {
405
            if (!is_scalar($value)) {
406
                Yii::error('When Combo has ID, property $current must be set manually, or attribute value must be a scalar. Value ' . var_export($value, true) . ' is not a scalar.', __METHOD__);
407
                return [];
408
            }
409
410
            return [$value => $value];
411
        } else {
412
            if (is_array($value)) {
413
                return array_combine(array_values($value), array_values($value));
414
            }
415
416
            return [$value => $value];
417
        }
418
    }
419
}
420