Basic radio validation with vee-validate - radio-button

I'm attempting to incorporate vee-validate validation (version 2) against a basic/simple radio input, such as:
<div id="app">
<form id="webform" action='post.php' method='POST' #submit.prevent="doSubmit()">
<input type='radio' name='color' value='blue'> Blue<br>
<input type='radio' name='color' value='pink'> Pink
</form></div> <!-- div id app -->
<script>
Vue.use(VeeValidate);
new Vue({
el: "#app",
template: '#app',
data() {
return {
p_arr_condition_id: null,
};
},
methods: {
doSubmit() {
this.$validator.validateAll().then(function(result){
if (!result){
//this means a validation failed, so exit without doing anything
return;
}
//here you would put your form submit stuff
window.onbeforeunload = null;
document.getElementById('webform').submit();
});
}
}
});
</script>
But I'm unsure where to begin. I discovered a working example here, however I'm not showing how to actually implement their example. Their Docs link results in a 404.
Any help/guidance would be super appreciated.

The demo iframe has a not-so-obvious slider on the left side that allows you to see the code used in the example. You can also go directly to the code sandbox

Related

VueJs: bind `v-on` on a custom component to replace an existing one

In order to ease the styling of my page, I'd like to create a bunch of mini components like, and exploit how attributes are merged in VueJs. So for example, here is a minimal js file also hosted on this JSFiddle:
Vue.component('my-button', {
template: '<button style="font-size:20pt;"><slot></slot></button>'
})
var app = new Vue({
el: "#app",
data: {
message: "world",
},
methods: {
sayHello: function () {
alert("Hello");
}
}
})
and then in my html I just want to use <my-button> instead of button:
<div id="app">
Hello {{message}} <my-button #click="sayHello" style="color:red;">Style works, but not click</my-button> <button v-on:click="sayHello" style="color:red;">Both works</button>
</div>
Unfortunately, it seems that attributes are merged, but not listeners, so it means that I can't do v-on:click on my new button... Any way to make it possible?
Thanks!
-- EDIT --
I saw the proposition of Boussadjra Brahim of using .native, and it works, but then I found this link that explains why it's not a great practice and how to use v-on="$listeners" to map all listeners to a specific sub-button. However, I tried, to just change my template with:
template: `<button style="font-size:20pt;" v-on="$listeners"><slot></slot></button>`,
but I get an error:
Vue warn: Property or method "$listeners" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option."
Here is the JSFiddle.
Your fiddle didn't work because you were using an old version of Vue, $listeners was added in Vue 2.4.0.
Here's a demo:
Vue.component('my-button', {
template: '<button style="color: red" v-on="$listeners"><slot/></button>'
})
new Vue({
el: '#app',
methods: {
sayHello() {
alert('Hello')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<my-button #click="sayHello">Custom Button</my-button>
<button #click="sayHello">Ordinary Button</button>
</div>

Is it possible to DRY html in vuejs components without creating more components?

Lets say you have:
<template>
<div>
<!-- html for buttons -->
<!-- your form -->
<!-- html for buttons -->
</div>
</template>
<!-- rest of your component -->
Is it possible to DRY up the html for the html for buttons without using a separate component? It seems a lot of work to keep adding components just to save repeating 3-4 lines of html?
I don't know any Vue api that allows to do that properly, however there is a way.
There is v-html which would serve you for DRY html, but it would get rendered as plain HTML, so you cannot use Vue events from there -which I guess your buttons do-.
For instance:
//template
<div id="app">
<div v-html="dryContent"></div>
<p>{{content}}</p>
<div v-html="dryContent"></div>
<div v-html="computedString"></div>
</div>
//script
new Vue({
el: '#app',
data: {
content: 'some sentence',
dryContent: `<div>
<p>Hello world!</p>
</div>`
},
computed: {
computedString() {
return `<p>${this.content}</p>`
}
}
});
Will render the HTML properly. But you cannot setup vue event listeners in the rendered HTML.
You can still, however, setup native listeners:
dryContent: `<div>
<p onclick="console.log('foo')">Hello world!</p>
</div>`
And it will work.
And, well, there is this really obscure pattern which I totally don't suggest but that actually will fit your needs:
new Vue({
el: '#app',
data: {
content: 'some sentence',
dryContent: `<div>
<p onclick="modifyContent()">Hello world!</p>
</div>`
},
computed: {
computedString() {
return `<p>${this.content}</p>`
}
},
created() {
window.modifyContent = function() {
this.content = 'modified!!';
}.bind(this);
}
});
You export the component method to a window property, so you can call it from native code.
Don't know your use case, but I'm pretty sure I would just duplicate the HTML code or setup a new component instead of doing this.

Is it possible to globally bind to a change event?

I would like to trigger a method everytime the user switches to another field in a form. This does the job:
new Vue({
el: "#root",
data: {
theContent1: "",
theContent2: ""
},
methods: {
changeFun() {
console.log('change')
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="root">
<form>
<input v-model="theContent1" #change="changeFun()">
<input v-model="theContent2" #change="changeFun()">
</form>
</div>
It is however repetitive when there are many fields. Is there a way to globally bind to a method for any change?
Note: I do not want to set a watch on the content of data - I need the method to be trigerred when the edited element changes, not when its content does. A practical example would be a submit once a field is completed and the user leaves it (but wihout submitting at each change of the value of the field while it is edited).
Just put your event listener at the root of your div. All Dom events traverse the Dom down from the root, to the element that generated the event, then back up again ! You can listen for them at any level. Use event.target and event.currentTarget to find out what generated the event and what captured it.
Note that, for this reason, it's super agressive to stop the propagation of an event. All kinds of things above your element might have an interest in the events it generates.
new Vue({
el: "#root",
data: {
theContent1: "",
theContent2: ""
},
methods: {
changeFun(event) {
console.log('change from '+event.target.id)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.6/vue.js"></script>
<div id="root" #change="changeFun">
<form>
<input id="one" v-model="theContent1">
<input id="two" v-model="theContent2">
</form>
</div>
You could use watchers:
new Vue({
el: "#root",
data: {
theContent1: "",
theContent2: ""
},
watch: {
theContent1(newVal) {
console.log('change from theContent1: ', newVal)
},
...
}
})
With this you'll have to set watchers for each too...

Extend Vue.js v-on:click directive

I'm new to vuejs. I'm trying to create my first app. I would like to show a confirm message on every click on buttons.
Example:
<button class="btn btn-danger" v-on:click="reject(proposal)">reject</button>
My question is: Can I extend the v-on:click event to show the confirm everywhere? I would make my custom directive called v-confirm-click that first executes a confirm and then, if I click on "ok", executes the click event. Is it possible?
I would recommend a component instead. Directives in Vue are generally used for direct DOM manipulation. In most cases where you think you need a directive, a component is better.
Here is an example of a confirm button component.
Vue.component("confirm-button",{
props:["onConfirm", "confirmText"],
template:`<button #click="onClick"><slot></slot></button>`,
methods:{
onClick(){
if (confirm(this.confirmText))
this.onConfirm()
}
}
})
Which you could use like this:
<confirm-button :on-confirm="confirm" confirm-text="Are you sure?">
Confirm
</confirm-button>
Here is an example.
console.clear()
Vue.component("confirm-button", {
props: ["onConfirm", "confirmText"],
template: `<button #click="onClick"><slot></slot></button>`,
methods: {
onClick() {
if (confirm(this.confirmText))
this.onConfirm()
}
}
})
new Vue({
el: "#app",
methods: {
confirm() {
alert("Confirmed!")
}
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
<confirm-button :on-confirm="confirm" confirm-text="Are you sure?">
Confirm
</confirm-button>
</div>
I do not know of a way to extend a directive. It is easy enough to include a confirm call in the click handler. It won't convert every click to a confirmed click, but neither would writing a new directive; in either case, you have to update all your code to use the new form.
new Vue({
el: '#app',
methods: {
reject(p) {
alert("Rejected " + p);
}
}
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<div id="app">
<button class="btn btn-danger" #click="confirm('some message') && reject('some arg')">reject</button>
</div>

Load default value from HTML input to vue.js data

I wonder if it is possible to load value of tag value as default value for vue.js data?
Works fine with
coupon: $('#coupon').val(),
but I really do not want to use jQuery.
Code is available here: https://jsfiddle.net/4a80dnfo/1/
So I found a way to get this done using the beforeMount() hook.
Assuming this HTML:
<div id="coupon-code">
<input ref="coupon" v-model="coupon" value="99PI_ORG" />
</div>
This Javascript should get the job done:
new Vue({
el: '#coupon-code',
data: {
coupon: ''
},
beforeMount: function() {
this.coupon = this.$el.querySelector('[ref="coupon"]').value;
}
// methods and such here.
}
});
I ended up using the beforeMount() hook after some trial an error. Some things I've learned which may be relevant:
this.$refs does not exist in the created() hook.
this.$refs.coupon.value returns an empty string in the mounted() hook.
The ref attribute of an element is deleted before the mounted() hook, so something like this.$el.querySelector('input[ref=coupon]') does not work.
You can use this.$el.querySelector('input[data-ref=coupon]').attributes['value'].value inside mounted(), but this seems worse to me than using beforeMount().
If anyone has suggestions on how to do this better, I'm very open to feedback. This solution still feels sub-optimal to me.
In your jsfiddle you are using v-model, so in your data() you just have to set your desired value
Edit: updated code to use v-bind
new Vue({
el: '#app',
data: {
coupon: 'You value here',
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<input id="coupon" type="text" name="coupon" :value="coupon" />
</div>
<input name="coupon" v-model="coupon" value="def val">
window.app = new Vue({
el: '#xxx',
data: {
coupon: document.querySelector("input[name=coupon]").value
}
...
});