Passed
Push — master ( cdf82f...341a7c )
by Bruno
06:59
created

FrontendVueGenerator::makeVueCrud()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 12
rs 10
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Modelarium\Frontend;
4
5
use Formularium\Datatype;
6
use Formularium\Element;
7
use Formularium\Field;
8
use Formularium\Model;
9
use Formularium\FrameworkComposer;
10
use Formularium\Frontend\HTML\Element\Button;
11
use Formularium\Frontend\HTML\Element\Table;
12
use Formularium\Frontend\Vue\Element\Pagination as PaginationVue;
13
use Formularium\Frontend\Vue\Framework as FrameworkVue;
14
use Modelarium\GeneratedCollection;
15
use Modelarium\GeneratedItem;
16
use Modelarium\Options;
17
use Formularium\StringUtil;
18
19
use function Safe\file_get_contents;
20
use function Safe\scandir;
21
use function Safe\substr;
22
23
class FrontendVueGenerator
24
{
25
    /**
26
     * @var FrontendGenerator
27
     */
28
    protected $generator = null;
29
30
    public function __construct(FrontendGenerator $generator)
31
    {
32
        $this->generator = $generator;
33
        $this->buildTemplateParameters();
34
    }
35
36
    public function getOptions(): Options
37
    {
38
        return $this->generator->getOptions();
39
    }
40
41
    public function getStubDir()
42
    {
43
        return $this->generator->getStubDir() . '/Vue/';
44
    }
45
46
    protected function getCollection(): GeneratedCollection
47
    {
48
        return $this->generator->getCollection();
49
    }
50
51
    public function generate(): void
52
    {
53
        /**
54
         * @var FrameworkVue $vue
55
         */
56
        $vue = $this->generator->getComposer()->getByName('Vue');
57
        $vueCode = $vue->getVueCode();
58
59
        // set basic data for vue
60
        $extraprops = [
61
            [
62
                'name' => 'id',
63
                'type' => 'String',
64
                'required' => true
65
            ]
66
        ];
67
        $vueCode->setExtraProps($extraprops);
68
69
        // build basic vue components
70
        $this->vuePublish();
71
        $this->makeVuePaginationComponent();
72
73
        $this->vueCard($vue);
74
        $this->vueLink($vue);
75
        $this->vueTableItem($vue);
76
        $this->vueTable($vue);
77
78
        $this->makeVue($vue, 'List', 'viewable');
79
        $this->makeVue($vue, 'Show', 'viewable');
80
        $this->makeVue($vue, 'Edit', 'editable');
81
        $this->vueForm($vue);
82
        $this->makeVueRoutes();
83
        $this->makeVueCrud();
84
        $this->makeVueIndex();
85
    }
86
87
    public function buildTemplateParameters(): void
88
    {
89
        $hasVueRouter = $this->generator != null; //TODO: temporary true condition while we don't have a setting for this
90
91
        $hasCan = $this->generator->getModel()->getExtradataValue('hasCan', 'value', false);
92
        $routeBase = $this->generator->getRouteBase();
93
        $keyAttribute = $this->generator->getKeyAttribute();
94
        $targetAttribute = $hasVueRouter ? 'to' : 'href';
95
96
        $buttonCreate = $this->generator->getComposer()->nodeElement(
97
            'Button',
98
            [
99
                Button::TYPE => ($hasVueRouter ? 'router-link' : 'a'),
100
                Button::ATTRIBUTES => [
101
                    $targetAttribute => "/{$routeBase}/edit",
102
                ] + ($hasCan ? [ "v-if" => 'can(\'create\')' ]: []),
103
            ]
104
        )->setContent(
105
            '<i class="fa fa-plus"></i> ' . $this->getOptions()->getOption('frontend', 'messages')['addNew'],
106
            true,
107
            true
108
        )->getRenderHTML();
109
110
        $buttonEdit = $this->generator->getComposer()->nodeElement(
111
            'Button',
112
            [
113
                Button::TYPE => ($hasVueRouter ? 'router-link' : 'a'),
114
                Button::ATTRIBUTES => [
115
                    $targetAttribute => "'/{$routeBase}/' + model.{$keyAttribute} + '/edit'",
116
                ] + ($hasCan ? [ "v-if" => 'can(\'edit\')' ]: []),
117
            ]
118
        )->setContent(
119
            '<i class="fa fa-pencil"></i> ' . $this->getOptions()->getOption('frontend', 'messages')['edit'],
120
            true,
121
            true
122
        )->getRenderHTML();
123
124
        $buttonDelete = $this->generator->getComposer()->nodeElement(
125
            'Button',
126
            [
127
                Button::TYPE => 'a',
128
                Button::COLOR => Button::COLOR_WARNING,
129
                Button::ATTRIBUTES => [
130
                    'href' => '#',
131
                    '@click.prevent' => 'remove(model.id)',
132
                ] + ($hasCan ? [ "v-if" => 'can(\'delete\')' ]: []),
133
            ]
134
        )->setContent(
135
            '<i class="fa fa-trash"></i> ' . $this->getOptions()->getOption('frontend', 'messages')['delete'],
136
            true,
137
            true
138
        )->getRenderHTML();
139
140
        if (!$hasCan && $this->getOptions()->getOption('vue', 'actionButtonsNoCan') === false) {
141
            $this->generator->templateParameters['buttonCreate'] = '';
142
            $this->generator->templateParameters['buttonEdit'] = '';
143
            $this->generator->templateParameters['buttonDelete'] = '';
144
        } else {
145
            $this->generator->templateParameters['buttonCreate'] = $buttonCreate;
146
            $this->generator->templateParameters['buttonEdit'] = $buttonEdit;
147
            $this->generator->templateParameters['buttonDelete'] = $buttonDelete;
148
        }
149
        $this->generator->templateParameters['options'] = $this->getOptions()->options;
150
151
        $this->generator->templateParameters['tableItemFields'] =
152
            array_values(array_map(function (Field $f) {
153
                $required = $f->getValidator('required', false);
154
                if ($f->getDatatype()->getBasetype() === 'relationship') {
155
                    $name = $f->getName();
156
                    return "<{$name}-link " . ($required ? '' : "v-if=\"{$name}\"") . "v-bind=\"{$name}\"></{$name}-link>";
157
                }
158
                return '{{ ' . $f->getName() . ' }}';
159
            }, $this->generator->getTableFields()));
160
    }
161
162
    /**
163
     * Publishes the Vue component dependencies
164
     *
165
     * @return void
166
     */
167
    protected function vuePublish(): void
168
    {
169
        $this->getCollection()->push(
170
            new GeneratedItem(
171
                GeneratedItem::TYPE_FRONTEND,
172
                $this->generator->templateCallback(
173
                    __DIR__ . "/Vue/Renderable/RelationshipAutocomplete.vue",
174
                    $this->generator->getComposer()->getByName('Vue'),
0 ignored issues
show
Bug introduced by
It seems like $this->generator->getComposer()->getByName('Vue') can also be of type null; however, parameter $f of Modelarium\Frontend\Fron...tor::templateCallback() does only seem to accept Formularium\Framework, 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

174
                    /** @scrutinizer ignore-type */ $this->generator->getComposer()->getByName('Vue'),
Loading history...
175
                    [],
176
                    $this->generator->getModel()
177
                ),
178
                "Modelarium/RelationshipAutocomplete.vue"
179
            )
180
        );
181
        $this->getCollection()->push(
182
            new GeneratedItem(
183
                GeneratedItem::TYPE_FRONTEND,
184
                $this->generator->templateCallback(
185
                    __DIR__ . "/Vue/Renderable/RelationshipSelect.vue",
186
                    $this->generator->getComposer()->getByName('Vue'),
187
                    [],
188
                    $this->generator->getModel()
189
                ),
190
                "Modelarium/RelationshipSelect.vue"
191
            )
192
        );
193
        // $this->getCollection()->push(
194
        //     new GeneratedItem(
195
        //         GeneratedItem::TYPE_FRONTEND,
196
        //         file_get_contents(__DIR__ . "/Vue/Renderable/RelationshipSelectMultiple.vue"),
197
        //         "Modelarium/RelationshipSelectMultiple.vue"
198
        //     )
199
        // );
200
    }
201
202
    protected function makeVuePaginationComponent(): void
203
    {
204
        // TODO: this is called once per type
205
        $pagination = $this->generator->getComposer()->nodeElement(
206
            'Pagination',
207
            [
208
            ]
209
        );
210
        $html = $pagination->getRenderHTML();
211
        $script = PaginationVue::script();
212
213
        $this->getCollection()->push(
214
            new GeneratedItem(
215
                GeneratedItem::TYPE_FRONTEND,
216
                "<template>\n$html\n</template>\n<script>\n$script\n</script>\n",
217
                "Modelarium/Pagination.vue"
218
            )
219
        );
220
    }
221
222
    /**
223
     * Appends computed item
224
     *
225
     * @param FrameworkVue $vue
226
     * @return void
227
     */
228
    protected function vueCodeLinkItem(FrameworkVue $vue): void
229
    {
230
        $vue->getVueCode()->appendComputed(
231
            'link',
232
            'return "/' . $this->generator->getRouteBase() .
233
                '/" + this.escapeIdentifier(this.' . $this->generator->getKeyAttribute() . ')'
234
        );
235
    }
236
237
    protected function vueCard(FrameworkVue $vue): void
238
    {
239
        $vueCode = $vue->getVueCode();
240
        // set basic data for vue
241
        $extraprops = [
242
            [
243
                'name' => 'id',
244
                'type' => 'String',
245
                'required' => true
246
            ]
247
        ];
248
        $cardFieldNames = array_map(function (Field $f) {
249
            return $f->getName();
250
        }, $this->generator->getCardFields());
251
        $vueCode->setExtraProps($extraprops);
252
        $this->vueCodeLinkItem($vue);
253
254
        foreach ($this->generator->getCardFields() as $f) {
255
            $vueCode->appendExtraProp($f->getName(), [
256
                'name' => $f->getName(),
257
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
258
                'required' => true
259
            ]);
260
        }
261
        $vueCode->appendMethod(
262
            'escapeIdentifier(identifier)',
263
            $this->getOptions()->getOption('vue', 'escapeIdentifierBody')
264
        );
265
266
        $this->makeVue($vue, 'Card', 'viewable', $cardFieldNames);
267
    }
268
269
    protected function vueLink(FrameworkVue $vue): void
270
    {
271
        $vueCode = $vue->getVueCode();
272
        // set basic data for vue
273
        $vueCode->setExtraProps([]);
274
        $cardFieldNames = array_map(function (Field $f) {
275
            return $f->getName();
276
        }, $this->generator->getCardFields());
277
        foreach ($this->generator->getCardFields() as $f) {
278
            $vueCode->appendExtraProp($f->getName(), [
279
                'name' => $f->getName(),
280
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
281
                'required' => true
282
            ]);
283
        }
284
        foreach ($this->generator->getTitleFields() as $f) {
285
            $vueCode->appendExtraProp($f->getName(), [
286
                'name' => $f->getName(),
287
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
288
                'required' => true
289
            ]);
290
        }
291
292
        if (!$vueCode->getExtraProps()) {
293
            $vueCode->appendExtraProp('id', [
294
                'name' => 'id',
295
                'type' => 'String',
296
                'required' => true
297
            ]);
298
        }
299
300
        $this->vueCodeLinkItem($vue);
301
        $this->makeVue($vue, 'Link', 'viewable', $cardFieldNames);
302
    }
303
304
    public function vueTableItem(FrameworkVue $vue): void
305
    {
306
        $vueCode = $vue->getVueCode();
307
        $tableFieldNames = array_map(function (Field $f) {
308
            return $f->getName();
309
        }, $this->generator->getTableFields());
310
        $vueCode->setExtraProps([]);
311
        $vueCode->appendExtraProp(
312
            'id',
313
            [
314
                'name' => 'id',
315
                'type' => 'String',
316
                'required' => true
317
            ]
318
        );
319
320
        foreach ($this->generator->getTableFields() as $f) {
321
            /**
322
             * @var Field $f
323
             */
324
            $required = $f->getValidator('required', false);
325
            $prop = [
326
                'name' => $f->getName(),
327
                'type' => $vueCode->mapTypeToJs($f->getDatatype()),
328
                'required' => $required
329
            ];
330
            if (!$required) {
331
                if ($f->getDatatype()->getBasetype() === 'relationship') {
332
                    $prop['default'] = '() => null';
333
                } else {
334
                    $prop['default'] = $f->getDatatype()->getDefault;
0 ignored issues
show
Bug introduced by
The property getDefault does not seem to exist on Formularium\Datatype.
Loading history...
335
                }
336
            }
337
338
            $vueCode->appendExtraProp($f->getName(), $prop);
339
        }
340
        $this->makeVue($vue, 'TableItem', 'viewable', $tableFieldNames);
341
    }
342
343
    public function vueTable(FrameworkVue $vue): void
344
    {
345
        $this->makeVue($vue, 'Table', 'viewable');
346
    }
347
348
    public function vueForm(FrameworkVue $vue): void
349
    {
350
        $vueCode = $vue->getVueCode();
351
        $vueCode->setExtraProps([]);
352
353
        $createGraphqlVariables = $this->generator->getModel()->mapFields(
354
            function (Field $f) {
355
                if (!$f->getRenderable('form', true)) {
356
                    return null;
357
                }
358
                $d = $f->getDatatype();
359
                if ($d->getBasetype() == 'relationship') {
360
                    return $f->getName() . ": {connect: this.model." . $f->getName() . '}';
361
                }
362
                return $f->getName() . ": this.model." . $f->getName();
363
            }
364
        );
365
        $createGraphqlVariables = join(",\n", array_filter($createGraphqlVariables));
366
367
        $this->generator->templateParameters['createGraphqlVariables'] = $createGraphqlVariables;
368
        // they're the same now
369
        $this->generator->templateParameters['updateGraphqlVariables'] = $createGraphqlVariables;
370
371
        $this->makeVue(
372
            $vue,
373
            'Form',
374
            'editable',
375
            function (Field $f) {
376
                if (!$f->getExtradata('modelFillable')) {
377
                    return false;
378
                }
379
                return $f->getRenderable('form', true);
380
            }
381
        );
382
    }
383
384
    /**
385
     * @param FrameworkVue $vue
386
     * @param string $component
387
     * @param string $mode
388
     * @param string[]|callable $restrictFields
389
     * @return void
390
     */
391
    protected function makeVue(FrameworkVue $vue, string $component, string $mode, $restrictFields = null): void
392
    {
393
        $path = $this->generator->getModel()->getName() . '/' .
394
            $this->generator->getModel()->getName() . $component . '.vue';
395
396
        $stub = $this->getStubDir() . "/Vue{$component}.mustache.vue";
397
398
        if ($mode == 'editable') {
399
            $vue->setEditableTemplate(
400
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
401
                    return $this->generator->templateCallback($stub, $vue, $data, $m);
402
                }
403
            );
404
405
            $this->getCollection()->push(
406
                new GeneratedItem(
407
                    GeneratedItem::TYPE_FRONTEND,
408
                    $this->generator->getModel()->editable($this->generator->getComposer(), [], $restrictFields),
409
                    $path
410
                )
411
            );
412
        } else {
413
            $vue->setViewableTemplate(
414
                function (FrameworkVue $vue, array $data, Model $m) use ($stub) {
415
                    return $this->generator->templateCallback($stub, $vue, $data, $m);
416
                }
417
            );
418
419
            $this->getCollection()->push(
420
                new GeneratedItem(
421
                    GeneratedItem::TYPE_FRONTEND,
422
                    $this->generator->getModel()->viewable($this->generator->getComposer(), [], $restrictFields),
423
                    $path
424
                )
425
            );
426
        }
427
        $vue->resetVueCode();
428
        $vue->getVueCode()->setFieldModelVariable('model.');
429
    }
