Passed
Push — master ( 0c861c...680902 )
by Bruno
04:26
created

Framework::setFieldModelVariable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
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
     * @var VueCode
61
     */
62
    protected $vueCode = null;
63
64
    public function __construct(string $name = 'Vue')
65
    {
66
        parent::__construct($name);
67
        $this->vueCode = new VueCode();
68
    }
69
70
    /**
71
     * Static counter to generate unique ids.
72
     *
73
     * @return integer
74
     */
75
    public static function counter(): int
76
    {
77
        static $counter = 0;
78
        return $counter++;
79
    }
80
81
    protected function getContainerAttributes(Model $m): array
82
    {
83
        // TODO: this replicates code from HTML::Framework. See if there's a cleaner way to handle this.
84
        $atts = [
85
            'class' => 'formularium-base'
86
        ];
87
        $schemaItemScope = $this->getOption('itemscope', $m->getRenderable('itemscope', false));
88
        if ($schemaItemScope) {
89
            $atts['itemscope'] = '';
90
        }
91
        $schemaItemType = $this->getOption('itemtype', $m->getRenderable('itemtype', null));
92
        if ($schemaItemType) {
93
            $atts['itemtype'] = $schemaItemType;
94
            $atts['itemscope'] = '';
95
        }
96
        return $atts;
97
    }
98
99
    /**
100
     * Collapses an array into a HTML list of attributes. Not particularly
101
     * safe function, but data goes through htmlspecialchars()
102
     *
103
     * @param array $f
104
     * @return string
105
     */
106
    protected function collapseHTMLAttributes(array $f): string
107
    {
108
        $x = [];
109
        foreach ($f as $k => $v) {
110
            $x[] = htmlspecialchars($k) . '="' . htmlspecialchars($v) . '"';
111
        }
112
        return join(' ', $x);
113
    }
114
115
    /**
116
     * Get the tag used as container for fields in viewable()
117
     *
118
     * @return  string
119
     */
120
    public function getViewableContainerTag(): string
121
    {
122
        return $this->viewableContainerTag;
123
    }
124
125
    /**
126
     * Set the tag used as container for fields in viewable()
127
     *
128
     * @param  string  $viewableContainerTag  The tag used as container for fields in viewable()
129
     *
130
     * @return  self
131
     */
132
    public function setViewableContainerTag(string $viewableContainerTag): Framework
133
    {
134
        $this->viewableContainerTag = $viewableContainerTag;
135
        return $this;
136
    }
137
138
    public function getEditableContainerTag(): string
139
    {
140
        return $this->editableContainerTag;
141
    }
142
    
143
    /**
144
     * @param string $tag
145
     * @return self
146
     */
147
    public function setEditableContainerTag(string $tag): Framework
148
    {
149
        $this->editableContainerTag = $tag;
150
        return $this;
151
    }
152
153
    /**
154
     * Get the value of editableTemplate
155
     * @return string|callable|null
156
     */
157
    public function getEditableTemplate()
158
    {
159
        return $this->editableTemplate;
160
    }
161
162
    /**
163
     * Set the value of editableTemplate
164
     *
165
     * @param string|callable|null $editableTemplate
166
     * @return self
167
     */
168
    public function setEditableTemplate($editableTemplate): Framework
169
    {
170
        $this->editableTemplate = $editableTemplate;
171
172
        return $this;
173
    }
174
    
175
    /**
176
     * Get viewable template
177
     *
178
     * @return string|callable|null
179
     */
180
    public function getViewableTemplate()
181
    {
182
        return $this->viewableTemplate;
183
    }
184
185
    /**
186
     * Set viewable tempalte
187
     *
188
     * @param string|callable|null  $viewableTemplate
189
     *
190
     * @return  self
191
     */
192
    public function setViewableTemplate($viewableTemplate)
193
    {
194
        $this->viewableTemplate = $viewableTemplate;
195
196
        return $this;
197
    }
198
    
199
    /**
200
     * Sets the vue render mode, single file component or embedded
201
     *
202
     * @param string $mode self::VUE_MODE_EMBEDDED or self::VUE_MODE_SINGLE_FILE
203
     * @return Framework
204
     */
205
    public function setMode(string $mode): Framework
206
    {
207
        $this->mode = $mode;
208
        return $this;
209
    }
210
211
    /**
212
     * Get the value of mode
213
     *
214
     * @return string
215
     */
216
    public function getMode(): string
217
    {
218
        return $this->mode;
219
    }
220
221
    public function htmlHead(HTMLNode &$head)
222
    {
223
        $head->prependContent(
224
            [
225
                HTMLNode::factory('script', ['src' => "https://vuejs.org/js/vue.js"]),
226
                HTMLNode::factory('script', [], 'Vue.config.devtools = true')
227
            ]
228
        );
229
    }
