Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Having multiple sections #9

Closed
cosimolemma opened this issue Feb 9, 2018 · 18 comments
Closed

Having multiple sections #9

cosimolemma opened this issue Feb 9, 2018 · 18 comments

Comments

@cosimolemma
Copy link

Hi,
I would reach a similar result to a form I'm building.

immagine

May I do a form similar to this with only a JSON file? Thanks in advance 👍

@aocneanu
Copy link
Member

aocneanu commented Feb 9, 2018

The package does not support sections yet, but it will, soon. This feature is pretty straightforward.

But from what I see, at least in the upper section, the fields seem to be of a random/custom width (even height for the select) I don't think we would target this anytime soon :)

I'm kinda' curios to see how this form would look on a mobile device.

@cosimolemma
Copy link
Author

Sections would be a really great idea, because you could group some fields into them.

However custom width is not a problem if you have sections to group some fields. I didn't notice the different heights for the select.

Visualization on a mobile device of this form is pretty good too :) each fields is rendered in a different row.

Thanks for the instant answer 👍

@cosimolemma
Copy link
Author

I added in VueForm.vue the following line at row 105:
<slot name="custom_fk_section"></slot>

Maybe it could be useful to someone to have a custom rendering of the form in a custom Vue file using the following code

<template slot="custom_fk_section">
....
</template>

You can still use fields contained in JSON file just setting the hidden property to true.

Hoping that you like this customization and that you'll integrate this little modification in VueForm.vue file. It could be a first little step with the custom sections feature you wrote

@aocneanu
Copy link
Member

Having two custom slots, one for the form content and one for custom buttons might be a good idea. It would allow the user to add custom widgets to the form.

Is this what you are thinking?

I'm not sure I understand the "hidden" part. You don't need to pass arguments in the template, and to bypass rendering by hiding them, only to access them in the front end. With a custom slot, you can write directly in the frontend whatever logic you need

@mauthi
Copy link
Contributor

mauthi commented Mar 10, 2018

How is the status with the section-topic?

I would need this 2 features too:

  • Group form fields into different sections with headlines
  • Set different column amounts for each section

Thx!

@aocneanu
Copy link
Member

Whenever I will have the time, but for sure is one of the most urgent one.

Group form fields into different sections with headlines
Set different column amounts for each section

This is my understanding of sections too.

@mauthi
Copy link
Contributor

mauthi commented Mar 10, 2018 via email

@aocneanu
Copy link
Member

It would be nice if you would try some different visual mockups and share them here, so we can get it right from the first time.

@mauthi
Copy link
Contributor

mauthi commented Mar 10, 2018 via email

@cosimolemma
Copy link
Author

Hi again guys, I modified the code in this way at the moment to have two custom sections (one at the beginning and one at the ending).

