vuejs2 and chosen select issue - vue.js

Good day, pls have a look at this bin. It was written Vue 0.12 version and chosen js. How can i make it work with vue2 version. i really need this as a directive not as a component.
`<div id='search`-results'>
Vue model value <br>
{{city | json}}
<hr>
Select value:
<!-- note the `v-model` and argument for `v-chosen` -->
<select class="cs-select" v-model="city" options="cities" v-chosen="city"></select>
<select v-model="city" options="cities"></select>
Vue.directive('chosen', {
twoWay: true, // note the two-way binding
bind: function () {
$(this.el)
.chosen({
inherit_select_classes: true,
width: '30%',
disable_search_threshold: 999
})
.change(function(ev) {
this.set(this.el.value);
}.bind(this));
},
update: function(nv, ov) {
// note that we have to notify chosen about update
$(this.el).trigger("chosen:updated");
}
});
var vm = new Vue({
data: {
city: 'Toronto',
cities: [{text: 'Toronto', value: 'Toronto'},
{text: 'Orleans', value: 'Orleans'}]
}
}).$mount("#search-results");

Here it is implemented as a wrapper component that supports v-model and a slot for the options. This makes it a drop-in replacement for a standard select widget, at least as far as basic functionality. The updated(), happily, will notice changes to the options list as well as to the value.
Since two-way directives are not supported in Vue2, I do not believe there is a way to implement this as a directive. If you really need that, you will want to use Vue1.
var vm = new Vue({
el: '#search-results',
data: {
city: 'Toronto',
cities: [{
text: 'Toronto',
value: 'Toronto'
}, {
text: 'Orleans',
value: 'Orleans'
}]
},
components: {
'chosenSelect': {
template: '<select class="cs-select" v-model="proxyValue" ><slot></slot></select>',
props: ['value', 'options'],
computed: {
proxyValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue);
}
}
},
mounted() {
$(this.$el)
.chosen({
inherit_select_classes: true,
width: '30%',
disable_search_threshold: 999
})
.change((ev) => {
this.proxyValue = ev.target.value;
});
},
updated() {
$(this.$el).trigger('chosen:updated');
}
}
}
});
setTimeout(() => { vm.cities.push({text: 'Houston', value: 'Worth it'}); }, 1000);
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.4.2/chosen.proto.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/chosen/1.4.2/chosen.jquery.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/chosen/1.4.2/chosen.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.min.js"></script>
<div id='search-results'>
Vue model value
<br> {{city | json}}
<hr> Select value:
<chosen-select v-model="city">
<option v-for="item in cities" :value="item.value">{{item.text}}</option>
</chosen-select>
<select v-model="city">
<option v-for="item in cities" :value="item.value">{{item.text}}</option>
</select>
</div>

Related

Vue-Router doesn't work in a Select Option

