Completed
Push — master ( 3c8880...e2c218 )
by Dmitry
06:42 queued 02:46
created

ArraySpoiler::init()   D

Complexity

Conditions 10
Paths 66

Size

Total Lines 39
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 0
Metric Value
cc 10
eloc 22
nc 66
nop 0
dl 0
loc 39
ccs 0
cts 31
cp 0
crap 110
rs 4.8196
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * HiPanel core package
5
 *
6
 * @link      https://hipanel.com/
7
 * @package   hipanel-core
8
 * @license   BSD-3-Clause
9
 * @copyright Copyright (c) 2014-2016, HiQDev (http://hiqdev.com/)
10
 */
11
12
namespace hipanel\widgets;
13
14
use ArrayObject;
15
use hipanel\helpers\ArrayHelper;
16
use Yii;
17
use yii\base\InvalidValueException;
18
use yii\base\Widget;
19
use yii\helpers\Html;
20
use yii\helpers\Inflector;
21
use yii\helpers\Json;
22
use yii\helpers\StringHelper;
23
use yii\web\View;
24
25
/**
26
 * ArraySpoiler displays limited count of array's elements and hides all others behind a spoiler (badge).
27
 *
28
 * The following example will show first two elements of array concatenated with semicolon and bold-narrowed
29
 *
30
 * ```php
31
 * ArraySpoiler::widget([
32
 *      'data' => ['10.0.0.1', '10.0.0.2', '10.0.0.3', '10.0.0.4'],
33
 *      'formatter' => function ($v) {
34
 *          return Html::tag('b', $v);
35
 *      },
36
 *      'visibleCount' => 2,
37
 *      'delimiter' => '; ',
38
 *      'button' => [
39
 *          'label' => '+{count}',
40
 *          'popoverOptions' => [
41
 *              'html' => true
42
 *          ],
43
 *      ],
44
 * ]);
45
 * ```
46
 *
47
 * Also widget can split string in 'data' field into array by comma symbol.
48
 *
49
 * @author SilverFire <[email protected]>
50
 */
51
class ArraySpoiler extends Widget
52
{
53
    const MODE_POPOVER = 'popover';
54
    const MODE_SPOILER = 'spoiler';
55
56
    /**
57
     * @var string The mode. See `MODE_` constants for list of available constants.
58
     * Default - [[MODE_POPOVER]]
59
     */
60
    public $mode;
61
62
    /**
63
     * @var array|string|int Data to be proceeded
64
     */
65
    public $data;
66
67
    /**
68
     * @var string the template that is used to arrange show items, activating button and hidden inputs
69
     * The following tokens will be replaced when [[render()]] is called: `{shown}`, `{button}`, `{hidden}`.
70
     */
71
    public $template = "{visible}\n{button}\n{hidden}";
72
73
    /**
74
     * @var array different parts of the field (e.g. show, hidden). This will be used together with
75
     * [[template]] to generate the final field HTML code. The keys are the token names in [[template]],
76
     * while the values are the corresponding HTML code. Valid tokens include `{visible}`, `{button}` and `{hidden}`.
77
     * Note that you normally don't need to access this property directly as
78
     * it is maintained by various methods of this class.
79
     */
80
    public $parts = [];
81
82
    /**
83
     * @var callable the function will be called for every element to format it.
84
     * Gets two arguments - value and key
85
     */
86
    public $formatter = null;
87
88
    /**
89
     * @var int count of elements, that are visible out of spoiler
90
     */
91
    public $visibleCount = 1;
92
93
    /**
94
     * @var string delimiter to join elements
95
     */
96
    public $delimiter = ', ';
97
98
    /**
99
     * @var array|string When string - will be auto-converted to an array. Array will be passed to [[Html::tag()]]
100
     * as option argument. Special options that will be extracted:
101
     *  - label - string
102
     *  - i18n - string|bool whether to pass `label` through [[Yii::t()]]. String will be used as dictionary name.
103
     * Available substitutions: count
104
     *  - tag - html tag that will be rendered (default - `span`)
105
     *
106
     * For other special options see [[renderSpoiler()]] and [[renderPopover()]] methods.
107
     */
108
    public $button = '+{count}';
109
110
    /**
111
     * @var array configuration will be passed to [[Html::tag()]] as options argument
112
     */
113
    public $hidden = [];
114
115
    public function init()
116
    {
117
        parent::init();
118
119
        if (is_string($this->data) || is_numeric($this->data)) {
120
            $this->data = StringHelper::explode($this->data);
121
        } elseif ($this->data === null) {
122
            $this->data = [];
123
        }
124
125
        if (empty($this->mode)) {
126
            $this->mode = static::MODE_POPOVER;
127
        }
128
129
        if (!is_callable([$this, 'renderButton' . Inflector::id2camel($this->mode)])) {
130
            throw new InvalidValueException('Do not know, how to render button of this type');
131
        }
132
133
        if (is_string($this->button)) {
134
            $this->button = ['label' => $this->button];
135
        }
136
137
        $this->button = ArrayHelper::merge([
138
            'tag' => 'a',
139
            'id' => $this->id,
140
        ], $this->button);
141
142
        if (!is_array($this->data)) {
143
            throw new InvalidValueException('Input can not be processed as an array');
144
        }
145
146
        if (is_callable($this->formatter)) {
147
            $this->data = array_map($this->formatter, $this->data, array_keys($this->data));
148
        }
149
150
        if (is_callable($this->button['label'])) {
151
            $this->button['label'] = call_user_func($this->button['label'], $this);
152
        }
153
    }
154
155
    /**
156
     * Method returns first [[visibleCount]] items from [[data]].
157
     * @return array
158
     */
159 View Code Duplication
    protected function getVisibleItems()
0 ignored issues
show
Duplication introduced by
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...
160
    {
161
        if (count($this->data) <= $this->visibleCount) {
162
            return $this->data;
163
        }
164
        $visible = [];
165
        $iterator = (new ArrayObject($this->data))->getIterator();
166
        while (count($visible) < $this->visibleCount && $iterator->valid()) {
167
            $visible[] = $iterator->current();
168
            $iterator->next();
169
        }
170
171
        return $visible;
172
    }
173
174
    /**
175
     * Method returns all items from [[data]], skipping first [[visibleCount]].
176
     * @return array
177
     */
178 View Code Duplication
    protected function getSpoiledItems()
0 ignored issues
show
Duplication introduced by
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...
179
    {
180
        if (count($this->data) <= $this->visibleCount) {
181
            return [];
182
        }
183
        $spoiled = [];
184
        $iterator = (new ArrayObject($this->data))->getIterator();
185
        $iterator->seek($this->visibleCount);
186
        while ($iterator->valid()) {
187
            $spoiled[] = $iterator->current();
188
            $iterator->next();
189
        }
190
        return $spoiled;
191
    }
192
193
    /**
194
     * Renders visible part of spoiler.
195
     */
196
    private function renderVisible()
197
    {
198
        $this->parts['{visible}'] = implode($this->delimiter, $this->getVisibleItems());
199
    }
200
201
    /**
202
     * Renders spoiled items. Uses [[$this->mode]] value to run proper renderer.
203
     */
204 View Code Duplication
    private function renderSpoiled()
0 ignored issues
show
Duplication introduced by
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...
205
    {
206
        if (count($this->getSpoiledItems()) === 0) {
207
            $this->parts['{button}'] = '';
208
            return;
209
        }
210
211
        $method = 'renderButton' . Inflector::id2camel($this->mode);
212
        call_user_func([$this, $method]);
213
    }
214
215
    /**
216
     * Renders spoiled items. Uses [[$this->mode]] value to run proper renderer.
217
     */
218 View Code Duplication
    private function renderHidden()
0 ignored issues
show
Duplication introduced by
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...
219
    {
220
        if (count($this->getSpoiledItems()) === 0) {
221
            $this->parts['{hidden}'] = '';
222
            return;
223
        }
224
225
        $method = 'renderHidden' . Inflector::id2camel($this->mode);
226
        call_user_func([$this, $method]);
227
    }
228
229
    /**
230
     * Renders a popover-activating button.
231
     * Additional special options, that will be extracted from [[$this->button]]:
232
     *  - `data-popover-group` - Group of popovers. Is used to close all other popovers in group, when new one is opening. Default: 'main'
233
     *  - `popoverOptions` - Array of options that will be passed to `popover` JS call. Refer to bootstrap docs.
234
     *
235
     * @see http://getbootstrap.com/javascript/#popovers-options
236
     */
237
    protected function renderButtonPopover()
238
    {
239
        $options = ArrayHelper::merge([
240
            'data-popover-group' => 'main',
241
            'data-content' => $this->renderHiddenPopover(),
242
            'class' => 'badge',
243
            'popoverOptions' => [],
244
        ], $this->button);
245
246
        $label = $this->getButtonLabel(ArrayHelper::remove($options, 'label'));
247
248
        $this->getView()->registerJs("
249
             $('#{$this->id}').popover(" . Json::htmlEncode(ArrayHelper::remove($options, 'popoverOptions')) . ").on('show.bs.popover', function(e) {
250
                $('[data-popover-group=\"{$options['data-popover-group']}\"]').not(e.target).popover('hide');
251
             });
252
         ", View::POS_READY);
253
254
        $this->parts['{button}'] = Html::tag(ArrayHelper::remove($options, 'tag'), $label, $options);
255
    }
256
257
    /**
258
     * Renders a popover-activated hidden part.
259
     * Actually is does not render anything. Sets `{hidden}` to an empty string and returns the value of spoiled items.
260
     * @return string
261
     */
262
    public function renderHiddenPopover()
263
    {
264
        $this->parts['{hidden}'] = '';
265
        return implode($this->delimiter, $this->getSpoiledItems());
266
    }
267
268
    /**
269
     * Renders a button for spoiler activator.
270
     *
271
     * Additional special options, that will be extracted from [[$this->button]]:
272
     *  - `data-popover-group` - Group of popovers. Is used to close all other popovers in group, when new one is opening. Default: 'main'
273
     *
274
     * @see http://getbootstrap.com/javascript/#popovers-options
275
     */
276
    protected function renderButtonSpoiler()
277
    {
278
        $options = ArrayHelper::merge([
279
            'role' => 'button',
280
            'data-spoiler-group' => 'main',
281
            'data-spoiler-toggle' => true,
282
            'data-target' => $this->button['id'] . '-body',
283
        ], $this->button);
284
285
        $label = $this->getButtonLabel(ArrayHelper::remove($options, 'label'));
286
287
        $this->parts['{button}'] = Html::tag(ArrayHelper::remove($options, 'tag'), $label, $options);
288
        $this->getView()->registerJs("
289
             $('[data-spoiler-toggle]').click(function (e) {
290
                $('#'+$(this).data('target')).toggle();
291
                $('[data-spoiler-group=\"{$options['data-spoiler-group']}\"]').not($(this)).trigger('hide');
292
             }).on('hide', function () {
293
                $('#'+$(this).data('target')).hide();
294
             })",
295
            View::POS_READY);
296
    }
297
298
    /**
299
     * Renders a spoiler-button-activated hidden part.
300
     */
301
    public function renderHiddenSpoiler()
302
    {
303
        $options = ArrayHelper::merge([
304
            'id' => $this->button['id'] . '-body',
305
            'tag' => 'span',
306
            'value' => implode($this->delimiter, $this->getSpoiledItems()),
307
            'class' => 'collapse',
308
            'data-spoiler-body' => true,
309
        ], $this->hidden);
310
311
        $this->parts['{hidden}'] = Html::tag(ArrayHelper::remove($options, 'tag'), ArrayHelper::remove($options, 'value'), $options);
312
    }
313
314
    /**
315
     * Returns the button label.
316
     *
317
     * @param $label string|\Closure
318
     * @return mixed|string
319
     */
320
    public function getButtonLabel($label)
321
    {
322
        if ($label instanceof \Closure) {
323
            $label = call_user_func($label, $this);
324
        } else {
325
            $label = Yii::t('app', $label, ['count' => count($this->getSpoiledItems())]);
326
        }
327
328
        return $label;
329
    }
330
331
    /**
332
     * Renders the all the widget.
333
     */
334
    public function run()
335
    {
336
        if (!isset($this->parts['{visible}'])) {
337
            $this->renderVisible();
338
        }
339
        if (!isset($this->parts['{button}'])) {
340
            $this->renderSpoiled();
341
        }
342
        if (!isset($this->parts['{hidden}'])) {
343
            $this->renderHidden();
344
        }
345
346
        echo strtr($this->template, $this->parts);
347
    }
348
}
349