The use of bidirectional binding of components in Vue.js - vue.js

I'm a new learner of Vue.js and trying to implement the example (example of currency filter) on the official guideline.
However, when implementing, I rename the property of the component (value) to (priceValue). After the change, the input box cannot format the value - it always shows '0' instead of the formatted value.
It is the only change I made. What is the problem?
Vue.component('currency-input', {
template: '\
<div>\
<label v-if="label">{{ label }}</label>\
$\
<input\
ref="input"\
v-bind:value="priceValue"\
v-on:input="updateValue($event.target.value)"\
v-on:focus="selectAll"\
v-on:blur="formatValue"\
>\
</div>\
',
props: {
priceValue: {
type: Number,
default: 0
},
label: {
type: String,
default: ''
}
},
mounted: function () {
this.formatValue()
},
methods: {
updateValue: function (value) {
var result = currencyValidator.parse(value, this.priceValue)
if (result.warning) {
this.$refs.input.value = result.value
}
this.$emit('input', result.value)
},
formatValue: function () {
// console log here always get 0
this.$refs.input.value = currencyValidator.format(this.priceValue)
},
selectAll: function (event) {
setTimeout(function () {
event.target.select()
}, 0)
}
}
})
new Vue({
el: '#app',
data: {
price: 0,
shipping: 0,
handling: 0,
discount: 0
},
computed: {
total: function () {
return ((
this.price * 100 +
this.shipping * 100 +
this.handling * 100 -
this.discount * 100
) / 100).toFixed(2)
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://cdn.rawgit.com/chrisvfritz/5f0a639590d6e648933416f90ba7ae4e/raw/98739fb8ac6779cb2da11aaa9ab6032e52f3be00/currency-validator.js"></script>
<div id="app">
<currency-input
label="Price"
v-model="price"
></currency-input>
<currency-input
label="Shipping"
v-model="shipping"
></currency-input>
<currency-input
label="Handling"
v-model="handling"
></currency-input>
<currency-input
label="Discount"
v-model="discount"
></currency-input>
<p>Total: ${{ total }}</p>
</div>

According to DOCS:
For a component to work with v-model, it should (these can be
configured in 2.2.0+):
accept a value prop
emit an input event with the new value
This can be configured sinse 2.2.x with a model options block:
Vue.component('currency-input', {
model: {
prop: 'propValue',
// event: 'input' - you can also customize event name, not needed in your case
},
With this in place, your code will work again: https://jsfiddle.net/wostex/j3a616a5/

Related

How to change prop dynamically in vue-status-indicator?

I am new to VueJS and after reading this doc section and this question, I can't figure how to change dynamically the prop active|positive|intermediary|negative and pulse of the following component (it could be another): vue-status-indicator
eg: with user.status = positive and the following wrong code :
<span v-for="user in users" :key="user.id">
<status-indicator {{ user.status }}></status-indicator>
</span>
What is the correct syntax to set theses type of props ?
You could do something like this.. I had to write a wrapper for it to make it functional..
[CodePen Mirror]
Edit To be clear - you cannot interpolate inside an attribute.. This has to do with boolean attributes in Vue..
This:
<status-indicator active pulse />
...is the same exact thing as doing this:
<status-indicator :active="true" :pulse="true" />
The "wrapper" component I wrote allows you to supply a string to set the status (like you are wanting to do):
<v-indicator status="active" pulse></v-indicator>
<!-- OR -->
<v-indicator status="positive" pulse></v-indicator>
<!-- OR -->
<v-indicator status="intermediary" pulse></v-indicator>
<!-- OR -->
<v-indicator status="negative" pulse></v-indicator>
Here is the full "wrapper" component, in .vue format: (added a validator for the 'status' prop)
<template>
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</template>
<script>
export default {
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
</script>
Snippet:
const vIndicator = {
template: "#v-indicator",
props: {
status: {
type: String,
required: true,
validator: (prop) => [
'active',
'positive',
'intermediary',
'negative',
].includes(prop)
},
pulse: {
type: Boolean,
required: false,
},
},
data() {
return {
indicatorStatus: {
active: false,
positive: false,
intermediary: false,
negative: false,
}
}
},
watch: {
status() {
this.handleStatusChange(this.status);
}
},
methods: {
handleStatusChange(newStatus) {
Object.keys(this.indicatorStatus).forEach(v => this.indicatorStatus[v] = false);
this.indicatorStatus[newStatus] = true;
}
},
mounted() {
this.handleStatusChange(this.status);
}
}
new Vue({
el: '#app',
components: {
vIndicator
},
data: {
currentStatus: '',
isPulse: '',
},
computed: {
currentJson() {
let cj = {
currentStatus: this.currentStatus,
isPulse: this.isPulse,
};
return JSON.stringify(cj, null, 2);
}
},
mounted() {
let statuses = ["active", "positive", "intermediary","negative"];
let c = 0;
let t = 0;
this.currentStatus = statuses[c];
this.isPulse = true;
setInterval(() => {
t = c + 1 > 3 ? t + 1 : t;
c = c + 1 > 3 ? 0 : c + 1;
this.currentStatus = statuses[c];
this.isPulse = (t % 2 == 0) ? true : false;
}, 2000)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vue-status-indicator#latest/dist/vue-status-indicator.min.js"></script>
<link href="https://unpkg.com/vue-status-indicator#latest/styles.css" rel="stylesheet"/>
<div id="app">
<p>Will alternate status as well as pulsing (pulse changes after each full loop)</p>
<!--
[status]active|positive|intermediary|negative
[pulse]true|false
-->
<v-indicator :status="currentStatus" :pulse="isPulse"></v-indicator>
<pre>{{ currentJson }}</pre>
</div>
<!-- WRAPPER COMPONENT -->
<script type="text/x-template" id="v-indicator">
<status-indicator
:active="indicatorStatus.active"
:positive="indicatorStatus.positive"
:intermediary="indicatorStatus.intermediary"
:negative="indicatorStatus.negative"
:pulse="pulse"
></status-indicator>
</script>

How to stop input event if my validation failed?

I have simple input and I only need integers, but if I use e.preventDefault() and return to stop input event, input event will be still work.
input
v-on:input="changeFraction"
name="denominator"
type="text"
v-bind:value="fraction.denominator"
data() {
return {
fraction: {
numerator: '',
denominator: '',
},
};
},
methods: {
changeFraction(e) {
const el = e.target;
if (!/[0-9]/g.test(el.value)) {
e.preventDefault();
return null;
}
this.fraction[el.name] = el.value;
},
},
Just use computed for validation and null for #input event when input is not valid.
For instance:
<input type="text" v-model="fraction.numerator" #input="numeratorValid ? changeFraction : null"/>
computed: {
numeratorValid () {
return Number.isInteger(this.fraction.numerator)
}
}
<template>
<div class = "fraction">
<input
#change = "changeFraction"
name = "numerator"
type = "number"
v-model.number = "fraction.numerator"
/>
<input
#change = "changeFraction"
name = "denominator"
type = "number"
v-model.number = "fraction.denominator"
/>
</div>
</template>
<script>
export default {
name: 'FractionItem',
data() {
return {
fraction: {
numerator: '',
denominator: '',
},
};
},
methods: {
changeFraction(e) {
const el = e.target;
//need add plus before value, because writting letter in input change data value to string type
if (!/[0-9]/g.test(+this.fraction[el.name])) {
e.preventDefault();
}
this.$parent.changeFractionInput({
id: this.id,
[el.name]: +this.fraction[el.name],
key: el.name,
});
},
},
};
</script>
<style lang = "scss" scoped></style>

Vue.js validator isn't showing errors

Here's the prop's validator:
props: {
task: {
id: {
type: Number,
validator: function(value) {
if (value < 5) {
console.log("error");
return false;
}
return true;
}
}
here's the data I'm sending:
export default {
name: "tasklist",
data() {
return {
tasks: [
{
id: 1}
According to the validator I made I shouldn't have it to pass without a warning.
And I don't get any warning does anyone knows what I can do to get an error there.
You can't put a validator or specify the type of a specific property of a component's prop like you are trying to do.
You can specify the type of the task prop as Object, and then add a validator function to validate the type and value of the task object's id property.
Here's an example:
Vue.component('task', {
template: `<div>{{ task.name }}</div>`,
props: {
task: {
type: Object,
validator(task) {
if (typeof task.id !== 'number') {
console.error("error: task id should be a number");
return false;
}
if (task.id < 5) {
console.error("error: task id should not be less than 5");
return false;
}
}
}
}
})
new Vue({ el: '#app' })
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.3/vue.js"></script>
<div id="app">
<task :task="{ name: 'foo', id: 1 }"></task>
<task :task="{ name: 'bar', id: '9' }"></task>
<task :task="{ name: 'baz', id: 6 }"></task>
</div>

input field prefilled with vuejs and a reactive character count

As a vuejs component, I want to be able to display a character counter next to my input field.
The field is initially set up using a prop (this.initialValue).
When the method this.updateCounter is called the input textfield is blocked : typing into the field won't update its value. If I don't set the maxlength prop, the field is working fine : I can update the textfield.
Usage in a template :
<textfield maxlength="50" name="title" initialValue="Test"></textfield>
Here is the component code :
<template>
<div class="input">
<div class="input__field">
<span class="input__limit f--small">{{ counter }}</span>
<input type="text" :name="name" :maxlength="computedMaxlength" v-model="currentValue" />
</div>
</div>
</template>
<script>
export default {
name: 'Textfield',
props: {
name: {
default: ''
},
maxlength: {
default: 0
},
initialValue: {
default: ''
}
},
computed: {
hasMaxlength: function () {
return this.maxlength > 0;
},
computedMaxlength: function () {
if(this.hasMaxlength) return this.maxlength;
else return false;
},
currentValue: {
get: function() {
return this.initialValue;
},
set: function(newValue) {
this.updateCounter(newValue);
this.$emit("change", newValue);
}
}
},
data: function () {
return {
counter: 0
}
},
methods: {
updateCounter: function (newValue) {
if(this.maxlength > 0) this.counter = this.maxlength - newValue.length;
}
},
mounted: function() {
this.updateCounter(this.initialValue);
}
}
</script>
Edit
I have fixed my issue by not using v-model but instead using a value and an input event.
data: function () {
return {
value: this.initialValue,
counter: 0
}
},
methods: {
updateCounter: function (newValue) {
if(this.maxlength > 0) this.counter = this.maxlength - newValue.toString().length;
},
onInput: function(event) {
const newValue = event.target.value;
this.value = newValue;
this.updateCounter(newValue);
this.$emit("change", newValue);
}
},

Vue filters migration from vue 1 to vue 2

I have problem with migratiing filters from vue 1 to vue 2, I created exactly what I need here (highlighting text which match the input text):
Vue.component('demo-grid', {
template: '#grid-template',
props: {
filterKey: String
},
data: function () {
return {
searchParams: [
{ key: '' }
],
suggestions: [
{ message: 'Foo' },
{ message: 'Bar' },
{ message: 'Foobar' },
{ message: 'pikachu' },
{ message: 'raichu' }
]
}
},
filters: {
highlight: function(words, query){
var iQuery = new RegExp(query, "ig");
return words.replace(iQuery, function(matchedTxt,a,b){
return ('<span class=\'highlight\'>' + matchedTxt + '</span>');
});
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#demo'
})
https://jsfiddle.net/t5ac1quc/23/ VUE-1 resource
https://jsfiddle.net/t5ac1quc/25/ VUE-2 resource
I would be very grateful, for all the answers
Updated fiddle.
<template id="grid-template">
<ul>
<li v-for="suggest in suggestions" v-html="highlight(suggest.message, filterKey)"></li>
</ul>
</template>
<div id="demo">
<form>
Search <input v-model="searchParams.key">
</form>
<demo-grid :filter-key="searchParams.key"></demo-grid>
</div>
Vue.component('demo-grid', {
template: '#grid-template',
props: {
filterKey: String
},
data: function () {
return {
suggestions: [
{ message: 'Foo' },
{ message: 'Bar' },
{ message: 'Foobar' },
{ message: 'pikachu' },
{ message: 'raichu' }
]
}
},
methods: {
highlight: function(words, query) {
var iQuery = new RegExp(query, "ig");
return words.replace(iQuery, function(matchedTxt,a,b){
return ('<span class=\'highlight\'>' + matchedTxt + '</span>');
});
}
}
})
new Vue({
el: '#demo',
data: {
searchParams: {
key: '',
},
},
});
Summary:
When using <script> tags to store templates, set type="template" (or similar) to prevent the browser from executing the template as JavaScript. Or better yet use <template> instead.
{{{ html }}} syntax is no longer supported. In Vue 2 you must use the v-html directive instead.
Since v-html is a directive (and doesn't use {{ }} interpolation), it doesn't use the filter syntax. Use a method instead.
You had some issues with the scope of the data. The root component needs to define data for searchParams which is used in its template. Also searchParams was an array but you weren't using it as an array (searchParams.key); this will not work with Vue 2 (all reactive data properties must be properly declared upfront).