Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

Using form-related components such as

and

in Element UI, you can set the size, disabled and other attributes of the child components by setting the form attributes in

and

. And form verification.



So how do custom components implement these functions?

Business background

Recently, the product put forward a requirement to me. When creating the product, add the label to the product. The label needs not only the name but also the color. So I wrote a component.

▶ Click to view the code
<template>
    <div class="tag-input">
        <div :class="['tag-list', size, 'disabled']" v-if="disabled">
            <el-tag v-for="(item, index) in tagList"
                    :size="size"
                    class="tag-item"
                    :key="item.name"
                    :color="item.color">
                {{ item.name }}
            </el-tag>
        </div>

        <el-popover placement="bottom-start" @show="onShow" v-model="visible" v-else>
            <div style="width: 100%">
                <div style="display: flex; margin-bottom: 10px;">
                    <el-input v-model="newTag.name"
                              ref="tagInput"
                              style="width: 200px; margin-right: 16px;"
                              placeholder="Fill in label name"
                              size="mini"
                    />
                    <el-color-picker v-model="newTag.color" size="mini" />
                </div>
                <div style="text-align: right">
                    <el-button size="mini" type="primary" @click="addTag">addition</el-button>
                </div>
            </div>

            <div :class="['tag-list', size]" slot="reference">
                <el-tag v-for="(item, index) in tagList"
                        :size="size"
                        @close="onClose(index)"
                        class="tag-item"
                        :key="item.name"
                        :color="item.color"
                        closable>
                    {{ item.name }}
                </el-tag>
            </div>
        </el-popover>
    </div>
</template>

<script>
    export default {
        name: 'TagInput'.props: {
            value: {
                type: Array,},disabled: {
                type: Boolean.default: false,},size: {
                type: String}},data() {
            return {
                newTag: {
                    name: undefined.color: undefined,},tagList: this.value,
                visible: false}; },watch: {
            value(val) {
                if(val ! = =this.tagList) {
                    this.tagList = val;
                    this.newTag = {
                        name: undefined.color: undefined}; }}},methods: {
            onShow() {
                this.$nextTick(() = > {
                    this.$refs.tagInput.focus();
                });
            },
            addTag() {
                if (!this.newTag.name) {
                    return;
                }
                this.tagList.push(this.newTag);
                this.newTag = {
                    name: undefined.color: undefined};this.onChange();
                this.visible = false;
            },
            onClose(index) {
                this.tagList.splice(index, 1);
                this.onChange();
            },
            onChange() {
                this.$emit('change'.this.tagList);
                this.$emit('input'.this.tagList); }}}</script>

<style scoped>
    .tag-list {
        border: 1px solid #DCDFE6;
        border-radius: 4px;
        padding: 3px 6px;
        cursor: pointer;
        min-height: 36px;
    }
    .tag-list.large {
        min-height: 40px;
    }
    .tag-list.medium {
        min-height: 36px;
    }
    .tag-list.small {
        min-height: 32px;
    }
    .tag-list.mini {
        min-height: 28px;
    }
    .tag-list.disabled {
        background: #F5F7FA;
        cursor: not-allowed;
    }
    .tag-item:not(:last-child) {
        margin-right: 4px;
    }
</style>
Copy the code

Show the components in action.

The size and disabled attributes are passed through

Vue has a less-used function, provide and inject.

This pair of options needs to be used together to allow an ancestor component to inject a dependency into all of its descendants, regardless of how deep the component hierarchy is, and remain in effect for as long as its upstream and downstream relationships are established.

Note that it cannot replace vuex, provide and Inject binding are not responsive.

You can see in the ElForm source code that it injects the ElForm attribute into the child component, passing in itself.

export default {
    // ...
    name: 'ElForm'.// ...
    provide() {
      return {
        elForm: this}; }},Copy the code

The ElFormItem attribute is injected in the same way

export default {
    // ...
    name: 'ElFormItem'.// ...
    provide() {
      return {
        elFormItem: this}; }},Copy the code

