Completed
Push — master ( 727021...cafeab )
by Dmitry
02:57
created

Combo::getCurrentOptions()   C

Complexity

Conditions 7
Paths 6

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 0
loc 27
ccs 0
cts 14
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 15
nc 6
nop 0
crap 56
1
<?php
2
3
/*
4
 * Combo widget for Yii2
5
 *
6
 * @link      https://github.com/hiqdev/yii2-combo
7
 * @package   yii2-combo
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2015-2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hiqdev\combo;
13
14
use Yii;
15
use yii\base\Model;
16
use yii\base\Widget;
17
use yii\helpers\ArrayHelper;
18
use yii\helpers\Html;
19
use yii\helpers\Json;
20
use yii\helpers\Url;
21
use yii\web\JsExpression;
22
use yii\web\View;
23
24
/**
25
 * Widget Combo.
26
 *
27
 * @property mixed $return see [[_return]]
28
 * @property mixed $rename see [[_rename]]
29
 * @property mixed $filter see [[_filter]]
30
 * @property mixed $pluginOptions see [[_pluginOptions]]
31
 * @property mixed $primaryFilter see [[_primaryFilter]]
32
 * @property mixed hasId
33
 */
34
class Combo extends Widget
35
{
36
    /**
37
     * @var Model
38
     */
39
    public $model;
40
41
    /**
42
     * @var string the attribute name
43
     */
44
    public $attribute;
45
46
    /**
47
     * @var array the url that will be passed to [[Url::to()]] method to create the request URL
48
     */
49
    public $url;
50
51
    /**
52
     * @var string the type of the field.
53
     * Usual should be module/comboName.
54
     * For example: client/client, hosting/account, domain/domain.
55
     * In case of the combo overriding with some specific filters,
56
     * the type should represent the filter.
57
     * For example: if the hosting/service combo is extended with filter
58
     * to show only DB services, the type should be hosting/service/db or hosting/dbService.
59
     * The decision of the style depends on overall code style and readability
60
     */
61
    public $type;
62
63
    /**
64
     * @var string the name of the representative field in the model.
65
     * Used by [[getPrimaryFilter]] to create the name of the filtering field
66
     * @see getPrimaryFilter()
67
     */
68
    public $name;
69
70
    /**
71
     * @var string md5 of the configuration.
72
     * Appears only after the combo registration in [[register]]
73
     * @see register()
74
     */
75
    public $configId;
76
77
    /**
78
     * @var array the HTML options for the input element
79
     */
80
    public $inputOptions = [];
81
82
    /**
83
     * @var string the outer element selector, that holds all of related Combos
84
     */
85
    public $formElementSelector = 'form';
86
87
    /**
88
     * @var string the language. Default is application language
89
     */
90
    public $language;
91
92
    /**
93
     * @var bool allow multiple selection
94
     */
95
    public $multiple;
96
97
    /**
98
     * @var array
99
     */
100
    public $current;
101
102
    /**
103
     * @var mixed returning arguments
104
     * Example:
105
     *
106
     * ```
107
     *  ['id', 'password', 'another_column']
108
     * ```
109
     *
110
     * @see getReturn()
111
     * @see setReturn()
112
     */
113
    protected $_return;
114
115
    /**
116
     * @var array renamed arguments
117
     * Example:
118
     *
119
     * ```
120
     *  [
121
     *      'new_col_name' => 'old_col_name',
122
     *      'text' => 'login',
123
     *      'deep' => 'array.subarray.value' // can extract some value from an array
124
     *  ]
125
     * ```
126
     *
127
     * @see getName()
128
     * @see setName()
129
     */
130
    protected $_rename;
131
132
    /**
133
     * @var array the static filters
134
     * Example:
135
     *
136
     * ```
137
     * [
138
     *      'someStaticValue' => ['format' => 'the_value'],
139
     *      'type'            => ['format' => 'seller'],
140
     *      'is_active'       => [
141
     *          'field'  => 'server',
142
     *          'format' => new JsExpression('function (id, text, field) {
143
     *              if (field.isSet()) {
144
     *                  return 1;
145
     *              }
146
     *          }'),
147
     *      ]
148
     * ]
149
     * @see setFilter()
150
     * @see getFilter()
151
     */
152
    protected $_filter = [];
153
154
    /**
155
     * @var string the name of the primary filter. Default: [[name]]_like
156
     * @see getPrimaryFilter
157
     * @see setPrimaryFilter
158
     */
159
    protected $_primaryFilter;
160
161
    /**
162
     * @var boolean|string whether the combo has a primary key
163
     *   null (default) - decision will be taken automatically.
164
     *                    In case when [[attribute]] has the `_id` postfix, this property will be treated as `true`
165
     *            false - the combo does not have an id. Meaning the value of the attribute will be used as the ID
166
     *           string - the explicit name of the ID attribute
167
     */
168
    public $_hasId;
169
170
    /**
171
     * Options that will be passed to the plugin.
172
     *
173
     * @var array
174
     * @see getPluginOptions()
175
     */
176
    public $_pluginOptions = [];
177
178
    /** {@inheritdoc} */
179 2
    public function init()
180
    {
181 2
        parent::init();
182
183
        // Set language
184 2
        if ($this->language === null && ($language = Yii::$app->language) !== 'en-US') {
185 2
            $this->language = substr($language, 0, 2);
186 2
        }
187 2
        if (!$this->_return) {
188 2
            $this->return = ['id'];
189 2
        }
190 2
        if (!$this->rename) {
191 2
            $this->rename = ['text' => $this->name];
192 2
        }
193 2
        if (!$this->inputOptions['id']) {
194
            $this->inputOptions['id'] = Html::getInputId($this->model, $this->attribute);
195
        }
196 2
        if ($this->multiple) {
197
            $this->inputOptions['multiple'] = true;
198
        }
199 2
        if ($this->inputOptions['readonly']) {
200
            // According to the HTML specification, the `select` element does not support
201
            // property `readonly`. Solution: render `readonly` field as disabled and prepend hidden
202
            // input to submit the attribute value.
203
            $this->inputOptions['disabled'] = true;
204
        }
205 2
        if (!$this->inputOptions['data-combo-field']) {
206 2
            $this->inputOptions['data-combo-field'] = $this->name;
207 2
        }
208 2
        if (!isset($this->inputOptions['unselect'])) {
209 2
            $this->inputOptions['unselect'] = null;
210 2
        }
211 2
    }
212
213
    public function run()
214
    {
215
        $this->registerClientConfig();
216
        $this->registerClientScript();
217
        return $this->renderInput();
218
    }
219
220
    /**
221
     * Renders text input that will be used by the plugin.
222
     * Must apply [[inputOptions]] to the HTML element.
223
     *
224
     * @return string
225
     */
226
    protected function renderInput()
227
    {
228
        $html = [];
229
        if ($this->inputOptions['readonly']) {
230
            // As it was said in comments of `init` method, the `select` element does not support property `readonly`.
231
            // However, disabled select will not be submitted.
232
            // Solution: render hidden input to submit the attribue value.
233
            $html[] = Html::activeHiddenInput($this->model, $this->attribute, [
234
                'id' => $this->inputOptions['id'] . '-hidden',
235
            ]);
236
        }
237
        $html[] = Html::activeDropDownList($this->model, $this->attribute, $this->getCurrentOptions(), $this->inputOptions);
238
239
        return implode('', $html);
240
    }
241
242
    public function registerClientConfig()
243
    {
244
        $view = $this->view;
245
        ComboAsset::register($view);
246
247
        $pluginOptions = Json::encode($this->pluginOptions);
248
        $this->configId = md5($this->type . $pluginOptions);
249
        $view->registerJs("$.comboConfig().add('{$this->configId}', $pluginOptions);", View::POS_READY, 'combo_' . $this->configId);
250
    }
251
252
    public function registerClientScript()
253
    {
254
        $selector = $this->inputOptions['id'];
255
        $js = "$('#$selector').closest('{$this->formElementSelector}').combo().register('#$selector', '$this->configId');";
256
257
        $this->view->registerJs($js);
258
    }
259
260
    public function getReturn()
261
    {
262
        return $this->_return;
263
    }
264
265
    /**
266
     * @return mixed
267
     */
268 2
    public function getRename()
269
    {
270 2
        return $this->_rename;
271
    }
272
273
    /**
274
     * @return mixed
275
     */
276
    public function getFilter()
277
    {
278
        return $this->_filter;
279
    }
280
281
    /**
282
     * @param mixed $filter
283
     */
284
    public function setFilter($filter)
285
    {
286
        $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...
287
    }
288
289
    /**
290
     * @param mixed $rename
291
     */
292 2
    public function setRename($rename)
293
    {
294 2
        $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...
295 2
    }
296
297
    /**
298
     * @param mixed $return
299
     */
300 2
    public function setReturn($return)
301
    {
302 2
        $this->_return = $return;
303 2
    }
304
305
    /**
306
     * @return string
307
     * @see _primaryFilter
308
     */
309
    public function getPrimaryFilter()
310
    {
311
        return $this->_primaryFilter ?: $this->name . '_like';
312
    }
313
314
    /**
315
     * @param $primaryFilter
316
     * @see _primaryFilter
317
     */
318
    public function setPrimaryFilter($primaryFilter)
319
    {
320
        $this->_primaryFilter = $primaryFilter;
321
    }
322
323
    /**
324
     * Returns the config of the Combo, merges with the passed $config.
325
     *
326
     * @param array $options
327
     * @return array
328
     */
329
    public function getPluginOptions($options = [])
330
    {
331
        $defaultOptions = [
332
            'name' => $this->name,
333
            'type' => $this->type,
334
            'hasId' => $this->hasId,
335
            'select2Options' => [
336
                'width'       => '100%',
337
                'placeholder' => '----------',
338
                'minimumInputLength' => '0',
339
                'ajax'        => [
340
                    'url'    => Url::toRoute($this->url),
341
                    'type'   => 'post',
342
                    'return' => $this->return,
343
                    'rename' => $this->rename,
344
                    'filter' => $this->filter,
345
                    'data' => new JsExpression("
346
                        function (event) {
347
                            return $(this).data('field').createFilter($.extend(true, {
348
                                '{$this->primaryFilter}': {format: event.term}
349
                            }, event.filters || {}));
350
                        }
351
                    "),
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