Vue, v-bind: Passing data as a property - javascript

I am trying to pass data as a property in a component. v-bind is required, but i can't seem to make it work. Error is one line 3 in the HTML, the v-bind directive doesn't work and returns an error: "Custom elements in iteration require a "v-bind:key" directive"
<script>
export default {
name: "ChatLog",
props: {
},
components: {ChatMessage},
data() {
return {
messages: [
{
message: "Hey",
user: "James"
},
{
message: "Hola",
user: "Jimmy"
},
]
}
}
}
</script>
<template>
<div class="log">
<ChatMessage v-for="(message) in messages" v-bind:message="message"> </ChatMessage>
</div>
</template>

You may try this (The key is required in a loop):
<template>
<div class="log">
<ChatMessage
v-for="(message, key) in messages"
v-bind:message="message"
v-bind:key="key"
></ChatMessage>
</div>
</template>
Btw, you may use shorthand alternative for v-bind like this:
<ChatMessage v-for="(message, key) in messages" :message="message" :key="key" />
Also, if message is unique, then you may use the message as the value for key like this:
<ChatMessage v-for="message in messages" :message="message" :key="message" />
Note: Read more about the key here.

You need to specify a key for the given element in the for loop.
<ChatMessage v-for="(message) in messages" v-bind:message="message" :key="message"> </ChatMessage>

Related

vue.js and slot in attribute

I'm trying to build a vue.js template that implements following:
<MyComponent></MyComponent> generates <div class="a"></div>
<MyComponent>b</MyComponent> generates <div class="a" data-text="b"></div>.
Is such a thing possible?
EDIT
Here is the best I can reach:
props: {
text: {
type: [Boolean, String],
default: false
}
},
and template
<template>
<div :class="classes()" :data-text="text">
<slot v-bind:text="text"></slot>
</div>
</template>
but the binding does not work, text always contains false.
You can use the mounted() method to get text using $slot.default property of the component to get the enclosing text. Create a text field in data and update inside mounted() method like this :
Vue.component('mycomponent', {
data: () => ({
text: ""
}),
template: '<div class="a" :data-text=text></div>',
mounted(){
let slot = this.$slots.default[0];
this.text=slot.text;
}
});
Note: It will only work for text, not for Html tags or components.
You're mixing slots and properties here. You'll have to pass whatever you want to end up as your data-text attribute as a prop to your component.
<MyComponent text="'b'"></MyComponent>
And in your template you can remove the slot
<template>
<div :class="classes()" :data-text="text"></div>
</template>
Another thing: it looks like your binding your classes via a method. This could be done via computed properties, take a look if you're not familiar.
You can try this.
<template>
<div :class="classes()">
<slot name="body" v-bind:text="text" v-if="hasDefaultSlot">
</slot>
</div>
</template>
computed: {
hasDefaultSlot() {
console.log(this)
return this.$scopedSlots.hasOwnProperty("body");
},
}
Calling
<MyComponent>
<template v-slot:body="props">
b
</template>
</MyComponent>

Vuejs removing element from array can not remove it completely

When I try to run the following code, and I remove one item from the array, the item is not removed completely(there are other checkboxes part of each row which are not removed) I have added a :key="index" and doesn't help it.
Nevertheless when I have changed the :key="index" to :key="item" it works, but then the problem is I get the warning [Vue warn]: Avoid using non-primitive value as key, use string/number value instead
<template>
<div>
<filters-list-item v-for="(item, index) in items" :key="index" v-on:deleteItem="deleteItem(index)" :items="items" :item="item" :filterButtonSetting="filterButtonSetting" class="pb-3 pt-3 space-line"></m-filters-list-item>
<div class="pt-3">
<button class="btn" #click="add()">
add
</button>
</div>
</div>
</template>
<script>
import FiltersListItem from './FiltersListItem';
export default {
name: 'FiltersList',
components: {
FiltersListItem
},
props: {
items: Array,
filterButtonSetting: Object
},
methods: {
add() {
this.items.push({});
},
deleteItem(index) {
this.$delete(this.items, index);
},
}
};
Using the index is fine as long as you are not interacting with any of the elements in the loop.
But, if you are, then it is recommended not to do this.
You should use another unique item's identifier, maybe providing one from the backend.

How to check if list is empty inside a vue.js template?

