Completed
Branch master (fd4ef9)
by Dmitrijs
03:57 queued 01:47
created

Card::init()   A

Complexity

Conditions 6
Paths 24

Size

Total Lines 34
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 19
dl 0
loc 34
rs 9.0111
c 0
b 0
f 0
cc 6
nc 24
nop 0
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
 *     'options' => ['class' => 'light-blue darken-4'],
25
 *     'image' => [
26
 *         'title' => 'Image Card Title',
27
 *         'url' => 'https://materializecss.com/images/sample-1.jpg',
28
 *         'fab' => [
29
 *             'type' => ButtonType::FLOATING,
30
 *             'size' => Size::MEDIUM,
31
 *             'waves' => Waves::LIGHT,
32
 *             'icon' => ['name' => 'add'],
33
 *             'options' => ['class' => 'light-blue accent-2']
34
 *         ]
35
 *     ],
36
 *     'content' => [
37
 *         'value' => '<p>I am a very simple card. I am good at containing small bits of information. '
38
 *             . 'I am convenient because I require little markup to use effectively.</p>',
39
 *         'options' => ['class' => 'white-text']
40
 *     ],
41
 *     'actions' => [
42
 *         ['label' => 'This is a link #1'],
43
 *         ['label' => 'This is a link #2']
44
 *     ],
45
 * ]);
46
 * ```
47
 * @see https://materializecss.com/cards.html
48
 * @package widgets
49
 */
50
class Card extends Widget
51
{
52
    /**
53
     * @var bool whether to HTML-encode the link labels and card title
54
     */
55
    public $encodeLabels = true;
56
57
    /**
58
     * @var array a list of attributes to be displayed in the card content tag. Item should be an array
59
     * of the following structure:
60
     * - title: string, title for the card image. Value will be HTML-encoded.
61
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
62
     * - value: string, the HTML content for the card body. It will NOT be HTML-encoded.
63
     *   Therefore you can pass in HTML code. If this is coming from end users,
64
     *   you should consider encode() it to prevent XSS attacks.
65
     * - options: array, the HTML attributes for the card content tag.
66
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
67
     *   (name for the icon), if you want to add icon to the right side of the title.
68
     */
69
    public $content = [];
70
71
    /**
72
     * @var array a list of attributes to be displayed in the card image tag. Item should be an array
73
     * of the following structure:
74
     * - title: string, title for the card image. Value will be HTML-encoded.
75
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
76
     * - url: string the image URL. This parameter will be processed by [[Url::to()]].
77
     * - fab: array list of attributes for floating action button. Value will be passed to [[Button]] widget.
78
     *   You can set extra param 'url' to render it as link.
79
     * - options: array, the HTML attributes for the card image tag.
80
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
81
     *   (name for the icon), if you want to add icon to the right side of the title.
82
     * - imageOptions: array the HTML attributes for the image tag.
83
     */
84
    public $image = [];
85
86
    /**
87
     * @var array list of card action items. Each action item should be an array of the following structure:
88
     * - label: string, specifies the action item label. When [[encodeLabels]] is true, the label
89
     *   will be HTML-encoded.
90
     * - encode: boolean, optional, whether this item`s label should be HTML-encoded. This param will override
91
     *   global [[encodeLabels]] param.
92
     * - url: string or array, optional, specifies the URL of the action item. It will be processed by [[Url::to]].
93
     * - icon: string or array, optional, icon name or array with 'name' and 'options'.
94
     * - options: array, optional, the HTML attributes for the action container tag.
95
     */
96
    public $actions = [];
97
98
    /**
99
     * @var array the HTML attributes for the action wrapper tag of the card view.
100
     * You can use attribute "sticky" to change, whether card actions must be always visible.
101
     * Default value is false.
102
     * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
103
     */
104
    public $actionOptions = [];
105
106
    /**
107
     * @var array a list of attributes to be displayed in the card reveal tag. Item should be an array
108
     * of the following structure:
109
     * - title: string, title for the card image. Value will be HTML-encoded.
110
     *   You can change this, by setting extra attribute ["encode" => false] in "titleOptions" attribute
111
     * - value: string, the HTML content for the card body. It will NOT be HTML-encoded.
112
     *   Therefore you can pass in HTML code. If this is coming from end users,
113
     *   you should consider encode() it to prevent XSS attacks.
114
     * - options: array, the HTML attributes for the card content tag.
115
     * - titleOptions: array the HTML attributes for the title tag. You can specify 'icon' attribute
116
     *   (name for the icon), if you want to add icon to the right side of the title.
117
     */
