Completed
Push — master ( f7f242...bd8e6d )
by Klochok
27:47 queued 12:46
created

IndexPage::horizontalClientScriptInit()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 43

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 43
ccs 0
cts 41
cp 0
rs 9.232
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 2
1
<?php
2
/**
3
 * HiPanel core package
4
 *
5
 * @link      https://hipanel.com/
6
 * @package   hipanel-core
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2014-2019, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hipanel\widgets;
12
13
use hipanel\assets\StickySidebarAsset;
14
use hipanel\grid\RepresentationCollectionFinder;
15
use hipanel\helpers\ArrayHelper;
16
use hipanel\models\IndexPageUiOptions;
17
use hiqdev\higrid\representations\RepresentationCollectionInterface;
18
use hiqdev\yii2\export\widgets\IndexPageExportLinks;
19
use Yii;
20
use yii\base\InvalidParamException;
21
use yii\base\Model;
22
use yii\base\Widget;
23
use yii\bootstrap\ButtonDropdown;
24
use yii\data\DataProviderInterface;
25
use yii\helpers\Html;
26
use yii\helpers\Inflector;
27
use yii\helpers\Json;
28
use yii\helpers\Url;
29
use yii\web\JsExpression;
30
use yii\web\View;
31
32
class IndexPage extends Widget
33
{
34
    /**
35
     * @var string
36
     */
37
    protected $_layout;
38
39
    /**
40
     * @var Model the search model
41
     */
42
    public $model;
43
44
    /**
45
     * @var IndexPageUiOptions
46
     */
47
    private $uiModel;
48
49
    /**
50
     * @var object original view context.
51
     * It is used to render sub-views with the same context, as IndexPage
52
     */
53
    public $originalContext;
54
55
    /**
56
     * @var DataProviderInterface
57
     */
58
    public $dataProvider;
59
60
    /**
61
     * @var array Hash of document blocks, that can be rendered later in the widget's views
62
     * Blocks can be set explicitly on widget initialisation, or by calling [[beginContent]] and
63
     * [[endContent]]
64
     *
65
     * @see beginContent
66
     * @see endContent
67
     */
68
    public $contents = [];
69
70
    /**
71
     * @var string the name of current content block, that is under the render
72
     * @see beginContent
73
     * @see endContent
74
     */
75
    protected $_current = null;
76
77
    /**
78
     * @var array
79
     */
80
    public $searchFormData = [];
81
82
    /**
83
     * @var array
84
     */
85
    public $searchFormOptions = [];
86
87
    /**
88
     * @var string the name of view file that contains search fields for the index page. Defaults to `_search`
89
     * @see renderSearchForm()
90
     */
91
    public $searchView = '_search';
92
93
    /** {@inheritdoc} */
94
    public function init()
95
    {
96
        parent::init();
97
        $searchFormId = Json::htmlEncode("#{$this->getBulkFormId()}");
98
        $this->originalContext = Yii::$app->view->context;
99
        $view = $this->getView();
100
        // Fix a very narrow select2 input in the search tables
101
        $view->registerCss('#content-pjax .select2-dropdown--below { min-width: 170px!important; }');
102
        $view->registerJs(<<<"JS"
103
        // Checkbox
104
        var bulkcontainer = $('.box-bulk-actions fieldset');
105
        $($searchFormId).on('change', 'input[type="checkbox"]', function(event) {
106
            var checkboxes = $('input.grid-checkbox');
107
            if (checkboxes.filter(':checked').length > 0) {
108
                bulkcontainer.prop('disabled', false);
109
            } else if (checkboxes.filter(':checked').length === 0) {
110
                bulkcontainer.prop('disabled', true);
111
            }
112
        });
113
        // On/Off Actions TODO: reduce scope
114
        $(document).on('click', '.box-bulk-actions a', function (event) {
115
            var link = $(this);
116
            var action = link.data('action');
117
            var form = $($searchFormId);
118
            if (action) {
119
                form.attr({'action': action, method: 'POST'}).submit();
120
            }
121
        });
122
        // Do not open select2 when it is clearing
123
        var comboSelector = 'div[role=grid] :input[data-combo-field], .advanced-search :input[data-combo-field]';
124
        $(document).on('select2:unselecting', comboSelector, function(e) {
125
            $(e.target).data('unselecting', true);
126
        }).on('select2:open', comboSelector, function(e) { // note the open event is important
127
            var el = $(e.target);
128
            if (el.data('unselecting')) {
129
                el.removeData('unselecting'); // you need to unset this before close
130
                el.select2('close');
131
            }
132
        });
133
JS
134
        );
135
    }
136
137
    public function getUiModel()
138
    {
139
        if ($this->uiModel === null) {
140
            $this->uiModel = $this->originalContext->indexPageUiOptionsModel;
141
        }
142
143
        return $this->uiModel;
144
    }
145
146
    /**
147
     * Begins output buffer capture to save data in [[contents]] with the $name key.
148
     * Must not be called nested. See [[endContent]] for capture terminating.
149
     * @param string $name
150
     */
