dojox/mvc/getStateful support hierarchy - dojo

Here is a dojo sandbox showing my intended use of getStateful(): http://dojo-sandbox.net/public/7917e/1
It seems that getStateful() initially creates watchCallbacks for each level of the model, but when called again in the button click getStateful() creates the watchCallbacks at the root level but not the nested level of my model.
I've looked at newStatefulModel also, but the documentation indicates that getStateful is the successor, and should be used.
If there are options I should pass to getStateful() to make this happen, how come it seemed to work initially?
EDIT: It looks like my problem is not with the getStateful method, but with the ModelRefController.set method, or more specifically the _Controller.set method.
I'm throwing a hierarchy at the ModelRefController, and it's accepting the entire hierarchy as the new "model" value, but only defining watchCallbacks on the "model" (root).
I would have expected it to accept the provided object and perform the wiring at all levels within the passed in value, instead of having to set each individual object in the hierarchy into the ModelRefController.
I may be going about it the wrong way, but I would like to have a template that knows all of the specifics about the fields it is capable of displaying, and have the widget (controller) concerned only with those properties on the model that drive logic in the widget (controller).
I'm generating the UI from a page definition, and at runtime providing additional decoration in the UI regarding the meta data of the model properties. I can easily generate the necessary calls to the ModelRefController to sync its contents with new data received from the backend, just didn't think it would require so much code.
Here's another Dojo sandbox: http://dojo-sandbox.net/public/e6946/0
The model's initial values are "Foo". And when new data is pushed into the model containing "Bar", it rebinds.

The new example in http://dojo-sandbox.net/public/ef38d/1 confirms that the application needs the "view model", which is the model the UI components looks at, to contain both of the two models to make it work.
Also, the update in the original question implies that there was an expectation for dojox/mvc/ModelRefController to support the notion of "recursive path watch", though it does not. I also have an impression that dojox/mvc/at supporting recursive path watch would the the most thing that would help http://dojo-sandbox.net/public/ef38d/1 as well as http://dojo-sandbox.net/public/e6946/0, though it's not supported. The impression is especially from http://dojo-sandbox.net/public/ef38d/1 that seems to have wanted to watch for dataModel.model.nextLevel.title path (though the code does not).
Though the implementation may not be something desirable, the best way I can think of to make the sample work is here:
<!DOCTYPE html>
<html>
<head>
<title>dojo/Stateful tree with dojox/mvc/getStateful</title>
<script type="text/javascript"
src="//ajax.googleapis.com/ajax/libs/dojo/1.10.0/dojo/dojo.js"
data-dojo-config="parseOnLoad: 0, async: 1, mvc: {debugBindings: 1}"></script>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.0/dojo/resources/dojo.css">
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.0/dijit/themes/claro/claro.css">
<script type="text/javascript">
require([
"dojo/_base/declare",
"dojo/Stateful",
"dojo/parser",
"dijit/Destroyable",
"dojox/mvc/getStateful",
"dojox/mvc/parserExtension",
"dojo/domReady!"
], function (declare, Stateful, parser, Destroyable, getStateful) {
var watchPath = (function () {
function getPathComps(path) {
return path === "" ? [] : typeof path.splice !== "function" ? path.split(".") : path;
}
function getObjectPath(o, path) {
for (var comps = getPathComps(path), i = 0, l = comps.length; i < l; ++i) {
var comp = comps[i];
o = o == null ? o : o[comp];
}
return o;
}
return function (model, path, callback) {
if (model && typeof model.watch === "function") {
var comps = getPathComps(path),
prop = comps.shift(),
remainder = comps,
observer = {
prop: prop,
remainder: remainder,
hProp: model.watch(prop, function (name, old, current) {
var hasRemainder = observer.remainder.length > 0;
if (old !== current) {
if (observer.hRemainder) {
observer.hRemainder.remove();
observer.hRemainder = null;
}
observer.hRemainder = watchPath(model[prop], remainder.slice(), callback);
}
callback(hasRemainder ? getObjectPath(old, observer.remainder) : old,
hasRemainder ? getObjectPath(current, observer.remainder) : current);
}),
hRemainder: watchPath(model[prop], remainder.slice(), callback),
remove: function () {
if (this.hRemainder) {
this.hRemainder.remove();
this.hRemainder = null;
}
if (this.hProp) {
this.hProp.remove();
this.hProp = null;
}
}
};
return observer;
}
};
})();
var States = declare([Stateful, Destroyable], {
constructor: function (model) {
var self = this;
}
}),
App = declare(Stateful, {
firstDisabled: false,
secondDisabled: false,
model: null,
constructor: function () {
this.switchModel();
},
createModel: function () {
var model = getStateful({
title: "Foo",
nextLevel: {
title: "Bar",
nextLevel: {
title: "Baz"
}
}
}),
states = new Stateful({
firstDisabled: model.nextLevel.title === "Foo",
secondDisabled: model.nextLevel.nextLevel.title === "Foo"
}),
h0 = watchPath(model, "nextLevel.title", function (old, current) {
states.set("firstDisabled", current === "Foo");
}),
h1 = watchPath(model, "nextLevel.nextLevel.title", function (old, current) {
states.set("secondDisabled", current === "Foo");
});
model.set("states", states);
model.nextLevel.set("states", states);
model.nextLevel.nextLevel.set("states", states);
model.destroy = function () {
if (h0) {
h0.remove();
h0 = null;
}
if (h1) {
h1.remove();
h1 = null;
}
}
return model;
},
switchModel: function () {
var oldModel = this.get("model"),
model = this.createModel();
if (oldModel) {
oldModel.destroy();
}
this.set("model", model);
}
});
window.app = new App();
parser.parse();
});
</script>
</head>
<body class="claro">
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div data-dojo-type="dojox/mvc/Group" data-dojo-props="target: at(app, 'model')">
Selected:
<span data-mvc-bindings="innerText: at('rel:', 'title')"></span>
<select type="combo" data-dojo-type="dijit/form/Select"
data-dojo-props="value: at('rel:', 'title'), disabled: at('rel:states', 'firstDisabled')">
<option value="Foo">Foo</option>
<option value="Bar">Bar</option>
<option value="Baz">Baz</option>
</select>
<div data-dojo-type="dojox/mvc/Group" data-dojo-props="target: at('rel:', 'nextLevel')">
Selected:
<span data-mvc-bindings="innerText: at('rel:', 'title')"></span>
<select type="combo" data-dojo-type="dijit/form/Select"
data-dojo-props="value: at('rel:', 'title'), disabled: at('rel:states', 'secondDisabled')">
<option value="Foo">Foo</option>
<option value="Bar">Bar</option>
<option value="Baz">Baz</option>
</select>
<div data-dojo-type="dojox/mvc/Group" data-dojo-props="target: at('rel:', 'nextLevel')">
Selected:
<span data-mvc-bindings="innerText: at('rel:', 'title')"></span>
<select type="combo" data-dojo-type="dijit/form/Select"
data-dojo-props="value: at('rel:', 'title')">
<option value="Foo">Foo</option>
<option value="Bar">Bar</option>
<option value="Baz">Baz</option>
</select>
</div>
</div>
</div>
<div data-dojo-type="dijit/form/Button">Switch model
<script type="dojo/on" data-dojo-event="click" data-dojo-args="evt">
app.switchModel();
</script>
</div>
</body>
</html>
Hope this helps.
Best, -Akira

dojox/mvc/getStateful creates a tree of dojo/Stateful from the given object. _watchCallbacks, as the name suggests, is a private property of dojo/Stateful, which is set when one or more guys started watching for changes in its properties. Therefore _watchCalbacks not being there does not necessarily mean there is anything wrong with dojox/mvc/getStateful; Neither is it with http://dojo-sandbox.net/public/7917e/1.
What I see in http://dojo-sandbox.net/public/7917e/1 is this; As it tries to, watching for dojo/Stateful hierarchy in UI side requires dojox/mvc/Group (or something equivalent) with the right target. It needs to be done in every level of hierarchy, though. It means that if you have dojox/mvc/Group watching for model.nested, you'll see _watchCallbacks set to nested. Here's an example how to declare hierarchcal watch in UI side (focus on dojox/mvc/Group usage):
<!DOCTYPE html>
<html>
<head>
<title>dojo/Stateful tree with dojox/mvc/getStateful</title>
<script type="text/javascript"
src="//ajax.googleapis.com/ajax/libs/dojo/1.10.0/dojo/dojo.js"
data-dojo-config="parseOnLoad: 0, async: 1, mvc: {debugBindings: 1}"></script>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.0/dojo/resources/dojo.css">
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/dojo/1.10.0/dijit/themes/claro/claro.css">
<script type="text/javascript">
require([
"dojo/_base/declare",
"dojo/Stateful",
"dojo/parser",
"dojox/mvc/getStateful",
"dojox/mvc/parserExtension",
"dojo/domReady!"
], function (declare, Stateful, parser, getStateful) {
var App = declare(Stateful, {
model: null,
constructor: function () {
this.switchModel();
},
createModel: function () {
return getStateful({
title: "Foo",
deeper: {
title: "Bar"
}
});
},
switchModel: function () {
this.set("model", this.createModel());
}
});
window.app = new App();
parser.parse();
});
</script>
</head>
<body class="claro">
<script type="dojo/require">at: "dojox/mvc/at"</script>
<div data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at(app, 'model')">
Selected:
<span data-mvc-bindings="innerText: at('rel:', 'title')"></span>
<select type="combo" data-dojo-type="dijit/form/Select"
data-dojo-props="value: at('rel:', 'title')">
<option value="Foo">Foo</option>
<option value="Bar">Bar</option>
<option value="Baz">Baz</option>
</select>
<div data-dojo-type="dojox/mvc/Group"
data-dojo-props="target: at('rel:', 'deeper')">
Selected:
<span data-mvc-bindings="innerText: at('rel:', 'title')"></span>
<select type="combo" data-dojo-type="dijit/form/Select"
data-dojo-props="value: at('rel:', 'title')">
<option value="Foo">Foo</option>
<option value="Bar">Bar</option>
<option value="Baz">Baz</option>
</select>
</div>
</div>
<div data-dojo-type="dijit/form/Button">Switch model
<script type="dojo/on" data-dojo-event="click" data-dojo-args="evt">
app.switchModel();
</script>
</div>
</body>
</html>
Best, -Akira

