Vue.js validator isn't showing errors - vue.js

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>

Related

Vuejs - Resolve deep nested v-model property at runtime

I have a dynamic form where the v-model of the input control is resolved at runtime. It works for simple 0 or 1 level deep objects. But I do not know how to get it working for nested properties that are more than 1 level deep.
My HTML is like:
<div v-for="element in elements" v-bind:key="element.name">
<q-input v-model="inputdata[element.model]"></q-input>
</div>
Javascript
<script>
export default {
data () {
return {
inputdata: {
account: {
name: '',
address: {
street: ''
}
},
},
}
},
}
</script>
Array with data:
elements: [
{
type: 'text',
hint: 'Address',
label: 'Street',
model: 'account.address.street', // does not work. i want to be able to set any level deep property
name: 'street'
}
]
As long as I try to set the property at 0 or 1st level (inputdata or inputdata.account), it works.
How to get a property as deep as inputdata.account.name or inputdata.account.address.street to work?
maybe you can use custom iterative methods instead of v-model
const getValueByModel = (model, data) => {
if(model.includes('.')){
model = model.split('.');
let key = model.shift();
return getValueByModel(model.join('.'), data[key]);
}
else{
return data[model];
}
}
const setValueByModel = (model, oldObject, newValue) => {
if(model.includes('.')){
model = model.split('.');
let key = model.shift();
oldObject[key] = setValueByModel(model.join('.'), oldObject[key], newValue);
}
else{
oldObject[model] = newValue;
}
return oldObject;
}
const getValueByModel = (model, data) => {
if(model.includes('.')){
model = model.split('.');
let key = model.shift();
return getValueByModel(model.join('.'), data[key]);
}
else{
return data[model];
}
}
const setValueByModel = (model, oldObject, newValue) => {
if(model.includes('.')){
model = model.split('.');
let key = model.shift();
oldObject[key] = setValueByModel(model.join('.'), oldObject[key], newValue);
}
else{
oldObject[model] = newValue;
}
return oldObject;
}
new Vue({
el: '#app',
data () {
return {
inputdata: {
account: {
name: '',
address: {
street: ''
}
},
},
elements: [
{
type: 'text',
hint: 'Name',
label: 'Name',
model: 'account.name',
name: 'name'
},
{
type: 'text',
hint: 'Address',
label: 'Street',
model: 'account.address.street',
name: 'street'
},
]
}
},
methods: {
getInputValue(model){
return getValueByModel(model, this.inputdata);
},
updateInputValue(model, event){
let newValue = event.target.value;
this.inputdata = {...setValueByModel(model, this.inputdata, newValue)};
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="app">
<div v-for="element in elements" v-bind:key="element.name">
<input :value="getInputValue(element.model)"
#input="updateInputValue(element.model, $event)"
:placeholder="element.name"/>
</div>
{{ inputdata }}
</main>

How to get value altLanguages and put that dynamically into a JSON, so it can be loaded into the Head

Currently i am trying to get the value of altLanguages and output that dynamically in a JSON, so it can be injected into the head. The altLanguages are the meta attribute values that should be added before rendering the page to avoid the error (altLanguages is undefined). Anyone know how to do that.
<template>
<header class="site-header">
<router-link to="/" class="logo">Example Site</router-link>
<nav>
<ul>
<li v-for="menuLink in menuLinks" :key="menuLink.id">
<prismic-link :field="menuLink.link">{{ $prismic.richTextAsPlain(menuLink.label) }}</prismic-link>
</li>
</ul>
</nav>
<alternate-languages :altLanguages="altLanguages" />
<!-- <alternate-content :altLanguages="altLanguages" /> -->
</header>
</template>
<script>
export default {
props: {
id: { type: String, default: "" },
altLanguages: { type: Array, default: () => ([]) }
},
data() {
return {
menuContent: [],
menuLinks: [],
// altLanguages: []
};
},
methods: {
async getMenu(lang) {
//Query to get menu content
const menuContent = await this.$prismic.client.getSingle("menu", {
lang: lang
});
this.menuContent = menuContent;
this.menuLinks = menuContent.data.menu_links;
}
},
created() {
// this.getLanguages(this.id);
this.getMenu(this.$route.params.lang);
},
watch: {
$route(to, from) {
this.getMenu(to.params.lang);
}
}
// beforeRouteUpdate(to, from, next) {
// console.log("new");
// this.getMenu(to.params.lang);
// next();
// }
};
</script>
//expected output
export default {
data: function () {
return {
title: 'My Title'
}
},
// Usage with context the component
head: {
// To use "this" in the component, it is necessary to return the object through a function
title: function () {
return {
inner: this.title
}
},
meta: [
// altLanguages should be output in here.....
{ name: 'description', content: 'My description', id: 'desc' }
]
}
...
}
}

Vue data, computed, and methods

Wondering if I should directly update a potentially large array in data() using a method, or instead, have the method set another primitive property in data() and use computed to return an altered array using that? Examples of both approaches below:
Approach #1:
data() {
return {
users: [
{
name: 'alice'
selected: true
},
name: 'bob'
selected: false
// ...
methods: {
handleSelection(selectedIndex) {
this.users = this.users.map((item, index) => {
item.selected = selectedIndex === index ? true : false;
return item;
});
}
Approach #2:
data() {
return {
selectedIndex: 0,
users: [
{
name: 'alice'
selected: true
},
name: 'bob'
selected: false
// ...
computed: {
usersSelected() {
return this.users.map((item, index) => {
item.selected = this.selectedIndex === index ? true : false;
return item;
});
}
//...
methods: {
handleSelection(selectedIndex) {
this.selectedIndex = selectedIndex;
}
Is one better than the other or any additional suggestions/improvements? Thanks!
You should use a computed property. That way, you don't need to care about remembering to call various methods, to keep data in sync.
Your computed property can be made a bit more simple, with your requirements.
new Vue({
el: '#app',
data: {
selectedUserIndex: null,
users: [{
name: 'Mark',
id: 1
},
{
name: 'John',
id: 2
},
{
name: 'Evan',
id: 3
}
]
},
computed: {
selectedUser() {
return this.selectedUserIndex >= 0
? this.users[this.selectedUserIndex]
: null
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<p>
Selected user:
<span v-if="selectedUser">{{selectedUser.name}}</span>
<em v-else>No user selected</em>
</p>
<ul>
<li #click="selectedUserIndex = index" v-for="(user, index) in users" :key="user.id">
{{user.name}}
</li>
</ul>
</div>

VueJs: Form handling with Vuex and inputs generated with an API

Here's an example of a component:
<script>
export default {
name: 'my-form',
computed: {
myModules() {
return this.$store.state.myModules;
}
}
</script>
<template>
<form>
<p v-for="module in myModules">
<input type="checkbox" :value="module.id" />
<label>module.name</label>
</p>
<button type="submit">Submit</button>
</form>
</template>
The associated store:
state: {
myModules: []
},
mutations: {
setModules(state, modules) {
state.myModules = modules;
}
},
actions: {
getModules({commit}) {
return axios.get('modules')
.then((response) => {
commit('setModules', response.data.modules);
});
}
}
And finally, an example of return of the API "getModules":
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
My question: what's the best way to change the "isActive" property of each module to "true" when I check the checkbox corresponding to the associated module, directly in the store?
I know that Vuex's documentation recommends to use "Two-way Computed Property" to manage the forms, but here I don't know the number of modules that the API can potentially return, and I don't know their name.
Thank you in advance!
This is a little bit wicked approach, but it works. You can create an accessor object for every item you access in a loop:
const store = new Vuex.Store({
mutations: {
setActive (state, {index, value}) {
state.modules[index].isActive = value
}
},
state: {
modules : [
{
id: 1,
name: 'Module 1',
isActive: false
},
{
id: 2,
name: 'Module 2',
isActive: false
},
{
id: 3,
name: 'Module 3',
isActive: false
}
]
}
});
const app = new Vue({
el: '#target',
store,
methods: {
model (id) {
const store = this.$store;
// here i return an object with value property that is bound to
// specific module and - thanks to Vue - retains reactivity
return Object.defineProperty({}, 'value', {
get () {
return store.state.modules[id].isActive
},
set (value) {
store.commit('setActive', {index: id, value});
}
});
}
}
})
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex/dist/vuex.min.js"></script>
<div id="target">
<div v-for="(item, id) in $store.state.modules">
Module #{{ item.id }} state: {{ item.isActive }}
</div>
<div v-for="(item, id) in $store.state.modules">
<label>
Module #{{ item.id }}
<input type="checkbox" v-model="model(id).value"/>
</label>
</div>
</div>
This is still quite a messy approach, but at least you don't have to commit mutations directly in template. With a little help of Vue.set() you can use this approach even to overcome standard reactivity caveats.
I have an alternative solution for you. You could make a child component for the checkboxes to clean up the code a bit.
UPD: I just realised that everything that I and #etki proposed is an overkill. I left the old version of my code below in case you still want to take a look. Here is a new one:
const modules = [{
id: 1,
name: 'Module 1',
isActive: true,
},
{
id: 2,
name: 'Module 2',
isActive: false,
},
{
id: 3,
name: 'Module 3',
isActive: false,
},
];
const store = new Vuex.Store({
state: {
myModules: [],
},
mutations: {
SET_MODULES(state, modules) {
state.myModules = modules;
},
TOGGLE_MODULE(state, id) {
state.myModules.some((el) => {
if (el.id === id) {
el.isActive = !el.isActive;
return true;
}
})
}
},
actions: {
getModules({
commit
}) {
return new Promise((fulfill) => {
setTimeout(() => {
commit('SET_MODULES', modules);
fulfill(modules);
}, 500)
});
}
}
});
const app = new Vue({
el: "#app",
store,
data: {},
methods: {
toggle(id) {
console.log(id);
this.$store.commit('TOGGLE_MODULE', id);
}
},
computed: {
myModules() {
return this.$store.state.myModules;
},
output() {
return JSON.stringify(this.myModules, null, 2);
},
},
mounted() {
this.$store.dispatch('getModules').then(() => console.log(this.myModules));
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.0.1/vuex.js"></script>
<script src="https://unpkg.com/vue"></script>
<div id="app">
<form>
<div v-for="data in myModules">
<label :for="data.id">{{ data.name }}: {{data.isActive}}</label>
<input type="checkbox" :id="data.id" :name="'checkbox-' + data.id" :checked="data.isActive" #change="toggle(data.id)">
</div>
</form>
<h3>Vuex state:</h3>
<pre v-text="output"></pre>
</div>
As you can see above you could just call a function on input change and pass an id as a parameter to a method that fires vuex action.
The old version of my code.
A new one on jsfiddle

The use of bidirectional binding of components in 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/