Passed
Push — master ( 8cb699...b39323 )
by Bruno
08:55
created

Framework::setExtraProps()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 10
c 1
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
19
    /**
20
     * @var string
21
     */
22
    protected $mode = self::VUE_MODE_EMBEDDED;
23
24
    /**
25
    * The tag used as container for fields in viewable()
26
    *
27
    * @var string
28
    */
29
    protected $viewableContainerTag = 'div';
30
31
    /**
32
     * The tag used as container for fields in editable()
33
     *
34
     * @var string
35
     */
36
    protected $editableContainerTag = 'div';
37
38
    /**
39
     * The viewable template.
40
     *
41
     * The following variables are replaced:
42
     *
43
     * {{form}}
44
     * {{jsonData}}
45
     * {{containerTag}}
46
     *
47
     * @var string|callable|null
48
     */
49
    protected $viewableTemplate = null;
50
51
    /**
52
     *
53
     *
54
     * @var string|callable|null
55
     */
56
    protected $editableTemplate = null;
57
58
    /**
59
     * Appended to the field variable names to handle models stored in an object field.
60
     *
61
     * Allows you to declare the model like this:
62
     *
63
     * data() {
64
     *   return {
65
     *       model: model,
66
     *   };
67
     * },
68
     *
69
     * @var string
70
     */
71
    protected $fieldModelVariable = '';
72
73
    /**
74
     * Extra props.
75
     *
76
     * @var array
77
     */
78
    protected $extraProps = [];
79
80
    public function __construct(string $name = 'Vue')