151
    public function beginContent($name)
152
    {
153
        if ($this->_current) {
154
            throw new InvalidParamException('Output buffer capture is already running for ' . $this->_current);
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\InvalidParamException has been deprecated with message: since 2.0.14. Use [[InvalidArgumentException]] instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
155
        }
156
        $this->_current = $name;
157
        ob_start();
158
        ob_implicit_flush(false);
159
    }
160
161
    /**
162
     * Terminates output buffer capture started by [[beginContent()]].
163
     * @see beginContent
164
     */
165
    public function endContent()
166
    {
167
        if (!$this->_current) {
168
            throw new InvalidParamException('Outout buffer capture is not running. Call beginContent() first');
0 ignored issues
show
Deprecated Code introduced by
The class yii\base\InvalidParamException has been deprecated with message: since 2.0.14. Use [[InvalidArgumentException]] instead.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
169
        }
170
        $this->contents[$this->_current] = ob_get_contents();
171
        ob_end_clean();
172
        $this->_current = null;
173
    }
174
175
    /**
176
     * Returns content saved in [[content]] by $name.
177
     * @param string $name
178
     * @return string
179
     */
180
    public function renderContent($name)
181
    {
182
        return $this->contents[$name];
183
    }
184
185
    public function run()
186
    {
187
        $layout = $this->getLayout();
188
        if ($layout === 'horizontal') {
189
            $this->horizontalClientScriptInit();
190
        }
191
192
        return $this->render($layout);
193
    }
194
195
    private function horizontalClientScriptInit()
196
    {
197
        $view = $this->getView();
198
        StickySidebarAsset::register($view);
199
        $view->registerCss(<<<'CSS'
200
            .advanced-search[min-width~="150px"] form > div {
201
                width: 100%;
202
                position: inherit;
203
            }
204
            .horizontal-view .content-sidebar {
205
                will-change: min-height;
206
            }
207
            .horizontal-view .content-sidebar .content-sidebar__inner {
208
                transform: translate(0, 0); /* For browsers don't support translate3d. */
209
                transform: translate3d(0, 0, 0);
210
                will-change: position, transform;
211
            }
212
            .horizontal-view .content-sidebar__inner > .btn,
213
            .horizontal-view .content-sidebar__inner .dropdown > .btn {
214
                display: block;
215
                margin-bottom: 10px;
216
            }
217
CSS
218
        );
219
        $view->registerJs(<<<"JS"
220
            var isDesktop = $(window).innerWidth() > 750;
221
            if (isDesktop) {
222
                var stickySidebar = new StickySidebar('.horizontal-view .content-sidebar', {
223
                    topSpacing: 10,
224
                    bottomSpacing: 20,
225
                    containerSelector: '.horizontal-content',
226
                    innerWrapperSelector: '.content-sidebar__inner'
227
                });
228
                $(document).on('pjax:end', stickySidebar.updateSticky);
229
            }
230
231
            $(document).on('pjax:end', function() {
232
                $('.advanced-search form > div').css({'width': '100%'});
233
                $(window).trigger('scroll'); // Fix left search block position
234
            });
235
JS
236
           , View::POS_LOAD);
237
    }
238
239
    public function detectLayout()
240
    {
241
        return $this->getUiModel()->orientation;
242
    }
243
244
    /**
245
     * @param array $data
246
     * @void
247
     */
248
    public function setSearchFormData($data = [])
249
    {
250
        $this->searchFormData = $data;
251
    }
252
253
    /**
254
     * @param array $options
255
     * @void
256
     */
257
    public function setSearchFormOptions($options = [])
258
    {
259
        $this->searchFormOptions = $options;
260
    }
261
262
    public function renderSearchForm($advancedSearchOptions = [])
263
    {
264
        $advancedSearchOptions = array_merge($advancedSearchOptions, $this->searchFormOptions);
265
        ob_start();
266
        ob_implicit_flush(false);
267
        try {
268
            $search = $this->beginSearchForm($advancedSearchOptions);
269
            echo Yii::$app->view->render($this->searchView, array_merge(compact('search'), $this->searchFormData), $this->originalContext);
270
            $search->end();
271
        } catch (\Exception $e) {
272
            ob_end_clean();
273
            throw $e;
274
        }
275
276
        return ob_get_clean();
277
    }
278
279
    public function beginSearchForm($options = [])
280
    {
281
        return AdvancedSearch::begin(array_merge(['model' => $this->model], $options));
282
    }
283
284
    public function renderSearchButton()
285
    {
286
        return AdvancedSearch::renderButton() . "\n";
287
    }
288
289
    public function renderLayoutSwitcher()
290
    {
291
        return IndexLayoutSwitcher::widget(['uiModel' => $this->getUiModel()]);
292
    }
293
294
    public function renderPerPage()
