IndexPage::renderContent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
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 ?? $this->originalContext->uiModel;
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
                    containerSelector: '.horizontal-content',
225
                    innerWrapperSelector: '.content-sidebar__inner'
226
                });
227
                $(document).on('pjax:end', stickySidebar.updateSticky);
228
            }
229
230
            $(document).on('pjax:end', function() {
231
                $('.advanced-search form > div').css({'width': '100%'});
232
                $(window).trigger('scroll'); // Fix left search block position
233
            });
234
JS
235
           , View::POS_LOAD);
236
    }
237
238
    public function detectLayout()
239
    {
240
        return $this->getUiModel()->orientation;
241
    }
242
243
    /**
244
     * @param array $data
245
     * @void
246
     */
247
    public function setSearchFormData($data = [])
248
    {
249
        $this->searchFormData = $data;
250
    }
251
252
    /**
253
     * @param array $options
254
     * @void
255
     */
256
    public function setSearchFormOptions($options = [])
257
    {
258
        $this->searchFormOptions = $options;
259
    }
260
261
    public function renderSearchForm($advancedSearchOptions = [])
262
    {
263
        $advancedSearchOptions = array_merge($advancedSearchOptions, $this->searchFormOptions);
264
        ob_start();
265
        ob_implicit_flush(false);
266
        try {
267
            $search = $this->beginSearchForm($advancedSearchOptions);
268
            echo Yii::$app->view->render($this->searchView, array_merge(compact('search'), $this->searchFormData), $this->originalContext);
269
            $search->end();
270
        } catch (\Exception $e) {
271
            ob_end_clean();
272
            throw $e;
273
        }
274
275
        return ob_get_clean();
276
    }
277
278
    public function beginSearchForm($options = [])
279
    {
280
        return AdvancedSearch::begin(array_merge(['model' => $this->model], $options));
281
    }
282
283
    public function renderSearchButton()
284
    {
285
        return AdvancedSearch::renderButton() . "\n";
286
    }
287
288
    public function renderLayoutSwitcher()
289
    {
290
        return IndexLayoutSwitcher::widget(['uiModel' => $this->getUiModel()]);
291
    }
292
293
    public function renderPerPage()
294
    {
295
        $items = [];
296
        foreach ([25, 50, 100, 200, 500] as $pageSize) {
297
            $items[] = ['label' => $pageSize, 'url' => Url::current(['per_page' => $pageSize])];
298
        }
299
300
        return ButtonDropdown::widget([
301
            'label' => Yii::t('hipanel', 'Per page') . ': ' . $this->getUiModel()->per_page,
302
            'options' => ['class' => 'btn-default btn-sm'],
303
            'dropdown' => [
304
                'items' => $items,
305
            ],
306
        ]);
307
    }
308
309
    /**
310
     * Renders button to choose representation.
311
     * Returns empty string when nothing to choose (less then 2 representations available).
312
     *
313
     * @param RepresentationCollectionInterface $collection
314
     * @return string rendered HTML
315
     */
316
    public function renderRepresentations($collection)
317
    {
318
        $current = $this->getUiModel()->representation;
319
320
        $representations = $collection->getAll();
321
        if (count($representations) < 2) {
322
            return '';
323
        }
324
325
        $items = [];
326
        foreach ($representations as $name => $representation) {
327
            $items[] = [
328
                'label' => $representation->getLabel(),
329
                'url' => Url::current(['representation' => $name]),
330
            ];
331
        }
332
333
        return ButtonDropdown::widget([
334
            'label' => Yii::t('hipanel:synt', 'View') . ': ' . $collection->getByName($current)->getLabel(),
335
            'options' => ['class' => 'btn-default btn-sm'],
336
            'dropdown' => [
337
                'items' => $items,
338
            ],
339
        ]);
340
    }
341
342
    public function renderSorter(array $options)
343
    {
344
        return LinkSorter::widget(array_merge([
345
            'show' => true,
346
            'uiModel' => $this->getUiModel(),
347
            'dataProvider' => $this->dataProvider,
348
            'sort' => $this->dataProvider->getSort(),
349
            'buttonClass' => 'btn btn-default dropdown-toggle btn-sm',
350
        ], $options));
351
    }
352
353
    public function renderExport()
354
    {
355
        $isGridExportActionExists = (bool) Yii::$app->controller->createAction('export');
356
        /** @var RepresentationCollectionFinder $repColFinder */
357
        $repColFinder = Yii::createObject(RepresentationCollectionFinder::class);
358
        $collection = $repColFinder->findOrFallback();
359
        $isRepresentationExists = count($collection->getAll()) > 0;
360
        if ($isGridExportActionExists && $isRepresentationExists) {
361
            return IndexPageExportLinks::widget();
362
        }
363
    }
364
365
    public function getViewPath()
366
    {
367
        return parent::getViewPath() . DIRECTORY_SEPARATOR . (new \ReflectionClass($this))->getShortName();
368
    }
369
370
    public function getBulkFormId()
371
    {
372
        return 'bulk-' . Inflector::camel2id($this->model->formName());
373
    }
374
375
    public function beginBulkForm($action = '')
376
    {
377
        echo Html::beginForm($action, 'POST', ['id' => $this->getBulkFormId()]);
378
    }
379
380
    public function endBulkForm()
381
    {
382
        echo Html::endForm();
383
    }
384
385
    /**
386
     * @param string|array $action
387
     * @param string $text
388
     * @param array $options
389
     * @return string
390
     */
391
    public function renderBulkButton($action, $text, array $options = []): string
392
    {
393
        $color = ArrayHelper::remove($options, 'color', 'default');
394
        $confirm = ArrayHelper::remove($options, 'confirm', false);
395
        if ($confirm) {
396
            $options['onclick'] = new JsExpression("return confirm('{$confirm}');");
397
        }
398
        $defaultOptions = [
399
            'class' => "btn btn-$color btn-sm",
400
            'form' => $this->getBulkFormId(),
401
            'formmethod' => 'POST',
402
            'formaction' => Url::toRoute($action),
403
        ];
404
405
        return Html::submitButton($text, array_merge($defaultOptions, $options));
406
    }
407
408
    /**
409
     * @param string $permission
410
     * @param string $button
411
     * @return string|null
412
     */
413
    public function withPermission(string $permission, string $button): ?string
414
    {
415
        if (Yii::$app->user->can($permission)) {
416
            return $button;
417
        }
418
        return null;
419
    }
420
421
    /**
422
     * @param string|array $action
423
     * @param string|null $text
424
     * @param array $options
425
     * @return string
426
     */
427
    public function renderBulkDeleteButton($action, $text = null, array $options = []): string
428
    {
429
        $text = $text ?? Yii::t('hipanel', 'Delete');
430
        $options['color'] = $options['color'] ?? 'danger';
431
        $options['confirm'] = $options['confirm'] ?? Yii::t('hipanel', 'Are you sure you want to delete these items?');
432
433
        return $this->renderBulkButton($action, $text, $options);
434
    }
435
436
    /**
437
     * @return string
438
     */
439
    public function getLayout()
440
    {
441
        if ($this->_layout === null) {
442
            $this->_layout = $this->detectLayout();
443
        }
444
445
        return $this->_layout;
446
    }
447
448
    /**
449
     * @param string $layout
450
     */
451
    public function setLayout($layout)
452
    {
453
        $this->_layout = $layout;
454
    }
455
}
456