Completed
Push — master ( b0473b...35cc5a )
by Dmitrijs
04:14 queued 02:03
created

Card::renderActionButton()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 15
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
/**
3
 * @link https://github.com/DMGPage/yii2-materialize
4
 * @copyright Copyright (c) 2018 Dmitrijs Reinmanis
5
 * @license https://github.com/DMGPage/yii2-materialize/blob/master/LICENSE
6
 */
7
8
namespace dmgpage\yii2materialize\widgets;
9
10
use dmgpage\yii2materialize\helpers\Html;
11
use yii\helpers\ArrayHelper;
12
use yii\base\InvalidConfigException;
13
use dmgpage\yii2materialize\assets\MaterializeExtraAsset;
14
15
/**
16
 * Cards are a convenient means of displaying content composed of different types of objects.
17
 * They’re also well-suited for presenting similar objects whose size or supported actions can vary considerably,
18
 * like photos with captions of variable length.
19
 *
20
 * You can use Cards like this:
21
 *
22
 * ```php
23
 * echo Card::widget([
24
 *     'columnOptions' => ['class' => 's12 m3'],
25
 *     'cardOptions' => ['class' => 'light-blue darken-4'],
26
 *     'image' => [
27
 *         'title' => 'Image Card Title',
28
 *         'url' => 'https://materializecss.com/images/sample-1.jpg',
29
 *         'fab' => [
30
 *             'type' => ButtonType::FLOATING,
31
 *             'size' => Size::MEDIUM,
32
 *             'waves' => Waves::LIGHT,
33
 *             'icon' => ['name' => 'add'],
34
 *             'options' => ['class' => 'light-blue accent-2']
35
 *         ]
36
 *     ],
37
 *     'content' => [
38
 *         'value' => '<p>I am a very simple card. I am good at containing small bits of information. '
39
 *             . 'I am convenient because I require little markup to use effectively.</p>',
40
 *         'options' => ['class' => 'white-text']
41
 *     ],
42
 *     'actions' => [
43
 *         ['label' => 'This is a link #1'],
44
 *         ['label' => 'This is a link #2']
45
 *     ],
46
 * ]);
47
 * ```
48
 */
