Change value without trigger watch again - vuejs2

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>

Related

How can I get a specifc selection in select vue.js?

How are you?
I'm studying Vue and I'm stuck on the current task not knowing where to go.
I have a select that when I click I need to show on screen only what corresponds to that selection. For example, when placing the "to do" option in the select, only the tasks with a concluded=false should appear on the screen. I've only gotten this far and I need help to continue. Can you help me? Thanks
This is my App.vue
<template>
<div id="app">
<h1>Lista de Tarefas</h1>
<List :data="list" #remove="handleRemove"/>
<Form #add="addNewTask" #onChange="handleN"/>
</div>
</template>
<script>
import List from "./components/List.vue";
import Form from "./components/Form.vue";
export default {
components: {
List,
Form,
},
data() {
return {
list: [],
};
},
methods: {
addNewTask(newTask) {
this.list.push(newTask);
},
handleRemove(item) {
const index = this.list.findIndex(i => i.id === item.id)
this.list[index].excluded = true
},
handleN(item) {
const index = this.list.findIndex(i => i.id === item.id)
this.list[index].concluded = true
}
},
};
</script>
This is my List.vue
<template>
<ul>
<select v-model="selected" #change="onChange($event)">
<option disabled value="">Escolha a visualização</option>
<option v-for="option in options" :key="option.text">
{{ option.text }}
</option>
</select>
<li v-for="item in itens" :key="item.id">
<input type="checkbox" id="checkbox" v-model="item.concluded" />
<label for="checkbox"> {{ item.description }} </label>
<button #click="() => $emit('remove', item)">Excluir</button>
</li>
</ul>
</template>
<script>
export default {
props: {
data: {
type: Array,
default: () => {},
},
},
data() {
return {
selected: "",
options: [
{ text: "Todos", value: "1" },
{ text: "A fazer", value: "2" },
{ text: "Concluído", value: "3" },
{ text: "Deletado", value: "4" },
],
};
},
computed: {
itens() {
return this.data.filter((item) => item.excluded === false);
},
},
methods: {
onChange(event) {
console.log(event.target.value);
return this.data.filter((item) => item.concluded === false);
},
},
};
</script>
This is my Form.vue
<template>
<form #submit.prevent="handleNewTask">
<input type="text" v-model="newTask" placeholder="Insira a tarefa"/>
<input type="submit" value="Adicionar"/>
</form>
</template>
<script>
import Task from '../types/Task.js'
export default {
data() {
return {
newTask: "",
};
},
methods: {
handleNewTask() {
this.$emit('add', new Task(this.newTask))
this.newTask = ''
}
},
};
</script>
And this is my Task.js
export default class {
constructor(description) {
this.description = description,
this.id = Math.random(),
this.concluded = false,
this.excluded = false
}
}
I watch some tutorials, read the documentation and some StackOverflow questions but I really can't get out of here
Thanks in advance for the help
Based on how you have structured your app, our only concern should be with the List.vue file.
Your goal is to filter the results based on the selection (selected property). However, your issue is that you are not even using that anywhere.
I know you are hard coding the filter on the onChange method but that is, first of all wrong because you aren't really changing anything (you are returning an array), and secondly it's inefficient.
A better way to do it is to update the computed itens function like so:
itens() {
return this.data.filter((item) => {
if (this.selected === '1'){
return item.concluded === false
} else if (this.selected === '2'){
// filter another way
} else if (... // so on and so forth
});
},
Also, I would filter out the excluded items before sending them to the component. If you aren't going to use it, don't send it.
Remove the onChange event on the <select> and the associated method since they are now unused.

Show modal before change option in select BOOTSTRAP-VUEJS

I want to show a modal for confirm the action when you change the selected value in a b-form-selected. I can't to stop the event and it always changes the value before the modal shows. Is there an option for this?
<b-form-select
id="serviceType"
v-model="serviceTypeSelected"
class="dropdown textfield"
:data-value="serviceTypeSelected"
:value="serviceTypeSelected"
required
#change="changeServiceType">
<option
v-for="option in serviceTypeList"
:key="option.serviceTypeId"
:value="option.serviceTypeId">
{{ option.serviceTypeName }}
</option>
</b-form-select>
function changeServiceType () {
this.$bvModal.msgBoxConfirm('Please confirm that you want to delete everything.', {
title: 'Please Confirm',
size: 'sm',
okTitle: 'YES',
cancelTitle: 'NO',
centered: true
})
.then(value => {
if (value) {
//do things
} else {
//nothing
}
})
.catch(err => {
// An error occurred
})
}
Here's how i would suggest doing it.
You have one data property selectedOption which you bind to your b-select, this option will be what is shown in the select.
You then have another data property actualOption which is the final value. So when you change your b-select value, you open the dialog to confirm. If the user confirms, you set actualOption to the new selected value. If the user declines you set this.selectedOption back to the old value, which is the value of actualOption.
window.onload = () => {
new Vue({
el: '#app',
data() {
return {
selectedOption: 0,
actualOption: 0,
options: [
{ value: 0, label: 'Orange' },
{ value: 1, label: 'Apple' },
{ value: 2, label: 'Banana' },
{ value: 3, label: 'Strawberry' },
{ value: 4, label: 'Mango' }
]
}
},
methods: {
onOptionChanged(value) {
this.$bvModal.msgBoxConfirm('Please confirm that you want to delete everything.')
.then(confirmed => {
if(confirmed) {
this.actualOption = value;
} else {
this.selectedOption = this.actualOption;
}
}).
catch(() => {
/* Reset the value in case of an error */
this.selectedOption = this.actualOption;
})
}
}
})
}
<link href="https://unpkg.com/bootstrap#4.4.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<link href="https://unpkg.com/bootstrap-vue#2.3.0/dist/bootstrap-vue.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.3.0/dist/bootstrap-vue.js"></script>
<div id="app">
<b-select
v-model="selectedOption"
required
#change="onOptionChanged">
<option
v-for="option in options"
:key="option.value"
:value="option.value">
{{ option.label }}
</option>
</b-select>
</div>
I have used Sweet Alerts to replicate your situation, works the same just change it to your model.
Create an additional value in your data object which you are going to use to check against your model input.
In your #change function, you check if user agrees to change data or to cancel the change.
If user cancels : set serviceTypeSelected v-model to the new inputVal value (your history) to undo the change.
If user accepts : run confirmation dialog and set inputVal to the input value (this is to save your history)
data() {
return {
serviceTypeSelected: '',
inputVal: '',
}
},
methods: {
changeServiceType(id){
this.$swal({
title: "Are you sure ?",
text: "You are going to change the service type!",
type: "warning",
showCancelButton: true,
confirmButtonColor: "#f2ab59",
confirmButtonText: "Yes, change service type!",
cancelButtonText: "No, cancel!",
}).then((confirmed) => {
if (confirmed.value) {
this.$swal(
'Changed!',
'Service type has been changed.',
'success'
);
this.inputVal = id;
} else {
this.$swal("Cancelled", "Service type hasn't been changed !", "error");
this.serviceTypeSelected = this.inputVal;
// this.serviceTypeSelected = '';
// this.inputVal = '';
}
});
}
}
<b-form-select
id="serviceType"
v-model="serviceTypeSelected"
:data-value="serviceTypeSelected"
:value="serviceTypeSelected"
class="dropdown textfield"
required
#change="changeServiceType">
<option>Test1</option>
<option>Test2</option>
</b-form-select>
If interested in Sweet Alerts as I used it for this particular question.
vue-sweetalert2 npm
npm i vue-sweetalert2
Sweet Alert has a nice documentation and is good to use with vue.js.

Checkbox list without using v-model

Vue has a good example of using multiple checkboxes with the same name attribute bound to the same array using v-model
However, I can't use v-model for some reason so I must use #input to emit checked while keeping the value unchanged.
Its not working for me though, all the checkboxes are checked/unchecked at the same time or I have to change the value which I don't want.
Is there a workaround?
Code: https://codesandbox.io/s/71pm2wllp1?fontsize=14
Vue generates special code when compiling a template containing checkboxes bound in this way. Since you're not using v-model, you'll have to handle this functionality yourself.
Try something like this:
new Vue({
el: '#app',
data: {
checkboxes: [
{ name: 'jack', value: 'Jack' },
{ name: 'bob', value: 'Bob' },
{ name: 'alice', value: 'Alice' },
],
model: [],
},
computed: {
modelJson() {
return JSON.stringify(this.model);
},
},
methods: {
handleChange(value, checked) {
if (checked) {
this.model = this.model.concat(value);
} else {
this.model = this.model.filter(x => x !== value);
}
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="checkbox of checkboxes" :key="checkbox.name">
<input
type="checkbox"
:id="checkbox.name"
:name="checkbox.name"
:value="checkbox.value"
:checked="model.includes(checkbox.value)"
#change="handleChange(checkbox.value, $event.target.checked)"
/>
<label :for="checkbox.name">{{ checkbox.value }}</label>
</div>
<pre>{{ modelJson }}</pre>
</div>

Vue.js - Watcher for property with variable keypath

Is it possible, in Vue v2, to define a watcher using a keypath containing a variable?
For example, depending on the currentKey, I want to watch either the changes in obj.A or obj.B:
data() {
return {
currentKey: 'A',
obj: { A: { 'a': '' }, B: { 'b' :'' },
}
},
watch: {
'obj[currentKey]'(newItem, oldItem) {}
}
You can make a computed property which returns this.obj[this.currentKey] and then set a watcher on that.
But, if you want to watch changes to the properties of the dynamic object, you'll also need to set the deep property of the watcher to true.
Here's a simple example:
new Vue({
el: '#app',
data () {
return {
currentKey: 'A',
obj: {A: {value: ''}, B: {value:''} },
}
},
computed: {
selected() {
return this.obj[this.currentKey];
}
},
watch: {
selected: {
deep: true,
handler(object) {
console.log('selected object value', object.value);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.9/vue.min.js"></script>
<div id="app">
Selected object: {{ selected }}
<select v-model="currentKey">
<option v-for="i in ['A', 'B']" :key="i" :value="i">{{ i }}</option>
</select>
<br><br>
Text for selected object: <input v-model="selected.value">
</div>

vuejs2 and chosen select issue

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>