I have been working with simple dropdown, and I keep having this error returned https://prnt.sc/1xdusd2 (I solved the prop problem)
then I read a bit more on that specific problem and turns out this happens when vue instance cannot find your property.
But countries is there and it is within the instance. I can't understand where I went wrong.
So, the idea is to make the dropdown reactive to the countries data I am getting from api.
data exists only on the parent component and I am sending it as a prop to the child component where I am iterating and displaying within the ooption.
can someone help me what is wrong here specifically
drop down component (child component)
<template>
<div>
<select v-for="(country, ) in countries" :key="country">
<option >{{country.name}} </option>
</select>
</div>
</template>
<script>
export default {
name: "DropDown",
props:['countries'],
data() {
return {
selectedOption: null,
};
},
};
</script>
parent component ************
<template>
<div class="step1 flex flex-col">
<h1 class="self-start mb-5">Шаг 1.</h1>
<div class="flex justify-center ">
<h3 class="text-white font-medium text-xl">Выберите страну</h3>
<div class="mx-5" >
<DropDown :countries="countries" />
{{}}
</div>
</div>
</div>
</template>
<script>
//import Button from "./UI/Button.vue";
import DropDown from "./UI/DropDown.vue";
export default {
name: "Step1",
components: {
DropDown: DropDown,
//Button: Button,
},
data() {
return{
// countries: [
// {
// id: "RU",
// name: "Россия"
// },
// {
// id: "DE",
// name: "Германия"
// },
// {
// id: "EE",
// name: "Эстония"
// }
// ],
}
},
methods:{
async fetchCountry (){
const res = await fetch('api/countries')
let countries = await res.json();
return countries
}
},
created() {
}
};
Vue tries to load your country data before the api fetch has finished, to bypass this add an v-if="countries" in your <select v-for="(country, ) in countries" :key="country">, then vue will only try to load it if countries is not null
You need to have countries in your data in order for it to work in the template, try this in your parent:
import DropDown from "./UI/DropDown.vue";
export default {
name: "Step1",
components: {
DropDown,
},
data() {
return {
countries: [],
}
},
methods: {
async fetchCountry() {
const res = await fetch('api/countries')
this.countries = await res.json();
}
},
};
And this in your child
<template>
<select v-model="selectedOption">
<option
v-for="country in countries"
:key="country.name"
:value="country.name"
>
{{ country.name }}
</option>
</select>
</template>
<script>
export default {
name: "DropDown",
props: {
countries: {
type: Array,
default: () => [],
},
},
data() {
return {
selectedOption: null,
};
},
};
</script>
I'm trying to use a random string (UUID v4) with vue-uuid for current items and items added to the list in the future (this is a to-do list type app) but I'm not sure what to correct syntax is.
I installed it and added it to my project in main.js:
import UUID from 'vue-uuid';
Vue.use(UUID);
However, I don't know how to use it in my Vue component. This is what I tried:
Template:
<transition-group
name="list"
enter-active-class="animated bounceInUp"
leave-active-class="animated bounceOutDown"
>
<li v-for="item in skills" :key="uuid">{{ item.skill }}</li>
</transition-group>
Script:
import { uuid } from 'vue-uuid';
export default {
name: 'Skills',
data() {
return {
uuid: uuid.v4(),
skill: '',
skills: [{ skill: 'Vue.js' }, { skill: 'React' }]
};
},
};
For :key="uuid", I get an error saying Expected 'v-bind:key' directive to use the variables which are defined by the 'v-for' directive (vue/valid-v-for). I also tried changing it to :key="item.uuid" which makes that error go away, but then the list doesn't appear.
project repo (based on this Udemy Vue crash course)
Try this:
<template>
<div id="app">
<p :key="item.uuid" v-for="item in skills">{{ item.skill }}</p>
</div>
</template>
<script>
import { uuid } from "vue-uuid";
export default {
name: "App",
data() {
return {
skills: [
{ uuid: uuid.v4(), skill: "Vue.js" },
{ uuid: uuid.v4(), skill: "React" }
]
};
}
};
</script>
This is a working demo: https://codesandbox.io/s/nifty-sutherland-b0k9q
UPDATED
to be dynamic
There are two moments that you could add the uuid to each element in the skills array:
1 When adding a new skill:
addSkill() {
this.$validator.validateAll().then(result => {
if (result) {
this.skills.push({ uuid: uuid.v4(), skill: this.skill });
this.skill = "";
}
});
}
2 When rendering them, in this case, you might use a computed property like so:
import { uuid } from 'vue-uuid';
export default {
name: 'Skills',
data () {
return {
skill: '',
skills: [{ skill: 'Vue.js' }, { skill: 'React' }]
};
},
computed: {
computedSkills () {
return this.skills.map(skill => {...skill, uuid: uuid.v4() })
}
}
};
And then using the computedSkills computed property for rendering rather than the skills property. Something like:
<li v-for="item in computedSkills" :key="item.uuid">{{ item.skill }}</li>
I want to pass the default value of vue multiselect component use props, but I can't do this.
I use two selectors. When one option in select-1 selects I want the default value in select-2 is option select
No error just doesn't work properly. The value selected from the first selection does not fall into the default value of the second selection
multiselect component
<template>
<div>
<multiselect v-model="internalValue" id="currency_id" #input="onchange" placeholder="Select Your Currency" label="title" track-by="title" :options="options" :option-height="10" :show-labels="false">
<template slot="singleLabel" slot-scope="props"><img class="option__image" :src="props.option.img"><span class="option__desc"><span class="option__title">{{ props.option.title }}</span></span>
</template>
<template slot="option" slot-scope="props"><img class="option__image" :src="props.option.img">
<div class="option__desc"><span class="option__title" :id="props.option.id">{{
props.option.title }}</span><span class="option__small">{{ props.option.desc }}</span></div>
</template>
</multiselect>
</div>
</template>
import Vue from 'vue';
import Multiselect from 'vue-multiselect'
Vue.component('multiselect', Multiselect);
export default {
props: ['options', 'value'],
components: {
Multiselect
},
data() {
return {
internalValue: this.value,
}
},
methods: {
onchange(options) {
this.$emit('selectvalue', options.id);
}
},
watch: {
internalValue(v) {
this.$emit('input', v);
}
}
}
HTML
**select 1**
<multiselect #selectvalue="apiCalc":options="[
{
id: '1', title: 'Tether', img: 'https://coinnik.com/uploads/crypto-logos/006fe133d48ea7cd45cf8ccb8cb7ec42.png'
}
,
{
id: '2', title: 'ether', img: 'https://coinnik.com/uploads/crypto-logos/006fe133d48ea7cd45cf8ccb8cb7ec42.png'
}
,
{
id: '3', title: 'bitcoin', img: 'https://coinnik.com/uploads/crypto-logos/006fe133d48ea7cd45cf8ccb8cb7ec42.png'
}
]"
> </multiselect>
select2
<multiselect id="receive-currency" :options="receive_currency" v- model="selectedValue"></multiselect>
app.js
new Vue({
el: "#calculate",
data: {
receive_currency: [],
selectedValue: null,
},
methods: {
apiCalc(options) {
let self = this;
this.sendCurrencyId = options;
var receiveCurrency = [];
for (let item in responseData.data.direction.data) {
receiveCurrency.push({
title: responseData.data.direction.data[item].receiveCurrency.data.title,
img: '',
});
}
self.receive_currency = receiveCurrency;
self.selectedValue = receiveCurrency[0]
})
}
}
},
components: {
'multiselect': Multiselect
},
created() {
this.apiCalc();
},
});
In template:
<multiselect v-model="multiSelect1" :options="options" #input="onChange"></multiselect>
<multiselect v-model="multiSelect2" :options="options" :placeholder="placeholder"></multiselect>
In script:
data: () => ({
multiSelect1: "",
multiSelect2: "",
options: ["list", "of", "options"],
placeholder: "Select option"
}),
methods: {
onChange() {
this.multiSelect2 = this.multiSelect1
}
}
Please check this codesandbox: https://codesandbox.io/s/vue-template-t226h
I need to implement a button that would drop the filters in my application. The application is written on Vue. The filters themselves were implemented, but I do not know how to implement their reset.
<template>
<div id="app">
<input type="text" v-model="search">
<select name="sort" v-model="sort">
<option v-for="option in options" :value="option.value" :label="option.label"></option>
</select>
<table>...</table>
</div>
</template>
<script>
import goodsList from './api/data';
export default {
name: 'app',
data() {
return {
search: '',
sort: '',
options: [
{ label: 'Default', value: 'none' },
{ label: 'Brand', value: 'brand' },
{label: 'Price', value: 'price'}
],
goods: goodsList,
}
},
computed: {
filteredList() {
let filteredGoods = this.goods.filter( item => {
return item.name.toLowerCase().includes(this.search.toLowerCase());
});
switch (this.sort) {
case 'brand':
filteredGoods.sort((a, b) => a.brand.localeCompare(b.brand));
break;
case 'price':
filteredGoods.sort((a, b) => parseInt(a.price - b.price));
break;
}
return filteredGoods;
}
},
}
</script>
You will need a reset function which will assign the default selected value eg: 'none' to the v-model 'sort'. Since it is a two way binding, changing the value of 'sort' variable will eventually reset the selected option.
Function to be added:
resetOptions: function () {
this.sort='none';
}
Link below
https://jsfiddle.net/RohanPatil/68wced20/9/
Currently I use Vuetify for base components and would like to create reusable extensions. For example a list containing checkboxes, a datatable column with some functionality etc.
For this question I will take the list containing checkboxes example. I created the following component called CheckboxGroup.vue
<template>
<v-container>
<v-checkbox
v-for="(item, index) in items"
:key="index"
v-model="item.state"
:label="item.title"
></v-checkbox>
</v-container>
</template>
<script>
export default {
props: {
items: Array,
required: true
}
};
</script>
This component takes an array of objects as a property and creates a checkbox for each entry.
Important parts are v-model="item.state" and :label="item.title". Most of the time the state attribute will have a different name, same for the title attribute.
For testing purposes I created a view file called Home.vue holding an array of documents.
<template>
<v-container>
<CheckboxGroup :items="documents"/>
<v-btn #click="saveSettings">Save</v-btn>
</v-container>
</template>
<script>
import CheckboxGroup from "../components/CheckboxGroup";
export default {
components: {
CheckboxGroup
},
data: function() {
return {
documents: [
{
id: 1,
name: "Doc 1",
deleted: false
},
{
id: 2,
name: "Doc 2",
deleted: false
},
{
id: 3,
name: "Doc 3",
deleted: true
}
]
};
},
methods: {
saveSettings: function() {
console.log(this.documents);
}
}
};
</script>
This time title is called name and state is called deleted. Obviously CheckboxGroup is not able to manage the documents because the attribute names are wrong.
How would you solve this problem? Would you create a computed property and rename these attributes? Would be a bad idea I think...
And by the way, is using v-model a good idea? A different solution would be to listen to the changed event of a checkbox and emit an event with the item index. Then you would have to listen for the change in the parent component.
I don't think there is a way to create something like
<CheckboxGroup :items="documents" titleAttribute="name" stateAttribute="deleted"/>
because it would be bad design anyway. I hope that this is a very trivial problem and every Vue developer has been confronted with it, since the primary goal should always be to develop abstract components that can be reused multiple times.
Please keep in mind that this checkbox problem is just an example. A solution for this problem would also solve same or similar problems :)
If I understood what you wanted, it`s not so trivial. Using props is a good idea. You dont need to manage the documents attribute names, just set the attribute name to your component.
Note
Renaming the attributes or using proxies is more resource-intensive like this solution, because you need to run loop to rename the attribute names or apply aliases to data array objects.
Example
CheckboxGroup.vue
<template>
<v-container fluid>
<v-checkbox
v-for="(item, index) in items"
:key="index"
v-model="item[itemModel]"
:label="item[itemValue]"
></v-checkbox>
<hr>
{{items}}
</v-container>
</template>
<script>
export default {
name: "CheckboxGroup",
props: {
items: {
type: Array,
required:true
},
itemValue:{
type:String,
default: 'title',
// validate props if you need
//validator: function (value) {
// return ['title', 'name'].indexOf(value) !== -1
// }
// or make required
},
itemModel:{
type:String,
default: 'state',
// validate props if you need
//validator: function (value) {
// validate props if you need
// return ['state', 'deleted'].indexOf(value) !== -1
// }
// or make required
}
}
};
</script>
Home.vue
<template>
<div id="app">
<checkbox-group :items="documents"
item-value="name"
item-model="deleted"
>
</checkbox-group>
</div>
</template>
<script>
import CheckboxGroup from "./CheckboxGroup.vue";
export default {
name: "App",
components: {
// HelloWorld,
CheckboxGroup
},
data: function() {
return {
documents: [
{
id: 1,
name: "Doc 1",
deleted: false
},
{
id: 2,
name: "Doc 2",
deleted: false
},
{
id: 3,
name: "Doc 3",
deleted: true
}
]
}
}
};
</script>
Based on your example I`v tried to show how to create component to managing object attributes in child component. If you need more information, please let me know.
Some good answers here that definitely solve your issue - you are essentially wanting to pass data down to a child (which isn't bad design - you were on the right track!)..
I am kind of shocked that slots or scoped-slots haven't been mentioned yet... so I figured I would chime in..
Scoped-slots allow you to take advantage of data you are passing to a child - but within the parent. The child essentially "reflects" data back to the parent, which allows you to style the child component/slot however you wish, from the parent.
This is different than just passing data via a prop attribute, because you would have to rely on styling within the child - you couldn't change the styles on a 'per-use' basis. The styles you set in the child would be "hard coded"..
In this example I am riding on top of the already provided label slot that Vuetify provides - just passing my own custom scoped-slot to it.. How to find documentation on v-checkbox slots
I made some minor changes to help spice some things up, and to show how you have greater control over styles this way (and you can use any object prop for the label you want .name, .whatever, .label, etc..)
Lastly, it is important to note that Vuetify already provides a "grouped checkbox" component - v-radio-group - I know it's called "radio-group" but it supports checkboxes...
Edit: fixed the state "issue"...
Scoped Slots With Render Function - Original Answer Moved To Bottom
Thanks to #Estradiaz for collaborating on this with me!
Vue.component('checkboxgroup', {
props: {
items: { type: Array, required: true }
},
render (h) {
return h('v-container', this.items.map((item) => {
return this.$scopedSlots.checkbox({ item });
}));
},
})
new Vue({
el: "#app",
data: {
documents: [{
id: 1,
name: "Doc 1 - delete",
deleted: false,
icon: "anchor",
},
{
id: 12,
title: "Doc 1 - state",
state: false,
icon: "anchor",
},
{
id: 2,
name: "Doc 2 - delete",
deleted: false,
icon: "mouse"
},
{
id: 3,
name: "Doc 3 - delete",
deleted: true,
icon: "watch"
}
]
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<script src="https://unpkg.com/vuetify/dist/vuetify.min.js"></script>
<link href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons' rel="stylesheet" type="text/css">
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet" type="text/css"></link>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://use.fontawesome.com/releases/v5.0.8/css/all.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/MaterialDesign-Webfont/2.1.99/css/materialdesignicons.min.css" rel="stylesheet" />
<div id="app">
<v-app>
<v-container>
<CheckboxGroup :items="documents">
<template #checkbox={item}>
<v-checkbox
v-model="item[(item.name && 'deleted') || (item.title && 'state') ]" color="red">
<template #label>
<v-icon>mdi-{{item.icon}}</v-icon>
{{ item.name || item.title }}
{{ item }}
</template>
</v-checkbox>
</template>
</CheckboxGroup>
</v-container>
</v-app>
</div>
My attempt of a json to component parser
usefull names are welcome
so basically you can target element tagnames as slot #[slotname] or put slot names and target entries to overwrite the default component.
omiting tag property in the component will append children to the parent vnode
Consider:
[
{
ElementTag: 'Liste',
id: 1,
tag: 'p',
items: [
{
ElementTag: 'input',
id: 11,
type: 'checkbox',
title: "Sub Doc 1 - state",
state: true,
slotName: "slotvariant"
},
{
ElementTag: 'input',
id: 12,
type: 'date',
title: "Sub Doc 2 - Date",
date: "",
}
]
},
{
ElementTag: 'input',
id: 2,
type: 'checkbox',
title: "Doc 2 - deleted",
deleted: true,
slotName: 'deleted'
}
]
Example :
Vue.component('Liste', {
props:["tag", "items"],
render(h){
console.log(this.items)
let tag = this.tag || (this.$parent.$vnode && this.$parent.$vnode.tag)
if(tag === undefined) throw Error(`tag property ${tag} is invalid. Scope within valid vnode tag or pass valid component/ html tag as property`)
return h(tag, this.items.map(item => {
const {ElementTag, slotName, ...attrs} = item;
return (
this.$scopedSlots[slotName || ElementTag]
&& this.$scopedSlots[slotName || ElementTag]({item})
)
|| h(ElementTag, {
attrs: attrs,
scopedSlots: this.$scopedSlots
})
}))
}
})
new Vue({
data(){
return {
items: [
{
ElementTag: 'Liste',
id: 1,
tag: 'p',
items: [
{
ElementTag: 'input',
id: 11,
type: 'checkbox',
text: "Sub Doc 1 - state",
state: true,
slotName: "slotvariant"
},
{
ElementTag: 'input',
id: 12,
type: 'date',
title: "Sub Doc 2 - Date",
date: "",
}
]
},
{
ElementTag: 'input',
id: 2,
type: 'checkbox',
title: "Doc 2 - deleted",
deleted: true,
slotName: 'deleted'
}
]}
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<Liste tag="p" :items="items">
<template #input="{item}">
<label :for="item.id">
{{ item.title }}
</label>
<input :type="item.type" :id="item.id" v-model="item.date"/>
</template>
<template #slotvariant="{item}">
slotvariant - {{item.text}}<br>
</template>
<template #deleted="{item}">
<label :for="item.id">
{{ item.title }}
</label>
<input :type="item.type" :id="item.id" v-model="item.deleted"/>
</template>
</Liste>
</div>
Typescript:
import {Vue, Component, Prop} from 'vue-property-decorator'
export type AbstractElement = {
[key: string]: any // passed as $attrs | useable for assigned $props
ElementTag: string
slotName?: string
}
#Component<List>({
render(h){
let tag = this.tag
|| (this.$parent.$vnode && this.$parent.$vnode.tag)
|| (this.$parent.$el && this.$parent.$el.tagName)
if(tag === undefined) throw Error(`tag prperty: ${tag} is invalid. Scope within valid vnode tag or pass valid component/ html tag as property`)
return h(tag, this.items.map(item => {
const {ElementTag, slotName, ...attrs} = item;
console.log("slotName", slotName)
return (this.$scopedSlots[slotName || ElementTag]
&& this.$scopedSlots[slotName || ElementTag]({item}))
|| h(ElementTag, {
attrs: attrs,
slot: slotName || ElementTag,
scopedSlots: this.$scopedSlots
})
}))
}
})
export default class List extends Vue{
#Prop(String) readonly tag?: string
#Prop(Array) readonly items!: Array<AbstractElement>
}
will raise this here
You can use a Proxy to map the document property names during access.
Note
In my original answer, I used Proxy handlers for get and set, which is sufficient for plain javascript objects, but fails when used with Vue data properties because of the observer wrappers that Vue applies.
By also trapping has in the Proxy, this can be overcome. I left the original answer below for anyone interested in this problem.
Here is a demo of how to use Proxy to 'alias' Vue reactive properties to different names
without affecting the original data structure
without having to copy the data
console.clear()
Vue.config.productionTip = false
Vue.config.devtools = false
Vue.component('checkboxgroup', {
template: '#checkboxGroup',
props: { items: Array, required: true },
});
const aliasProps = (obj, aliasMap) => {
const handler = {
has(target, key) {
if (key in aliasMap) {
return true; // prevent Vue adding aliased props
}
return key in target;
},
get(target, prop, receiver) {
const propToGet = aliasMap[prop] || prop;
return Reflect.get(target, propToGet);
},
set(target, prop, value, receiver) {
const propToSet = aliasMap[prop] || prop;
return Reflect.set(target, propToSet, value)
}
};
return new Proxy(obj, handler);
}
new Vue({
el: '#app',
data: {
documents: [
{ id: 1, name: "Doc 1", deleted: false },
{ id: 2, name: "Doc 2", deleted: false },
{ id: 3, name: "Doc 3", deleted: true },
]
},
computed: {
checkBoxItems() {
const aliases = {
title: 'name',
state: 'deleted'
}
return this.documents.map(doc => aliasProps(doc, aliases));
}
},
methods: {
saveSettings: function() {
console.log(this.documents);
}
},
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify/dist/vuetify.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons" rel="stylesheet"/>
<link href="https://unpkg.com/vuetify/dist/vuetify.min.css" rel="stylesheet"/>
<div id="app">
<v-app id="theapp">
<v-container>
<checkboxgroup :items="checkBoxItems"></checkboxgroup>
<v-btn color="info"
#click="saveSettings">Save</v-btn>
</v-container>
</v-app>
</div>
<template id="checkboxGroup">
<v-container style="display: flex">
<v-checkbox
v-for="(item, index) in items"
:key="index"
v-model="item.state"
:label="item.title"
></v-checkbox>
</v-container>
</template>
Original answer
You can use a Proxy to map the document property names during access.
<template>
...
<CheckboxGroup :items="checkBoxItems"/>
...
</template>
<script>
export default {
...
computed: {
checkBoxItems() {
const handler = {
get: function(target, prop) {
return prop === 'title' ? target.name :
prop === 'state' ? target.deleted :
target[prop];
},
set(obj, prop, value) {
const propToSet =
prop === 'title' ? 'name' :
prop === 'state' ? 'deleted' :
prop;
obj[propToSet] = value;
}
};
return documents.map(doc => new Proxy(doc, handler))
},
},
...
}
</script>
Demo
const documents = [
{ id: 1, name: "Doc 1", deleted: false },
{ id: 2, name: "Doc 2", deleted: false },
{ id: 3, name: "Doc 3", deleted: true },
]
const handler = {
get: function(target, prop) {
return prop === 'title' ? target.name :
prop === 'state' ? target.deleted :
target[prop];
},
set(obj, prop, value) {
const propToSet =
prop === 'title' ? 'name' :
prop === 'state' ? 'deleted' :
prop;
obj[propToSet] = value;
}
};
const checkItems = documents.map(doc => new Proxy(doc, handler))
console.log('Accessing new property names via checkItems')
checkItems.forEach(ci => console.log(ci.id, ci.title, ci.state))
console.log('After update, values of documents')
checkItems.forEach(ci => ci.state = !ci.state )
documents.forEach(doc => console.log(doc.id, doc.name, doc.deleted))