Vuejs + Materializecss select field

2019-04-06 23:50发布

问题:

I have this code in my template:

<div class="input-field col s6">
    <select v-on:change="selectChaned" v-model="item.size">
        <option value="" disabled selected>Choose your option</option>
        <option v-on:click="optionClicked" v-for="size in case_sizes" v-bind:value="{{ size }}">{{ size }}</option>
    </select> 
    <label for="size">Size</label>
</div>

According to Materializecss docs, I call $('select').material_select(); to transform default select field into something cutie. What it also does - it replaces <select> and <option> tags with <ul> and <li>. As a result I can't access value of item.size in my ViewModel js file. I even tried to listen for a click on option field and call optionClicked method (which should simply alert a message then), tried to listen for selectChaned. Nothing.

How can I get option value in ViewModel?

p.s. just for information: I only have problem with select field. Input field for example works fine:

<input placeholder="" name="name" type="text" class="validate" v-model="item.name">

In ViewModel I'm able to access item.name

回答1:

It seems that Materialize doesn't dispatch any events so I couldn't find an elegant solution. But it does seem that the following Vuejs directive + jQuery workaround is working:

Vue.directive("select", {
    "twoWay": true,

    "bind": function () {
        $(this.el).material_select();

        var self = this;

        $(this.el).on('change', function() {
            self.set($(self.el).val());
        });
    },

    update: function (newValue, oldValue) {
        $(this.el).val(newValue);
    },

    "unbind": function () {
        $(this.el).material_select('destroy');
    }
});

And then in your HTML – bind <select> using v-select instead of v-model.



回答2:

Vue.js 2.0

Template:

<div v-text="selected"></div>
<material-select v-bind="selected = selected || options[0].value" v-model="selected">
     <option v-for="option in options" :value="option.value" v-text="option.name"></option>
</material-select>

Component:

"use strict";

Vue.component("material-select", {
    template: '<select><slot></slot></select>',
    props: ['value'],
    watch: {
        value: function (value) {

            this.relaod(value);
        }
    },
    methods:{
      relaod : function (value) {

          var select = $(this.$el);

          select.val(value || this.value);
          select.material_select('destroy');
          select.material_select();
      }
    },
    mounted: function () {

        var vm = this;
        var select = $(this.$el);

        select
            .val(this.value)
            .on('change', function () {

                vm.$emit('input', this.value);
            });

        select.material_select();
    },
    updated: function () {

        this.relaod();
    },
    destroyed: function () {

        $(this.$el).material_select('destroy');
    }
});


回答3:

Vue.directive('material-select', {
        bind:function(el,binding,vnode){
            $(function () {
                $(el).material_select();

            });
            var arg = binding.arg;
            if(!arg)arg="change";
            arg = "on"+arg;
            el[arg]=function() {
            	
                if (binding.expression) {
                    if (binding.expression in vnode.context.$data) {
                        vnode.context.$data[binding.expression] = el.value;

                    } else if (vnode.context[binding.expression] &&
                            vnode.context[binding.expression].length <= 1) {
                            vnode.context[binding.expression](el.value);

                    } else {
                        throw new Error('Directive v-' + binding.name + " can not take more than 1 argument");
                    }
                    

                }
                else {
                    throw new Error('Directive v-' + binding.name + " must take value");
                }
            }

        },
        unbind:function(el) {
            $(el).material_select('destroy');
        }
});


new Vue({
  el: '#exemple1',
  data:function(){
    return {
    	selected: '',
	    options:[
	        {value:"v1",text:'description 1'},
	        {value:"v2",text:'description 2'},
	        {value:"v3",text:'description 3'},
	        {value:"v4",text:'description 4'},
	        {value:"v5",text:'description 5'},
	    ]
	  }
	}
});
      
