2017-08-14 2 views
0

J'ai créé un élément d'entrée sur mesure pour select2 Vue 2.Vue 2 select2 sur mesure: pourquoi @change ne fonctionne pas alors que @input travaille

Ma question est: pourquoi est-

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

, mais

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

pas?

Puisque les éléments <input> normaux dans Vue ont un gestionnaire @change, ce serait bien si mon entrée select2 personnalisée a la même chose.


Quelques informations sur mon élément personnalisé:

Le but de cet élément est de pas rendre tous <option> éléments mais seulement ceux qui sont nécessaires, parce que nous avons beaucoup entrées Select2 sur une seule page et de nombreuses options à l'intérieur une entrée select2, ce qui ralentit le chargement de la page.

Cette solution le rend beaucoup plus rapide.

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') 
    } 

Répondre

2

La raison est parce que vous écoutez des événements sur un composant <select2> et non un noeud DOM réel. Les événements sur les composants se référeront à la custom events emitted from within, unless you use the .native modifier.

Les événements personnalisés sont différents des événements DOM natifs: ils ne génèrent pas de bulles dans l'arborescence DOM et ne peuvent être capturés que si vous utilisez le modificateur .native. From the docs:

Notez que le système d'événements de Vue est distinct de l'API EventTarget du navigateur. Bien qu'ils fonctionnent de manière similaire, $on et $emit ne sont pas des alias pour addEventListener et dispatchEvent.

Si vous regardez dans le code affiché, vous verrez cela à la fin de celui-ci:

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

Ce code émet un événement personnalisé input dans l'espace de noms d'événement VueJS, et n'est pas natif Événement DOM. Cet événement sera capturé par v-on:input ou @input sur votre composant VueJS <select2>. Inversement, puisque aucun événement change n'est émis en utilisant vm.$emit, la liaison v-on:change ne sera jamais déclenchée et donc la non-action que vous avez observée.

+1

j'ajouté '' [email protected] ('change', newValue); '' dans le '' nextTick'' pour que mon composant select2 écoute l'évènement '' @ change'' –

1

Terry souligné la raison, mais en fait vous pouvez simplement passer votre événement update au composant enfant comme accessoire. Vérifiez la démo ci-dessous.

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() { 
 
    \t console.log('on change'); 
 
    } 
 
    } 
 
});
<script src="https://unpkg.com/[email protected]/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

+1

Votre réponse est bien, mais '': change'' est toujours différent comportement de '' @ change''. La réponse de Terry m'a indiqué dans la bonne direction pour «réparer» le comportement du composant. –