118
    public $reveal = [];
119
120
    /**
121
     * @var bool whether image should be on left side. Default value is false
122
     */
123
    public $horizontal = false;
124
125
    /**
126
     * @var bool whether card should be spaced as panel. Default value is false.
127
     * Use this for a simpler card with less markup, because it has only padding and a shadow effect.
128
     */
129
    public $panel = false;
130
131
    /**
132
     * Initializes the widget.
133
     * @return void
134
     */
135
    public function init()
136
    {
137
        parent::init();
138
139
        $contentData = $this->content;
140
        $cardContentOptions = isset($this->content['options']) ? $this->content['options'] : [];
141
        Html::addCssClass($cardContentOptions, ['class' => 'card-content']);
142
        $this->setCardClass(); // set main css classes to card tag
143
144
        $html = Html::beginTag('div', $this->options);
145
        $html .= $this->renderImageContent();
146
147
        // Create stacked content, if horizontal attribute is true
148
        if ($this->horizontal) {
149
            $html .= Html::beginTag('div', ['class' => 'card-stacked']);
150
        }
151
152
        // Add reviel button to content title, if reveal attribute is not empty
153
        if (!empty($this->reveal)) {
154
            Html::addCssClass($contentData['titleOptions'], 'activator');
155
            $addIcon = !isset($contentData['titleOptions']['icon']);
156
            $contentData['titleOptions']['icon'] = $addIcon
157
                ? 'more_vert'
158
                : $contentData['titleOptions']['icon'];
159
        }
160
161
        // Create card content, if panel not is true
162
        if (!$this->panel) {
163
            $html .= Html::beginTag('div', $cardContentOptions);
164
        }
165
166
        $html .= $this->renderTitleContent($contentData);
167
168
        echo $html;
169
    }
170
171
    /**
172
     * Renders the widget.
173
     * @return void
174
     */
175
    public function run()
176
    {
177
        $this->registerPlugin('card');
178
179
        $html = isset($this->content['value']) ? $this->content['value'] : '';
180
181
        if (!$this->panel) {
182
            $html .= Html::endTag('div'); // ends card-content tag
183
        }
184
185
        if (!empty($this->actions)) {
186
            Html::addCssClass($this->actionOptions, ['class' => 'card-action']);
187
            $html .= Html::beginTag('div', $this->actionOptions);
188
189
            foreach ($this->actions as $action) {
190
                $html .= $this->renderActionItem($action);
191
            }
192
193
            $html .= Html::endTag('div');
194
        }
195
196
        if ($this->horizontal) {
197
            $html .= Html::endTag('div'); //ends card-stacked tag
198
        }
199
200
        // Add card reveal tag
201
        $html .= $this->renderRevealContent();
202
        $html .= Html::endTag('div'); //ends card tag
203
204
        echo $html;
205
    }
206
207
    /**
208
     * Sets card CSS class (or several classes) to the card options.
209
     * @return void
210
     */
211
    protected function setCardClass()
212
    {
213
        $useStickyActions = isset($this->actionOptions['sticky']) ? $this->actionOptions['sticky'] : false;
214
215
        if ($this->panel) {
216
            Html::addCssClass($this->options, ['card-panel']);
217
        } elseif ($this->horizontal) {
218
            Html::addCssClass($this->options, ['card', 'horizontal']);
219
        } elseif ($useStickyActions) {
220
            Html::addCssClass($this->options, ['card', 'sticky-action']);
221
            unset($this->actionOptions['sticky']);
222
        } else {
223
            Html::addCssClass($this->options, ['card']);
224
        }
225
    }
226
227
    /**
228
     * Renders card content title tag content.
229
     *
230
     * @param array $source data source for title and options
231
     * @return string the rendering result
232
     */
233
    protected function renderTitleContent($source)
234
    {
235
        $html = '';
236
237
        if (isset($source['title'])) {
238
            $titleValue = $source['title'];
239
            $titleOptions = isset($source['titleOptions']) ? $source['titleOptions'] : [];
240
            $encode = isset($titleOptions['encode']) ? $titleOptions['encode'] : $this->encodeLabels;
241
            $icon = isset($titleOptions['icon']) ? $titleOptions['icon'] : null;
242
            unset($titleOptions['encode']);
243
            Html::addCssClass($titleOptions, 'card-title');
244
            $title = $encode ? Html::encode($titleValue) : $titleValue;
245
            $title .= !empty($icon) ? Html::icon($icon, ['class' => 'material-icons right']) : '';
246
            unset($titleOptions['icon']);
247
            $html .= Html::tag('span', $title, $titleOptions);
248
        }
249
250
        return $html;
251
    }
252
253
    /**
254
     * Renders a single card action item.
255
     *
256
     * @param array $link the link to be rendered. It must contain the "label" element. The "url" and "icon" element is optional.
257
     * @return string the rendering result
258
     * @throws InvalidConfigException if `$link` does not have "label" element.
259
     */
260
    protected function renderActionItem($link)
261
    {
262
        $encodeLabel = ArrayHelper::remove($link, 'encode', $this->encodeLabels);
263
264
        if (array_key_exists('label', $link)) {
265
            $label = $encodeLabel ? Html::encode($link['label']) : $link['label'];
266
        } else {
267
            throw new InvalidConfigException('The "label" element is required for each link.');
268
        }
269
270
        // Add icon to label text
271
        if (isset($link['icon'])) {
272
            $view = $this->getView();
273
            MaterializeExtraAsset::register($view);
274
275
            // Has issues on positioning: https://github.com/google/material-design-icons/issues/206
276
            $label = $this->renderIcon($link['icon']) . $label;
277
        }
278
279
        $options = $link['options'];
280
281
        if (isset($link['url'])) {
282
            return Html::a($label, $link['url'], $options);
283
        } else {
284
            return Html::a($label, '#', $options);
285
        }
286
    }
287
288
    /**
289
     * Renders card-image tag content.
290
     * @return string the rendering result
291
     */
292
    protected function renderImageContent()
293
    {
294
        $html = '';
295
296
        if (!empty($this->image)) {
297
            $contentData = $this->image;
298
            $fabData = isset($contentData['fab']) ? $contentData['fab'] : [];
299
            $options = isset($contentData['options']) ? $contentData['options'] : [];
300
            $imageOptions = isset($contentData['imageOptions']) ? $contentData['imageOptions'] : [];
301
            $imageUrl = isset($contentData['url']) ? $contentData['url'] : [];
302
            Html::addCssClass($options, ['class' => 'card-image']);
303
304
            $html .= Html::beginTag('div', $options);
305
            $html .= Html::img($imageUrl, $imageOptions);
306
            $html .= $this->renderTitleContent($contentData);
307
            $html .= $this->renderActionButton($fabData);
308
            $html .= Html::endTag('div');
309
        }
310
311
        return $html;
312
    }
313
314
    /**
315
     * Renders floating action button tag.
316
     *
317
     * @param array $config attribute values and options for Button widget.
318
     * @return string the rendering result
319
     */
320
    protected function renderActionButton($config)
321
    {
322
        $html = '';
323
324
        if (!empty($config)) {
325
            if (!isset($config['options'])) {
326
                $config['options'] = ['class' => 'halfway-fab'];
327
            } else {
328
                Html::addCssClass($config['options'], 'halfway-fab');
329
            }
330
331
            $html .= Button::widget($config);
332
        }
333
334
        return $html;
335
    }
336
337
    /**
338
     * Renders card-reveal tag content.
339
     * @return string the rendering result
340
     */
341
    protected function renderRevealContent()
342
    {
343
        $html = '';
344
345
        if (!empty($this->reveal)) {
346
            $contentData = $this->reveal;
347
            $options = isset($contentData['options']) ? $contentData['options'] : [];
348
            $addIcon = !isset($contentData['titleOptions']['icon']);
349
            $contentData['titleOptions']['icon'] = $addIcon
350
                ? 'close'
351
                : $contentData['titleOptions']['icon'];
352
            Html::addCssClass($options, ['class' => 'card-reveal']);
353
354
            $html .= Html::beginTag('div', $options);
355
            $html .= $this->renderTitleContent($contentData);
356
            $html .= isset($contentData['value']) ? $contentData['value'] : '';
357
            $html .= Html::endTag('div');
358
        }
359
360
        return $html;
361
    }
362
}
363