Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Materialize + Framework not updating select value #2838

Open
maxflex opened this issue Feb 24, 2016 · 42 comments
Open

Materialize + Framework not updating select value #2838

maxflex opened this issue Feb 24, 2016 · 42 comments

Comments

@maxflex
Copy link

maxflex commented Feb 24, 2016

Model value is not updated when material_select() is applied to the <select>.

I've made a fiddle to show you what's going on:
http://jsfiddle.net/maxflex/j8xcm7d2/

There are also other people facing the same problem: one, two

@ferchoman09
Copy link

I have the same issue with AngularJS.

@MichaelDeBoey
Copy link

I have the same problem with Angular 2

@Thanood
Copy link

Thanood commented Feb 26, 2016

I don't know Vue.js enough but with Aurelia I had to issue a custom event on the jQuery change event to make that work. Maybe it helps.

This seems to work:

new Vue({
    el: 'body',
        ready: function() {
        var suspend = false;
        $('#materialize-select').material_select();
      $('#materialize-select').on('change', function() {
        if (!suspend) {
            suspend = true;
          var event = new CustomEvent('change', {
            detail: 'change',
            bubbles: true
          });
          $(this).get(0).dispatchEvent(event);
        }
      });
    }
})

But only once in this example since the custom change also triggers jQuery's change. You'd have to prevent stack overflow in a cleverer way than me. 😄
(f.i. use a real callback and reset suspend there)

Updated fiddle: http://jsfiddle.net/jk5800wh/

@ferchoman09
Copy link

Yes, with Angular I had to do a manual binding. When change the model, I execute a JQuery function to update de select component.

@Thanood
Copy link

Thanood commented Feb 26, 2016

Well, I guess you could encapsulate that behaviour in a component easily with either Angular, Angular2 or Vue.js 😄

@MichaelDeBoey
Copy link

Can you give the exact code you used @ferchoman09?

@ferchoman09
Copy link

of course @MichaelDeBoey

This is my select component:

                        <div class="input-field col s12 m6">
                            <select id="usuario" name="Consecutivo" ng-model="idUsuario" my-material-select>
                              <option value="" disabled selected>Seleccione un usuario</option>
                              <option ng-repeat="usuario in usuarios" value="{{usuario.idusuario}}">{{usuario.usr_login}}</option>
                            </select>
                            <label for="usuario">Usuario</label>
                        </div>  

I use a custom directive:

    .directive("myMaterialSelect", [ "$timeout", function($timeout) {
        return {
            restrict: 'A',
            require : 'ngModel',
            link : function(scope, element, attrs, ngModelCtrl) {
                $(function() {
                    $(element).material_select();

                    //Cambia el modelo cuando cambia el elemento seleccionado
                    $(element).change(function() {
                        ngModelCtrl.$setViewValue($(element).val());
                    });
                });
            }
        }
    }])

And in the controller I am watching the variable that I have in the ng-model of select component:

        $scope.$watch("formCaja.consecutivo.id", function(newValue, oldValue) {
            $timeout(function() {
                $("#consecutivo").val(newValue);
                $("#consecutivo").material_select();
            }, 0);
        });

So, I am simulating the two ways binding.
Note that you must call the function "material_select()" every time that changes the collection of your select component.

            $timeout(function() {
                $("#consecutivo").material_select();
            }, 0);

Not the best, but I had to do so.
Hope this can help you

@MichaelDeBoey
Copy link

Oh you're still using Angular.js, not Angular 2 @ferchoman09?

@ferchoman09
Copy link

