Add fields
This commit is contained in:
parent
288533efd3
commit
bde8d48807
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
class DropdownField extends Field
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'Dropdown';
|
||||
}
|
||||
|
||||
public static function meta(): array
|
||||
{
|
||||
return [
|
||||
'options' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public static function default()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
abstract class Field
|
||||
{
|
||||
|
||||
abstract public static function name(): string;
|
||||
abstract public static function meta(): array;
|
||||
abstract public static function default();
|
||||
|
||||
public static function asMeta(): array
|
||||
{
|
||||
return self::classNames()->map(fn ($class) => $class::allMeta())->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Collection<int, class-string<self>>
|
||||
*/
|
||||
private static function classNames(): Collection
|
||||
{
|
||||
return collect(glob(base_path('app/Form/Fields/*.php')))
|
||||
->filter(fn ($fieldClass) => preg_match('/[A-Za-z]Field\.php$/', $fieldClass) === 1)
|
||||
->map(fn ($fieldClass) => str($fieldClass)->replace(base_path(''), '')->replace('/app', '/App')->replace('.php', '')->replace('/', '\\')->toString())
|
||||
->values();
|
||||
}
|
||||
|
||||
public static function allMeta(): array
|
||||
{
|
||||
return [
|
||||
'id' => class_basename(static::class),
|
||||
'name' => static::name(),
|
||||
'default' => [
|
||||
'name' => '',
|
||||
'type' => class_basename(static::class),
|
||||
'columns' => ['mobile' => 2, 'tablet' => 4, 'desktop' => 12],
|
||||
'default' => static::default(),
|
||||
'required' => false,
|
||||
...static::meta(),
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
class RadioField extends Field
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'Radio';
|
||||
}
|
||||
|
||||
public static function meta(): array
|
||||
{
|
||||
return [
|
||||
'options' => [],
|
||||
];
|
||||
}
|
||||
|
||||
public static function default()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
class TextField extends Field
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'Text';
|
||||
}
|
||||
|
||||
public static function meta(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function default(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form\Fields;
|
||||
|
||||
class TextareaField extends Field
|
||||
{
|
||||
public static function name(): string
|
||||
{
|
||||
return 'Textarea';
|
||||
}
|
||||
|
||||
public static function meta(): array
|
||||
{
|
||||
return [
|
||||
'rows' => 5,
|
||||
];
|
||||
}
|
||||
|
||||
public static function default(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Form\Resources;
|
||||
|
||||
use App\Form\Fields\Field;
|
||||
use App\Lib\HasMeta;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
|
@ -32,7 +33,8 @@ class FormtemplateResource extends JsonResource
|
|||
public static function meta(): array
|
||||
{
|
||||
return [
|
||||
'fields' => [
|
||||
'fields' => Field::asMeta(),
|
||||
[
|
||||
[
|
||||
'id' => 'TextField',
|
||||
'name' => 'Text',
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories\Form\Models;
|
||||
|
||||
use App\Form\Models\Formtemplate;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* @extends Factory<Formtemplate>
|
||||
*/
|
||||
class FormtemplateFactory extends Factory
|
||||
{
|
||||
|
||||
public $model = Formtemplate::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->words(4, true),
|
||||
'config' => [],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -5,8 +5,20 @@
|
|||
<span v-show="required" class="text-red-800"> *</span>
|
||||
</span>
|
||||
<div class="real-field-wrap size-sm" :class="sizes[size].field">
|
||||
<input :name="name" :type="type" :value="transformedValue" :disabled="disabled" :placeholder="placeholder"
|
||||
@keypress="$emit('keypress', $event)" @input="onInput" @change="onChange" @focus="onFocus" @blur="onBlur" />
|
||||
<input
|
||||
:name="name"
|
||||
:type="type"
|
||||
:value="transformedValue"
|
||||
:disabled="disabled"
|
||||
:placeholder="placeholder"
|
||||
:min="min"
|
||||
:max="max"
|
||||
@keypress="$emit('keypress', $event)"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
<div v-if="hint" class="info-wrap">
|
||||
<div v-tooltip="hint">
|
||||
<ui-sprite src="info-button" class="info-button"></ui-sprite>
|
||||
|
@ -281,6 +293,12 @@ export default {
|
|||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
min: {
|
||||
default: () => '',
|
||||
},
|
||||
max: {
|
||||
default: () => '',
|
||||
},
|
||||
name: {},
|
||||
},
|
||||
data: function () {
|
||||
|
@ -324,7 +342,7 @@ export default {
|
|||
},
|
||||
created() {
|
||||
if (typeof this.modelValue === 'undefined') {
|
||||
this.$emit('input', this.default === undefined ? '' : this.default);
|
||||
this.$emit('update:modelValue', this.default === undefined ? '' : this.default);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
>{{ label }}<span v-show="required" class="text-red-800"> *</span></span
|
||||
>
|
||||
<textarea
|
||||
v-text="value"
|
||||
@input="trigger"
|
||||
:placeholder="placeholder"
|
||||
class="h-full outline-none bg-gray-700 border-gray-600 border-solid"
|
||||
:rows="rows"
|
||||
|
@ -28,6 +26,8 @@
|
|||
'rounded-lg text-sm border-2 p-2 text-gray-300': size === null,
|
||||
'rounded-lg py-2 px-2 text-xs border-2 text-gray-300': size == 'sm',
|
||||
}"
|
||||
@input="trigger"
|
||||
v-text="modelValue"
|
||||
></textarea>
|
||||
<div v-if="hint" v-tooltip="hint" class="absolute right-0 top-0 mr-2 mt-2">
|
||||
<ui-sprite src="info-button" class="w-5 h-5 text-indigo-200"></ui-sprite>
|
||||
|
@ -35,66 +35,57 @@
|
|||
</label>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
focus: false,
|
||||
};
|
||||
<script setup>
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const props = defineProps({
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
props: {
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
inset: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
size: {
|
||||
default: null,
|
||||
},
|
||||
rows: {
|
||||
default: function () {
|
||||
return 4;
|
||||
},
|
||||
},
|
||||
id: {
|
||||
required: true,
|
||||
},
|
||||
hint: {
|
||||
default: null,
|
||||
},
|
||||
value: {
|
||||
default: undefined,
|
||||
},
|
||||
mask: {
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
required: false,
|
||||
default: function () {
|
||||
return 'text';
|
||||
},
|
||||
},
|
||||
placeholder: {
|
||||
default: '',
|
||||
inset: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
size: {
|
||||
default: null,
|
||||
},
|
||||
rows: {
|
||||
default: function () {
|
||||
return 4;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
trigger(v) {
|
||||
this.$emit('input', v.target.value);
|
||||
id: {
|
||||
required: true,
|
||||
},
|
||||
hint: {
|
||||
default: null,
|
||||
},
|
||||
modelValue: {
|
||||
default: undefined,
|
||||
},
|
||||
mask: {
|
||||
default: undefined,
|
||||
},
|
||||
label: {
|
||||
default: false,
|
||||
},
|
||||
type: {
|
||||
required: false,
|
||||
default: function () {
|
||||
return 'text';
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (typeof this.value === 'undefined') {
|
||||
this.$emit('input', '');
|
||||
}
|
||||
placeholder: {
|
||||
default: '',
|
||||
},
|
||||
};
|
||||
});
|
||||
function trigger(v) {
|
||||
emit('update:modelValue', v.target.value);
|
||||
}
|
||||
if (typeof props.modelValue === 'undefined') {
|
||||
emit('update:modelValue', '');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scope>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<ui-box class="relative" :heading="heading" container-class="grid gap-3">
|
||||
<slot></slot>
|
||||
<template #in-title>
|
||||
<div class="flex ml-3 space-x-3 absolute right-0 top-0 mr-4 mt-4">
|
||||
<a href="#" @click.prevent="$emit('submit')">
|
||||
<ui-sprite src="save" class="text-zinc-400 w-4 h-4"></ui-sprite>
|
||||
</a>
|
||||
<a href="#" @click.prevent="$emit('close')">
|
||||
<ui-sprite src="close" class="text-zinc-400 w-4 h-4"></ui-sprite>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</ui-box>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const emit = defineEmits(['close', 'submit']);
|
||||
|
||||
const props = defineProps({
|
||||
heading: {},
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="space-y-1">
|
||||
<span class="text-xs font-semibold text-gray-400">Optionen</span>
|
||||
<f-text
|
||||
v-for="(option, index) in modelValue.options"
|
||||
:id="`options-${index}`"
|
||||
:key="index"
|
||||
size="sm"
|
||||
:name="`options-${index}`"
|
||||
:model-value="option"
|
||||
@update:modelValue="$emit('update:modelValue', {...props.modelValue, options: props.modelValue.options.toSpliced(index, 1, $event)})"
|
||||
></f-text>
|
||||
<ui-icon-button icon="plus" @click="$emit('update:modelValue', {...modelValue, options: [...modelValue.options, '']})">Option einfügen</ui-icon-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: {},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
</script>
|
|
@ -27,6 +27,7 @@
|
|||
>
|
||||
<f-text id="fieldname" v-model="singleField.model.name" label="Name" size="sm" name="fieldname"></f-text>
|
||||
<f-switch id="fieldrequired" v-model="singleField.model.required" label="Erforderlich" size="sm" name="fieldrequired" inline></f-switch>
|
||||
<component :is="fields[singleField.model.type]" v-model="singleField.model"></component>
|
||||
</asideform>
|
||||
</div>
|
||||
<ui-box heading="Vorschau" container-class="grid gap-3" class="w-[800px]">
|
||||
|
@ -49,6 +50,9 @@
|
|||
import {computed, ref} from 'vue';
|
||||
import '!/eventform/dist/main.js';
|
||||
import Asideform from './Asideform.vue';
|
||||
import TextareaField from './TextareaField.vue';
|
||||
import DropdownField from './DropdownField.vue';
|
||||
import RadioField from './RadioField.vue';
|
||||
|
||||
const sectionVisible = ref(-1);
|
||||
const singleSection = ref(null);
|
||||
|
@ -60,6 +64,12 @@ const props = defineProps({
|
|||
});
|
||||
const emit = defineEmits(['submit', 'cancel']);
|
||||
|
||||
const fields = {
|
||||
TextareaField: TextareaField,
|
||||
DropdownField: DropdownField,
|
||||
RadioField: RadioField,
|
||||
};
|
||||
|
||||
function editSection(sectionIndex) {
|
||||
singleSection.value = {
|
||||
model: {...inner.value.config.sections[sectionIndex]},
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="space-y-1">
|
||||
<span class="text-xs font-semibold text-gray-400">Optionen</span>
|
||||
<f-text
|
||||
v-for="(option, index) in modelValue.options"
|
||||
:id="`options-${index}`"
|
||||
:key="index"
|
||||
size="sm"
|
||||
:name="`options-${index}`"
|
||||
:model-value="option"
|
||||
@update:modelValue="$emit('update:modelValue', {...props.modelValue, options: props.modelValue.options.toSpliced(index, 1, $event)})"
|
||||
></f-text>
|
||||
<ui-icon-button icon="plus" @click="$emit('update:modelValue', {...modelValue, options: [...modelValue.options, '']})">Option einfügen</ui-icon-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: {},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
</script>
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div>
|
||||
<f-text
|
||||
id="rows"
|
||||
label="Zeilen"
|
||||
size="sm"
|
||||
name="rows"
|
||||
:model-value="modelValue.rows"
|
||||
type="number"
|
||||
min="1"
|
||||
@update:modelValue="$emit('update:modelValue', {...modelValue, rows: $event})"
|
||||
></f-text>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
modelValue: {},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
</script>
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace Tests\Feature\Form;
|
||||
|
||||
use App\Form\Models\Formtemplate;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\TestCase;
|
||||
|
||||
|
@ -12,10 +13,27 @@ class FormtemplateIndexActionTest extends TestCase
|
|||
|
||||
public function testItDisplaysIndexPage(): void
|
||||
{
|
||||
$formtemplate = Formtemplate::factory()->create();
|
||||
|
||||
$this->login()->loginNami()->withoutExceptionHandling();
|
||||
|
||||
$this->get(route('formtemplate.index'))
|
||||
->assertInertiaPath('data.data.0.links', [
|
||||
'update' => route('formtemplate.update', ['formtemplate' => $formtemplate]),
|
||||
])
|
||||
->assertInertiaPath('data.meta.fields.0', [
|
||||
'id' => 'DropdownField',
|
||||
'name' => 'Dropdown',
|
||||
'default' => [
|
||||
'name' => '',
|
||||
'type' => 'DropdownField',
|
||||
'columns' => ['mobile' => 2, 'tablet' => 4, 'desktop' => 12],
|
||||
'default' => [],
|
||||
'required' => false,
|
||||
'options' => [],
|
||||
]
|
||||
])
|
||||
->assertInertiaPath('data.meta.fields.1', [
|
||||
'id' => 'TextField',
|
||||
'name' => 'Text',
|
||||
'default' => [
|
||||
|
@ -26,6 +44,18 @@ class FormtemplateIndexActionTest extends TestCase
|
|||
'required' => false,
|
||||
]
|
||||
])
|
||||
->assertInertiaPath('data.meta.fields.2', [
|
||||
'id' => 'TextareaField',
|
||||
'name' => 'Textarea',
|
||||
'default' => [
|
||||
'name' => '',
|
||||
'type' => 'TextareaField',
|
||||
'columns' => ['mobile' => 2, 'tablet' => 4, 'desktop' => 12],
|
||||
'default' => '',
|
||||
'required' => false,
|
||||
'rows' => 5,
|
||||
]
|
||||
])
|
||||
->assertInertiaPath('data.meta.default', [
|
||||
'name' => '',
|
||||
'config' => [
|
||||
|
|
Loading…
Reference in New Issue