81
    {
82
        parent::__construct($name);
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
    /**
97
     * Get the tag used as container for fields in viewable()
98
     *
99
     * @return  string
100
     */
101
    public function getViewableContainerTag(): string
102
    {
103
        return $this->viewableContainerTag;
104
    }
105
106
    /**
107
     * Set the tag used as container for fields in viewable()
108
     *
109
     * @param  string  $viewableContainerTag  The tag used as container for fields in viewable()
110
     *
111
     * @return  self
112
     */
113
    public function setViewableContainerTag(string $viewableContainerTag): Framework
114
    {
115
        $this->viewableContainerTag = $viewableContainerTag;
116
        return $this;
117
    }
118
119
    public function getEditableContainerTag(): string
120
    {
121
        return $this->editableContainerTag;
122
    }
123
    
124
    /**
125
     * @param string $tag
126
     * @return self
127
     */
128
    public function setEditableContainerTag(string $tag): Framework
129
    {
130
        $this->editableContainerTag = $tag;
131
        return $this;
132
    }
133
134
    /**
135
     * Get the value of editableTemplate
136
     * @return string|callable|null
137
     */
138
    public function getEditableTemplate()
139
    {
140
        return $this->editableTemplate;
141
    }
142
143
    /**
144
     * Set the value of editableTemplate
145
     *
146
     * @param string|callable|null $editableTemplate
147
     * @return self
148
     */
149
    public function setEditableTemplate($editableTemplate): Framework
150
    {
151
        $this->editableTemplate = $editableTemplate;
152
153
        return $this;
154
    }
155
    
156
    /**
157
     * Get viewable template
158
     *
159
     * @return string|callable|null
160
     */
161
    public function getViewableTemplate()
162
    {
163
        return $this->viewableTemplate;
164
    }
165
166
    /**
167
     * Set viewable tempalte
168
     *
169
     * @param string|callable|null  $viewableTemplate
170
     *
171
     * @return  self
172
     */
173
    public function setViewableTemplate($viewableTemplate)
174
    {
175
        $this->viewableTemplate = $viewableTemplate;
176
177
        return $this;
178
    }
179
    
180
    /**
181
     * Sets the vue render mode, single file component or embedded
182
     *
183
     * @param string $mode self::VUE_MODE_EMBEDDED or self::VUE_MODE_SINGLE_FILE
184
     * @return Framework
185
     */
186
    public function setMode(string $mode): Framework
187
    {
188
        $this->mode = $mode;
189
        return $this;
190
    }
191
192
    /**
193
     * Get the value of mode
194
     *
195
     * @return string
196
     */
197
    public function getMode(): string
198
    {
199
        return $this->mode;
200
    }
201
202
    public function htmlHead(HTMLNode &$head)
203
    {
204
        $head->prependContent(
205
            HTMLNode::factory('script', ['src' => "https://cdn.jsdelivr.net/npm/vue/dist/vue.js"])
206
        );
207
    }
208
209
    public function mapType(Datatype $type): string
210
    {
211
        if ($type instanceof Datatype_number) {
212
            return 'Number';
213
        } elseif ($type instanceof Datatype_bool) {
214
            return 'Boolean';
215
        }
216
        return 'String';
217
    }
218
219
    public function props(Model $m): array
220
    {
221
        $props = [];
222
        foreach ($m->getFields() as $field) {
223
            if ($field->getRenderable(self::VUE_PROP, true)) {
224
                $p = [
225
                    'name' => $field->getName(),
226
                    'type' => $this->mapType($field->getDatatype()),
227
                ];
228
                if ($field->getRenderable(Datatype::REQUIRED, false)) {
229
                    $p['required'] = true;
230
                }
231
                $props[] = $p;
232
            }
233
        }
234
        foreach ($this->extraProps as $p) {
235
            if (!array_key_exists('name', $p)) {
236
                throw new Exception('Missing prop name');
237
            }
238
            $props[] = $p;
239
        }
240
        
241
        return $props;
242
    }
243
244
    protected function serializeProps(array $props): string
245
    {
246
        $s = array_map(function ($p) {
247
            return "'{$p['name']}': { 'type': {$p['type']}" . ($p['required'] ?? false ? ", 'required': true" : '') . " } ";
248
        }, $props);
249
        return "{\n        " . implode(",\n        ", $s) . "\n    }\n";
250
    }
251
252
    public function viewableCompose(Model $m, array $elements, string $previousCompose): string
253
    {
254
        $data = array_merge($m->getDefault(), $m->getData());
255
        $viewableForm = join('', $elements);
256
        $jsonData = json_encode($data);
257
        $props = $this->props($m);
258
        $propsBind = array_map(
259
            function ($p) {
260
                return 'v-bind:' . $p . '="model.' . $p . '"';
261
            },
262
            array_keys($props)
263
        );
264
        $templateData = [
265
            'containerTag' => $this->getViewableContainerTag(),
266
            'form' => $viewableForm,
267
            'jsonData' => $jsonData,
268
            'props' => $props,
269
            'propsCode' => $this->serializeProps($props),
270
            'propsBind' => implode(' ', $propsBind)
271
        ];
272
273
        if (is_callable($this->viewableTemplate)) {
274
            return call_user_func(
275
                $this->viewableTemplate,
276
                $this,
277
                $templateData,
278
                $m
279
            );
280
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
281
            $viewableTemplate = $this->viewableTemplate ? $this->viewableTemplate : <<<EOF
282
<template>
283
<{{containerTag}}>
284
    {{form}}
285
</{{containerTag}}>
286
</template>
287
<script>
288
module.exports = {
289
    data: function () {
290
        return {{jsonData}};
291
    },
292
    methods: {
293
    }
294
};
295
</script>
296
<style>
297
</style>
298
EOF;
299
            
300
            return $this->fillTemplate(
301
                $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

301
                /** @scrutinizer ignore-type */ $viewableTemplate,
Loading history...
302
                $templateData,
303
                $m
304
            );
305
        } else {
306
            $id = 'vueapp' . static::counter();
307
            $t = new HTMLNode($this->getViewableContainerTag(), ['id' => $id], $viewableForm, true);
308
            $script = <<<EOF
309
const app_$id = new Vue({
310
    el: '#$id',
311
    data: $jsonData
312
});
313
EOF;
314
            $s = new HTMLNode('script', [], $script, true);
315
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
316
        }
317
    }
318
319
    public function editableCompose(Model $m, array $elements, string $previousCompose): string
320
    {
321
        $data = array_merge($m->getDefault(), $m->getData());
322
        $editableContainerTag = $this->getEditableContainerTag();
323
        $editableForm = join('', $elements);
324
        $jsonData = json_encode($data);
325
        $props = $this->props($m);
326
        $propsBind = array_map(
327
            function ($p) {
328
                return 'v-bind:' . $p . '="model.' . $p . '"';
329
            },
330
            array_keys($props)
331
        );
332
        $templateData = [
333
            'containerTag' => $editableContainerTag,
334
            'form' => $editableForm,
335
            'jsonData' => $jsonData,
336
            'props' => $props,
337
            'propsCode' => $this->serializeProps($props),
338
            'propsBind' => implode(' ', $propsBind),
339
            'methods' => [
340
                'changedFile' => <<<EOF
341
changedFile(name, event) {
342
    console.log(name, event);
343
    const input = event.target;
344
    const files = input.files;
345
    if (files && files[0]) {
346
        // input.preview = window.URL.createObjectURL(files[0]);
347
    }
348
}
349
EOF
350
            ]
351
        ];
352
353
        if (is_callable($this->editableTemplate)) {
354
            return call_user_func(
355
                $this->editableTemplate,
356
                $this,
357
                $templateData,
358
                $m
359
            );
360
        } elseif ($this->editableTemplate) {
361
            return $this->fillTemplate(
362
                $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

362
                /** @scrutinizer ignore-type */ $this->editableTemplate,
Loading history...
363
                $templateData,
364
                $m
365
            );
366
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
367
            $editableTemplate = <<<EOF
368
<template>
369
<{{containerTag}}>
370
    {{form}}
371
</{{containerTag}}>
372
</template>
373
<script>
374
module.exports = {
375
    data: function () {
376
        return {{jsonData}};
377
    },
378
    props: {
379
        {{propsCode}}
380
    },
381
    methods: {
382
        {{methods}}
383
    }
384
};
385
</script>
386
<style>
387
</style>
388
EOF;
389
            return $this->fillTemplate(
390
                $editableTemplate,
391
                $templateData,
392
                $m
393
            );
394
        } else {
395
            $id = 'vueapp' . static::counter();
396
            $t = new HTMLNode($editableContainerTag, ['id' => $id], $editableForm, true);
397
            $script = <<<EOF
398
const app_$id = new Vue({
399
    el: '#$id',
400
    data: function () {
401
        return $jsonData;
402
    },
403
    methods: {
404
    }
405
});
406
EOF;
407
            $s = new HTMLNode('script', [], $script, true);
408
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
409
        }
410
    }
411
412
    protected function fillTemplate(string $template, array $data, Model $m): string
413
    {
414
        foreach ($data as $name => $value) {
415
            $template = str_replace(
416
                '{{' . $name . '}}',
417
                $value,
418
                $template
419
            );
420
        }
421
422
        $template = str_replace(
423
            '{{modelName}}',
424
            $m->getName(),
425
            $template
426
        );
427
        $template = str_replace(
428
            '{{modelNameLower}}',
429
            mb_strtolower($m->getName()),
430
            $template
431
        );
432
        return $template;
433
    }
434
435
    /**
436
     * Get appended to the field variable names to handle models stored in an object field.
437
     *
438
     * @return  string
439
     */
440
    public function getFieldModelVariable(): string
441
    {
442
        return $this->fieldModelVariable;
443
    }
444
445
    /**
446
     * Set appended to the field variable names to handle models stored in an object field.
447
     *
448
     * @param  string  $fieldModelVariable  Appended to the field variable names to handle models stored in an object field.
449
     *
450
     * @return  self
451
     */
452
    public function setFieldModelVariable(string $fieldModelVariable): self
453
    {
454
        $this->fieldModelVariable = $fieldModelVariable;
455
456
        return $this;
457
    }
458
459
    /**
460
     * @return array
461
     */
462
    public function getExtraPropos(): array
463
    {
464
        return $this->extraProps;
465
    }
466
467
    /**
468
     *
469
     * @param array $extraProps
470
     *
471
     * @return  self
472
     */
473
    public function setExtraProps(array $extraProps): self
474
    {
475
        $this->extraProps = $extraProps;
476
477
        return $this;
478
    }
479
480
    /**
481
     *
482
     * @param array $extraProps
483
     *
484
     * @return  self
485
     */
486
    public function appendExtraProp(array $extra): self
487
    {
488
        $this->extraProps[] = $extra;
489
490
        return $this;
491
    }
492
}
493