@MichaelDeBoey
Oh! Sorry, is Angular 1.5. :(

@MichaelDeBoey
Copy link

No problem @ferchoman09 😄
It's defo a MaterializeCSS problem with the implementation of the select
When using the browser-default class it all works fine so... 😟

@ferchoman09
Copy link

Yes, I hope too that materializecss team improve the select implementation for compatibility with frameworks.

@maxflex maxflex changed the title Materialize + Vue.js not updating select value Materialize + Framework not updating select value Feb 26, 2016
@maxflex
Copy link
Author

maxflex commented Feb 26, 2016

Every two-way binding framework seems to be affected. There are surely ways to get around this problem, I use a custom directive myself – but yeah, we are waiting for some improvements on this one.

@Sophia-nguyen
Copy link

I have the same problem with Angular 2, any solution for this problem?

@tceydeliler
Copy link

tceydeliler commented Apr 27, 2016

My problem is little bit different:
I have an material-select

  1. I select a value and php write this value to database.
  2. I populate database as table
  3. When I click a row modal open and material-select but cant show selected value.
    If I use browser-default it works.
    All I want this When I click on green button Select shows 1024 or whatever in value in table (Bandwidth) in fiddle
    dont know how can I do this

http://jsfiddle.net/tceydeliler/9j8zzfn6

I think Its called as DOM manupulation.

@ferchoman09
Copy link

For change the materializecss select I had to do that:

    $("#idSelect").val(newValue);
    $("#idSelect").material_select();

@tceydeliler
Copy link

Where do I use "#idselect" ?

@tceydeliler
Copy link

It works.
Thnx

@virtualvoid
Copy link

@ferchoman09 thanks for the workaround. shame on you authors.

@jmherazo
Copy link

In angular2 you can use it:

For get:

$('select').material_select();
$('select').change((e) => {
     this.model[e.currentTarget.name] = e.currentTarget.value;
});

For set:

this.model.gender = 'male'; // or this.model = newValues;
$('select').material_select();

@GreyZact
Copy link

GreyZact commented Sep 13, 2016

@Thanood is on right track just add else.

new Vue({
    el: 'body',
    ready: function() {
        var suspend = false;
        $('#materialize-select').material_select();
        $('#materialize-select').on('change', function() {
            if (!suspend) {
                suspend = true;
                var event = new CustomEvent('change', {
                    detail: 'change',
                    bubbles: true
                });
                $(this).get(0).dispatchEvent(event);
            } else {
                suspend = false;
            }
        });
    }
})

http://jsfiddle.net/GreyZact/qukwodpv/

It even works if there are multiple select objects.

@scote
Copy link
Contributor

scote commented Mar 6, 2017

Any solution who works with template driven and reactive form in Angular 2?

@H5DevHoward
Copy link

@ferchoman09 @virtualvoid Can you guys tell me more about the solution for vue2?thanks!

@fega fega added the bug label Apr 11, 2017
@MelanyB
Copy link

MelanyB commented Apr 26, 2017

Anybody knows if there's any solution for this using reactive forms in Angular2?

@scote
Copy link
Contributor

scote commented Apr 26, 2017

You can use https://github.com/sherweb/ng2-materialize. We did a wrap for the select that work on both template driven and reactive forms.

@ograndebe
Copy link

ograndebe commented May 2, 2017

Hi guys, i solved using @GreyZact snippet, but i use *.vue templates. And created more productive solution:

let selectGambiCounter = 0;

export default class MyHelper {

	constructor() {
		throw new Error('Esta classe nao pode ser instanciada');
	}
	static gambiSelects () {
		selectGambiCounter = 0;
		$('.material-gambi').off('change');
    $('.material-gambi').on('change', function() {
			selectGambiCounter ++;
			if (selectGambiCounter == 1) {
                let event = new CustomEvent('change', {
                    detail: 'change',
                    bubbles: true
                });
                $(this).get(0).dispatchEvent(event);
				console.log(`trigger ${selectGambiCounter}`);
			} else if (selectGambiCounter >= 2)  {
				selectGambiCounter = 0;
			}
    });
	}
}

In template file I added class material-gambi on my selects

and in JS code:

import MyHelper from '../helpers/MyHelper';


updated() {
	$('select').material_select();

	MyHelper.gambiSelects();
}

Works lazy, but works

@DavHanna
Copy link

DavHanna commented Aug 2, 2017

Any updates on this, please?
I'm using Vue.JS and this issue is very frustrating.

@cslingerland
Copy link

I wish this were fixed. I can't use my forms the way I want to because of this.

@Dogfalo
Copy link
Owner

Dogfalo commented Aug 2, 2017 via email

@bergamamyo-bilgisayar
Copy link

bergamamyo-bilgisayar commented Aug 2, 2017

in vuejs + axios ajax call is promises logic.
you can solve your problem like that...

css regions:

<link type="text/css" rel="stylesheet" href="./node_modules/materialize-css/dist/css/materialize.min.css" media="screen,projection"/>

html regions:

                    <i class="material-icons prefix">map</i>
                    <select id="country"  v-model="country" >
                        <option v-for="option in countrylist" v-bind:value="option.code">
                        {{option.name}}
                        </option>
                    </select>
                    <label for="country">Country</label>
                </div>

scripts regions:

`<script type="text/javascript" src="node_modules/jquery/dist/jquery.min.js"></script>

<script type="text/javascript" src="node_modules/materialize-css/dist/js/materialize.js"></script> <script type="text/javascript" src="node_modules/vue/dist/vue.min.js"></script> <script type="text/javascript" src="node_modules/axios/dist/axios.min.js"></script> <script type="text/javascript" src="node_modules/vue-axios/dist/vue-axios.min.js"></script> <script type="text/javascript" src="node_modules/vue-router/dist/vue-router.min.js"></script> `

and app :

methods:{
    fetchCountry:function(){
        const self=this;
        this.$http.get("./app/country.json")
        .then(function(res){
            res.data.unshift({name:'Please Select Your Country',code:''});
            self.countrylist= res.data;
        }).then(function(){
            $('select').material_select();  // ***** important ******
        })
        .catch(function(err){
            console.log(err);
        });
    },
},

@JazielGuerrero
Copy link

Hi, for this issue specific for Vue.JS I created a component that contains a select and handle the add, update options with jQuery, and every time the user changes the selected value it emits the change with the new selected value. Hope it help some people. To not copy the entire code here, I link it.

Code: MaterialSelect.vue

@Dogfalo
Copy link
Owner

Dogfalo commented Aug 27, 2017

The issue seems to be related to jQuery's trigger('change') event not being caught by Angular. Possible fix is to trigger a change event this way, which seems to fire Angular's own handlers properly. Haven't testing this with other frameworks yet.

  var evt = document.createEvent("HTMLEvents");
    evt.initEvent("change", false, true);
    element.dispatchEvent(evt);

@Luis16287
Copy link

Luis16287 commented Sep 26, 2017

Hi, please excuse my english. I found this solution for Angular 2

HTML

<div class="input-field col s8 offset-s2">
                <select id="responsibleUser" class="icons" materialize="material_select" name="responsibleUser" [(ngModel)]="responsibleUser" (ngModelChange)="setResponsibleUser($event)" #responsibleUserRef required 
                [ngClass]="{'validate valid' : responsibleUserRef.valid, 'validate invalid': !responsibleUserRef.valid }"
                [materializeSelectOptions]="responsibleUser">
                  <option value="" disabled selected>Select user</option>
                  <option *ngFor="let user of users; let i = index" value="{{user.UserCode}}"data-icon="user.photo" class="circle left">{{user.lastName}}, {{user.name}}</option>
                </select>
                <label>Users</label>
            </div>

And in the .ts file i have

...
public ResponsibleUser: string;

ngOnInit() {
    ...
    this.ResponsibleUser = '';
    ...
}

setResponsibleUser(user: any) {
    console.log('UserCode selected  >>> ', user);
    this.ResponsibleUser = user;
  } 

@MichaelDeBoey @scote @MelanyB maybe it will help you guys

@wendellpereira
Copy link

Thank you @ferchoman09 for the tips!

@kapilgorve
Copy link

@rexsateesh
Copy link

Its working for me but its temporary hack

*.component.html

<div class="input-field col s12">
      <select formControlName="status" [(ngModel)]="selectedStatus" data-model="selectedStatus" class="select">
          <option [ngValue]="" disabled>Select</option>
          <option [ngValue]="1">Enable</option>
         <option [ngValue]="0">Disable</option>
     </select>
 <label>Status</label>
</div>

*.component.ts

ngAfterViewInit() {
        const self = this;
        jQuery(document).on('change', 'select', function(){
            var v = $(this).val().split(': '), model = $(this).attr('data-model');
            if(typeof v[1] != 'undefined') {
                self[model] = v[1];
            }
        });
 }

@master455
Copy link

@ferchoman09 (thanks) gave me a clue to update options in a Materialize Select ...

If you need load new options in a Materialize Select:

var new_options = <option value =”0” >Select…</option><option value=”1” >Value 1</option>;

$('#MaterializeSelect).html(new_options); // you could use Append (jQuery Function) if you don’t want delete before options

$("#MaterializeSelect ").material_select(); // This is point, you must execute material_select() function to update Select and show new options. 

@aneesshameed
Copy link

aneesshameed commented Mar 9, 2018

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>

@matej-janecek
Copy link

Problem is fixed for me after updating VueJS to 1.0.0-beta.

@andreandyp
Copy link

Hello, I've read @nihattunali 's solution and it worked for me. I'm using async/await which are on top of promises, however I will share my code just in case anyone needs it:

async mounted(){
        this.$store.commit("activarEspera");
        try{
            let respuesta = await this.$http.post("/api/v1.0/escuelas", {
                sigla: "IPN"
            });
            respuesta.body.escuelas.forEach(escuela => this.escuelas.push(escuela));

            //These two awaits are important. Both are necessary
            await M.updateTextFields();
            await M.FormSelect.init(document.querySelectorAll("select"));
        }
        catch(respuesta){
            M.toast({
                    html: respuesta.body,
                    displayLength: 1500
                });
        }
        this.$store.commit("desactivarEspera");
    }

Tested on VueJS 2.5.13 and Materialize 1.0.0-rc.2.

@DavidCorral94
Copy link

For those who still having problems using MaterializeCSS and AngularJs 1.x, I got a workaround. In my case, I have a select which options are added dynamically with the response of a GET request. In this case, this response is stored in an array in my controller ($scope.groups) and I want to display all this options in my select input form. So:

HTML

<select ng-model="newArticle.group" id="group">
            <option ng-repeat="group in groups" value="{{group._id}}"
                    ng-selected="group.selected == true">{{group.name}}
            </option>
</select>

CONTROLLER JS
Here, I perform the GET request, and then I store the response into the array of groups. In order to make then able to select in the select box, I have to set a small timeout and then perform $('#group').formSelect(); to refresh the select box.

function getGroups() {
        $http.get("/api/v1/groups")
            .then(function (response) {
                console.log('Groups retrieved');
                $scope.groups.push(...response.data);
                setTimeout(function () {
                    $('#group').find('option[value="0"]').prop('selected', true);
                    $('#group').formSelect();
                }, 2000);
            }, function (error) {
                console.log('Error retrieving groups', error);
                alert("Ups! Ha ocurrido un error al recuperar los grupos, inténtalo de nuevo en unos minutos.");
            });
    }

Hope it helps! :-)

@felipebraga
Copy link

felipebraga commented Nov 14, 2018

Hello, I've read @nihattunali 's solution and it worked for me. I'm using async/await which are on top of promises, however I will share my code just in case anyone needs it:

async mounted(){
        this.$store.commit("activarEspera");
        try{
            let respuesta = await this.$http.post("/api/v1.0/escuelas", {
                sigla: "IPN"
            });
            respuesta.body.escuelas.forEach(escuela => this.escuelas.push(escuela));

            //These two awaits are important. Both are necessary
            await M.updateTextFields();
            await M.FormSelect.init(document.querySelectorAll("select"));
        }
        catch(respuesta){
            M.toast({
                    html: respuesta.body,
                    displayLength: 1500
                });
        }
        this.$store.commit("desactivarEspera");
    }

Tested on VueJS 2.5.13 and Materialize 1.0.0-rc.2.

It works for me. What's the drawback about use await inside mounted?

@RavindraJijotia
Copy link

in angular 9: i just added a hack below code in ngAfterViewInit() reason is select initialized before it actually process all options
setTimeout(() => {
$('select').formSelect();
}, 1000);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests