I want to calculate the earnings from share using vue. I'm subtracting the day closing amount to the start one. I'm not able to display the result on the Dom.
JSfiddle: https://jsfiddle.net/4bep87sf/
This is the code:
let app = new Vue({
el: '#app',
data: {
s: '',
e: '',
tot: '0'
},
watch: {
e: function(){
this.tot = (this.e + this.s);
return this.f;
}
});
Use a computed property:
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
s: 0,
e: 0
}),
computed: {
tot() {
return Number(this.s) + Number(this.e);
}
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="s" type="number">
<input v-model="e" type="number">
<pre>{{s}} + {{e}} = {{tot}}</pre>
</div>
Also note you need to cast your values as Number() if you want the sum to be correct. If they're interpreted as strings a + b = ab.
Very close to tao answer. Only "fix" two User experience issues (Not directly related to Vue).
Issue 1: "030" or "30" ahhhh:
First, if you set a default value (0 for example), when the user focuses input and type "3" the output is 03! (or 30) ==> Very annoying (Especially on mobile).
Sometimes it's better to set the input value to null and show input placeholder (Fix this annoying issue).
Issue 2 (No meaning result):
The output 0 + 0 = 0 does not contribute too much to the user. Sometimes it's better to put the sum inside v-if.
<p v-if="number1 && number2">{{total}}</p>
Basic code example
Vue.config.devtools = false;
Vue.config.productionTip = false;
new Vue({
el: '#app',
data: () => ({
number1: {
type: Number,
value: null,
placeholder: "Enter number 1",
},
number2: {
type: Number,
value: null,
placeholder: "Enter number 2",
}
}),
computed: {
total() {
return Number(this.number1.value) + Number(this.number2.value);
}
},
})
span{
color: red;
font-weight: bold
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h3></h3>
<div>
<label>Number 1:</label>
<input autofocus v-model="number1.value" type="number" v-bind:placeholder="number1.placeholder">
</div>
<div>
<label>Number 2:</label>
<input v-model="number2.value" type="number" v-bind:placeholder="number2.placeholder">
</div>
<p>Total:<span v-if="number1.value && number2.value"> {{total}}</span></p>
</div>
v-model.lazy also sometimes useful for calucations:
By default, v-model syncs the input with the data after each input
event (with the exception of IME composition, as stated above). You
can add the lazy modifier to instead sync after change events. https://v2.vuejs.org/v2/guide/forms.html#lazy
Related
I'm using Google autocomplete address form. I found example at google official web page. Everything is fine. Everything works! but it's native Javascript,
I have Vue application and I don't like how I change text input values from JS script. The idea is that when I change something in main input, JS event listener should change values for other inputs:
document.getElementById(addressType).value = val;
Problem is that I should use "document" to change values:
document.getElementById('street_number').value
I would like to have something like tat:
<input type="text" v-model="input.address" ref="addressRef">
And to read values:
export default {
data() {
return {
input: {
address: "",
...
}
};
},
methods: {
test() {
console.log(this.input.address);
console.log(this.$refs.addressRef);
}
}
So the question is:
How to set the value from JS code to update binding values? Right now values are null because I use "getElementById("id").value = val"
You can emit input event afterwards which v-model relies on for updating its value:
let el = document.getElementById("id");
el.value = val;
el.dispatchEvent(new Event('input'));
In action:
Vue.config.devtools = false
const app = new Vue({
el: '#app',
data: {
message: null
},
methods: {
updateBinding() {
let el = document.getElementById("input");
el.value = 'Hello!';
el.dispatchEvent(new Event('input'));
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="updateBinding">Click me </button><br>
<input id="input" v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
</div>
I have a form where a datepicker follows a text input. After the text input has been given, the datepicker receives a date from an ajax call. The focus is now on the datepicker, which shows (as expected) the received date. When the user tabs to the next input, the datepicker reverts to its previous data, which ends up being the placeholder in most cases.
The vue app code:
var app = new Vue({
el: "#app",
data: {
theDate: '',
aNumber: 0,
bNumber: 0
},
mounted() {
$("#aDate").datepicker({
format: 'dd/mm/yyyy'
});
$("#aDate").datepicker().on(
"changeDate", () => {
this.theDate = $('#aDate').val()
}
);
},
methods: {
check() {
console.log('theDate: ' + this.theDate + ', aNumber: ' + this.aNumber + ', bNumber: ' + this.bNumber);
},
setDate() {
this.theDate = '18/12/2018';
}
}
})
And the html:
<body>
<div id="app">
<input type="text" id="bNumber" v-model="bNumber" /><br>
<input type="text" id="aDate" placeholder="mm/dd/yyyy" v-model="theDate" /><br>
<input type="text" id="aNumber" v-model="aNumber" /><br>
<button type="button" #click="check">Check</button>
<button type="button" #click="setDate">Set Date</button>
</div>
</body>
This can be observed in this fiddle if following these steps
Enter any number in the top input
Then tab to the datepicker input
Click the "Set Date" button (simulating received data)
Then focus on the datepicker input again by clicking in it
Now tab out to the next input
The date in the datepicker has reverted to the previous value
How can this be prevented and the data be made persistent?
I have tried the solution from this question but the behaviour is the same.
The problem is the actual date value being controlled by the datepicker, in which case there's no point in using v-model. So much that setting the theDate to any value does not really affect anything. This is understandable since you are using Bootstrap which has a jQuery dependency. I would recommend Vue-flatpickr as an alternative. But if you need to stick with this, here's a possible fix:
I've removed unnecessary attributes for brevity:
<input type="text" ref="datepicker" placeholder="mm/dd/yyyy" />
var app = new Vue({
el: "#app",
data: {
theDate: '',
aNumber: 0,
bNumber: 0
},
mounted() {
$(this.$refs.datepicker).datepicker({
format: 'dd/mm/yyyy'
})
.on("changeDate", e => {
this.update(e.target.value);
});
},
methods: {
check() {
console.log('theDate: ' + this.theDate + ', aNumber: ' + this.aNumber + ', bNumber: ' + this.bNumber);
},
setDate() {
this.update('18/12/2018');
},
update(value) {
$(this.$refs.datepicker).datepicker("update", value);
}
}
})
Basically, you need to update the date with the provided API.
I found a jsfiddle example that I forked and then edited. I don't understand what's going on or how to fix it. In my example I'm using checkboxes with values but when I click a checkbox the value is changed to true or false depending on if the checkbox is clicked.
const Checkboxes = {
template: '#checkboxTmpl',
data() {
return {
text: '',
options: [
{
name: 'Web',
slug: 'web'
},
{
name: 'iOS',
slug: 'ios'
},
{
name: 'Android',
slug: 'android'
}
]
};
},
created() {
this.$validator.extend('oneChecked', {
getMessage: field => 'At least one ' + field + ' needs to be checked.',
validate: (value, [testProp]) => {
const options = this.options;
// console.log('questions', value, testProp, options.some((option) => option[testProp]));
return value || options.some((option) => option[testProp]);
}
});
},
methods: {
validateBeforeSubmit(e) {
this.$validator.validateAll(); // why is oneChecked not validated here? --> manually trigger validate below
this.options.forEach((option) => {
this.$validator.validate('platforms', option.slug, ['checked'])
});
console.log('validator', this.errors);
if (!this.errors.any()) {
alert('succesfully submitted!');
}
}
}
};
Vue.use(VeeValidate);
const app = new Vue({
el: '#app',
render: (h) => h(Checkboxes)
})
<script src="https://cdn.jsdelivr.net/vee-validate/2.0.0-beta.18/vee-validate.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.js"></script>
<div id="app">
</div>
<script id="checkboxTmpl" type="text/template">
<form #submit.prevent="validateBeforeSubmit">
<label v-for="(option, index) in options">
<input type="checkbox"
v-model="option.slug"
name="platform"
v-validate.initial="option.slug"
data-vv-rules="oneChecked:checked"
data-vv-as="platform"/> {{option.name}}
</label>
<p v-show="errors.has('platform')">{{ errors.first('platform') }}</p>
<pre>{{options}}</pre>
<button type="submit">Submit</button>
</form>
</script>
I don't understand why all of the checkboxes are checked and unchecking one of them returns a validation error even though two are still checked. I like that errors are shown before the form is submitted but unchecking all and then submitting doesn't trigger the validation error.
I'm using VeeValidate because that is what the example uses but any other solution would be fine. I don't want to use jQuery in my vue.js application.
I would really like to understand what is going on.
There was two main problems going on :
Using v-model on the wrong key. In fact, each time the checkbox was checked or unchecked, it will emit an input event that will modify the original slug of the option (in your data). Instead, you need to add a checked field in your option. Then in your template add the :checked attribute and modify your v-model to be :option.checked.
As the docs of VeeValidate say, you can just use the required rule to make sure a checkbox has to be checked to submit your form. Here is the link towards the docs. Therefore, you don't need your created block.
Additionally, the validateAll function returns a promise containing the result of the validation. So no need to use this.errors.any() too.
Also, I upgraded the VeeValidate library to the latest as you used a beta.
Here is the working code :
const Checkboxes = {
template: '#checkboxTmpl',
data() {
return {
text: '',
options: [{
name: 'Web',
slug: 'web',
checked: false
},
{
name: 'iOS',
slug: 'ios',
checked: true
},
{
name: 'Android',
slug: 'android',
checked: true
}
]
};
},
methods: {
validateBeforeSubmit(e) {
this.$validator.validateAll().then(value => {
if (value) {
alert('successfully submitted')
}
})
}
}
};
Vue.use(VeeValidate);
const app = new Vue({
el: '#app',
render: (h) => h(Checkboxes)
})
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.js"></script>
<script src="https://unpkg.com/vee-validate#latest"></script>
<script id="checkboxTmpl" type="text/template">
<form #submit.prevent="validateBeforeSubmit">
<label v-for="(option, index) in options">
<input type="checkbox"
:checked="option.checked"
v-model="option.checked"
name="platform"
v-validate="'required'"/> {{option.name}}
</label>
<p v-show="errors.has('platform')">{{ errors.first('platform') }}</p>
<pre>{{options}}</pre>
<button type="submit">Submit</button>
</form>
</script>
Hope that helps!
I am trying to replicate the parent-child communication in components. The idea is to have a two-choice component based on radio buttons, reused several times:
Vue.component('chooser', {
template: '<form> <input type="radio" value="hello" v-model="picked"> Hello<br><input type="radio" value="world" v-model="picked"> World<br></form>',
data: function() {
return {
picked: null
}
},
watch: {
picked: function() {
this.$emit('picked')
}
}
})
var vm = new Vue({
el: "#root",
data: {
first: null,
second: null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<div id="root">
<chooser v-model="first"></chooser>
Here I want to get choice one: {{first}}
<chooser v-model="second"></chooser>
Here I want to get choice two: {{second}}
</div>
I do not get the information from the component back to the parent, why?
You are not using the $emit function correctly. To emulate v-model, you should emit an event of type 'input' (first argument) and the corresponding value (second argument).
Vue.component('chooser', {
template: '<form> <input type="radio" value="hello" v-model="picked"> Hello<br><input type="radio" value="world" v-model="picked"> World<br></form>',
data: function() {
return {
picked: null
}
},
watch: {
picked: function() {
this.$emit('input', this.picked);
}
}
})
var vm = new Vue({
el: "#root",
data: {
first: null,
second: null
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<div id="root">
<chooser v-model="first"></chooser>
Here I want to get choice one: {{first}}
<chooser v-model="second"></chooser>
Here I want to get choice two: {{second}}
</div>
I am working on a survey with Vue. I am using an array for all the questions and an index to navigate through them and display them one at a time. I'm using different input types for each question, e.g. number, radio, text etc. For some questions I'll need more than one input. I am using v-bind to pass the type of the question.
Now the problem that I encounter is that I'll need more than one input per question, e.g. when passing radio button I only get one when I need 2+. Same for labels for the buttons. I have also realized that I'm going to need two different input types for some questions (e.g. both input number and radio).
This is my working fiddle, to give you an idea of what I'm trying to accomplish. I would like to know if this is doable with my current approach, or if I need to use components for the questions and use different templates for each of them and how I would go about doing that.
I am writing this for the second time from memory since the first time I got an error, so I apologize if I failed to mention something important. Thanks in advance!
new Vue({
el: '#quizz',
data: {
questions:[
{question: 'What is your gender?', answer: '', type: 'radio', checked: 'true', label: 'Male'},
{question:'How old are you?', answer: '', type: 'number', checked: 'false'},
{question:'How many times do you workout per week?', answer: '', type: 'number', checked: 'false'},
],
index:0
},
computed:{
currentQuestion(){
return this.questions[this.index]
}
},
methods:{
next(){
if(this.index + 1 == this.questions.length)
this.index = 0;
else
this.index++;
},
previous(){
if(this.index - 1 < 0)
this.index = this.questions.length - 1;
else
this.index--;
}
}
})
I would probably handle this by building question "type" components. For example,
const RadioQuestion = {
props:["value", "question"],
template:`
<div>
<template v-for="label in question.labels">
<input type="radio" :id="label" :value="label" v-model="picked">
<label :for="label">{{label}}</label>
<br>
</template>
</div>
`,
data(){
return {
picked: this.value
}
},
watch:{
picked(){
this.$emit("input", this.picked)
}
}
}
const NumericInputQuestion = {
props:["value", "question"],
template:`
<div>
<input type="number" v-model="internalValue" #input="onInput" :value="value" />
</div>
`,
data(){
return {
internalValue: this.value
}
},
methods:{
onInput(){this.$emit("input", this.internalValue)}
}
}
Then build your data like this.
data: {
questions:[
{question: 'What is your gender?', type: RadioQuestion, labels:["Male", "Female"], answer: null},
{question:'How old are you?', type: NumericInputQuestion, answer: null},
{question:'How many times do you workout per week?', type: NumericInputQuestion, answer: null}
],
index:0
}
Modify your template accordingly.
<div id="quizz" class="question">
<h2>
{{ currentQuestion.question }}
</h2>
<component :key="currentQuestion" :is="currentQuestion.type" :question="currentQuestion" v-model="currentQuestion.answer"></component>
Current Question Answer: {{ currentQuestion.answer }}
<div class='button' id='next'>Next</div>
<div class='button' id='prev'>Prev</div>
</div>
Here is an updated fiddle demonstrating the technique.