Passed
Push — master ( 1e4020...b3ba86 )
by Bruno
07:15
created

Framework::serializeProps()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 2
eloc 4
c 1
b 1
f 0
nc 1
nop 1
dl 0
loc 6
rs 10
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\HTMLElement;
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
49
     */
50
    protected $viewableTemplate = '';
51
52
    /**
53
     *
54
     *
55
     * @var string
56
     */
57
    protected $editableTemplate = '';
58
59
    public function __construct(string $name = 'Vue')
60
    {
61
        parent::__construct($name);
62
    }
63
64
    /**
65
     * Static counter to generate unique ids.
66
     *
67
     * @return integer
68
     */
69
    public static function counter(): int
70
    {
71
        static $counter = 0;
72
        return $counter++;
73
    }
74
75
    /**
76
     * Get the tag used as container for fields in viewable()
77
     *
78
     * @return  string
79
     */
80
    public function getViewableContainerTag(): string
81
    {
82
        return $this->viewableContainerTag;
83
    }
84
85
    /**
86
     * Set the tag used as container for fields in viewable()
87
     *
88
     * @param  string  $viewableContainerTag  The tag used as container for fields in viewable()
89
     *
90
     * @return  self
91
     */
92
    public function setViewableContainerTag(string $viewableContainerTag): Framework
93
    {
94
        $this->viewableContainerTag = $viewableContainerTag;
95
        return $this;
96
    }
97
98
    public function getEditableContainerTag(): string
99
    {
100
        return $this->editableContainerTag;
101
    }
102
    
103
    /**
104
     * @param string $tag
105
     * @return self
106
     */
107
    public function setEditableContainerTag(string $tag): Framework
108
    {
109
        $this->editableContainerTag = $tag;
110
        return $this;
111
    }
112
113
    /**
114
     * Get the value of editableTemplate
115
     */
116
    public function getEditableTemplate(): string
117
    {
118
        return $this->editableTemplate;
119
    }
120
121
    /**
122
     * Set the value of editableTemplate
123
     *
124
     * @return self
125
     */
126
    public function setEditableTemplate(string $editableTemplate): Framework
127
    {
128
        $this->editableTemplate = $editableTemplate;
129
130
        return $this;
131
    }
132
    
133
    /**
134
     * Get {{containerTag}}
135
     *
136
     * @return  string
137
     */
138
    public function getViewableTemplate()
139
    {
140
        return $this->viewableTemplate;
141
    }
142
143
    /**
144
     * Set {{containerTag}}
145
     *
146
     * @param  string  $viewableTemplate  {{containerTag}}
147
     *
148
     * @return  self
149
     */
150
    public function setViewableTemplate(string $viewableTemplate)
151
    {
152
        $this->viewableTemplate = $viewableTemplate;
153
154
        return $this;
155
    }
156
    
157
    /**
158
     * Sets the vue render mode, single file component or embedded
159
     *
160
     * @param string $mode self::VUE_MODE_EMBEDDED or self::VUE_MODE_SINGLE_FILE
161
     * @return Framework
162
     */
163
    public function setMode(string $mode): Framework
164
    {
165
        $this->mode = $mode;
166
        return $this;
167
    }
168
169
    /**
170
     * Get the value of mode
171
     *
172
     * @return string
173
     */
174
    public function getMode(): string
175
    {
176
        return $this->mode;
177
    }
178
179
    public function htmlHead(HTMLElement &$head)
180
    {
181
        $head->prependContent(
182
            HTMLElement::factory('script', ['src' => "https://cdn.jsdelivr.net/npm/vue/dist/vue.js"])
183
        );
184
    }
185
186
    protected function mapType(Datatype $type): string
187
    {
188
        if ($type instanceof Datatype_number) {
189
            return 'Number';
190
        } elseif ($type instanceof Datatype_bool) {
191
            return 'Boolean';
192
        }
193
        return 'String';
194
    }
195
196
    protected function props(Model $m): array
197
    {
198
        $props = [];
199
        foreach ($m->getFields() as $field) {
200
            if ($field->getExtension(self::VUE_PROP, false)) { // TODO
201
                $p = [
202
                    'type' => $this->mapType($field->getDatatype()),
203
                ];
204
                if ($field->getExtension(Datatype::REQUIRED, false)) {
205
                    $p['required'] = true;
206
                }
207
                $props[$field->getName()] = $p;
208
            }
209
        }
210
        return $props;
211
    }
212
213
    protected function serializeProps(array $props): string
214
    {
215
        $s = array_map(function ($name, $p) {
216
            return "'$name': { 'type': {$p['type']}" . ($p['required'] ?? false ? ", 'required': true" : '') . " } ";
217
        }, array_keys($props), $props);
218
        return "{\n        " . implode(",\n        ", $s) . "\n    }\n";
219
    }
220
221
    public function viewableCompose(Model $m, array $elements, string $previousCompose): string
222
    {
223
        $data = array_merge($m->getDefault(), $m->getData(), ['pagination']);
224
        
225
        $viewableForm = join('', $elements);
226
        $jsonData = json_encode($data);
227
        $props = $this->props($m);
228
        $propsBind = array_map(
229
            function ($p) {
230
                return 'v-bind:' . $p . '="model.' . $p . '"';
231
            },
232
            array_keys($props)
233
        );
234
        $templateData = [
235
            'containerTag' => $this->getViewableContainerTag(),
236
            'form' => $viewableForm,
237
            'jsonData' => $jsonData,
238
            'props' => $this->serializeProps($props),
239
            'propsBind' => implode(' ', $propsBind)
240
        ];
241
242
        if ($this->viewableTemplate) {
243
            return $this->fillTemplate(
244
                $this->viewableTemplate,
245
                $templateData,
246
                $m
247
            );
248
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
249
            $viewableTemplate = <<<EOF
0 ignored issues
show
Unused Code introduced by
The assignment to $viewableTemplate is dead and can be removed.
Loading history...
250
<template>
251
<{{containerTag}}>
252
    {{form}}
253
</{{containerTag}}>
254
</template>
255
<script>
256
module.exports = {
257
    data: function () {
258
        return {{jsonData}};
259
    }
260
};
261
</script>
262
<style>
263
</style>
264
EOF;
265
            return $this->fillTemplate(
266
                $this->viewableTemplate,
267
                $templateData,
268
                $m
269
            );
270
        } else {
271
            $id = 'vueapp' . static::counter();
272
            $t = new HTMLElement($this->getViewableContainerTag(), ['id' => $id], $viewableForm, true);
273
            $script = <<<EOF
274
var app = new Vue({
275
    el: '#$id',
276
    data: $jsonData
277
});
278
EOF;
279
            $s = new HTMLElement('script', [], $script, true);
280
            return HTMLElement::factory('div', [], [$t, $s])->getRenderHTML();
281
        }
282
    }
283
284
    public function editableCompose(Model $m, array $elements, string $previousCompose): string
285
    {
286
        $data = array_merge($m->getDefault(), $m->getData());
287
        $editableContainerTag = $this->getEditableContainerTag();
288
        $editableForm = join('', $elements);
289
        $jsonData = json_encode($data);
290
        $props = $this->props($m);
291
        $propsBind = array_map(
292
            function ($p) {
293
                return 'v-bind:' . $p . '="model.' . $p . '"';
294
            },
295
            array_keys($props)
296
        );
297
        $templateData = [
298
            'containerTag' => $editableContainerTag,
299
            'form' => $editableForm,
300
            'jsonData' => $jsonData,
301
            'props' => $this->serializeProps($props),
302
            'propsBind' => implode(' ', $propsBind)
303
        ];
304
305
        $methods = <<<EOF
306
    methods: {
307
        changedFile(name, event) {
308
            console.log(name, event);
309
            const input = event.target;
310
            const files = input.files;
311
            if (files && files[0]) {
312
                // input.preview = window.URL.createObjectURL(files[0]);
313
            }
314
        }
315
    }        
316
EOF;
317
318
        if ($this->editableTemplate) {
319
            return $this->fillTemplate(
320
                $this->editableTemplate,
321
                $templateData,
322
                $m
323
            );
324
        } elseif ($this->mode === self::VUE_MODE_SINGLE_FILE) {
325
            $editableTemplate = <<<EOF
326
<template>
327
<{{containerTag}}>
328
    {{form}}
329
</{{containerTag}}>
330
</template>
331
<script>
332
module.exports = {
333
    data: function () {
334
        return {{jsonData}};
335
    },
336
    $methods
337
};
338
</script>
339
<style>
340
</style>
341
EOF;
342
            return $this->fillTemplate(
343
                $editableTemplate,
344
                $templateData,
345
                $m
346
            );
347
        } else {
348
            $id = 'vueapp' . static::counter();
349
            $t = new HTMLElement($editableContainerTag, ['id' => $id], $editableForm, true);
350
            $script = <<<EOF
351
var app = new Vue({
352
    el: '#$id',
353
    data: function () {
354
        return $jsonData;
355
    },
356
    $methods
357
});
358
EOF;
359
            $s = new HTMLElement('script', [], $script, true);
360
            return HTMLElement::factory('div', [], [$t, $s])->getRenderHTML();
361
        }
362
    }
363
364
    protected function fillTemplate(string $template, array $data, Model $m): string
365
    {
366
        foreach ($data as $name => $value) {
367
            $template = str_replace(
368
                '{{' . $name . '}}',
369
                $value,
370
                $template
371
            );
372
        }
373
374
        $template = str_replace(
375
            '{{modelName}}',
376
            $m->getName(),
377
            $template
378
        );
379
        $template = str_replace(
380
            '{{modelNameLower}}',
381
            mb_strtolower($m->getName()),
382
            $template
383
        );
384
        return $template;
385
    }
386
}
387