Related

vuejs3 dynamic attribute with no value

How can I set up a dynamic attribute within vuejs3. Vanilla js is a lot easier, but within Vue this is apparently not obvious.
I want to be able to use a variable as an attribute.
Something like this:
<q-input
outlined <---(This must be variable "item.design" without any value)
v-model="data.value"
maxlength="12"
class="super-small subshadow-25"
/>
I've read some examples and documentation but the examples are mainly for vuejs2.
Do I miss something?
You can bind data vars to attributes just as easily using v-bind: on the attribute (or the shorthand :):
<q-input
:outlined="outlined"
:filled="filled"
v-model="data.value"
maxlength="12"
class="super-small subshadow-25"
/>
// script (options api)
data() {
return {
item: {
design: 'filled',
},
data: {
value: null,
},
};
},
computed: {
filled() {
return this.item.design === 'filled';
},
outlined() {
return this.item.design === 'outlined';
},
}
Take a look at following snippet you can pass true/false to binded attributes:
const { ref, computed } = Vue
const app = Vue.createApp({
setup () {
const data = ref({value: null})
const item = ref({design: 'filled'})
const design = (type) => {
return item.value.design === 'filled' ? 'outlined' : 'filled'
}
return { data, item, design }
}
})
app.use(Quasar)
app.mount('#q-app')
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css">
<link href="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.prod.css" rel="stylesheet" type="text/css">
<div id="q-app">
<div class="q-pa-md">
<q-btn color="white" text-color="black" label="toogle design" #click="item.design = item.design === 'filled' ? 'outlined' : 'filled'" >
</q-btn>
<q-input
:filled="design(item.design)"
:outlined="design(item.design)"
v-model="data.value"
maxlength="12"
class="super-small subshadow-25"
/>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue#3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/quasar#2.5.5/dist/quasar.umd.prod.js"></script>

How to update Vuejs2 page content when changing select option on api rxjs observable api endpoint?

