在捕获由子组件触发的事件后,未呈现Vuejs 2 v-if指令

时间:2017-02-26 18:25:03

标签: javascript laravel-5.3 vuejs2 vue.js

我正在使用vuejs 2.0和laravel 5.3。我从子组件(一个预先输入组件)发出一个自定义事件,我正在使用v-on指令捕获此事件。

以下是我的组件的代码:

<template>
    <input ref="input" class="typeahead-suggestions"
           :class="classes"
           :id = "id"
           v-bind:value="value"
           v-on:input="updateValue($event.target.value)"
           v-on:blur="formatValue"
           :placeholder="placeholder">
</template>
<script>
    var Bloodhound = require('typeahead.js')
    export default {
        data: function() {
            var id =  'typeahead-suggestion' + parseInt(Math.random() *100000);
            return {
                id,
                defaultSuggestions: [],
                query: ''
            };
        },
        props: {
            param: {
                type: String,
                default: 'item'
            },
            value: {
                type: String,
                default: ''
            },
            classes: {
                type: String,
                default: ''
            },
            displayKey: {
                type: String,
                default: ''
            },
            suggestionTemplate: {
                type: String,
                default: ''
            },
            name: {
                type: String,
                default: 'Vue Auto Complete'
            },
            prefetch: {
                type: String,
                default: ''
            },
            defaultSuggestion: {
                type: Boolean,
                default: false
            },
            remote: {
                type: String,
                default: ''
            },
            placeholder: {
                type: String,
                default: ''
            },
            local: {
                type: Array,
                default: function () {
                    return [];
                }
            },
            responseWrapper: {
                type: String,
                default: ''
            }
        },
        watch: {
            local: function(newVal) {
                if (this.defaultSuggestion) {
                    this.defaultSuggestions = [...newVal];
                }
                this.resetTypeahead();
            }
        },
        mounted: function() {
            this.initTypeahead();
            if (this.local.length) {
                this.defaultSuggestions = [...this.local];
            }
        },
        methods: {
            updateValue: function (value) {
                this.$emit('input', value);
            },
            formatValue: function () {
                this.$refs.input.value = this.value;
            },
            transformer: function (response) {
                if (this.responseWrapper) {
                    response = response[this.responseWrapper];
                }
                if (this.defaultSuggestion && this.local.length === 0) {
                    this.defaultSuggestions = response.splice(0, 5);
                }
                return response;
            },
            bloodhoundOption: function() {
                var bloodhoundConfig = {};
                if (this.prefetch) {
                    var prefetch = {
                        cache: false,
                        url: this.prefetch
                    };
                    if (this.defaultSuggestion) {
                        prefetch = {...prefetch, transform: this.transformer};
                    }
                    bloodhoundConfig = { prefetch};
                }
                if (this.local) {
                    bloodhoundConfig = {
                        local: this.local,
                        ...bloodhoundConfig
                    }
                }
                if (this.remote) {
                    bloodhoundConfig = {
                        remote: {
                            url: this.remote,
                            wildcard: '%QUERY',
                            transform: this.transformer
                        },
                        ...bloodhoundConfig
                    }
                }
                return bloodhoundConfig;
            },
            parseTemplate: function(data) {
                var res = Vue.compile(this.suggestionTemplate);
                var vm = new Vue({
                    data,
                    render: res.render,
                    staticRenderFns: res.staticRenderFns
                }).$mount();
                return vm.$el;
            },
            getSource: function() {
                var self = this;
                var bloodhoundConfig = this.bloodhoundOption();
                var datumTokenizer = this.displayKey ? Bloodhound.tokenizers.obj.whitespace(this.displayKey)
                                                    :  Bloodhound.tokenizers.whitespace;
                var engine = new Bloodhound({
                    datumTokenizer,
                    queryTokenizer: Bloodhound.tokenizers.whitespace,
                    ...bloodhoundConfig
                });
                var source = function(q, sync, async) {
                    if (q === '' && self.defaultSuggestions.length>0 && self.defaultSuggestion) {
                        sync(self.defaultSuggestions);
                    } else {
                        engine.search(q, sync, async);
                    }
                };
                return this.defaultSuggestion ? source : engine;
            },
            resetTypeahead: function() {
                $(document).find('#' + this.id).typeahead('destroy');
                this.initTypeahead();
            },
            initTypeahead: function() {
                var self =  this;
                var templates = {};
                if (this.suggestionTemplate) {
                    templates = {suggestion: self.parseTemplate}
                };
                var dataset = {
                    name: 'Typeahead-Suggestion',
                    display: this.displayKey,
                    source: this.getSource(),
                    templates
                };
                $(document).find('#' + self.id).typeahead({
                    minLength: 0,
                    highlight: true
                }, dataset)
                .on('typeahead:select', function(event, suggession) {
                    self.$emit('input', self.displayKey? suggession[self.displayKey]: suggession);
                    self.$emit('selected', suggession);

                    // Set value of hidden input to the id of selected item
                    $('input[name="' + self.param + '"]').attr("value", suggession['id']);

                    self.$emit(self.param + '_typeahead');
                });
            }
        }
    }