430
431
    protected function makeVueIndex(): void
432
    {
433
        $path = $this->generator->getModel()->getName() . '/index.js';
434
        $name = $this->generator->getStudlyName();
435
436
        $contents = function ($basepath, $element) use ($name) {
0 ignored issues
show
Unused Code introduced by
The parameter $element is not used and could be removed. ( Ignorable by Annotation )

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

436
        $contents = function ($basepath, /** @scrutinizer ignore-unused */ $element) use ($name) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
437
            $dir = $basepath . '/' . $name;
438
            $import = [];
439
            $export = [];
440
            foreach (scandir($dir) as $i) {
441
                if (StringUtil::endsWith($i, '.vue')) {
442
                    $name = substr($i, 0, -4);
443
                    $import[] = "import $name from './$name.vue';";
444
                    $export[] = "    {$name},";
445
                }
446
            }
447
            return implode("\n", $import) . "\n\n" .
448
                "export default {\n" .
449
                implode("\n", $export) . "\n};\n";
450
        };
451
452
        $this->getCollection()->push(
453
            new GeneratedItem(
454
                GeneratedItem::TYPE_FRONTEND,
455
                $contents,
456
                $path
457
            )
458
        );
459
    }
460
461
    protected function makeVueIndexDynamic(): void
462
    {
463
        $path = $this->generator->getModel()->getName() . '/index.dynamic.js';
464
        $name = $this->generator->getStudlyName();
465
466
        $contents = function ($basepath, $element) use ($name) {
0 ignored issues
show
Unused Code introduced by
The parameter $element is not used and could be removed. ( Ignorable by Annotation )

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

466
        $contents = function ($basepath, /** @scrutinizer ignore-unused */ $element) use ($name) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
467
            $dir = $basepath . '/' . $name;
468
            $import = [];
469
            $export = [];
470
            foreach (scandir($dir) as $i) {
471
                if (StringUtil::endsWith($i, '.vue')) {
472
                    $name = substr($i, 0, -4);
473
                    $import[] = "const $name = () => import('./$name.vue');";
474
                    $export[] = "    {$name},";
475
                }
476
            }
477
            return implode("\n", $import) . "\n\n" .
478
                "export default {\n" .
479
                implode("\n", $export) . "\n};\n";
480
        };
481
        $this->getCollection()->push(
482
            new GeneratedItem(
483
                GeneratedItem::TYPE_FRONTEND,
484
                $contents,
485
                $path
486
            )
487
        );
488
    }
489
490
    protected function makeVueRoutes(): void
491
    {
492
        $path = $this->generator->getModel()->getName() . '/routes.js';
493
494
        $this->getCollection()->push(
495
            new GeneratedItem(
496
                GeneratedItem::TYPE_FRONTEND,
497
                $this->generator->templateFile(
498
                    $this->getStubDir() . "/routes.mustache.js",
499
                    [
500
                        'routeName' => $this->generator->getRouteBase(),
501
                        'keyAttribute' => $this->generator->getKeyAttribute()
502
                    ]
503
                ),
504
                $path
505
            )
506
        );
507
    }
508
 
509
    protected function makeVueCrud(): void
510
    {
511
        $path = $this->generator->getModel()->getName() . '/crud.js';
512
        
513
        $this->getCollection()->push(
514
            new GeneratedItem(
515
                GeneratedItem::TYPE_FRONTEND,
516
                $this->generator->templateFile(
517
                    $this->getStubDir() . "/crud.mustache.js",
518
                    $this->generator->templateParameters
519
                ),
520
                $path
521
            )
522
        );
523
    }
524
}
525