I'm a bit new at Vuejs2 and rxjs. So please be kind ^_^. I have an Observable api endpoint. I want to change the param value "food_type" via a select drop down on the same page. I want it so that when I select an item via the drop down the param value is updated, changing the end point and the data on the page gets reloaded. How can I achieve this?
here is my select drop down….
<div class="col-sm-2 divTableHead hand">
<select name="food_type" id="food_type" class="form-control" v-model="food_type">
<option value="" selected>Feeding</option>
<option value=“A”>One</option>
<option value=“AB”>Two Bee</option>
<option value=“BB”>Bee Bee</option>
<option value=“CB”>Cee Bee</option>
<option value=“CC”>Cee Cee</option>
</select>
</div>
here is what my Observable looks like…
data() {
return {
thisCat: [],
food_type: ''
}
},
subscriptions() {
return {
thisCat: Observable.from(axios.get(`${process.env.KITTY_URL}/api/v1/feedings/?cat__slug&cat__name=${this.$route.params.catName}&food_type=${""}`)
.catch(error => console.log(error)))
.pluck("data","results")
}
},
Thank you -_^
Seems like what you're looking for is a Watcher.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.
That's exactly the case!
Check out the example below I prepared for you using the JSONPlaceholder API.
var app = new Vue({
el: '#app',
data: {
postID: '',
loading: false,
postContent: null,
},
watch: {
postID: function () {
this.fetchPost()
}
},
methods: {
fetchPost: function(id) {
this.loading = true;
fetch('https://jsonplaceholder.typicode.com/posts/'+this.postID)
.then(response => response.json())
.then(json => {
this.postContent = {
title: json.title,
body: json.body
}
this.loading = false;
})
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<select v-model="postID">
<option value="" disabled>Select a post</option>
<option value="1">Post #1</option>
<option value="2">Post #2</option>
<option value="3">Post #3</option>
<option value="4">Post #4</option>
<option value="5">Post #5</option>
</select>
<h2 v-if="loading">Loading...</h2>
<div v-if="postContent" class="post_content">
<h3>{{postContent.title}}</h3>
<p>{{postContent.body}}</p>
</div>
</div>
As you can see, the watcher watches for any changes of that property and perform whatever you told it to do. In this case, call the fetchPost method and perform a fetch.

Vuejs open/toggle single item

I work with single file components and have a list in one of them. This list should work like a accordion, but as far as I can find in the Vuejs docs, it's not that easy to make each item open separately very easily. The data (questions and answers) is retrieved from an ajax call. I use jQuery for that, but would like to know how I can make the accordion work Vuejs-style. Any help would be appreciated!
Here's the code:
export default {
name: 'faq-component',
props: ['faqid', 'faqserviceurl', 'ctx'],
data: function () {
return {
showFaq: "",
totalFaqs: this.data,
isOpen: true
}
},
watch: {
'showFaq': function(val, faqid, faqserviceurl) {
var self = this;
$.ajax ({
url: this.faqserviceurl,
type: 'GET',
data: {id: this.faqid, q: val, scope:1},
success: function (data) {
self.totalFaqs = data;
},
error: function () {
$("#answer").html('Sorry');
}
});
}
},
methods: {
'toggle': function() {
this.isOpen = !this.isOpen
}
}
}
<template>
<div class="card faq-block">
<div class="card-block">
<form>
<div class="form-group">
<input class="form-control" type="text" placeholder="Your question" id="faq" v-model="showFaq">
</div>
</form>
<div id="answer"></div>
<ul class="faq">
<li v-for="faq in totalFaqs">
<p class="question" v-html="faq.vraag" v-bind:class={open:isOpen} #click="isOpen = !isOpen"></p>
<p class="answer" v-html="faq.antwoord"></p>
</li>
</ul>
</div>
</div>
</template>
Add an isOpen property to each object in totalFaqs and use that instead of your single isOpen property in data.
<p class="question" v-html="faq.vraag" v-bind:class={open: faq.isOpen} #click="faq.isOpen = !faq.isOpen"></p>
If you can't change the model from the server side, then add it client side.
success: function (data) {
data.forEach(d => self.$set(d, 'isOpen', false))
self.totalFaqs = data
}

Vue.js watched data chang twice with only one request

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="app">
<div class="layui-input-block" style="width:510px;">
<form class="layui-form" action="">
<select v-model="form.entrCode">
<option value="">please select an entry</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
</form>
</div>
</div>
</body>
</html>
<script src="//cdn.bootcss.com/vue/2.2.4/vue.min.js"></script>
<script type="text/javascript">
var app = new Vue({
el: "#app",
data: {
action: '',
form: {
entrCode: '',
}
},
watch: {
action: function (val) {
if (val !== "add"){
var vm = this;
//$.get("/park/GetLEDDtl", { areaId: vm.form.code }, function (rs) {
// vm.form = rs;
//}, "json");
//simulate setting on ajax.success
vm.form = { "entrCode": "20" };
}
},
"form.entrCode": function (val, old) {
alert("【entryCode changed】 new:" + val + " old:" + old);
}
},
created: function () {
this.action = "edit";
}
});
</script>
Please look at my code. I've only set app.form = object once, why there are two value changed be watched?
First, it changes from '' to '20', which is what I'm expected, but suddenly it changes from 20 to undefined.
(The code patsed I commented ajax request, and set value directly.)
What just happened?
There is no option with the value you are setting the variable to. The select object cannot be synced up to show the value, so it reverts the value to undefined.

Vuejs + Materializecss select field

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
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.
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');
}
});
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>
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>
You can make the dynamic select in Vue + Materializecss work with simple hacks
$('#select').val(1).material_select(); // Set value and reinitialize materializecss select
mounted () {
$("#select").change(function(){
this.update_result.category = $("#select").val();
}.bind(this)); // To set the user selected value to the data property
update_result.
}
If you are using meterializecss beta version the function name to initialize the select will differ.
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.
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>
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>
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>
I did something much more simple, only on mounted:
....
mounted() {
$(this.$el)
.find(".mdb-select")
.material_select();
const self = this;
$(this.$el).on("change", function(e) {
self.$emit('input', this.inputValue);
});
},
.....