Passed
Push — master ( 5fa81e...edc575 )
by Bruno
09:36
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\HTMLNode;
9
use Formularium\Model;
10
11
class Framework extends \Formularium\Framework
12
{
13
    const VUE_MODE_SINGLE_FILE = 'VUE_MODE_SINGLE_FILE';
14
    const VUE_MODE_EMBEDDED = 'VUE_MODE_EMBEDDED';
15
16
    const VUE_PROP = 'VUE_PROP';
17
18
    const VUE_VARS = 'VUE_VARS';
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
     * {{jsonData}}
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
     * @var string
63
     */
64
    protected $fieldModelVariable = '';
65
66
    public function __construct(string $name = 'Vue')
67
    {
68
        parent::__construct($name);
69
    }
70
71
    /**
72
     * Static counter to generate unique ids.
73
     *
74
     * @return integer
75
     */
76
    public static function counter(): int
77
    {
78
        static $counter = 0;
79
        return $counter++;
80
    }
81
82
    /**
83
     * Get the tag used as container for fields in viewable()
84
     *
85
     * @return  string
86
     */
87
    public function getViewableContainerTag(): string
88
    {
89
        return $this->viewableContainerTag;
90
    }
91
92
    /**
93
     * Set the tag used as container for fields in viewable()
94
     *
95
     * @param  string  $viewableContainerTag  The tag used as container for fields in viewable()
96
     *
97
     * @return  self
98
     */
99
    public function setViewableContainerTag(string $viewableContainerTag): Framework
100
    {
101
        $this->viewableContainerTag = $viewableContainerTag;
102
        return $this;
103
    }
104
105
    public function getEditableContainerTag(): string
106
    {
107
        return $this->editableContainerTag;
108
    }
109
    
110
    /**
111
     * @param string $tag
112
     * @return self
113
     */
114
    public function setEditableContainerTag(string $tag): Framework
115
    {
116
        $this->editableContainerTag = $tag;
117
        return $this;
118
    }
119
120
    /**
121
     * Get the value of editableTemplate
122
     * @return string|callable|null
123
     */
124
    public function getEditableTemplate()
125
    {
126
        return $this->editableTemplate;
127
    }
128
129
    /**
130
     * Set the value of editableTemplate
131
     *
132
     * @param string|callable|null $editableTemplate
133
     * @return self
134
     */
135
    public function setEditableTemplate($editableTemplate): Framework
136
    {
137
        $this->editableTemplate = $editableTemplate;
138
139
        return $this;
140
    }
141
    
142
    /**
143
     * Get viewable template
144
     *
145
     * @return string|callable|null
146
     */
147
    public function getViewableTemplate()
148
    {
149
        return $this->viewableTemplate;
150
    }
151
152
    /**
153
     * Set viewable tempalte
154
     *
155
     * @param string|callable|null  $viewableTemplate
156
     *
157
     * @return  self
158
     */
159
    public function setViewableTemplate($viewableTemplate)
160
    {
161
        $this->viewableTemplate = $viewableTemplate;
162
163
        return $this;
164
    }
165
    
166
    /**
167
     * Sets the vue render mode, single file component or embedded
168
     *
169
     * @param string $mode self::VUE_MODE_EMBEDDED or self::VUE_MODE_SINGLE_FILE
170
     * @return Framework
171
     */
172
    public function setMode(string $mode): Framework
173
    {
174
        $this->mode = $mode;
175
        return $this;
176
    }
177
178
    /**
179
     * Get the value of mode
180
     *
181
     * @return string
182
     */
183
    public function getMode(): string
184
    {
185
        return $this->mode;
186
    }
187
188
    public function htmlHead(HTMLNode &$head)
189
    {
190
        $head->prependContent(
191
            HTMLNode::factory('script', ['src' => "https://cdn.jsdelivr.net/npm/vue/dist/vue.js"])
192
        );
193
    }
194
195
    public function mapType(Datatype $type): string
196
    {
197
        if ($type instanceof Datatype_number) {
198
            return 'Number';
199
        } elseif ($type instanceof Datatype_bool) {
200
            return 'Boolean';
201
        }
202
        return 'String';
203
    }
204
205
    public function props(Model $m): array
206
    {
207
        $props = [];
208
        foreach ($m->getFields() as $field) {
209
            if ($field->getRenderable(self::VUE_PROP, true)) { // TODO
210
                $p = [
211
                    'name' => $field->getName(),
212
                    'type' => $this->mapType($field->getDatatype()),
213
                ];
214
                if ($field->getRenderable(Datatype::REQUIRED, false)) {
215
                    $p['required'] = true;
216
                }
217
                $props[] = $p;
218
            }
219
        }
220
        return $props;
221
    }
222
223
    protected function serializeProps(array $props): string
224
    {
225
        $s = array_map(function ($p) {
226
            return "'{$p['name']}': { 'type': {$p['type']}" . ($p['required'] ?? false ? ", 'required': true" : '') . " } ";
227
        }, $props);
228
        return "{\n        " . implode(",\n        ", $s) . "\n    }\n";
229
    }
230
231
    public function viewableCompose(Model $m, array $elements, string $previousCompose): string
232
    {
233
        $data = array_merge($m->getDefault(), $m->getData());
234
        $viewableForm = join('', $elements);
235
        $jsonData = json_encode($data);
236
        $props = $this->props($m);
237
        $propsBind = array_map(
238
            function ($p) {
239
                return 'v-bind:' . $p . '="model.' . $p . '"';
240
            },
241
            array_keys($props)
242
        );
243
        $templateData = [
244
            'containerTag' => $this->getViewableContainerTag(),
245
            'form' => $viewableForm,
246
            'jsonData' => $jsonData,
247
            'props' => $props,
248
            'propsCode' => $this->serializeProps($props),
249
            'propsBind' => implode(' ', $propsBind)
250
        ];
251
252
        if (is_callable($this->viewableTemplate)) {
253
            return call_user_func(
254
                $this->viewableTemplate,
255
                $this,
256
                $templateData,
257
                $m
258
            );
259
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
260
            $viewableTemplate = $this->viewableTemplate ? $this->viewableTemplate : <<<EOF
261
<template>
262
<{{containerTag}}>
263
    {{form}}
264
</{{containerTag}}>
265
</template>
266
<script>
267
module.exports = {
268
    data: function () {
269
        return {{jsonData}};
270
    },
271
    methods: {
272
    }
273
};
274
</script>
275
<style>
276
</style>
277
EOF;
278
            
279
            return $this->fillTemplate(
280
                $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

280
                /** @scrutinizer ignore-type */ $viewableTemplate,
Loading history...
281
                $templateData,
282
                $m
283
            );
284
        } else {
285
            $id = 'vueapp' . static::counter();
286
            $t = new HTMLNode($this->getViewableContainerTag(), ['id' => $id], $viewableForm, true);
287
            $script = <<<EOF
288
const app_$id = new Vue({
289
    el: '#$id',
290
    data: $jsonData
291
});
292
EOF;
293
            $s = new HTMLNode('script', [], $script, true);
294
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
295
        }
296
    }
297
298
    public function editableCompose(Model $m, array $elements, string $previousCompose): string
299
    {
300
        $data = array_merge($m->getDefault(), $m->getData());
301
        $editableContainerTag = $this->getEditableContainerTag();
302
        $editableForm = join('', $elements);
303
        $jsonData = json_encode($data);
304
        $props = $this->props($m);
305
        $propsBind = array_map(
306
            function ($p) {
307
                return 'v-bind:' . $p . '="model.' . $p . '"';
308
            },
309
            array_keys($props)
310
        );
311
        $templateData = [
312
            'containerTag' => $editableContainerTag,
313
            'form' => $editableForm,
314
            'jsonData' => $jsonData,
315
            'props' => $props,
316
            'propsCode' => $this->serializeProps($props),
317
            'propsBind' => implode(' ', $propsBind),
318
            'methods' => [
319
                'changedFile' => <<<EOF
320
changedFile(name, event) {
321
    console.log(name, event);
322
    const input = event.target;
323
    const files = input.files;
324
    if (files && files[0]) {
325
        // input.preview = window.URL.createObjectURL(files[0]);
326
    }
327
}
328
EOF
329
            ]
330
        ];
331
332
        if (is_callable($this->editableTemplate)) {
333
            return call_user_func(
334
                $this->editableTemplate,
335
                $this,
336
                $templateData,
337
                $m
338
            );
339
        } elseif ($this->editableTemplate) {
340
            return $this->fillTemplate(
341
                $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

341
                /** @scrutinizer ignore-type */ $this->editableTemplate,
Loading history...
342
                $templateData,
343
                $m
344
            );
345
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
346
            $editableTemplate = <<<EOF
347
<template>
348
<{{containerTag}}>
349
    {{form}}
350
</{{containerTag}}>
351
</template>
352
<script>
353
module.exports = {
354
    data: function () {
355
        return {{jsonData}};
356
    },
357
    props: {
358
        {{propsCode}}
359
    },
360
    methods: {
361
        {{methods}}
362
    }
363
};
364
</script>
365
<style>
366
</style>
367
EOF;
368
            return $this->fillTemplate(
369
                $editableTemplate,
370
                $templateData,
371
                $m
372
            );
373
        } else {
374
            $id = 'vueapp' . static::counter();
375
            $t = new HTMLNode($editableContainerTag, ['id' => $id], $editableForm, true);
376
            $script = <<<EOF
377
const app_$id = new Vue({
378
    el: '#$id',
379
    data: function () {
380
        return $jsonData;
381
    },
382
    methods: {
383
        {{methods}}
384
    }
385
});
386
EOF;
387
            $s = new HTMLNode('script', [], $script, true);
388
            return HTMLNode::factory('div', [], [$t, $s])->getRenderHTML();
389
        }
390
    }
391
392
    protected function fillTemplate(string $template, array $data, Model $m): string
393
    {
394
        foreach ($data as $name => $value) {
395
            $template = str_replace(
396
                '{{' . $name . '}}',
397
                $value,
398
                $template
399
            );
400
        }
401
402
        $template = str_replace(
403
            '{{modelName}}',
404
            $m->getName(),
405
            $template
406
        );
407
        $template = str_replace(
408
            '{{modelNameLower}}',
409
            mb_strtolower($m->getName()),
410
            $template
411
        );
412
        return $template;
413
    }
414
415
    /**
416
     * Get appended to the field variable names to handle models stored in an object field.
417
     *
418
     * @return  string
419
     */
420
    public function getFieldModelVariable(): string
421
    {
422
        return $this->fieldModelVariable;
423
    }
424
425
    /**
426
     * Set appended to the field variable names to handle models stored in an object field.
427
     *
428
     * @param  string  $fieldModelVariable  Appended to the field variable names to handle models stored in an object field.
429
     *
430
     * @return  self
431
     */
432
    public function setFieldModelVariable(string $fieldModelVariable): self
433
    {
434
        $this->fieldModelVariable = $fieldModelVariable;
435
436
        return $this;
437
    }
438
}
439