VueJs Watch per key of a model - vue.js

I have an object (model), as an input field,for a search method, and I want that the watcher to detect changes per key, and not for all.
Right now ,if an input is changed, the watcher is called 10 times (the number of inputs that I have).
<b-form-input
v-model="search[field.column]"
type="search"
id="filterInput"
placeholder="search.."
></b-form-input>
watch:{
search: {
handler(){
// do somenthing
},
deep: true
}
}

You can watch a computed value that return the specific field that you want to watch.
Vue.config.devtools = false;
Vue.config.productionTip = false;
var app = new Vue({
el: '#app',
data: {
form: {
name : '',
lastname: ''
}
},
computed: {
formName() {
return this.form.name;
}
},
watch: {
formName() {
console.log("Name has changed")
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
Name :
<input v-model="form.name" />
Lastname :
<input v-model="form.lastname" />
</div>

You can explicitly watch the required key only.
new Vue({
el: '#app',
data: {
search: {
name: { placeholder: 'search name', value: '' },
age: { placeholder: 'search age', value: '' },
country: { placeholder: 'search country', value: '' }
}
},
watch:{
'search.name.value': { // Explicitly watch the required key.
handler(){
console.log('name changed...')
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<form>
<input
v-for="(value, key, i) in search"
:key="i"
v-model="search[key].value"
type="search"
id="filterInput"
:placeholder="value.placeholder"
/>
</form>
</div>
So, here watch's handler is called only when the name is searched & does not get called when age or country is searched.

Related

Search Turkish Character Problem on VueJs Bootstrap Vue Table

I have a problem. I used bootstrap vue table. And I have a search box. I have a yield as "Istanbul". It doesn't see it when I press i in lower case. It accepts a capital letter I. I tried toLocaleLowerCase() but didn't run.
I type "istanbul" in the search box, but it does not find it in the table. It finds it when you write it as "İstanbul".
This is my template and dataset:
<template>
<div>
<b-table striped hover :fields="fields" :items="cities"></b-table>
</div>
</template>
<script>
export default {
data() {
return {
cities : [
{key:1,city:'İstanbul'},
{key:2,city:'İzmir'},
{key:3,city:'Adana'},
],
cityCopyArray : [
{key:1,city:'İstanbul'},
{key:2,city:'İzmir'},
{key:3,city:'Adana'},
],
fields:["city"]
}
}
</script>
This is my input:
<input
:placeholder="'City Name"
:id="'cityNamr'"
v-model="citySearchSearch"></input>
This is my watch:
citySearchSearch: {
handler(val) {
this.cities = this.cityCopyArray.filter((city) => {
return this.converter(city.name).includes(this.converter(val))
})t
},
},
And I used this code as converter :
converter(text){
var trMap = {
'çÇ':'c',
'ğĞ':'g',
'şŞ':'s',
'üÜ':'u',
'ıİ':'i',
'öÖ':'o',
};
for(var key in trMap) {
text = text.replace(new RegExp('['+key+']','g'), trMap[key]);
}
return text.replace(/[^-a-zA-Z0-9\s]+/ig, '')
.replace(/\s/gi, "-")
.replace(/[-]+/gi, "-")
.toLowerCase();
},
You can compare Turkish characters using toLocaleUpperCase('tr-TR') like:
const firstWord = 'istanbul';
const secondWord = 'İstanbul';
// If firstWord contains secondWord, firstWordContainsSecondWord will be true otherwise false.
const firstWordContainsSecondWord = firstWord.toLocaleUpperCase('tr-TR').indexOf(secondWord.toLocaleUpperCase('tr-TR')) !== -1;
Simple example:
new Vue({
el: '#app',
data: {
firstWord: 'istanbul',
secondWord: 'İstanbul',
result: null,
},
watch: {
firstWord() {
this.contains();
},
secondWord() {
this.contains();
}
},
mounted() {
this.contains();
},
methods: {
contains() {
// If firstWord contains secondWord, result will be true otherwise false.
this.result = this.firstWord.toLocaleUpperCase('tr-TR').indexOf(this.secondWord.toLocaleUpperCase('tr-TR')) !== -1;
}
}
});
<script src="https://cdn.jsdelivr.net/vue/latest/vue.js"></script>
<div id="app">
<input placeholder="firstWord" v-model="firstWord">
<input placeholder="secondWord" v-model="secondWord">
<br/><br/>
<div>
Result =>
<br/> {{ firstWord }} contains {{ secondWord }} : {{ result }}
</div>
</div>

Custom Vue Material md-Input how to get isDirty or isTouched

I would like to create my own CustomMdInput, with basic validation. I would like to implement a input that work that way:
I use a <fp-input v-model="test"></fp-input>, and It is important, that when this input is required, when someone clicked on it or typesomething (turns 'touched' or 'dirty' property), and next defocus this input and go to the another input, the previous one stays Invalid with all the validation, so i have something like this:
<template>
<div class="md-layout-item">
<md-field>
<label :for="id">Imię</label>
<md-input :name="id" :id="id" :required="required" v-model="value" :ref="id" #input="emitValue()"></md-input>
<span class="md-error">Imię jest obowiązkowe</span>
</md-field>
</div>
</template>
<script>
export default {
name: 'FpInput',
props: {
value: {
required: true
},
id: {
required: true,
type: String
},
required: {
default: false,
type: Boolean
}
},
methods: {
emitValue () {
this.$emit('input', this.$refs[this.id].value)
}
}
}
</script>
<style scoped>
</style>
But i don't know how to check if this input isDirty or isTouched, and how can i set Validity of this input to check isFormValid after submit
give you an example
const MyInput = {
template: '#myInput',
props: ['value'],
data () {
return {
inputValue: this.value,
dirty: false,
touched: false,
inValid: false
}
},
methods: {
validate () {
if(this.inputValue.length<5){
this.inValid = true
this.dirty = true
}else{
this.inValid = false
this.dirty = false
this.touched = false
}
},
emitValue() {
this.validate()
this.$emit('input', this.inputValue);
}
}
}
var app = new Vue({
el: '#app',
components: {MyInput},
data () {
return {
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<my-input value="5"></my-input>
</div>
<script type="text/x-template" id="myInput">
<div>
<input v-model="inputValue" #input="emitValue()" #touchstart="touched = true" #mousedown="touched = true"/>
<span v-show="(dirty || touched) && inValid">must be at least 5 letters</span>
<div>dirty:{{dirty}}</div>
<div>touched:{{touched}}</div>
<div>inValid:{{inValid}}</div>
</div>
</script>
give you a full example
const MyInput = {
template: '#myInput',
props: ['value'],
data () {
return {
inputValue: this.value,
dirty: false,
touched: false,
inValid: false
}
},
methods: {
validate () {
if(('' + this.inputValue).length<5){
this.inValid = true
this.dirty = true
}else{
this.inValid = false
this.dirty = false
this.touched = false
}
},
emitValue(e) {
this.validate()
this.$emit('input', this.inputValue);
}
},
created () {
this.inputValue = this.value;
this.validate();
this.dirty = false;
}
}
var app = new Vue({
el: '#app',
components: {MyInput},
data () {
return {
inputList: new Array(4).fill('').map(o=>({val:5}))
}
},
methods: {
submit () {
if(this.$refs.inputs.some(o=>o.inValid)){
alert('you have some input invalid')
}else{
alert('submit data...')
}
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<form #submit.prevent="submit">
<my-input ref="inputs" v-for="item in inputList" v-model="item.val"></my-input>
<button type="submit">submit</button>
</form>
</div>
<script type="text/x-template" id="myInput">
<div>
<input v-model="inputValue" #input="emitValue()" #touchstart="touched = true"/>
<span v-show="(dirty || touched) && inValid">must be at least 5 letters</span>
</div>
</script>

How to defer form input binding until user clicks the submit button?

I wanted to make a two-way data binding on my form input in Vue.js 2.3. However, I cannot use the v-model directive, because I want the data to be updated only on clicking the submit button. Meanwhile, the input value may be updated from another Vue method, so it should be bound to the data property text. I made up something like this jsFiddle:
<div id="demo">
<input :value="text" ref="input">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
text: ''
};
},
methods: {
update: function () {
this.text = this.$refs.input.value;
}
}
});
It works, but it does not scale well when there are more inputs. Is there a simpler way to accomplish this, without using $refs?
You can use an object and bind its properties to the inputs. Then, in your update method, you can copy the properties over to another object for display purposes. Then, you can set a deep watcher to update the values for the inputs whenever that object changes. You'll need to use this.$set when copying the properties so that the change will register with Vue.
new Vue({
el: '#demo',
data: function() {
return {
inputVals: {
text: '',
number: 0
},
displayVals: {}
};
},
methods: {
update() {
this.copyObject(this.displayVals, this.inputVals);
},
copyObject(toSet, toGet) {
Object.keys(toGet).forEach((key) => {
this.$set(toSet, key, toGet[key]);
});
}
},
watch: {
displayVals: {
deep: true,
handler() {
this.copyObject(this.inputVals, this.displayVals);
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
If you're using ES2015, you can copy objects directly, so this isn't as verbose:
new Vue({
el: '#demo',
data() {
return {
inputVals: { text: '', number: 0 },
displayVals: {}
};
},
methods: {
update() {
this.displayVals = {...this.inputVals};
},
},
watch: {
displayVals: {
deep: true,
handler() {
this.inputVals = {...this.displayVals};
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="demo">
<input v-model="inputVals.text">
<input v-model="inputVals.number">
<button #click="update">OK</button>
<input v-for="val, key in displayVals" v-model="displayVals[key]">
</div>
You can use two separate data properties, one for the <input>'s value, the other for the committed value after the OK button is clicked.
<div id="demo">
<input v-model="editText">
<button #click="update">OK</button>
<p id="result">{{text}}</p>
</div>
new Vue({
el: '#demo',
data: function() {
return {
editText: '',
text: ''
};
},
methods: {
update: function () {
this.text = this.editText;
}
}
});
Updated fiddle
With a slightly different approach than the other answers I think you can achieve something that is easily scalable.
This is a first pass, but using components, you could build your own input elements that submitted precisely when you wanted. Here is an example of an input element that works like a regular input element when it is outside of a t-form component, but only updates v-model on submit when inside a t-form.
Vue.component("t-input", {
props:["value"],
template:`
<input type="text" v-model="internalValue" #input="onInput">
`,
data(){
return {
internalValue: this.value,
wrapped: false
}
},
watch:{
value(newVal){
this.internalValue = newVal
}
},
methods:{
update(){
this.$emit('input', this.internalValue)
},
onInput(){
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted(){
if(this.$parent.isTriggeredForm){
this.$parent.register(this)
this.wrapped = true
}
}
})
Here is an example of t-form.
Vue.component("t-form",{
template:`
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data(){
return {
isTriggeredForm: true,
inputs:[]
}
},
methods:{
submit(){
for(let input of this.inputs)
input.update()
},
register(input){
this.inputs.push(input)
}
}
})
Having those in place, your job becomes very simple.
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
This template will only update the bound expressions when the button is clicked. You can have as many t-inputs as you want.
Here is a working example. I included t-input elements both inside and outside the form so you can see that inside the form, the model is only updated on submit, and outside the form the elements work like a typical input.
console.clear()
//
Vue.component("t-input", {
props: ["value"],
template: `
<input type="text" v-model="internalValue" #input="onInput">
`,
data() {
return {
internalValue: this.value,
wrapped: false
}
},
watch: {
value(newVal) {
this.internalValue = newVal
}
},
methods: {
update() {
this.$emit('input', this.internalValue)
},
onInput() {
if (!this.wrapped)
this.$emit('input', this.internalValue)
}
},
mounted() {
if (this.$parent.isTriggeredForm) {
this.$parent.register(this)
this.wrapped = true
}
}
})
Vue.component("t-form", {
template: `
<form #submit.prevent="submit">
<slot></slot>
</form>
`,
data() {
return {
isTriggeredForm: true,
inputs: []
}
},
methods: {
submit() {
for (let input of this.inputs)
input.update()
},
register(input) {
this.inputs.push(input)
}
}
})
new Vue({
el: "#app",
data: {
text: "bob",
text2: "mary",
text3: "jane",
text4: "billy"
},
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<t-form>
<t-input v-model="text"></t-input><br>
<t-input v-model="text2"></t-input><br>
<t-input v-model="text3"></t-input><br>
<t-input v-model="text4"></t-input><br>
<button>Submit</button>
</t-form>
Non-wrapped:
<t-input v-model="text"></t-input>
<h4>Data</h4>
{{$data}}
<h4>Update Data</h4>
<button type="button" #click="text='jerome'">Change Text</button>
</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.

How do you activate a class for individual elements within an array? [Vue.js]

I want to activate a class for each input individually. I have two inputs bound to the same v-model and class. I have a method that checks for something to be true, and if true enables the bound class. Currently it enables the class on all inputs. (The end goal is to search multiple inputs for an element within an array and if it matches, the class activates only for that element)
<input v-model="highlightTest" id="1" v-bind:class="{ active: active }" v-on:keyup="Highlighting"></input>
<input v-model="highlightTest" id="2" v-bind:class="{ active: active }" v-on:keyup="Highlighting"></input>
Highlighting: function() {
if (this.highlightTest != '') {
this.active = true;
}
else {
this.active = false;
}
How about this:
<template>
<input v-for="(hi,index) of highlights" v-model="highlights[]" v-bind:class="{ active: highlights[index] }" v-on:keyup="highlighting(index)"></input>
</template>
<script>
export default{
data() {
return {
highlights: []
};
},
created() {
this.$http.get('some/api').then(res => {
// map: convert 0,1 to false,true
this.highlights = res.json().map(h => h==1);
});
},
methods: {
highlighting(index) {
if (this.highlights[index]) {
// this.highlights[index] = false won't let vue detect the data change(thus no view change)
this.highlights.splice(index, 1, false);
} else {
this.highlights.splice(index, 1, true);
}
}
}
}
</script>
Here's one way to do it (sorry for the delay btw)
HTML:
<div id="app">
<p :class="{'active': activateWord(word)}" v-for="word in words">#{{ word }}</p>
<input type="text" v-model="inputText">
</div>
CSS:
.active {
color: red;
}
JS:
const app = new Vue({
el: '#app',
data: {
inputText: '',
words: [
'foo',
'bar'
]
},
methods: {
activateWord(word) {
return this.inputText === word
},
},
})
here's a fiddle