</script>

在上面的代码中,我通过以下语句在完成预先选择('typeahead:select')后发出一个自定义事件:

self.$emit(self.param + '_typeahead');

这是我的root vue实例:

var typeahead = require('./components/typeahead.vue');

if (formElm = document.getElementById('form')) {

    new Vue({
        el: "#form",
        data: {
            form: new Form(formElm),
            initialData: (typeof initialData !== 'undefined') ? initialData : [],
            categories: (typeof categories !== 'undefined') ? categories : [],
            parents: (typeof parents !== 'undefined') ? parents : []
        },
        components: {
            'typeahead': typeahead
        },
        mounted() {
            if (typeof initialData !== 'undefined') {
                for (let field in initialData) {
                    this.form[field] = initialData[field];
                }
            }
        },
        methods: {
            onSubmit() {

                // get all the fields
                let formData = this.form.data();
                let form     = this.form;
                let formInfo = form.info(formElm);

                // setup the constraints for validate.js
                var constraints = {
                    name: {
                        presence: {
                            message: "is required."
                        }
                    },
                    description: {
                        presence: {
                            message: "is required."
                        }
                    },
                    category_id: {
                        presence: {
                            message: "is required."
                        }
                    }
                };

                // validate the fields
                validate.async(formData, constraints)
                        .then(function(success) {
                            if (formInfo['method'] == 'PUT') {
                                form.put(formInfo['url']);
                            } else {
                                form.post(formInfo['url']);
                            }
                        })
                        .catch(function(error) {
                            form.onFail(error);
                        });
            },

            onSuccess(response) {
                form.reset();
            }
        }
    });
}

以下是我使用此组件的刀片模板:

            <form method="post" id="form" action="{{ url('admin/eventType') }}" @submit.prevent="onSubmit"
                  @keydown="form.errors.clear($event.target.name)">
                {{ csrf_field() }}
                <div class="form-group">
                    <label for="name">Name</label>
                    <input type="text" class="form-control" id="name" name="name" v-model="form.name" placeholder="Name">
                    <span class="help text-danger" v-if="form.errors.has('name')" v-text="form.errors.get('name')"></span>
                </div>
                <div class="form-group">
                    <label for="description">Description</label>
                    <textarea class="form-control" id="description" name="description" v-model="form.description" placeholder="Description"></textarea>
                    <span class="help text-danger" v-if="form.errors.has('description')" v-text="form.errors.get('description')"></span>
                </div>
                <div class="form-group">
                    <label for="category_id">Category</label>
                    <input type="hidden" id="category_id" name="category_id" v-model="form.category_id" />
                    <typeahead v-on:category_id_typeahead="form.errors.clear('category_id')" param="category_id" :default-suggestion="true" :local="categories" display-key="value" classes="form-control">
                    </typeahead>
                    <span class="help text-danger" v-if="form.errors.has('category_id')" v-text="form.errors.get('category_id')"></span>
                </div>
                <div class="form-group">
                    <label for="parent_id">Parent</label>
                    <input type="hidden" id="parent_id" name="parent_id" v-model="form.parent_id" />
                    <typeahead param="parent_id" :default-suggestion="true" :local="parents" display-key="value" classes="form-control">
                    </typeahead>
                </div>
                <button type="submit" class="btn btn-default" :disabled="form.errors.any()">Create New Event Type</button>
            </form>

在上面的模板中,我通过v-on指令捕获组件标记中的事件:

<typeahead v-on:category_id_typeahead="form.errors.clear('category_id')"
           param="category_id" :default-suggestion="true"
           :local="categories" display-key="value"
           classes="form-control">

