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

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>

Related

How can I use the method from other component

I want use the method 'pause()' in the component 'vue-count-to',but the webstorm tips Unresolved function or method pause() .How can I use the method in 'vue-count-to'?Thank you
<template>
<div>
<countTo ref="countTo1" :startVal='startVal' :endVal='endVal' :duration='3000'>
</countTo>
<input type="text" v-model="endVal">
<Button v-on:click="handleClick" >reset</Button>
</div>
</template>
<script>
import CountTox from 'vue-count-to';
export default {
components: {
countTo: CountTox},
data () {
return {
startVal: 0,
endVal: 2017,
autoplay: false
}
},
methods: {
handleClick() {
this.$refs.countTo1.pause();
}
}
}
</script>
you can use $root.$emit() and $root.$on()
const componentA = {
template: `
<button #click="callMethodInComponentB">
call method in component-b
</button>
`,
methods: {
callMethodInComponentB() {
this.$root.$emit('call-to-component-b', 2);
}
}
}
const componentB = {
template: `
<h1>Value: {{ value.toString() }}</h1>
`,
data(){
return {
value: 0
}
},
methods: {
plus(add) {
this.value += add
}
},
mounted() {
this.$root.$on('call-to-component-b', add => {
this.plus(add)
});
}
};
new Vue({
el: '#app',
components: {
componentA,
componentB
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="app">
<component-a></component-a>
<component-b></component-b>
</main>
define the method in App.vue and access it from probs and emit

Property or method "isOpen" is not defined on the instance but referenced during render

I am relatively new to vue.js. I am trying to create a modal dialog that has an initial displayed state set to false. This dialog is used in another component like it is shown billow.
I cannot figure out why the data is isOpen is undefined
// My main component here
<template>
<button #click="openMyModal">Open</button>
<MyDialog ref="dialog"/>
</template>
<script>
...
methods: {
openMyModal(){
this.$refs.dialog.open().then((confirm) => {
console.log("confirm", confirm)
return true
}).catch();
}
}
...
</script>
<template>
<div class="overlay" v-if="isOpen">
<div class="modal">
<h1>My modal dialog here</h1>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'my-dialog'
}
data () {
return {
isOpen: false
...
}
}
methods() {
open() {
this.isOpen = true;
...
},
close() {
this.isOpen = false;
},
}
</script>
It is mostly because of syntax errors. Here is an example after debugging your code:
In the parent:
methods: {
openMyModal() {
this.$refs.dialog.open();
}
}
In the child:
export default {
name: "my-dialog",
data() {
return {
isOpen: false
};
},
methods: {
open() {
this.isOpen = true;
},
close() {
this.isOpen = false;
}
}
};
Something is missing in your example because from what you gave to us it's working as intended:
Vue.component('MyDialog', {
template: `
<div>
isOpen: {{ isOpen }}
<div v-if="isOpen">
<h1>My modal dialog here</h1>
</div>
</div>
`,
data () {
return {
isOpen: false
}
},
methods: {
open() {
this.isOpen = true;
},
close() {
this.isOpen = false;
},
}
})
Vue.config.productionTip = false
new Vue({
el: '#app',
template: `
<div>
<button #click="openMyModal">Open</button>
<button #click="closeMyModal">Close</button>
<MyDialog ref="dialog"/>
</div>
`,
methods: {
openMyModal(){
this.$refs.dialog.open()
},
closeMyModal(){
this.$refs.dialog.close()
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<body>
<div id="app" />
</body>

VueJs Watch per key of a model

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.

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>

How to passing data from Vue instance into a Vue.component?

I have a Vue instance with two components. If the user clicks on a button in the second component I want to hide the template of first ones.
I have this code:
<div id="app">
<mycomp-one :active="active"></mycomp-one>
<mycomp-two></mycomp-two>
</div>
<template id="mycomponent-one">
<div v-show="!active">
<!-- ... --->
</div>
</template>
<template id="mycomponent-two">
<button v-on:click="setActive">Click Me</button>
</template>
With this script code:
Vue.component('mycomp-one', {
template: '#mycompnent-one',
// etc...
});
Vue.component('mycomp-two', {
template: '#mycomponent-two',
data: function() {
return {
active: false
};
},
methods: {
setActive: function() {
this.$parent.$options.methods.setActive();
},
},
});
new Vue({
el:'#app',
data:{
active: false
},
methods: {
setActive: function() {
this.active = !this.active;
},
},
});
If the button is clicked it working good to pass the information from the component to the instance. But here is stopping the data flow, the mycomp-one component did not get the change. How can I fix that? I want to hide the mycomp-one if the active is true.
this.$parent.$options.methods.setActive() is not a method bound to the Vue. I'm not sure how you got here, but this is not how you call a method on the parent.
console.clear()
Vue.component('mycomp-one', {
props:["active"],
template: '#mycomponent-one',
});
Vue.component('mycomp-two', {
template: '#mycomponent-two',
data: function() {
return {
active: false
};
},
methods: {
setActive: function() {
this.$parent.setActive();
},
},
});
new Vue({
el:'#app',
data:{
active: false
},
methods: {
setActive: function() {
this.active = !this.active;
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<mycomp-one :active="active"></mycomp-one>
<mycomp-two></mycomp-two>
</div>
<template id="mycomponent-one">
<div v-show="!active">
Stuff
</div>
</template>
<template id="mycomponent-two">
<button v-on:click="setActive">Click Me</button>
</template>
Beyond that, components shouldn't call methods on their parent. They should emit events the parent listens to. This is covered well in the documentation.
console.clear()
Vue.component('mycomp-one', {
props:["active"],
template: '#mycomponent-one',
});
Vue.component('mycomp-two', {
template: '#mycomponent-two',
data: function() {
return {
active: false
};
},
methods: {
setActive: function() {
this.active = !this.active
this.$emit("set-active", this.active)
},
},
});
new Vue({
el:'#app',
data:{
active: false
},
methods: {
setActive: function() {
this.active = !this.active;
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<div id="app">
<mycomp-one :active="active"></mycomp-one>
<mycomp-two #set-active="active = $event"></mycomp-two>
</div>
<template id="mycomponent-one">
<div v-show="!active">
Stuff
</div>
</template>
<template id="mycomponent-two">
<button v-on:click="setActive">Click Me</button>
</template>