voyager-admin/voyager

View on GitHub
resources/assets/components/Formfields/DynamicInput/Formfield.vue

Summary

Maintainability
Test Coverage
<template>
    <div v-if="action == 'query'">
        
    </div>
    <div v-else-if="action == 'browse'">
        
    </div>
    <div v-else-if="action == 'edit' || action == 'add'" class="w-full" :class="options.inline ? 'flex space-x-4' : 'space-y-4'">
        <div v-for="(input, num) in inputs" class="w-full" :class="options.outline ? 'input-group' : null">
            <label v-if="input.title" class="label">{{ input.title }}</label>
            <template v-if="input.type == 'select'">
                <select class="input mt-2 w-full" @change="setSelectValue(input, $event)" :multiple="input.multiple" :id="`input-${column.column}-${num}`">
                    <option
                        v-for="(title, value) in input.options"
                        :value="value"
                        :selected="optionSelected(input, value)"
                    >
                        {{ title }}
                    </option>
                </select>
            </template>
            <template v-else-if="input.type == 'text'">
                <input
                    type="text"
                    class="input mt-2 w-full"
                    v-bind:value="getValue(input)"
                    @input="setValue(input, $event.target.value)"
                    :placeholder="input.placeholder || ''"
                >
            </template>
            <template v-else-if="input.type == 'number'">
                <input
                    type="number"
                    class="input mt-2 w-full"
                    v-bind:value="getValue(input)"
                    @input="setValue(input, $event.target.value)"
                    :placeholder="input.placeholder || ''"
                    :min="input.min"
                    :max="input.max"
                >
            </template>
            <template v-else-if="input.type == 'checkbox' || input.type == 'radio'">
                <template v-for="(title, value) in input.options">
                    <div class="flex space-x-1.5 items-center">
                        <input
                            :type="input.type"
                            class="input"
                            :value="value"
                            @change="setSelectValue(input, $event)"
                            :id="`${column.column}-${value}`"
                            :name="`${column.column}-${input.key}`"
                            :checked="optionSelected(input, value)"
                        >
                        <label :for="`${column.column}-${value}`" class="label">{{ title }}</label>
                    </div>
                </template>
            </template>
            <template v-else-if="input.type == 'switch'">
                <div class="flex space-x-1.5 items-center">
                    <input
                        type="checkbox"
                        class="input"
                        @change="setValue(input, $event.target.checked)"
                        :id="`${column.column}-${input.key}`"
                        :checked="getValue(input)"
                    >
                    <label :for="`${column.column}-${input.key}`" class="label">{{ input.title }}</label>
                </div>
            </template>
        </div>
    </div>
</template>

<script>
import axios from 'axios';
import { debounce } from 'debounce';

import formfield from '@mixins/formfield';
import EventBus from '@/eventbus';

export default {
    mixins: [formfield],
    data() {
        return {
            inputs: [],
            should: 0,
            has: 0,
            load: null,
        };
    },
    methods: {
        optionSelected(input, value) {
            let current = this.sanitizeValue(this.getValue(input));
            value = this.sanitizeValue(value);
            if (Array.isArray(current)) {
                return current.includes(value);
            }

            return current == value;
        },
        getValue(input) {
            if (input.key === null) {
                return this.modelValue || input.value || null;
            }

            if (this.modelValue) {
                return this.modelValue[input.key] || input.value || null;
            }

            return input.value || null;
        },
        setSelectValue(input, select) {
            let value = null;
            if (input.multiple) {
                value = [];

                if (input.type == 'select') {
                    Array.from(select.target.options).forEach((option, key) => {
                        if (option.selected) {
                            if (option.value === '') {
                                value.push(null);
                            } else {
                                value.push(option.value);
                            }
                        }
                    });
                } else if (input.type == 'checkbox') {
                    value = this.getValue(input);
                    if (!Array.isArray(value)) {
                        value = [];
                    }
                    if (select.target.checked) {
                        value.push(select.target.value);
                    } else {
                        value = value.filter((v) => v !== select.target.value);
                    }
                }
            } else {
                value = select.target.value;
                if (value === '') {
                    value = null;
                }
            }

            this.setValue(input, value);
        },
        setValue(input, value) {
            if (this.isNumeric(value)) {
                value = parseInt(value);
            }
            value = this.sanitizeValue(value);
            if (input.key === null) {
                this.$emit('update:modelValue', value);
            } else {
                let current = this.modelValue;
                if (!current || typeof current !== 'object' || current.constructor !== Object) {
                    current = {};
                }
                current[input.key] = value;
                this.$emit('update:modelValue', current);
            }
        },
        isNumeric(input) {
            return !isNaN(parseFloat(input)) && isFinite(input);
        },
        sanitizeValue(value) {
            if (this.isNumeric(value)) {
                return parseInt(value);
            }

            return value;
        }
    },
    created() {
        const loadInputs = debounce((data = {}) => {
            if (!this.options.route_name) {
                return;
            }
            try {
                this.route(this.options.route_name);
            } catch (e) {
                new this.$notification(this.__('voyager::formfields.dynamic_input.route_warning', { route: this.options.route_name })).color('red').timeout().show();
                return;
            }
            axios.post(this.route(this.options.route_name), { ...this.modelValue, bread_action: this.action, data })
            .then((response) => {
                this.inputs = response.data;
                // Add default value if the property does not exist
                response.data.forEach((input) => {
                    if (input.key) {
                        var current = this.modelValue;
                        if (!current || typeof current !== 'object' || current.constructor !== Object) {
                            current = {};
                        }
                        if (!current.hasOwnProperty(input.key)) {
                            current[input.key] = input.value;

                            this.$emit('update:modelValue', current);
                        }
                        
                    } else if (this.modelValue === null && input.value) {
                        this.$emit('update:modelValue', input.value);
                    }
                });
            }).catch((response) => {});
        }, this.options.debounce || 200, false);

        this.$watch(() => this.modelValue, () => {
            loadInputs();
        }, { immediate: true, deep: true });

        this.$watch(() => this.options.route_name, (route, old) => {
            if (route !== old) {
                loadInputs();
            }
        });

        EventBus.on('output', (data) => {
            // TODO: When there is a config option that this formfields should refresh when other formfields changed value.
            // Might need to remove immediate: true on the modelValue watcher above and instead call `EventBus.emit('output', this.output)` from Bread/EditAdd when mounted.
            // Also consider settings. They have another structure
            //this.loadInputs(data);
        });
    }
}
</script>