问题:我正在使用v-if指令决定根据方法的输出显示错误文本:

<span class="help text-danger" v-if="form.errors.has('category_id')" v-text="form.errors.get('category_id')"></span>

由于触发'category_id_typeahead'会清除此字段的错误,因此form.errors.has('category_id')方法的输出应为false,因此应删除错误但不会删除。根据我通过console.log打印的内容,错误对象在触发事件后不包含此字段的错误,但在对表单进行其他更改之前不会删除文本。似乎v-if不会在此步骤中呈现。

以下是我的表单类的代码:

const formToJSON = elements => [].reduce.call(elements, (data, element) => {
    if (!['submit'].includes(element.type) && !['_token'].includes(element.name)) {
        data[element.name] = element.value;
    }
    return data;
}, {});

const formInfo = elements => [].reduce.call(elements, (data, element) => {
    if (['_method'].includes(element.name)) {
        data['method'] = element.value;
    }
    if (typeof initialData !== 'undefined' && data['method'] == "PUT") {
        data['id'] = initialData['id'];
    }
    return data;
}, {});

// get entity
const currentEntity = function() {
    url           = window.location.href;
    adminPosition = url.indexOf('admin/') + 6;
    entity        = url.substring(adminPosition, url.indexOf('/', adminPosition));

    return entity;
}

class Errors {
    constructor() {
        this.errors = { };
    }

    has(field) {
        console.log('has error field : ', field, ' => ', this.errors.hasOwnProperty(field));

        return this.errors.hasOwnProperty(field);
    }

    any() {
        return Object.keys(this.errors).length > 0;
    }

    get(field) {
        if (this.errors[field]) {
            return this.errors[field][0];
        }
    }

    record(errors) {
        this.errors = errors;
    }

    clear(field) {
        if (field) {
            console.log('Before : clear error of ', field, ' error:', this.errors[field]);
            delete this.errors[field];

            return;
        }

        this.errors = {};
    }
}

class Form {
    constructor(formElm) {
        // get form data
        data = formToJSON(formElm.elements);

        this.originalData = data;

        for (let field in data) {
            this[field] = data[field];
        }

        this.errors = new Errors();
    }

    info(formElm) {
        data   = formInfo(formElm.elements);
        entity = currentEntity();
        if (data['method'] == 'PUT') {
                data['url'] = '/admin' + '/' + entity + '/' + data['id'];
        } else {
                data['url'] = '/admin' + '/' + entity;
        }

        return data;
    }

    data() {
        let data = {};
        for (let property in this.originalData) {
            var propertyValue  = document.getElementById(property);
            if (typeof propertyValue !== 'undefined' && propertyValue.value !== null &&
                propertyValue.value !== "") {
                data[property] = propertyValue.value; 
            } else if (typeof initialData !== 'undefined' &&
                       typeof initialData[property] !== 'undefined') {
                data[property] = initialData[property];
            }
        }

        return data;
    }

    reset() {
        for (let field in this.originalData) {
            this[field] = '';
        }

        typeaheads = document.querySelectorAll('[id^="typeahead"]');
        for (let item in typeaheads) {
            typeaheads[item].value = '';
        }

        this.errors.clear();
    }

    post(url) {
        return this.submit('post', url);
    }

    put(url) {
        return this.submit('put', url);
    }

    submit(requestType, url) {
        return new Promise((resolve, reject) => {
            axios[requestType](url, this.data())
                 .then(response => {
                     this.onSuccess(response.data);
                     resolve(response.data);
                 })
                 .catch(error => {
                     this.onFail(error.response.data);
                     reject(error.response.data);
                 });
        });
    }

    onSuccess(data) {
        this.reset();
    }

    onFail(errors) {
        this.errors.record(errors);
    }
}

module.exports = Form;

我该如何解决这个问题?我错过了什么吗?

1 个答案:

答案 0 :(得分:0)

您尝试对复杂的ojbects(Form)属性进行更改。 Vue将getter和setter添加到触发(UI)更新的属性。 https://vuejs.org/v2/guide/reactivity.html

在您的情况下,表单更新中某处的更新不会通知视图执行渲染更新。

你应该让v-on:category_id_typeahead="form.errors.clear('category_id')"调用一个单独的方法来执行form.errors.clear('category_id');以及触发ui更新的内容。这可能是https://vuejs.org/v2/api/#vm-forceUpdate或更改的新data:{showError:false...属性。