295
    {
296
        $items = [];
297
        foreach ([25, 50, 100, 200, 500] as $pageSize) {
298
            $items[] = ['label' => $pageSize, 'url' => Url::current(['per_page' => $pageSize])];
299
        }
300
301
        return ButtonDropdown::widget([
302
            'label' => Yii::t('hipanel', 'Per page') . ': ' . $this->getUiModel()->per_page,
303
            'options' => ['class' => 'btn-default btn-sm'],
304
            'dropdown' => [
305
                'items' => $items,
306
            ],
307
        ]);
308
    }
309
310
    /**
311
     * Renders button to choose representation.
312
     * Returns empty string when nothing to choose (less then 2 representations available).
313
     *
314
     * @param RepresentationCollectionInterface $collection
315
     * @return string rendered HTML
316
     */
317
    public function renderRepresentations($collection)
318
    {
319
        $current = $this->getUiModel()->representation;
320
321
        $representations = $collection->getAll();
322
        if (count($representations) < 2) {
323
            return '';
324
        }
325
326
        $items = [];
327
        foreach ($representations as $name => $representation) {
328
            $items[] = [
329
                'label' => $representation->getLabel(),
330
                'url' => Url::current(['representation' => $name]),
331
            ];
332
        }
333
334
        return ButtonDropdown::widget([
335
            'label' => Yii::t('hipanel:synt', 'View') . ': ' . $collection->getByName($current)->getLabel(),
336
            'options' => ['class' => 'btn-default btn-sm'],
337
            'dropdown' => [
338
                'items' => $items,
339
            ],
340
        ]);
341
    }
342
343
    public function renderSorter(array $options)
344
    {
345
        return LinkSorter::widget(array_merge([
346
            'show' => true,
347
            'uiModel' => $this->getUiModel(),
348
            'dataProvider' => $this->dataProvider,
349
            'sort' => $this->dataProvider->getSort(),
350
            'buttonClass' => 'btn btn-default dropdown-toggle btn-sm',
351
        ], $options));
352
    }
353
354
    public function renderExport()
355
    {
356
        $isGridExportActionExists = (bool) Yii::$app->controller->createAction('export');
357
        /** @var RepresentationCollectionFinder $repColFinder */
358
        $repColFinder = Yii::createObject(RepresentationCollectionFinder::class);
359
        $collection = $repColFinder->findOrFallback();
360
        $isRepresentationExists = count($collection->getAll()) > 0;
361
        if ($isGridExportActionExists && $isRepresentationExists) {
362
            return IndexPageExportLinks::widget();
363
        }
364
    }
365
366
    public function getViewPath()
367
    {
368
        return parent::getViewPath() . DIRECTORY_SEPARATOR . (new \ReflectionClass($this))->getShortName();
369
    }
370
371
    public function getBulkFormId()
372
    {
373
        return 'bulk-' . Inflector::camel2id($this->model->formName());
374
    }
375
376
    public function beginBulkForm($action = '')
377
    {
378
        echo Html::beginForm($action, 'POST', ['id' => $this->getBulkFormId()]);
379
    }
380
381
    public function endBulkForm()
382
    {
383
        echo Html::endForm();
384
    }
385
386
    /**
387
     * @param string|array $action
388
     * @param string $text
389
     * @param array $options
390
     * @return string
391
     */
392
    public function renderBulkButton($action, $text, array $options = []): string
393
    {
394
        $color = ArrayHelper::remove($options, 'color', 'default');
395
        $confirm = ArrayHelper::remove($options, 'confirm', false);
396
        if ($confirm) {
397
            $options['onclick'] = new JsExpression("return confirm('{$confirm}');");
398
        }
399
        $defaultOptions = [
400
            'class' => "btn btn-$color btn-sm",
401
            'form' => $this->getBulkFormId(),
402
            'formmethod' => 'POST',
403
            'formaction' => Url::toRoute($action),
404
        ];
405
406
        return Html::submitButton($text, array_merge($defaultOptions, $options));
407
    }
408
409
    /**
410
     * @param string $permission
411
     * @param string $button
412
     * @return string|null
413
     */
414
    public function withPermission(string $permission, string $button): ?string
415
    {
416
        if (Yii::$app->user->can($permission)) {
417
            return $button;
418
        }
419
        return null;
420
    }
421
422
    /**
423
     * @param string|array $action
424
     * @param string|null $text
425
     * @param array $options
426
     * @return string
427
     */
428
    public function renderBulkDeleteButton($action, $text = null, array $options = []): string
429
    {
430
        $text = $text ?? Yii::t('hipanel', 'Delete');
431
        $options['color'] = $options['color'] ?? 'danger';
432
        $options['confirm'] = $options['confirm'] ?? Yii::t('hipanel', 'Are you sure you want to delete these items?');
433
434
        return $this->renderBulkButton($action, $text, $options);
435
    }
436
437
    /**
438
     * @return string
439
     */
440
    public function getLayout()
441
    {
442
        if ($this->_layout === null) {
443
            $this->_layout = $this->detectLayout();
444
        }
445
446
        return $this->_layout;
447
    }
448
449
    /**
450
     * @param string $layout
451
     */
452
    public function setLayout($layout)
453
    {
454
        $this->_layout = $layout;
455
    }
456
}
457