<template>

    <div class="box">
        <h5 class="title is-5"
            v-if="data.icon || data.title">
            <span class="icon"
                v-if="data.icon">
                <fa :icon="data.icon"></fa>
            </span>
            <span v-if="data.title">
                {{ data.title }}
            </span>
        </h5>
        <form class="is-marginless"
            @submit.prevent="submit()">
            <slot name="custom_fk_header_section"></slot>
            <div class="columns is-multiline">
                <div v-for="field in data.fields"
                    :class="['column', columnSize]"
                    :key="field.name"
                    v-if="!field.meta.hidden">
                    <div class="field">
                        <label class="label">
                            {{ __(field.label) }}
                            <p v-if="errors.has(field.name)"
                                class="help is-danger is-pulled-right">
                                {{ errors.get(field.name) }}
                            </p>
                        </label>
                        <span v-if="field.meta.custom">
                            <slot :name="field.name"
                                :field="field"
                                :errors="errors">
                            </slot>
                        </span>
                        <span v-else>
                            <div :class="['control', { 'has-icons-right': errors.has(field.name) }]"
                                v-if="field.meta.type === 'input' && field.meta.content !== 'checkbox'">
                                <input
                                    class="input"
                                    v-model="field.value"
                                    :type="field.meta.content"
                                    :class="{ 'is-danger': errors.has(field.name) }"
                                    :readonly="field.meta.readonly"
                                    :disabled="field.meta.disabled"
                                    @keydown="$emit('update');"
                                    @input="errors.clear(field.name);"
                                    :step="field.meta.step"
                                    :min="field.meta.min"
                                    :max="field.meta.max">
                                <span class="icon is-small is-right has-text-danger"
                                    v-if="errors.has(field.name)">
                                    <fa icon="exclamation-triangle"></fa>
                                </span>
                            </div>
                            <vue-switch v-if="field.meta.type === 'input' && field.meta.content === 'checkbox'"
                                v-model="field.value"
                                size="is-large"
                                type="is-success"
                                :disabled="field.meta.disabled || field.meta.readonly"
                                @click="$emit('update')">
                            </vue-switch>
                            <vue-select v-if="field.meta.type === 'select'"
                                :has-error="errors.has(field.name)"
                                @input="errors.clear(field.name);"
                                v-model="field.value"
                                :options="field.meta.options"
                                :key-map="field.meta.keyMap"
                                :source="field.meta.source"
                                :multiple="field.meta.multiple"
                                :disabled="field.meta.disabled"
                                :placeholder="field.meta.placeholder">
                            </vue-select>
                            <datepicker v-if="field.meta.type === 'datepicker'"
                                @input="errors.clear(field.name)"
                                v-model="field.value"
                                :format="field.meta.format"
                                :time="field.meta.time"
                                :disabled="field.meta.disabled">
                            </datepicker>
                            <datepicker v-if="field.meta.type === 'timepicker'"
                                @input="errors.clear(field.name)"
                                v-model="field.value"
                                :format="field.meta.format"
                                time-only
                                :disabled="field.meta.disabled">
                            </datepicker>
                            <div class="control has-icons-right"
                                v-if="field.meta.type === 'textarea'">
                                <textarea @input="errors.clear(field.name)"
                                    class="textarea"
                                    :class="{ 'is-danger': errors.has(field.name) }"
                                    v-model="field.value"
                                    :rows="field.meta.rows"
                                    :disabled="field.meta.disabled">
                                </textarea>
                                <span class="icon is-small is-right has-text-danger"
                                    v-if="errors.has(field.name)">
                                    <fa icon="exclamation-triangle"></fa>
                                </span>
                            </div>
                        </span>
                    </div>
                </div>
            </div>
            <slot name="custom_fk_footer_section"></slot>
            <button class="button"
                v-if="data.actions.destroy"
                :disabled="data.actions.destroy.forbidden"
                :class="data.actions.destroy.button.class"
                @click.prevent="showModal = true">
                <span>{{ __(data.actions.destroy.button.label) }}</span>
                <span class="icon">
                    <fa :icon="data.actions.destroy.button.icon"></fa>
                </span>
            </button>
            <button class="button"
                :class="data.actions.create.button.class"
                @click.prevent="create()"
                v-if="data.actions.create"
                :disabled="data.actions.create.forbidden">
                <span>{{ __(data.actions.create.button.label) }}</span>
                <span class="icon">
                    <fa :icon="data.actions.create.button.icon"></fa>
                </span>
            </button>
            <button type="submit"
                v-if="data.actions.store"
                class="button is-pulled-right"
                :class="[data.actions.store.button.class, { 'is-loading': loading }]"
                :disabled="data.actions.store.forbidden || errors.any()">
                <span>{{ __(data.actions.store.button.label) }}</span>
                <span class="icon">
                    <fa :icon="data.actions.store.button.icon"></fa>
                </span>
            </button>
            <button type="submit"
                v-if="data.actions.update"
                class="button is-pulled-right"
                :class="[data.actions.update.button.class, { 'is-loading': loading }]"
                :disabled="data.actions.update.forbidden || errors.any()">
                <span>{{ __(data.actions.update.button.label) }}</span>
                <span class="icon">
                    <fa :icon="data.actions.update.button.icon"></fa>
                </span>
            </button>
            <div class="is-clearfix"></div>
        </form>
        <modal v-if="data.actions.destroy"
            :show="showModal"
            :__="__"
            :message="data.actions.destroy.button.message"
            @cancel="showModal = false"
            @commit="destroy()">
        </modal>
    </div>

</template>

<script>

import { mapGetters } from 'vuex';
import fontawesome from '@fortawesome/fontawesome';
import {
    faTrashAlt, faPlus, faCheck, faExclamationTriangle, faUndo,
} from '@fortawesome/fontawesome-free-solid/shakable.es';
import Errors from './classes/Errors';
import Modal from './Modal.vue';
import VueSwitch from './VueSwitch.vue';
import VueSelect from '../select/VueSelect.vue';
import Datepicker from './Datepicker.vue';

fontawesome.library.add(faTrashAlt, faPlus, faCheck, faExclamationTriangle, faUndo);