new Vue({
  el: '#exemple2',
  data:function() {
    return{
    	selected: null,
	    options:[
	        {value:"v1",text:'description 1'},
	        {value:"v2",text:'description 2'},
	        {value:"v3",text:'description 3'},
	        {value:"v4",text:'description 4'},
	        {value:"v5",text:'description 5'},
	    ]
	}
  },
  methods:{
    change:function(value){
        this.selected = value;
        alert(value);
    }
  }
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script>
<h4>vue js materialize</h4>
<h5>Exemple1</h5>
<div id="exemple1">
    <select v-material-select:change="selected" class="blue-text">
        <option value="" disabled selected ><slot>Defaut message</slot></option>
        <option v-for="option in options" :value="option.value">{{ option.text}}</option>
    </select>
</div>

<h5>Exemple2</h5>
<div id="exemple2">
    <select v-material-select:change="change" class="blue-text">
        <option  disabled selected ><slot>Choisir Votre Abonnement</slot></option>
        <option v-for="option in options" :value="option.value">{{ option.text}}</option>
    </select>
</div>



回答4:

The top answer was nice but didn't work for Vue 2.

Here is an update of which works (probably still a little hacky). I moved the jQuery hook into update() as the bind function called too early for materialize.

Vue.directive("select", {
    "twoWay": true,

    update: function(el, binding, vnode) {
        if(!vnode.elm.dataset.vueSelectReady) {

            $(el).on('change', function() {
                vnode.context.$set(vnode.context, binding.expression, el.value);
            });

            $(el).material_select();
            vnode.elm.dataset.vueSelectReady = true
        }
    },

    unbind: function(el, binding, vnode) {
        $(el).material_select('destroy');
    }
});

HTML:

<select v-select=selected>
    <option value="" disabled selected>Choose your option</option>
    <option :value="item" v-for='item in items'>{{ item }}</option>
    <label>Materialize Select</label>
</select>


回答5:

I had a similar problem. The catch here is, you need to issue $('select').material_select(); only after the DOM of your Vue app is ready. So you can add a ready method to your Vue app and include $('select').material_select(); inside your ready method.

var vm = new Vue({
data: function() {
   locations: ["Clayton", "Mt Meigs", "Birmingham", "Helena", "Albertville", "Albertville", "Grant"]
},
ready: function() {
    $('select').material_select();
}});

Just make sure you include Jquery first, then materialize.js followed by Vue.js in your html file.



回答6:

I want to include a working fiddle of custom select2 directive which I built for my project. It also supports multiple selects: fiddle

data: function() {
  return {
      names: [
      {id: 1, value: 'Alice'},
      {id: 1, value: 'Bob'},
      {id: 1, value: 'Simona'}
    ],
      myStudents: {
      names: ['Alice', 'Bob'],
    }
  }
},

directives: {
  'select': {
    twoWay: true,
    params: ['options'],
    bind: function () {
      var self = this
      $(this.el).select2().on('change', function() {
        self.set($(self.el).val())
      })
    },
    update: function (value) {
      $(this.el).val(value).trigger('change')
    },
  },
},

<select multiple v-select="myStudents.names" name="names" v-model="myStudents.names">
  <option v-for="name in names" value="{{ name.value }}">{{ name.value }}</option>
</select>


回答7:

v- VueJs2.4 None of the above answers were for multiple select element. I got it working by traversing the select element options. This is not a correct approach and kind of hack but works.

Plunker

    <h4>vue js materialize select</h4>
        <div class="row" id="app" style="padding-bottom:2em;">
        <div class="input-field col s12 m8">
          <select multiple v-material-select:change="selected">
                <option value="AngularJs">AngularJs</option>
                <option value="Bootstrap3">Bootstrap3</option>
                <option value="Bootstrap4">Bootstrap4</option>
                <option value="SCSS">SCSS</option>
                <option value="Ionic">Ionic</option>
                <option value="Angular2">Angular2</option>
                <option value="Angular4">Angular4</option>
                <option value="React">React</option>
                <option value="React Native">React Native</option>
                <option value="Html5">Html5</option>
                <option value="CSS3">CSS3</option>
                <option value="UI/UX">UI/UX</option>
              </select>
          <label>Technologies Used</label>
        </div>
         <h2>Your selected options</h2>
       <p>{{$data.selected}}</p>
       </div>

       <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
        <script src="https://unpkg.com/vue@2.4.4/dist/vue.js"></script>
        <script>  Vue.directive("material-select", {
    bind: function(el, binding, vnode) {
      $(function() {
        $(el).material_select();
      });
      var arg = binding.arg;
      if (!arg) arg = "change";
      arg = "on" + arg;
      el[arg] = function() {
        vnode.context.$data.selected = [];
        for (let i = 0; i < 12; i++) {
          if (el[i].selected === true) {
            vnode.context.$data.selected.push(el[i].value);
          }
        }
      };
    },
    unbind: function(el) {
      $(el).material_select("destroy");
    }
  }); 
           var app = new Vue({el: "#app",data: { selected: []},
     ready: function() {
          $("select").material_select(); }});</script>


回答8:

The possible solution that I found is to use an input, and attach it to a dropdown content. It works well with vue even when you are dynamically creating dropdown. And its reactive, that you don't have to emit any other event to bind values.

Codepen: https://codepen.io/aaha/project/editor/DGJNLE

<style>
    input{
        cursor: pointer;
    }
    .caret{
        float:right;
        position: relative;
        cursor: pointer;
        top:-50px;
    }
    ul{
      width: 100%;
    }
</style>
<script>
    Vue.component('paper-dropdown', {
            template: '<div> \
                          <div class="input-field">\
                             <input type="text" class="dropdown-button" v-bind:data-activates="_id"\
                              v-bind:value="value"> \
                             <label>{{label}}</label> \
                          </div> \
                          <i class="material-icons caret">arrow_drop_down</i>\
                          <ul v-bind:id="_id" class="dropdown-content"> \
                             <li v-for="item in options" v-on:click="setselected"><a v-bind:value="item">{{item}}</a></li> \
                          </ul>\
                       </div>',
                watch: {
                    value: function(){
                        Materialize.updateTextFields();
                    }
                },
                computed:{
                    _id: function(){
                        if(this.id != null) return this.id;
                        return Math.random().toString(36).substr(2);
                    }
                },
                props: {
                    label:{
                        type: [String, Number],
                        default: ''
                    },
                    options:{
                        type: Array,
                        default: []
                    },
                    placeholder:{
                        type: String,
                        default: 'Choose your option'
                    },
                    value:{
                        type: String,
                        default: ''
                    },
                    id:{
                        type: String,
                        default: 'me'
                    }
                },
                methods:{
                    setselected: function(e){
                        this.$emit('input', e.target.getAttribute("value"));                   
                    }
                },
                mounted: function(){
                    $('.dropdown-button').dropdown({
                      inDuration: 300,
                      outDuration: 225,
                      constrainWidth: false, // Does not change width of dropdown to that of the activator
                      hover: false, // Activate on hover
                      gutter: 0, // Spacing from edge
                      belowOrigin: false, // Displays dropdown below the button
                      alignment: 'left', // Displays dropdown with edge aligned to the left of button
                      stopPropagation: false // Stops event propagation
                    }
                    );
                }
            });
    </script>