These two properties are then available in the child component

export default {
    // ...
    inject: {
        elForm: {
            default: ' '
        },
        elFormItem: {
            default: ' '}}},Copy the code

You can then use these attributes in the child component and reference the form’s uniform attributes in the child component by changing size and Disabled to tagInputSize and tagInputDisabled.

Where this.$ELEMENT is the global configuration object passed in when ELEMENT is introduced.

export default {
    // ...
    computed: {
        _elFormItemSize() {
            return (this.elFormItem || {}).elFormItemSize;
        },
        tagInputSize() {
            return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
        },
        tagInputDisabled() {
            return this.disabled || (this.elForm || {}).disabled; }}},Copy the code

Verify custom components

When you configure form validation rules, the child component triggers validation of form items when it triggers the specified event (blur or change).

Reading the source code, ElFromItem listens for the following two events

this.$on('el.form.blur'.this.onFieldBlur);
this.$on('el.form.change'.this.onFieldChange);
Copy the code

The validation logic is performed in onFieldBlur() and onFieldChange(). So we need to trigger the el.form.blur or el.form.change event in the child component.

We introduced emitter in Element UI, which fires events through its defined Dispatch function. Go back to our component and add the following code.

import emitter from 'element-ui/src/mixins/emitter';

export default {
    // ...
    mixins: [emitter],
    // ...
    methods: {
        // ...
        onChange() {
            // ...
            this.dispatch('ElFormItem'.'el.form.change'.this.tagList); }}}Copy the code

Dispatch (componentName, eventName, Params) searches for the component named componentName and triggers the eventName event.

In addition, we also need to add some corresponding styles. In case of verification failure, the component will have a red border. We will unify the error styles of the component and other components.

.el-form-item.is-error .tag-input .tag-list {
    border-color: #F56C6C;
}
.el-form-item.is-error .tag-input .tag-list.disabled {
    border-color: #DCDFE6;
}
Copy the code

The complete code

▶ Click to view the code
<template>
    <div class="tag-input">
        <div :class="['tag-list', tagInputSize, 'disabled']" v-if="tagInputDisabled">
            <el-tag v-for="(item, index) in tagList"
                    :size="tagInputSize"
                    class="tag-item"
                    :key="item.name"
                    :color="item.color">
                {{ item.name }}
            </el-tag>
        </div>

        <el-popover placement="bottom-start" @show="onShow" v-model="visible" v-else>
            <div style="width: 100%">
                <div style="display: flex; margin-bottom: 10px;">
                    <el-input v-model="newTag.name"
                              ref="tagInput"
                              style="width: 200px; margin-right: 16px;"
                              placeholder="Fill in label name"
                              size="mini"
                    />
                    <el-color-picker v-model="newTag.color" size="mini" />
                </div>
                <div style="text-align: right">
                    <el-button size="mini" type="primary" @click="addTag">addition</el-button>
                </div>
            </div>

            <div :class="['tag-list', tagInputSize]" slot="reference">
                <el-tag v-for="(item, index) in tagList"
                        :size="tagInputSize"
                        @close="onClose(index)"
                        class="tag-item"
                        :key="item.name"
                        :color="item.color"
                        closable>
                    {{ item.name }}
                </el-tag>
            </div>
        </el-popover>
    </div>
</template>