Pretty simple question.
In my vue.js template I have:
<template v-for="node in nodes">
[[ node ]]
</template>
I want to return nodes in the list or print "N/A" if the list is empty.
I know how to do this with a js function, but not sure how to accomplish the same thing from within the template.
You should check for both undefined and length using Vue's v-if. If you ever use asyncComputed for example, it will be undefined until the async method resolves. So you are covered for nodes=null, nodes=undefined, and nodes=[].
<template v-if="!nodes">
Loading
</template>
<template v-else-if="!nodes.length">
Empty
</template>
<template v-else v-for="node in nodes">
{{ node }}
</template>
Or combine them
<template v-if="!nodes || !nodes.length">
Nothing To Show
</template>
<template v-else v-for="node in nodes">
{{ node }}
</template>
I like using !nodes.length instead of length !== 0 just in case something unexpected besides an array ends up in the data, in which case it will most likely use the empty template and not throw an exception. Less typing too!
You can check like:
<template v-if="nodes.length"> // When there are some records available in nodes
// Iterate it and display the data
</template >
<template v-else>
// No record found
</template>
Reference
Use render function like
export default {
data() {
return {
nodes: ['item 1', 'item 2']
}
},
render(h) {
if (this.nodes.length == 0) {
return h('p', 'No item')
} else {
return h(
'p',
{},
this.nodes.map((item) => h('p', item))
)
}
}
}
to check if a list is empty, you just need to use v-if, in that way your template will be like below :
<template v-if="nodes.length === 0">
N/A
</template>
<template v-for="node in nodes" v-else>
[[ node ]]
</template>
for more information about Conditional Rendering
VueJs please check the doc.

How pass a v-model on the parent into a template

I'm trying to compose the UI for a search page, but wanna use components to reuse code. However, I need a way to pass the model of the page to the search component, and don't see how:
In index.html:
<template id="search">
<q-search inverted placeholder="Look" float-label="Search" v-model="search" /> <-- BIND HERE
</template>
<template id="ListCustomersPage">
<q-layout>
<q-layout-header>
<search v-model="search"></search> <-- HOW PASS INTO THIS
</q-layout-header>
</q-layout>
</template>
And the code:
const search = {
template: '#search',
props: ['search']
};
const ListCustomersPage = {
key: 'ListCustomersPage',
template: '#ListCustomersPage',
components: { search },
data() {
return {
title: 'Select Customer',
search:'' <-- FROM THIS TO 'BIND HERE'
}
}
};
I'm not sure if I'm 100% following what you're asking, but it seems like you just want to pass a property to a child component?
<search :search="search"></search> <-- HOW PASS THIS
Passing a prop to a child is done with v-bind or the colon short hand for it.
<child-component :property="parent_data"></child-component>
<child-component v-bind:property="parent_data"></child-component>
See the documentation here.

Vuejs computed properties that depend on other, asynchronous, computed properties

In my Vuejs app I need to pass two computed properties to a component called avatar: an image surce and a string.
The problem is that not all items have a picture, and when they don't, vuejs throws an error because it cannot read property apples of undefined (because album_id is undefined).
The error is being thrown from that very long-winded src property in the avatar component, below:
<template>
<div class="apples">
<div id="mast" class="f3 b bb b--black-10">
<h2 class="ttc">Apple's List</h2>
</div>
<div id="content">
<li v-for="apple in apples" class="list-item">
<avatar :src="apple[ 'album_id '][ 'apples' ][ '256' ]" :size="50" :appletype="apple.type"></avatar>
</li>
</div>
</div>
</template>
<script>
import Avatar from './Avatar.vue';
import Apples from '#/api/apples';
export default {
name: 'apples',
asyncComputed: {
apples: {
async get() {
const apples = await Apples.getAll();
return apples.data;
},
default: []
}
},
components: {
Avatar
}
};
</script>
I need to somehow treat the data that I receive from the api before I use it in the html template, but I am unsure as to how to proceed. Creating a separate pictures array within the get() function just seems wrong.
Using v-if and v-else to check if there is a src property to pass down also seems very clumsy.
Do I create another, separate, computed method that iterates through my list of items and gets the picture src if the item has one?
I would suggest that you need to create getters for your apple sauce.. er.. source.. er.. src :)
<template>
<div class="apples">
<div id="mast" class="f3 b bb b--black-10">
<h2 class="ttc">Apple's List</h2>
</div>
<div id="content">
<li v-for="apple in apples" class="list-item">
<avatar :src="getAvatar(apple, 256)" :size="50" :appletype="apple.type"></avatar>
</li>
</div>
</div>
</template>
<script>
import Avatar from './Avatar.vue';
import Apples from '#/api/apples';
export default {
name: 'apples',
methods: {
getAvatar: function(obj, id) {
return obj.album_id.apples[ id ] | ''
}
},
asyncComputed: {
apples: {
async get() {
const apples = await Apples.getAll();
return apples.data;
},
default: []
}
},
components: {
Avatar
}
};
</script>
This allows you to create a graceful fallback with whatever arguments and implementation you choose.

Categories

Resources