49
class Card extends Widget
50
{
51
    /**
52
     * @var array the HTML attributes for the column container tag of the card view.
53
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
54
     */
55
    public $columnOptions = ['class' => 's12 m6'];
56
57
    /**
58
     * @var array the HTML attributes for the content wrapper tag of the card view.
59
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
60
     */
61
    public $cardOptions = [];
62
63
    /**
64
     * @var bool whether to HTML-encode the link labels and card title
65
     */
66
    public $encodeLabels = true;
67
68
    /**
69
     * @var array a list of attributes to be displayed in the card content tag. Item should be an array
70
     * of the following structure:
71
     * - title: string, title for the card image. Value will be HTML-encoded.
72
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
73
     * - value: string, the HTML content for the card body. It will NOT be HTML-encoded.
74
     *   Therefore you can pass in HTML code. If this is coming from end users,
75
     *   you should consider encode() it to prevent XSS attacks.
76
     * - options: array, the HTML attributes for the card content tag.
77
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
78
     *   (name for the icon), if you want to add icon to the right side of the title.
79
     */
80
    public $content = [];
81
82
    /**
83
     * @var array a list of attributes to be displayed in the card image tag. Item should be an array
84
     * of the following structure:
85
     * - title: string, title for the card image. Value will be HTML-encoded.
86
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
87
     * - url: string the image URL. This parameter will be processed by [[Url::to()]].
88
     * - fab: array list of attributes for floating action button. Value will be passed to [[Button]] widget.
89
     *   You can set extra param 'url' to render it as link.
90
     * - options: array, the HTML attributes for the card image tag.
91
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
92
     *   (name for the icon), if you want to add icon to the right side of the title.
93
     * - imageOptions: array the HTML attributes for the image tag.
94
     */
95
    public $image = [];
96
97
    /**
98
     * @var array list of card action items. Each action item should be an array of the following structure:
99
     * - label: string, specifies the action item label. When [[encodeLabels]] is true, the label
100
     *   will be HTML-encoded.
101
     * - encode: boolean, optional, whether this item`s label should be HTML-encoded. This param will override
102
     *   global [[encodeLabels]] param.
103
     * - url: string or array, optional, specifies the URL of the action item. It will be processed by [[Url::to]].
104
     * - icon: string or array, optional, icon name or array with 'name' and 'options'.
105
     * - options: array, optional, the HTML attributes for the action container tag.
106
     */
107
    public $actions = [];
108
109
    /**
110
     * @var array the HTML attributes for the action wrapper tag of the card view.
111
     * You can use attribute "sticky" to change, whether card actions must be always visible.
112
     * Default value is false.
113
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
114
     */
115
    public $actionOptions = [];
116
117
    /**
118
     * @var array a list of attributes to be displayed in the card reveal tag. Item should be an array
119
     * of the following structure:
120
     * - title: string, title for the card image. Value will be HTML-encoded.
121
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
122
     * - value: string, the HTML content for the card body. It will NOT be HTML-encoded.
123
     *   Therefore you can pass in HTML code. If this is coming from end users,
124
     *   you should consider encode() it to prevent XSS attacks.
125
     * - options: array, the HTML attributes for the card content tag.
126
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
127
     *   (name for the icon), if you want to add icon to the right side of the title.
128
     */
129
    public $reveal = [];
130
131
    /**
132
     * @var bool whether image should be on left side. Default value is false
133
     */
134
    public $horizontal = false;
135
136
    /**
137
     * @var bool whether card should be spaced as panel. Default value is false.
138
     * Use this for a simpler card with less markup, because it has only padding and a shadow effect.
139
     */
140
    public $panel = false;
141
142
    /**
143
     * Initializes the widget.
144
     * @return void
145
     */
146
    public function init()
147
    {
148
        parent::init();
149
150
        $contentData = $this->content;
151
        $useStickyActions = isset($this->actionOptions['sticky']) ? $this->actionOptions['sticky'] : false;
152
        $cardContentOptions = isset($this->content['options']) ? $this->content['options'] : [];
153
        Html::addCssClass($cardContentOptions, ['class' => 'card-content']);
154
155
        if ($this->panel) {
156
            Html::addCssClass($this->cardOptions, ['card-panel']);
157
        } elseif ($this->horizontal) {
158
            Html::addCssClass($this->cardOptions, ['card', 'horizontal']);
159
        } elseif ($useStickyActions) {
160
            Html::addCssClass($this->cardOptions, ['card', 'sticky-action']);
161
            unset($this->actionOptions['sticky']);
162
        } else {
163
            Html::addCssClass($this->cardOptions, ['card']);
164
        }
165
166
        $html = Html::beginGridRow($this->options);
167
        $html .= Html::beginGridCol($this->columnOptions);
168
        $html .= Html::beginTag('div', $this->cardOptions);
169
        $html .= $this->renderImageContent($contentData);
0 ignored issues
show
Unused Code introduced by
The call to dmgpage\yii2materialize\...d::renderImageContent() has too many arguments starting with $contentData. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

169
        $html .= $this->/** @scrutinizer ignore-call */ renderImageContent($contentData);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
170
171
        // Create stacked content, if horizontal attribute is true
172
        if ($this->horizontal) {
173
            $html .= Html::beginTag('div', ['class' => 'card-stacked']);
174
        }
175
176
        // Add reviel button to content title, if reveal attribute is not empty
177
        if (!empty($this->reveal)) {
178
            Html::addCssClass($contentData['titleOptions'], 'activator');
179
            $addIcon = !isset($contentData['titleOptions']['icon']);
180
            $contentData['titleOptions']['icon'] = $addIcon
181
                ? 'more_vert'
182
                : $contentData['titleOptions']['icon'];
183
        }
184
185
        // Create card content, if panel not is true
186
        if (!$this->panel) {
187
            $html .= Html::beginTag('div', $cardContentOptions);
188
        }
189
190
        $html .= $this->renderTitleContent($contentData);
191
192
        echo $html;
193
    }
194
195
    /**
196
     * Renders the widget.
197
     * @return void
198
     */
199
    public function run()
200
    {
201
        $this->registerPlugin('card');
202
203
        $html = isset($this->content['value']) ? $this->content['value'] : '';
204
205
        if (!$this->panel) {
206
            $html .= Html::endTag('div'); // ends card-content tag
207
        }
208
209
        if (!empty($this->actions)) {
210
            Html::addCssClass($this->actionOptions, ['class' => 'card-action']);
211
            $html .= Html::beginTag('div', $this->actionOptions);
212
213
            foreach ($this->actions as $action) {
214
                $html .= $this->renderActionItem($action);
215
            }
216
217
            $html .= Html::endTag('div');
218
        }
219
220
        if ($this->horizontal) {
221
            $html .= Html::endTag('div'); //ends card-stacked tag
222
        }
223
224
        // Add card reveal tag
225
        $html .= $this->renderRevealContent();
226
        $html .= Html::endTag('div'); //ends card tag
227
        $html .= Html::endGridCol();
228
        $html .= Html::endGridRow();
229
230
        echo $html;
231
    }
232
233
    /**
234
     * Renders card content title tag content.
235
     *
236
     * @param array $source data source for title and options
237
     * @return string the rendering result
238
     */
239
    protected function renderTitleContent($source)
240
    {
241
        $html = '';
242
243
        if (isset($source['title'])) {
244
            $titleValue = $source['title'];
245
            $titleOptions = isset($source['titleOptions']) ? $source['titleOptions'] : [];
246
            $encode = isset($titleOptions['encode']) ? $titleOptions['encode'] : $this->encodeLabels;
247
            $icon = isset($titleOptions['icon']) ? $titleOptions['icon'] : null;
248
            unset($titleOptions['encode']);
249
            Html::addCssClass($titleOptions, 'card-title');
250
            $title = $encode ? Html::encode($titleValue) : $titleValue;
251
            $title .= !empty($icon) ? Html::icon($icon, ['class' => 'material-icons right']) : '';
252
            unset($titleOptions['icon']);
253
            $html .= Html::tag('span', $title, $titleOptions);
254
        }
255
256
        return $html;
257
    }
258
259
    /**
260
     * Renders a single card action item.
261
     *
262
     * @param array $link the link to be rendered. It must contain the "label" element. The "url" and "icon" element is optional.
263
     * @return string the rendering result
264
     * @throws InvalidConfigException if `$link` does not have "label" element.
265
     */
266
    protected function renderActionItem($link)
267
    {
268
        $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
269
270
        if (array_key_exists('label', $link)) {
271
            $label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
272
        } else {
273
            throw new InvalidConfigException('The "label" element is required for each link.');
274
        }
275
276
        // Add icon to label text
277
        if (isset($link['icon'])) {
278
            $view = $this->getView();
279
            MaterializeExtraAsset::register($view);
280
281
            // Has issues on positioning: https://github.com/google/material-design-icons/issues/206
282
            $label = $this->renderIcon($link['icon']) . $label;
283
        }
284
285
        $options = $link['options'];
286
287
        if (isset($link['url'])) {
288
            return Html::a($label, $link['url'], $options);
289
        } else {
290
            return Html::a($label, '#', $options);
291
        }
292
    }
293
294
    /**
295
     * Renders card-image tag content.
296
     * @return string the rendering result
297
     */
298
    protected function renderImageContent()
299
    {
300
        $html = '';
301
302
        if (!empty($this->image)) {
303
            $contentData = $this->image;
304
            $fabData = isset($contentData['fab']) ? $contentData['fab'] : [];
305
            $options = isset($contentData['options']) ? $contentData['options'] : [];
306
            $imageOptions = isset($contentData['imageOptions']) ? $contentData['imageOptions'] : [];
307
            $imageUrl = isset($contentData['url']) ? $contentData['url'] : [];
308
            Html::addCssClass($options, ['class' => 'card-image']);
309
310
            $html .= Html::beginTag('div', $options);
311
            $html .= Html::img($imageUrl, $imageOptions);
312
            $html .= $this->renderTitleContent($contentData);
313
            $html .= $this->renderActionButton($fabData);
314
            $html .= Html::endTag('div');
315
        }
316
317
        return $html;
318
    }
319
320
    /**
321
     * Renders floating action button tag.
322
     *
323
     * @param array $config attribute values and options for Button widget.
324
     * @return string the rendering result
325
     */
326
    protected function renderActionButton($config)
327
    {
328
        $html = '';
329
330
        if (!empty($config)) {
331
            if (!isset($config['options'])) {
332
                $config['options'] = ['class' => 'halfway-fab'];
333
            } else {
334
                Html::addCssClass($config['options'], 'halfway-fab');
335
            }
336
337
            $html .= Button::widget($config);
338
        }
339
340
        return $html;
341
    }
342
343
    /**
344
     * Renders card-reveal tag content.
345
     * @return string the rendering result
346
     */
347
    protected function renderRevealContent()
348
    {
349
        $html = '';
350
351
        if (!empty($this->reveal)) {
352
            $contentData = $this->reveal;
353
            $options = isset($contentData['options']) ? $contentData['options'] : [];
354
            $addIcon = !isset($contentData['titleOptions']['icon']);
355
            $contentData['titleOptions']['icon'] = $addIcon
356
                ? 'close'
357
                : $contentData['titleOptions']['icon'];
358
            Html::addCssClass($options, ['class' => 'card-reveal']);
359
360
            $html .= Html::beginTag('div', $options);
361
            $html .= $this->renderTitleContent($contentData);
362
            $html .= isset($contentData['value']) ? $contentData['value'] : '';
363
            $html .= Html::endTag('div');
364
        }
365
366
        return $html;
367
    }
368
}
369