<script>
    import emitter from 'element-ui/src/mixins/emitter';

    export default {
        name: 'TagInput'.mixins: [emitter],
        inject: {
            elForm: {
                default: ' '
            },
            elFormItem: {
                default: ' '}},props: {
            value: {
                type: Array,},disabled: {
                type: Boolean.default: false,},size: {
                type: String}},data() {
            return {
                newTag: {
                    name: undefined.color: undefined,},tagList: this.value,
                visible: false}; },computed: {
            _elFormItemSize() {
                return (this.elFormItem || {}).elFormItemSize;
            },
            tagInputSize() {
                return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
            },
            tagInputDisabled() {
                return this.disabled || (this.elForm || {}).disabled; }},watch: {
            value(val) {
                if(val ! = =this.tagList) {
                    this.tagList = val;
                    this.newTag = {
                        name: undefined.color: undefined};this.dispatch('ElFormItem'.'el.form.change'.this.tagList); }}},methods: {
            onShow() {
                this.$nextTick(() = > {
                    this.$refs.tagInput.focus();
                });
            },
            addTag() {
                if (!this.newTag.name) {
                    return;
                }
                this.tagList.push(this.newTag);
                this.newTag = {
                    name: undefined.color: undefined};this.onChange();
                this.visible = false;
            },
            onClose(index) {
                this.tagList.splice(index, 1);
                this.onChange();
            },
            onChange() {
                this.$emit('change'.this.tagList);
                this.$emit('input'.this.tagList);
                this.dispatch('ElFormItem'.'el.form.change'.this.tagList); }}}</script>

<style scoped>
    .tag-list {
        border: 1px solid #DCDFE6;
        border-radius: 4px;
        padding: 3px 6px;
        cursor: pointer;
        min-height: 36px;
    }
    .tag-list.large {
        min-height: 40px;
    }
    .tag-list.medium {
        min-height: 36px;
    }
    .tag-list.small {
        min-height: 32px;
    }
    .tag-list.mini {
        min-height: 28px;
    }
    .tag-list.disabled {
        background: #F5F7FA;
        cursor: not-allowed;
    }
    .tag-item:not(:last-child) {
        margin-right: 4px;
    }
    .el-form-item.is-error .tag-input .tag-list {
        border-color: #F56C6C;
    }
    .el-form-item.is-error .tag-input .tag-list.disabled {
        border-color: #DCDFE6;
    }
</style>
Copy the code

The test results

The Demo code:

▶ Click to view the code
<template>
    <div class="test-page">
        <div class="options-box">
            <el-form label-width="60px" size="mini">
                <el-form-item label="size">
                    <el-select v-model="options.size">
                        <el-option value="large"></el-option>
                        <el-option value="medium"></el-option>
                        <el-option value="small"></el-option>
                        <el-option value="mini"></el-option>
                    </el-select>
                </el-form-item>
                <el-form-item label="disabled">
                    <el-switch v-model="options.disabled"></el-switch>
                </el-form-item>
            </el-form>
        </div>

        <el-form :model="formData"
                 ref="formRef"
                 label-width="80px"
                 :size="options.size"
                 :disabled="options.disabled"
        >
            <el-form-item label="Trade Name" prop="name" :rules="{required: true, message: 'Please fill in the commodity name ', trigger: 'blur'}">
                <el-input v-model="formData.name"></el-input>
            </el-form-item>
            <el-form-item label="Commodity Label" prop="tags" :rules="{required: true, type: 'array', message: 'Please select the item label ', trigger: 'change'}">
                <TagInput v-model="formData.tags" @change="onChange"></TagInput>
            </el-form-item>
            <el-form-item>
                <el-button @click="reset">Heavy set</el-button>
                <el-button @click="submit" type="primary">To hand in</el-button>
            </el-form-item>
        </el-form>
    </div>
</template>

<script>
    import TagInput from './TagInput';

    export default {
        components: {
            TagInput
        },
        data() {
            return {
                formData: {
                    name: ' '.tags: []},options: {
                    size: 'medium'.disabled: false,}}; },methods: {
            reset() {
                this.$refs.formRef.resetFields();
            },
            submit() {
                this.$refs.formRef.validate().then(() = > {
                    console.log(this.formData);
                    this.$message.success('Created successfully ~');
                });
            },
            onChange(v) {
                console.log(v)
            }
        }
    }
</script>

<style scoped>
    .test-page {
        background: #fff;
        padding: 20px;
        border: 1px solid #d7d7d7;
        border-radius: 5px;
        width: 400px;
    }
    .options-box {
        border: 1px solid #e9e9e9;
        margin-bottom: 24px;
        padding: 16px;
        background: #f6f6f6;
    }
</style>
Copy the code