export default {
    name: 'VueForm',

    components: {
        VueSwitch, Modal, VueSelect, Datepicker,
    },

    props: {
        data: {
            type: Object,
            required: true,
        },
        params: {
            type: Object,
            default: null,
        },
    },

    data() {
        return {
            loading: false,
            showModal: false,
            errors: new Errors(),
        };
    },

    computed: {
        ...mapGetters('locale', ['__']),
        columnSize() {
            return `is-${parseInt(12 / this.data.columns, 10)}`;
        },
        path() {
            return this.data.method === 'post'
                ? this.data.actions.store.path
                : this.data.actions.update.path;
        },
    },

    methods: {
        create() {
            this.$emit('create');
            this.$router.push({ name: this.data.actions.create.route });
        },
        submit() {
            this.loading = true;

            axios[this.data.method](this.path, this.formData()).then(({ data }) => {
                this.loading = false;
                this.$toastr.success(data.message);
                this.$emit('submit');

                if (data.redirect) {
                    this.$router.push({
                        name: data.redirect,
                        params: { id: data.id },
                    });
                }
            }).catch((error) => {
                const { status, data } = error.response;
                this.loading = false;

                if (status === 422) {
                    this.errors.set(data.errors);

                    return;
                }

                this.handleError(error);
            });
        },
        formData() {
            return this.data.fields.reduce((object, field) => {
                object[field.name] = field.value;
                return object;
            }, { _params: this.params });
        },
        destroy() {
            this.showModal = false;
            this.loading = true;

            axios.delete(this.data.actions.destroy.path).then(({ data }) => {
                this.loading = false;
                this.$toastr.success(data.message);
                this.$emit('destroy');

                if (data.redirect) {
                    this.$router.push({ name: data.redirect });
                }
            }).catch((error) => {
                this.loading = false;
                this.handleError(error);
            });
        },
    },
};

</script>

<style lang="scss" scoped>

    .title {
        .icon {
            vertical-align: text-bottom;
        }
    }

</style>

Maybe this could be an exhaustive solution to everyone because two sections could be enough for the majority of web applications. Headlines in the screenshot were a CSS customization of headings.

@aocneanu
Copy link
Member

aocneanu commented Mar 17, 2018

A preview of the new feature

image

and the template to achieve this:

{
    "title": "Edit User",
    "icon": "user",
    "routePrefix": "administration.users",
    "sections": [{
        "columns": "2",
        "divider": true,
        "title": "Personal Info",
        "fields": [
            {
                "label": "First Name",
                "name": "first_name",
                "value": null,
                "meta": {
                    "type": "input",
                    "content": "text"
                }
            },
            {
                "label": "Last Name",
                "name": "last_name",
                "value": null,
                "meta": {
                    "type": "input",
                    "content": "text"
                }
            },
            {
                "label": "Email",
                "name": "email",
                "value": null,
                "meta": {
                    "type": "input",
                    "content": "email"
                }
            },
            {
                "label": "Phone Number",
                "name": "phone",
                "value": null,
                "meta": {
                    "type": "input",
                    "content": "text"
                }
            }
        ]
    }, {
        "columns": "3",
        "divider": true,
        "title": "Administrative",
        "fields": [
            {
                "label": "Entity",
                "name": "owner_id",
                "value": null,
                "meta": {
                    "custom": true,
                    "type": "select",
                    "multiple": false,
                    "options": [],
                    "source": "administration.owners.selectOptions"
                }
            },
            {
                "label": "Role",
                "name": "role_id",
                "value": null,
                "meta": {
                    "custom": true,
                    "type": "select",
                    "multiple": false,
                    "options": [],
                    "source": "system.roles.selectOptions"
                }
            },
            {
                "label": "Active",
                "name": "is_active",
                "value": false,
                "meta": {
                    "type": "input",
                    "content": "checkbox"
                }
            }
        ]
    }]
}

@aocneanu aocneanu changed the title Template customization Having multiple sections Mar 17, 2018
@aocneanu
Copy link
Member

aocneanu commented Mar 17, 2018

@gandesc please update the docs for:

Pics (we have now the old vue-select)

  • example template is missing
  • dividerTitlePlacement (config / template)
  • sections
  • divider
  • title (section title)
  • columns moved to the section object. can take values in [1, 2, 3, 4, 6, 12, 'custom'].
  • column in thefield object when using custom columns (1 to 12)
  • field helper function
  • authorize (present in the docs but not properly explained)

aocneanu added a commit that referenced this issue Mar 17, 2018
aocneanu added a commit that referenced this issue Mar 17, 2018
aocneanu added a commit that referenced this issue Mar 17, 2018
@aocneanu
Copy link
Member

You can even customize the widths inside a section

image

@shonhor22
Copy link

after updated user.json edit form not render following error shown console.
image

composer update, npm update done

@aocneanu
Copy link
Member

@shonhor22

the User edit.vue and create.vue are also updated. please look here

https://github.com/laravel-enso/Enso/tree/master/resources/assets/js/pages/administration/users

@cosimolemma
Copy link
Author

@aocneanu how did you customize the widths into the section? Have you got an example of the code? I was interested in having the same result of your last screenshot

@aocneanu
Copy link
Member

@cosimolemma I used the "columns": "custom" option :)

Take a look here, at the second section

@cosimolemma
Copy link
Author

@aocneanu perfect. Thank you 👍

raftx24 pushed a commit to raftx24/forms that referenced this issue Jan 30, 2020
Ability to Show/Hide tabs in form dinamically
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants