I use bootstrap-vue. I took an example from the documentation - https://bootstrap-vue.js.org/docs/components/table/#table-body-transition-support.
Everything works well. But if I replace the data in th cells with my data, the effect stops working. Why it happens? How do i fix this?
<template>
<div class="container">
<div class="row">
<div class="col-sm-12 chartjs">
<b-table
id="table-transition-example"
:items="itemsForTable"
:fields="fieldsForTable"
striped
small
primary-key="a"
:tbody-transition-props="transProps"
></b-table>
</div>
</div>
</div>
<script>
export default {
data: function () {
return {
fieldsForTable: [
{ key: 'a1', sortable: true },
{ key: 'b', sortable: true },
],
itemsForTable: [
{ a1: 2, b: 'Two'},
{ a1: 1, b: 'Three'}
],
transProps: {
name: 'flip-list'
}
};
},
computed: {
},
mounted() {
},
methods: {
}
}
You also need to update primary-key to be a2. Without that it won't know which rows in the updated table are equivalent to the rows in the original table, so it can't perform the transition.
The value of that field is used to generate the Vue key for each row. It isn't exactly the same as the underlying Vue key, the table adds a prefix and suffix, but for most practical purposes you can think of them as being the same thing.
in my case i forgot to put id="table-transition-example" and its work now like a charm.
Related
I'm trying to pass an array of object to a childComponent as prop but when I add an object in it, it doesn't render. (Note: I'm working on vuejs 2.6)
I suppose it has a link with the "monitoring" of the items of the array and not the array itself? Stuff is that if I do not pass the prop and use the default value instead, it's working perfectly. I think I'm missing something here. Could someone help me ?
By curiosity is this kind of behavior still stand with vue3js ?
As you can see below:
App.vue:
<template>
<div id="app">
<Card
v-for="user in users"
:key="user.userId"
:userId="user.userId"
:username="getUsernameFromUserId(user.userId)"
:links="getUserLinksFromUserId(user.userId)"
/>
</div>
</template>
<script>
import Card from "./components/Card.vue";
export default {
name: "App",
components: {
Card,
},
data: function () {
return {
users: [
{ userId: 1, name: "Bob" },
{ userId: 2, name: "Alice" },
{ userId: 3, name: "Eliot" },
],
links: [
{ userId: 1, link: "hello->world" },
{ userId: 1, link: "world->!" },
{ userId: 3, link: "hello->back" },
{ userId: 4, link: "hello->you" },
],
};
},
methods: {
getUsernameFromUserId: function (userId) {
return this.users.filter((obj) => obj.userId == userId)?.[0]?.name ?? "Not found";
},
getUserLinksFromUserId: function (userId) {
return this.links.filter((obj) => obj.userId == userId);
},
},
};
</script>
Card.vue
<template>
<div class="card">
<h1>{{ username }}</h1>
<button #click="addLink">Add One link</button><br><br>
<span v-if="links.length == 0">No links</span>
<div class="links">
<Link v-for="link in links" :key="links.indexOf(link)" :link="link"></Link>
</div>
</div>
</template>
<script>
import Link from '../components/Link'
export default {
components:{Link},
props: {
userId: Number,
username: String,
links: { type: Array, default: () => [], required: false },
},
methods:{
addLink: function(){
this.links.push({
userId: this.userId,
link: 'newlink->cool'
});
}
}
}
</script>
Link.vue
<template>
<div>
<span>UserId: {{ this.link.userId }} Link: {{ this.link.link }</span>
</div>
</template>
<script>
export default {
props: {
link: { type: Object, default: () => [], required: false },
},
};
</script>
This is a bad way to work with props
Note: do not focus on Dev Tools too much as it can be "buggy" at times - especially if you use Vue in a wrong way. Focus on your app output
Your Card.vue component is modifying (push) a prop, which is not recommended but it sort of works if the prop is object/Array and you do not replace it, just modify it's content (as you do)
But in your case, the values passed to props are actually generated by a method! The getUserLinksFromUserId method is generating a new array every time it is called, and this array is NOT reactive. So by pushing to it, your component will not re-render and what is worse, parent's links array is not changed at all! (on top of that - if App.vue ever re-renders, it will generate new arrays, pass it to pros and your modified arrys will be forgoten)
So intead of modifying links prop in Card.vue, just emit an event and do the modification in App.vue
Trying to figure out why the following code calls someFunction() 4 times instead of 1 time:
html:
<div id="app">
<div v-for="item in items" :key="item.id">
<input v-model="item.test">
<div>{{ someFunction(item) }}</div>
</div>
</div>
vue:
new Vue({
el: '#app',
data: {
items: [
{
id: 0,
test: 'test1',
},
{
id: 1,
test: 'testSomething',
},
{
id: 2,
test: 'foo',
},
{
id: 3,
test: 'bar',
},
]
},
methods: {
someFunction(item) {
console.log(item.test);
return item.test;
},
}
})
You can play with the code here
So my real world application has 30 items and a more complex someFunction(). At the moment, someFunction() is called once for every item as soon as only one item changes. But why is vue calling the function n times instead of only the one time needed?
EDIT: My problem are NOT the n function calls when the page is loaded. My problem are the n function calls when the input of only one input field is changed and thus only one function call is necessary, because all the other input values remain the same and thus the function result remains the same.
By using v-model on nested item in "items" you are invoking global change on "items" variable, and by that the whole html is re-rendered because there was sensed change.
You want this re-render because v-model made some changes you want to keep displaying updated data.
<div id="app">
<div v-for="item in items" :key="item.id"> <------- 1.LOOP here
<input v-model="item.test">
<div>{{ someFunction(item) }}</div> <--------2.FUNCTION call here
</div>
</div>
that means you go through each item in items array.
each iteration calls the method someFunction(item)
if you want a conditional executing you should <div v-if="onlyIfIwant">{{ someFunction(item) }}</div> wrap it into a v-if to prevent the function call
update
if you want conditional executes of the someFunction() then of course you have to add a flag in your objects like this:
data: {
items: [
{
id: 0,
test: 'test1',
execute: true
},
{
id: 1,
test: 'testSomething',
execute: false
},
{
id: 2,
test: 'foo',
execute: false
},
{
id: 3,
test: 'bar',
execute: true
},
]
},
your v-if just needs the following:
<div v-if="item.execute">{{ someFunction(item) }}</div>
Very simple question. I'm learning VueJS and have created a simple component:
Vue.component('blog-post', {
props: ['title'],
template: '<h3>{{ title }}</h3>'
})
I then have parsed some data to it like this:
new Vue({
el: '#blog-post-demo',
data: {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
})
My question is how can get the title of a specefic element based on the id in my HTML? For now I can only render through the items and get them all, but I want to be able to specify which title I want to display based on the Id. Here is my HTML which gives me all the data:
<div id="blog-post-demo">
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
</div>
You can achieve with COMPUTED property, like that
<template>
<div id="blog-post-demo">
<p v-for="post in thisOne" :key="post.id" >
{{post.title}}
</p>
</div>
</template>
<script>
export default {
el: '#blog-post-demo',
data() {
return {
posts: [
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
]
}
},computed: {
thisOne(){
return this.posts.filter(x => x.id === 3); /*choose your id*/
}
}
};
</script>
Or you can use event too to select the id of the posts to display (more dynamically)
Tip: If you start with VueJS, learn about the properties of VueJs (DATA, COMPUTED, CREATED, METHOD) and look at the uses and strengths of each one. For my part, the VueJS site is very very well done for beginners: https://v2.vuejs.org/v2/guide/
I'm not sure if I understand correctly what you want to do. But if you want to go through all posts and display title of particular post then you can try this way:
<blog-post
v-for="post in posts"
:key="post.id"
:title="setTitle(post)"
/>
(: instead of :v-bind it's a short form, also if you don't pass slots in your component you can go with self-closing tag)
Then in your methods section you can create a method:
setTitle(post) {
if(post.id === 2) return post.title
}
I have several accordions (every one is a single Vue component) and they are expanded by default. There's also a 'copy' function allowing to make a duplicate of every component.
Vue.component("Accordion", {
template: "#accordion-template",
data: function () {
return {
open: true
}
},
methods: {
toggle: function () {
this.open = !this.open;
}
}
});
new Vue({
el: '#vue-root',
data: {
devices: [
{
name: "a", description: "first"
},
{
name: "b", description: "second"
},
{
name: "c", description: "third"
}
]
},
methods: {
copy: function (device) {
var index = this.devices.indexOf(device) + 1;
var copy = {
name: device.name + "_copy",
description: device.description + "_copy"
};
this.devices.splice(index, 0, copy);
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.js"></script>
<div id="vue-root">
<div class="device" v-for="device in devices">
<accordion>
<div slot="acc-head">
<span>{{ device.name }}</span><br/>
<button #click="copy(device)">copy</button>
</div>
<div slot="acc-body">
{{ device.description }}
</div>
</accordion>
</div>
</div>
<script type="text/x-template" id="accordion-template">
<div>
<slot name="acc-head"></slot>
<button #click="toggle">Open: {{ open }}</button>
<div :class="open ? 'active' : 'hidden'">
<slot name="acc-body"></slot>
</div>
</div>
</script>
When all accordions are collapsed (in other words 'open: false') and I try to duplicate an accordion from the middle of list (for example b), I expect appearing of the new component named 'name'_copy and it must be expanded by default. But instead of this, the new component has the same values of all attributes as the duplicated one and the last component in the list becomes expanded.
How can I solve this issue?
Fiddle: https://jsfiddle.net/j3ydt1m7/
Short answer
Add a key in your v-for loop: v-for="device in devices" :key="{something here}". Your key must be unique and identify each device, even after device copy
Code
Please check: https://jsfiddle.net/Al_un/9cradxvp/. For debugging purpose, I changed few things:
I put device as props of <accordion> so that I can use device properties in console.log
Copying device is now emitted from <accordion>. Vue doc on listening to child component events
I have added mounted() and updated() hooks. More about Lifecycle hooks
Each device has an ID
Long answer
About list rendering
If key is not provided in v-for loop, Vue does not know how to update a List. From Vue documentation:
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item.
Let's consider your list (I have added one element)
[
{id: 1, name: "a"},
{id: 2, name: "b"},
{id: 3, name: "c"},
{id: 4, name: "d"},
]
Now, let's copy node "b". Without :key="device.id", the console output is
4: d is mounted
3: c is updated
5: b_copy is updated
With :key="device.id", the console output is only:
5: b_copy is mounted
Basically, without keys, there are:
two updates: c becomes b_copy, d becomes c
one insert: d is created
Consequently, the last element is recreated every time you proceed to a copy. As open default value is true, obviously, d has open = true.
If each element has a :key="device.id", then only element b_copy is created
To check that, remove the :key="device.id" from my fiddle and see what happens in the console
Selecting a key
As the key must uniquely identify your device, you should not use index as a key as device index in the array changes whenever you copy a device
Additionally, an ID field is preferred because there is no guarantee that your devices names are unique. What if you initialise the list with
[
{ name: "a"},
{ name: "b"},
{ name: "a"}
]
From a functional point of view, this is correct.
When working with Vue and lists you should add a key prop to the element with v-for. Using the key like this, let's Vue know that you mean a specific element.
<div class="device" v-for="device in devices" :key="device.name">
I believe the reason for this is that due to performance reasons Vue by default adds a new element as the last element and then updates the data in the other nodes. Thus, the new element that you add is actually the last one in the list which has open set as true.
A little addition to your code:
Instead of providing the "device" object and searching for its index you could just pass the index directly.
This is what i mean: jsFiddle
Vue.component("Accordion", {
template: "#accordion-template",
data: function() {
return {
open: true
}
},
methods: {
toggle: function() {
this.open = !this.open;
}
}
});
new Vue({
el: '#vue-root',
data: {
devices: [{
name: "a",
description: "first"
},
{
name: "b",
description: "second"
},
{
name: "c",
description: "third"
},
]
},
methods: {
copy: function(index) {
var device = this.devices[index];
var copy = {
name: device.name + "_copy",
description: device.description + "_copy"
};
this.devices.splice(index + 1, 0, copy);
},
remove: function(index) {
this.devices.splice(index, 1);
}
}
});
.device {
margin: 10px 0;
}
.active {
display: block;
}
.hidden {
display: none;
}
div.device {
border: 1px solid #000000;
box-shadow: 1px 1px 2px 1px #a3a3a3;
width: 300px;
padding: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-root">
<div class="device" v-for="(device, index) in devices" :key="device.name">
<accordion>
<div slot="acc-head">
<span>{{ device.name }}</span><br/>
<button #click="copy(index)">copy</button>
<button #click="remove(index)">remove</button>
</div>
<div slot="acc-body">
{{ device.description }}
</div>
</accordion>
</div>
</div>
<script type="text/x-template" id="accordion-template">
<div>
<slot name="acc-head"></slot>
<button #click="toggle">Open: {{ open }}</button>
<div :class="open ? 'active' : 'hidden'">
<slot name="acc-body"></slot>
</div>
</div>
</script>
I'm faced with an issue where my semantic drop down in my vue project won't activate when clicking on the arrow icon but works when I click on the rest of the element. The drop down also works when I set the dropdown to activate on hover, but just not on click. Solutions I've tried:
tested if the dynamic id are at fault
tested if the back ticks are confusing things
placed the values directly into the semantic drop down
Aside from the dropdown not activating, the code below works as intended and brings back the selected value to the parent component and can be displayed.
Dropdown.vue:
<template>
<div class="ui selection dropdown" :id="`drop_${dropDownId}`">
<input type="hidden" name="gender" v-model="selected">
<i class="dropdown icon"></i>
<div class="default text">Gender</div>
<div class="menu">
<div class="item" v-for="option in options" v-bind:data-value="option.value">
{{ option.text }}
</div>
</div>
</div>
</template>
<script>
export default {
data: function () {
return {
selected: {}
}
},
watch: {
selected: function (){
this.$emit("dropDownChanged", this.selected)
}
},
props: {
options: Array, //[{text, value}]
dropDownId: String
},
mounted () {
let vm = this;
$(`#drop_${vm.dropDownId}`).dropdown({
onChange: function (value, text, $selectedItem) {
vm.selected = value;
},
forceSelection: false,
selectOnKeydown: false,
showOnFocus: false,
on: "click"
});
}
}
</script>
The component usage:
<vue-drop-down :options="dropDownOptions" dropDownId="drop1" #dropDownChanged="dropDownSelectedValue = $event"></vue-drop-down>
The data in the parent:
dropDownOptions: [
{ text: 'One', value: 'A' },
{ text: 'Two', value: 'B' },
{ text: 'Three', value: 'C' }
],
dropDownSelectedValue: ""
Here is a fiddle of the above but simplified to use a flatter project. However the problem doesn't reproduce :(
https://jsfiddle.net/eywraw8t/210520/
I'm not sure what is causing your issue (as the examples on the Semantic Ui website look similar), but there is a workaround. For you arrow icon:
<i #click="toggleDropDownVisibility" class="dropdown icon"></i>
And then in the methods section of your Vue component:
methods: {
toggleDropDownVisibility () {
$(`#drop_${this.dropDownId}`)
.dropdown('toggle');
}
},