230
   
231
    public function viewableCompose(Model $m, array $elements, string $previousCompose): string
232
    {
233
        $containerAtts = $this->getContainerAttributes($m);
234
        $templateData = [
235
            'containerTag' => $this->getViewableContainerTag(),
236
            'containerAtts' => $this->collapseHTMLAttributes($containerAtts),
237
            'form' => join('', $elements),
238
            'script' => $this->vueCode->toScript($m, $elements)
239
        ];
240
241
        if (is_callable($this->viewableTemplate)) {
242
            return call_user_func(
243
                $this->viewableTemplate,
244
                $this,
245
                $templateData,
246
                $m
247
            );
248
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
249
            $viewableTemplate = $this->viewableTemplate ? $this->viewableTemplate : <<<EOF
250
<template>
251
<{{containerTag}} {{containerAtts}}>
252
    {{form}}
253
</{{containerTag}}>
254
</template>
255
<script>
256
{{{script}}}
257
</script>
258
<style>
259
</style>
260
EOF;
261
            
262
            return $this->fillTemplate(
263
                $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

263
                /** @scrutinizer ignore-type */ $viewableTemplate,
Loading history...
264
                $templateData,
265
                $m
266
            );
267
        } else {
268
            // TODO: this is likely broken
269
            $id = 'vueapp' . static::counter();
270
            $containerAtts['id'] = $id;
271
            $t = new HTMLNode(
272
                $this->getViewableContainerTag(),
273
                $containerAtts,
274
                $templateData['form'],
275
                true
276
            );
277
            $vars = $this->vueCode->toVariable($m, $elements);
278
            $this->vueCode->appendOther('el', "#$id");
279
            $script = "const app_$id = new Vue({$vars});";
280
            $s = new HTMLNode('script', [], $script, true);
281
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
282
        }
283
    }
284
285
    public function editableCompose(Model $m, array $elements, string $previousCompose): string
286
    {
287
        $containerAtts = $this->getContainerAttributes($m);
288
        $templateData = [
289
            'containerTag' => $this->getEditableContainerTag(),
290
            'containerAtts' => $this->collapseHTMLAttributes($containerAtts),
291
            'form' => join('', $elements),
292
            'script' => $this->vueCode->toScript($m, $elements)
293
        ];
294
        
295
        if (is_callable($this->editableTemplate)) {
296
            return call_user_func(
297
                $this->editableTemplate,
298
                $this,
299
                $templateData,
300
                $m
301
            );
302
        } elseif ($this->editableTemplate) {
303
            return $this->fillTemplate(
304
                $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

304
                /** @scrutinizer ignore-type */ $this->editableTemplate,
Loading history...
305
                $templateData,
306
                $m
307
            );
308
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
309
            $editableTemplate = <<<EOF
310
<template>
311
<{{containerTag}} {{containerAtts}}>
312
    {{form}}
313
</{{containerTag}}>
314
</template>
315
<script>
316
{{{script}}}
317
</script>
318
<style>
319
</style>
320
EOF;
321
            return $this->fillTemplate(
322
                $editableTemplate,
323
                $templateData,
324
                $m
325
            );
326
        } else {
327
            $id = 'vueapp' . static::counter();
328
            $containerAtts['id'] = $id;
329
            $t = new HTMLNode(
330
                $templateData['containerTag'],
331
                $containerAtts,
332
                $templateData['form'],
333
                true
334
            );
335
            $this->vueCode->appendOther('el', "'#$id'");
336
            $vars = $this->vueCode->toVariable($m, $elements);
337
            $script = "const app_$id = new Vue({{$vars}});";
338
            $s = new HTMLNode('script', [], $script, true);
339
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
340
        }
341
    }
342
343
    protected function fillTemplate(string $template, array $data, Model $m): string
344
    {
345
        foreach ($data as $name => $value) {
346
            $template = str_replace(
347
                '{{' . $name . '}}',
348
                $value,
349
                $template
350
            );
351
        }
352
353
        $template = str_replace(
354
            '{{modelName}}',
355
            $m->getName(),
356
            $template
357
        );
358
        $template = str_replace(
359
            '{{modelNameLower}}',
360
            mb_strtolower($m->getName()),
361
            $template
362
        );
363
        return $template;
364
    }
365
    
366
    /**
367
     * Get the value of vueCode
368
     *
369
     * @return  VueCode
370
     */
371
    public function getVueCode()
372
    {
373
        return $this->vueCode;
374
    }
375
376
    public function resetVueCode(): void
377
    {
378
        $this->vueCode = new VueCode();
379
    }
380
}
381