Passed
Push — master ( b53b80...d3b52d )
by Bruno
07:51
created

Framework::getContainerAttributes()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 16
rs 9.9332
cc 3
nc 4
nop 1
1
<?php declare(strict_types=1);
2
3
namespace Formularium\Frontend\Vue;
4
5
use Formularium\Datatype;
6
use Formularium\Datatype\Datatype_bool;
7
use Formularium\Datatype\Datatype_number;
8
use Formularium\Exception\Exception;
9
use Formularium\HTMLNode;
10
use Formularium\Model;
11
12
class Framework extends \Formularium\Framework
13
{
14
    const VUE_MODE_SINGLE_FILE = 'VUE_MODE_SINGLE_FILE';
15
    const VUE_MODE_EMBEDDED = 'VUE_MODE_EMBEDDED';
16
    const VUE_PROP = 'VUE_PROP';
17
    const VUE_VARS = 'VUE_VARS';
18
    const VUE_CODE = 'VUE_CODE';
19
20
    /**
21
     * @var string
22
     */
23
    protected $mode = self::VUE_MODE_EMBEDDED;
24
25
    /**
26
    * The tag used as container for fields in viewable()
27
    *
28
    * @var string
29
    */
30
    protected $viewableContainerTag = 'div';
31
32
    /**
33
     * The tag used as container for fields in editable()
34
     *
35
     * @var string
36
     */
37
    protected $editableContainerTag = 'div';
38
39
    /**
40
     * The viewable template.
41
     *
42
     * The following variables are replaced:
43
     *
44
     * {{form}}
45
     * {{script}}
46
     * {{containerTag}}
47
     *
48
     * @var string|callable|null
49
     */
50
    protected $viewableTemplate = null;
51
52
    /**
53
     *
54
     *
55
     * @var string|callable|null
56
     */
57
    protected $editableTemplate = null;
58
59
    /**
60
     * Appended to the field variable names to handle models stored in an object field.
61
     *
62
     * Allows you to declare the model like this:
63
     *
64
     * data() {
65
     *   return {
66
     *       model: model,
67
     *   };
68
     * },
69
     *
70
     * @var string
71
     */
72
    protected $fieldModelVariable = '';
73
74
    /**
75
     * @var VueCode
76
     */
77
    protected $vueCode = null;
78
79
    public function __construct(string $name = 'Vue')
80
    {
81
        parent::__construct($name);
82
        $this->vueCode = new VueCode();
83
    }
84
85
    /**
86
     * Static counter to generate unique ids.
87
     *
88
     * @return integer
89
     */
90
    public static function counter(): int
91
    {
92
        static $counter = 0;
93
        return $counter++;
94
    }
95
96
    protected function getContainerAttributes(Model $m): array
97
    {
98
        // TODO: this replicates code from HTML::Framework. See if there's a cleaner way to handle this.
99
        $atts = [
100
            'class' => 'formularium-base'
101
        ];
102
        $schemaItemScope = $this->getOption('itemscope', $m->getRenderable('itemscope', false));
103
        if ($schemaItemScope) {
104
            $atts['itemscope'] = '';
105
        }
106
        $schemaItemType = $this->getOption('itemtype', $m->getRenderable('itemtype', null));
107
        if ($schemaItemType) {
108
            $atts['itemtype'] = $schemaItemType;
109
            $atts['itemscope'] = '';
110
        }
111
        return $atts;
112
    }
113
114
    /**
115
     * Collapses an array into a HTML list of attributes. Not particularly
116
     * safe function, but data goes through htmlspecialchars()
117
     *
118
     * @param array $f
119
     * @return string
120
     */
121
    protected function collapseHTMLAttributes(array $f): string
122
    {
123
        $x = [];
124
        foreach ($f as $k => $v) {
125
            $x[] = htmlspecialchars($k) . '="' . htmlspecialchars($v) . '"';
126
        }
127
        return join(' ', $x);
128
    }
129
130
    /**
131
     * Get the tag used as container for fields in viewable()
132
     *
133
     * @return  string
134
     */
135
    public function getViewableContainerTag(): string
136
    {
137
        return $this->viewableContainerTag;
138
    }
139
140
    /**
141
     * Set the tag used as container for fields in viewable()
142
     *
143
     * @param  string  $viewableContainerTag  The tag used as container for fields in viewable()
144
     *
145
     * @return  self
146
     */
147
    public function setViewableContainerTag(string $viewableContainerTag): Framework
148
    {
149
        $this->viewableContainerTag = $viewableContainerTag;
150
        return $this;
151
    }
152
153
    public function getEditableContainerTag(): string
154
    {
155
        return $this->editableContainerTag;
156
    }
157
    
158
    /**
159
     * @param string $tag
160
     * @return self
161
     */
162
    public function setEditableContainerTag(string $tag): Framework
163
    {
164
        $this->editableContainerTag = $tag;
165
        return $this;
166
    }
167
168
    /**
169
     * Get the value of editableTemplate
170
     * @return string|callable|null
171
     */
172
    public function getEditableTemplate()
173
    {
174
        return $this->editableTemplate;
175
    }
176
177
    /**
178
     * Set the value of editableTemplate
179
     *
180
     * @param string|callable|null $editableTemplate
181
     * @return self
182
     */
183
    public function setEditableTemplate($editableTemplate): Framework
184
    {
185
        $this->editableTemplate = $editableTemplate;
186
187
        return $this;
188
    }
189
    
190
    /**
191
     * Get viewable template
192
     *
193
     * @return string|callable|null
194
     */
195
    public function getViewableTemplate()
196
    {
197
        return $this->viewableTemplate;
198
    }
199
200
    /**
201
     * Set viewable tempalte
202
     *
203
     * @param string|callable|null  $viewableTemplate
204
     *
205
     * @return  self
206
     */
207
    public function setViewableTemplate($viewableTemplate)
208
    {
209
        $this->viewableTemplate = $viewableTemplate;
210
211
        return $this;
212
    }
213
    
214
    /**
215
     * Sets the vue render mode, single file component or embedded
216
     *
217
     * @param string $mode self::VUE_MODE_EMBEDDED or self::VUE_MODE_SINGLE_FILE
218
     * @return Framework
219
     */
220
    public function setMode(string $mode): Framework
221
    {
222
        $this->mode = $mode;
223
        return $this;
224
    }
225
226
    /**
227
     * Get the value of mode
228
     *
229
     * @return string
230
     */
231
    public function getMode(): string
232
    {
233
        return $this->mode;
234
    }
235
236
    public function htmlHead(HTMLNode &$head)
237
    {
238
        $head->prependContent(
239
            [
240
                HTMLNode::factory('script', ['src' => "https://vuejs.org/js/vue.js"]),
241
                HTMLNode::factory('script', [], 'Vue.config.devtools = true')
242
            ]
243
        );
244
    }
245
   
246
    public function viewableCompose(Model $m, array $elements, string $previousCompose): string
247
    {
248
        $containerAtts = $this->getContainerAttributes($m);
249
        $templateData = [
250
            'containerTag' => $this->getViewableContainerTag(),
251
            'containerAtts' => $this->collapseHTMLAttributes($containerAtts),
252
            'form' => join('', $elements),
253
            'script' => $this->vueCode->toScript($m, $elements)
254
        ];
255
256
        if (is_callable($this->viewableTemplate)) {
257
            return call_user_func(
258
                $this->viewableTemplate,
259
                $this,
260
                $templateData,
261
                $m
262
            );
263
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
264
            $viewableTemplate = $this->viewableTemplate ? $this->viewableTemplate : <<<EOF
265
<template>
266
<{{containerTag}} {{containerAtts}}>
267
    {{form}}
268
</{{containerTag}}>
269
</template>
270
<script>
271
{{{script}}}
272
</script>
273
<style>
274
</style>
275
EOF;
276
            
277
            return $this->fillTemplate(
278
                $viewableTemplate,
0 ignored issues
show
Bug introduced by
It seems like $viewableTemplate can also be of type callable; however, parameter $template of Formularium\Frontend\Vue\Framework::fillTemplate() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

278
                /** @scrutinizer ignore-type */ $viewableTemplate,
Loading history...
279
                $templateData,
280
                $m
281
            );
282
        } else {
283
            // TODO: this is likely broken
284
            $id = 'vueapp' . static::counter();
285
            $containerAtts['id'] = $id;
286
            $t = new HTMLNode(
287
                $this->getViewableContainerTag(),
288
                $containerAtts,
289
                $templateData['form'],
290
                true
291
            );
292
            $vars = $this->vueCode->toVariable($m, $elements);
293
            $this->vueCode->appendOther('el', "#$id");
294
            $script = "const app_$id = new Vue({$vars});";
295
            $s = new HTMLNode('script', [], $script, true);
296
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
297
        }
298
    }
299
300
    public function editableCompose(Model $m, array $elements, string $previousCompose): string
301
    {
302
        $containerAtts = $this->getContainerAttributes($m);
303
        $templateData = [
304
            'containerTag' => $this->getEditableContainerTag(),
305
            'containerAtts' => $this->collapseHTMLAttributes($containerAtts),
306
            'form' => join('', $elements),
307
            'script' => $this->vueCode->toScript($m, $elements)
308
        ];
309
        
310
        if (is_callable($this->editableTemplate)) {
311
            return call_user_func(
312
                $this->editableTemplate,
313
                $this,
314
                $templateData,
315
                $m
316
            );
317
        } elseif ($this->editableTemplate) {
318
            return $this->fillTemplate(
319
                $this->editableTemplate,
0 ignored issues
show
Bug introduced by
It seems like $this->editableTemplate can also be of type callable; however, parameter $template of Formularium\Frontend\Vue\Framework::fillTemplate() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

319
                /** @scrutinizer ignore-type */ $this->editableTemplate,
Loading history...
320
                $templateData,
321
                $m
322
            );
323
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
324
            $editableTemplate = <<<EOF
325
<template>
326
<{{containerTag}} {{containerAtts}}>
327
    {{form}}
328
</{{containerTag}}>
329
</template>
330
<script>
331
{{{script}}}
332
</script>
333
<style>
334
</style>
335
EOF;
336
            return $this->fillTemplate(
337
                $editableTemplate,
338
                $templateData,
339
                $m
340
            );
341
        } else {
342
            $id = 'vueapp' . static::counter();
343
            $containerAtts['id'] = $id;
344
            $t = new HTMLNode(
345
                $templateData['containerTag'],
346
                $containerAtts,
347
                $templateData['form'],
348
                true
349
            );
350
            $this->vueCode->appendOther('el', "'#$id'");
351
            $vars = $this->vueCode->toVariable($m, $elements);
352
            $script = "const app_$id = new Vue({{$vars}});";
353
            $s = new HTMLNode('script', [], $script, true);
354
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
355
        }
356
    }
357
358
    protected function fillTemplate(string $template, array $data, Model $m): string
359
    {
360
        foreach ($data as $name => $value) {
361
            $template = str_replace(
362
                '{{' . $name . '}}',
363
                $value,
364
                $template
365
            );
366
        }
367
368
        $template = str_replace(
369
            '{{modelName}}',
370
            $m->getName(),
371
            $template
372
        );
373
        $template = str_replace(
374
            '{{modelNameLower}}',
375
            mb_strtolower($m->getName()),
376
            $template
377
        );
378
        return $template;
379
    }
380
381
    /**
382
     * Get appended to the field variable names to handle models stored in an object field.
383
     *
384
     * @return  string
385
     */
386
    public function getFieldModelVariable(): string
387
    {
388
        return $this->fieldModelVariable;
389
    }
390
391
    /**
392
     * Set appended to the field variable names to handle models stored in an object field.
393
     *
394
     * @param  string  $fieldModelVariable  Appended to the field variable names to handle models stored in an object field.
395
     *
396
     * @return  self
397
     */
398
    public function setFieldModelVariable(string $fieldModelVariable): self
399
    {
400
        $this->fieldModelVariable = $fieldModelVariable;
401
402
        return $this;
403
    }
404
405
    /**
406
     * Get the value of vueCode
407
     *
408
     * @return  VueCode
409
     */
410
    public function getVueCode()
411
    {
412
        return $this->vueCode;
413
    }
414
415
    public function resetVueCode(): void
416
    {
417
        $this->vueCode = new VueCode();
418
    }
419
}
420