Vue 2自定义选择2:为什么在@input工作时@change不工作

时间:2017-08-14 08:12:56

标签: vuejs2 jquery-select2 vue-component

我为Vue 2创建了一个自定义select2输入元素。

我的问题是:为什么

<select2 v-model="vacancy.staff_member_id" @input="update(vacancy)"></select2>

工作,但

<select2 v-model="vacancy.staff_member_id" @change="update(vacancy)"></select2>

由于Vue中的普通<input>元素有一个@change处理程序,如果我的自定义select2输入具有相同的效果,那就太好了。

我的自定义元素的一些信息:

此元素的目的是渲染所有<option>元素,但仅渲染所需元素,因为我们在一个页面上有很多select2输入,而select2输入中有很多选项,导致页面加载变慢。

此解决方案使其更快。

Vue.component('select2', {
    props: ['options', 'value', 'placeholder', 'config', 'disabled'],
    template: '<select><slot></slot></select>',
    data: function() {
        return {
            newValue: null
        }
    },
    mounted: function () {

        var vm = this;

        $.fn.select2.amd.require([
            'select2/data/array',
            'select2/utils'
        ], function (ArrayData, Utils) {

            function CustomData ($element, options) {
                CustomData.__super__.constructor.call(this, $element, options);
            }

            Utils.Extend(CustomData, ArrayData);

            CustomData.prototype.query = function (params, callback) {

                if (params.term && params.term !== '') {
                    // search for term
                    var results;
                    var termLC = params.term.toLowerCase();
                    var length = termLC.length;

                    if (length < 3) {
                        // if only one or two characters, search for words in string that start with it
                        // the string starts with the term, or the term is used directly after a space
                        results = _.filter(vm.options, function(option){
                            return option.text.substr(0,length).toLowerCase() === termLC ||
                                _.includes(option.text.toLowerCase(), ' '+termLC.substr(0,2));
                        });
                    }

                    if (length > 2 || results.length < 2) {
                        // if more than two characters, or the previous search give less then 2 results
                        // look anywhere in the texts
                        results = _.filter(vm.options, function(option){
                            return _.includes(option.text.toLowerCase(), termLC);
                        });
                    }

                    callback({results: results});
                } else {
                    callback({results: vm.options}); // no search input -> return all options to scroll through
                }
            };

            var config = {
                // dataAdapter for displaying all options when opening the input
                // and for filtering when the user starts typing
                dataAdapter: CustomData,

                // only the selected value, needed for un-opened display
                // we are not using all options because that might become slow if we have many select2 inputs
                data:_.filter(vm.options, function(option){return option.id === parseInt(vm.value);}),

                placeholder:vm.placeholder
            };

            for (var attr in vm.config) {
                config[attr] = vm.config[attr];
            }

            if (vm.disabled) {
                config.disabled = vm.disabled;
            }

            if (vm.placeholder && vm.placeholder !== '') {
                $(vm.$el).append('<option></option>');
            }

            $(vm.$el)
            // init select2
                .select2(config)
                .val(vm.value)
                .trigger('change')
                // prevent dropdown to open when clicking the unselect-cross
                .on("select2:unselecting", function (e) {
                    $(this).val('').trigger('change');
                    e.preventDefault();
                })
                // emit event on change.
                .on('change', function () {
                    var newValue = $(this).val();
                    if (newValue !== null) {
                        Vue.nextTick(function(){
                            vm.$emit('input', newValue);
                        });
                    }
                })
        });

    },
    watch: {
        value: function (value, value2) {

            if (value === null) return;

            var isChanged = false;
            if (_.isArray(value)) {
                if (value.length !== value2.length) {
                    isChanged = true;
                } else {
                    for (var i=0; i<value.length; i++) {
                        if (value[i] !== value2[i]) {
                            isChanged = true;
                        }
                    }
                }
            } else {
                if (value !== value2) {
                    isChanged = true;
                }
            }

            if (isChanged) {

                var selectOptions = $(this.$el).find('option');
                var selectOptionsIds = _.map(selectOptions, 'value');

                if (! _.includes(selectOptionsIds, value)) {
                    var missingOption = _.find(this.options, {id: value});

                    var missingText = _.find(this.options, function(opt){
                        return opt.id === parseInt(value);
                    }).text;

                    $(this.$el).append('<option value='+value+'>'+missingText+'</option>');
                }

                // update value only if there is a real change
                // (without checking isSame, we enter a loop)
                $(this.$el).val(value).trigger('change');
            }
        }
    },
    destroyed: function () {
        $(this.$el).off().select2('destroy')
    }

2 个答案:

答案 0 :(得分:5)

原因是您正在侦听组件<select2>上的事件而不是实际的DOM节点。组件上的事件将引用custom events emitted from withinunless you use the .native modifier

自定义事件与本机DOM事件不同:它们不会冒泡DOM树,除非使用.native修饰符,否则无法捕获它们。 From the docs

  

请注意,Vue的事件系统与浏览器的EventTarget API是分开的。虽然它们的工作方式类似,但$on$emit不是addEventListener和dispatchEvent的别名。

如果你查看你发布的代码,你会在最后看到这个:

Vue.nextTick(function(){
    vm.$emit('input', newValue);
});

此代码在VueJS事件命名空间中发出自定义事件input,而不是本机DOM事件。此事件将由v-on:input VueJS组件上的@input<select2>捕获。相反,由于使用change不会发出vm.$emit事件,因此绑定v-on:change将永远不会被触发,因此您已观察到非动作。

答案 1 :(得分:1)

Terry指出了原因,但实际上您可以简单地将update事件作为道具传递给子组件。查看下面的演示。

Vue.component('select2', {
  template: '<select @change="change"><option value="value1">Value 1</option><option value="value2">Value 2</option></select>',
  props: [ 'change' ]
})

new Vue({
  el: '#app',
  methods: {
    onChange() {
    	console.log('on change');
    }
  }
});
<script src="https://unpkg.com/vue@2.4.2/dist/vue.min.js"></script>
<div id="app">
  <div>
    <p>custom select</p>
    <select2 :change="onChange"></select2>
  </div>
  <div>
    <p>default select</p>
    <select @change="onChange">
      <option value="value1">Value 1</option>
      <option value="value2">Value 2</option>
    </select>
  </div>
</div>

fiddle