I I try to create a simple navigation from a select option but without results.
<router-link> does not work inside <select><option>
Here's what I tried :
vue
<div>
<select v-model="selected">
<option v-for="orga in organisations" :key="orga.name">
<router-link :to="{name: 'Box', params: {orga: orga.route}}">
{{ orga.name }}
</router-link>
</option>
</select>
</div>
javascript
data() {
return {
selected: this.$route.params.orga,
organisations: [
{ name: "Abc", route: "abc" },
{ name: "Lmn", route: "lmn" },
{ name: "Xyz", route: "xyz" }
]
};
}
If you want to change your view based on the select option, you can't use a router-link inside an option tag.
However, you can achieve this by a workaround shown below. Here we will be switching the views based on the select option and changing the route.
Vue.component('compA', {
template: '<div>{{name}}</div>',
data() {
return {
name: 'Component A'
}
}
})
Vue.component('compB', {
template: '<div>{{name}}</div>',
data() {
return {
name: 'Component B'
}
}
})
const routes = [{
path: '/a',
component: {
template: '<compA/>'
}
},
{
path: '/b',
component: {
template: '<compB/>'
}
}
];
const router = new VueRouter({
routes
})
new Vue({
el: "#app",
data() {
return {
selected: ''
}
},
router,
methods: {
routeChange: function(e) {
this.$router.push(e.target.value)
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<div>
<select #change="routeChange">
<option></option>
<option v-for="(c, i) in ['a', 'b']" :key="i">
{{ c }}
</option>
</select>
</div>
<router-view/>
</div>
But this scenario can be alternatively achieved by dynamic components. The docs explains more about this which can be used to switch between components or dynamic render.
Vue.component('CompA', {
template: '<div>new component A</div>'
})
Vue.component('CompB', {
template: '<div>new component B</div>'
})
new Vue({
el: "#app",
data() {
return {
value: ""
}
},
template: `
<div>
<select v-model="value">
<option v-for="c in ['compA', 'compB']">{{c}}</option>
</select>
<component :is="value" />
</div>
`
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<div id="app"></div>

Change value without trigger watch again

I'm trying to find a workaround to the problem of changing 2-way binding inside a watch of the same property avoiding calling the watch again. for example:
<select v-model="language">
<option ... />
</select>
watch:{
language(newVal // 'fr' , oldVal // 'en'){
if(condition){
// do something
} else {
// roll back to the old language
this.language = "en" // will call watch again.
// Looking for something like this:
// Vue.set(this, 'language', 'en', { watch: false })
}
}
}
I thought about using #change but it won't help cause I have to set the value again with an object and not a plain value.
I know I can use other 2-way property and use it as a flag, but I look for something more elegant.
Why roll back the user's selection in the first place? You can use a computed property to provide a filtered list of valid options to provide a better user experience.
The example below will only let you select en if the condition checkbox is true.
Vue.config.productionTip = false;
new Vue({
el: '#app',
template: `
<div>
<p>Condition: <input type="checkbox" v-model="condition" /></p>
<p>Selection: {{selection}}</p>
<select v-model="selection">
<option v-for="opt in filteredOptions" :key="opt.value" :value="opt.value">{{opt.label}}</option>
</select>
</div>
`,
data: () => ({
selection: undefined,
condition: true,
options: [
{ label: 'English', value: 'en' },
{ label: 'French', value: 'fr' },
{ label: 'Spanish', value: 'es' },
],
}),
computed: {
filteredOptions() {
return this.condition ? this.options.filter(x => x.value === 'en') : this.options;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

Vue.js Component with v-model

I have been able to accomplish a single level deep of v-model two-way binding on a custom component, but need to take it one level deeper.
Current working code:
<template lang="html">
<div class="email-edit">
<input ref="email" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Used like this: <email-edit-input v-model="emailModel" />
However, if I add this piece, the value no longer propagates upwards:
<div class="email-edit">
<line-editor ref="email" :title="'Email'" :value="value.email" #input="updateInput()"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
</div>
</template>
<script type="text/javascript">
import LineEditor from './LineEditor.vue'
export default {
components: {
LineEditor
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input',{
email: this.$refs.email.value,
body: this.$refs.body.value
})
}
},
data: function(){
return {}
},
props: {
value: {
default: {
email: "",
body: ""
},
type:Object
}
}
}
</script>
Using this second custom component:
<template lang="html">
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" ref="textInput" type="text" :value="value" #input="updateInput()" />
</div>
</template>
<script type="text/javascript">
export default {
components: {
},
computed: {
},
methods: {
updateInput: function(){
this.$emit('input', this.$refs.textInput.value)
}
},
data: function(){
return {}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
</script>
The first code-block works fine with just an input. However, using two custom components does not seem to bubble up through both components, only the LineEditor. How do I get these values to bubble up through all custom components, regardless of nesting?
I've updated your code a bit to handle using v-model on your components so that you can pass values down the tree and also back up the tree. I also added watchers to your components so that if you should update the email object value from outside the email editor component, the updates will be reflected in the component.
console.clear()
const LineEditor = {
template:`
<div class="line-edit">
<div class="line-edit__title">{{title}}</div>
<input class="line-edit__input" type="text" v-model="email" #input="$emit('input',email)" />
</div>
`,
watch:{
value(newValue){
this.email = newValue
}
},
data: function(){
return {
email: this.value
}
},
props: {
title:{
default:"",
type:String
},
value: {
default: "",
type: String
}
}
}
const EmailEditor = {
components: {
LineEditor
},
template:`
<div class="email-edit">
<line-editor :title="'Email'" v-model="email" #input="updateInput"/>
<input :value="value.body" v-model="body" #input="updateInput"/>
</div>
`,
watch:{
value(newValue){console.log(newValue)
this.email = newValue.email
this.body = newValue.body
}
},
methods: {
updateInput: function(value){
this.$emit('input', {
email: this.email,
body: this.body
})
},
},
data: function(){
return {
email: this.value.email,
body: this.value.body
}
},
props: {
value: {
default: {
email: "",
body: ""
},
type: Object
}
}
}
new Vue({
el:"#app",
data:{
email: {}
},
components:{
EmailEditor
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<email-editor v-model="email"></email-editor>
<div>
{{email}}
</div>
<button #click="email={email:'testing#email', body: 'testing body' }">change</button>
</div>
In the example above, entering values in the inputs updates the parent. Additionally I added a button that changes the parent's value to simulate the value changing outside the component and the changes being reflected in the components.
There is no real reason to use refs at all for this code.
In my case, having the passthrough manually done on both components did not work. However, replacing my first custom component with this did:
<line-editor ref="email" :title="'Email'" v-model="value.email"/>
<input ref="body" :value="value.body" #input="updateInput()"/>
Using only v-model in the first component and then allowing the second custom component to emit upwards did the trick.

Vue.js directive within a template

Based on this example https://vuejs.org/examples/select2.html
I try to create component for reuse in a future.
Unfortunately, my code doesn't work.
HTML:
<template id="my-template">
<p>Selected: {{selected}}</p>
<select v-select="selected" :options="options">
<option value="0">default</option>
</select>
</template>
<div id="app">
<my-component></my-component>
</div>
Vue:
Vue.component('my-component', {
template: '#my-template'
})
Vue.directive('select', {
twoWay: true,
priority: 1000,
params: ['options'],
bind: function () {
var self = this
$(this.el)
.select2({
data: this.params.options
})
.on('change', function () {
self.set(this.value)
})
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
unbind: function () {
$(this.el).off().select2('destroy')
}
})
var vm = new Vue({
el: '#app',
data: {
selected: 0,
options: [
{ id: 1, text: 'hello' },
{ id: 2, text: 'what' }
]
}
})
https://jsfiddle.net/xz62be63/
Thank you for any help!
your problem has nothing to do with the directive itself.
selected and options is defined in the data of your Vue main instance, not in the data of my-component - so it's not available in its template.
But you can pass it from the main instance to the component using props:
<div id="app">
<my-component :selected.sync="selected" :options="options"></my-component>
<!-- added the .sync modifier to transfer the value change back up to the main instance-->
</div>
and in the code:
Vue.component('my-component', {
template: '#my-template',
props: ['selected','options']
})
updated fiddle: https://jsfiddle.net/Linusborg/rj1kLLuc/

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);
});
},
.....