How to enforce render table dynamically - javascript

I'm using bootstrap-vue b-table and b-pagination component. But since there two many records, so I'm using pagination call to backed, which means pass the paging information to the backend, only fetch corresponding records for each page.
Good thing:
1. Data are passing correctly between backend and frontend.
2. Debug the frontend page through VUE debug tool, the properties of the b-table component are correct.
The problem:
The table is not rendering, it looks blank in the page, if I check the elements through chrome debugging tool. The elements (table body) for table container is empty.
A brief sample code :
<b-container>
<div v-if="guards_for_asyc_fetch_data_call">
<b-row><b-col><h2>Title</h2></b-col></b-row>
<b-row>
<b-col>
<b-table
:items="items"
:fields="fields"
:current-page="currentPage"
:per-page="perPage"
:stacked="true"
small
striped
fixed
hover/>
</b-col>
</b-row>
<b-row>
<b-col>
<b-pagination
:total-rows="totalRows"
:per-page="perPage"
:number-of-pages="totalPages"
v-model="currentPage"
align="center"
/>
</b-col>
</b-row>
</div>
</b-container>
</template>
<script>
export default {
computed: {
totalRows() {
return {dynamic_udpate};
},
perPage() {
return {dynamic_udpate};
},
currentPage: {
get: function() {
return {dynamic_udpate};
},
set: function (newPage) {
{asyc_call_fetch_data_from_backend}
}},
totalPages() {
return {dynamic_udpate};
},
items() {
return {dynamic_udpate};
}
}
}
</script>
Expectation:
Table content is updated dynamically when the data is refreshed after the backend call.
Actual result is:
When I click the page navigation except the 1st page, the container just show blank.
For vue debugger you can see the elements are the new fetched elements, id started from 6 since I set to present 5 items per page. Here's a link!
If I go check the general page elements in Chrome debugger, the table content is empty. Here's a link!
Anyone gets an idea how I can solve this problem?

Related

Vue slot not working - child elements show as empty array

I have the following code where I am trying to pass individual tab components into a tabs component via slots. However, the tabs do not seem to be getting passed. Putting {{ tabs }} on the template only shows an empty array.
<template>
<article>
{{ tabs }} // empty array
<header class="tabs">
<ul>
<li v-for="(tab, index) in tabs" :key="index">
<div class="nav-item"
:class="{ 'is-active': tab.isActive }"
#click="selectTab(tab)">
{{ tab.name }}
</div>
</li>
</ul>
</header>
<section class="tabs-details">
<slot></slot>
</section>
</article>
</template>
<script>
export default {
data: () => {
return {
tabs: []
}
},
methods: {
selectTab(selectedTab) {
this.tabs.forEach(tab => {
tab.isActive = tab.name === selectedTab.name;
});
}
},
created() {
console.log('created[Tabs.vue]-> ', this.$children) // nothing here
this.tabs = this.$children; // not working
}
}
</script>
I have my components registered in the parent like so:
import Tab from '#/components/Tab'
import Tabs from '#/components/Tabs'
export default {
components: {
Datepicker,
Tab,
Tabs
},
...
The template is pretty straight forward. Note, the contents of the individual tabs display fine when selected="true"
<Tabs>
<Tab name="hours" selected="true">Contents here...</Tab>
<Tab name="pay"></Tab>
</Tabs>
I have checked in the browser console and, although the nav-item <li> elements are being created, they have no content.
I'm new to slots so maybe I am missing something obvious or the code syntax I am using is outdated. Any help is much appreciated.
Try to use this.$slots :
this.tabs = this.$slots.default();//returns vnodes, that can be rendered via render function
$children has been deprecated in Vue 3:
In 3.x, the $children property is removed and no longer supported. Instead, if you need to access a child component instance, we recommend using template refs.
If you're trying to get children of <slot></slot>, use this.$slots.default(), as suggested in Boussadjra's answer.
In Vue2, the syntax for getting the default slot contents is this.$slots.default (not a function).
Notes:
you'll get back an array of VNodes, not actual DOM,
you can only access $slots after component has been mounted (e.g: in mounted() hook)
If you want specific data from the vNodes, you'll have to log them and see where exactly it is in your version of Vue (vNodes have had minor changes over time - they're considered internal API).
My advice would be to upgrade to a newer version of 2. If not 2.7.10, at least 2.6.14. Some of the available documentation on Vue 2 describes features which were not yet added in 2.2.3. $slots had a major revamp in 2.6.
For example, I was using slots at the time, but if you ask me now how they looked like, I couldn't tell you with certainty.

Vue.js - hide a text on a page passed from App.vue

I am really new to Vue. I have multiple pages such as App.vue, Waiting.vue, Reference.vue, Switch.vue and so on. The text defined on **App.vue** is passed along to other pages correctly. But now I dont want to display WELCOME text passed from App.vue to Waiting.vue page. Is there a way that I can hide WELCOME from Waiting.Vue page only?
App.vue
<div id="app">
<b-container>
<b-col lg="5">
WELCOME
</b-col>
</b-container>
</div>
Waiting.vue
<div id="app">
<p class="teamTitle">TEAMS ON DECK</p>
</div>
Ouput for Waiting.vue
WELCOME
TEAMS ON DECK
Let suppose that the Waiting page path is /waiting so you should do :
<div id="app">
<b-container>
<b-col lg="5" v-if="$route.path!=='/waiting'">
WELCOME
</b-col>
</b-container>
</div>
I am assuming that App.vue is acting as parent component and has content that is visible in the page. That being the case, there are multiple ways of hiding information.
If you use a router and Waiting.vue is on a distinct route you can check for route params to selectively hide text. For e.g. in App.vue.
<b-col lg="5" v-if="!this.$route.fullPath === '/waiting'">
WELCOME
</b-col>
If you use global storage like Vuex, just set a variable within Vuex when Waiting.vue is loaded and set the variable to something else in other components
If you are having everything on a single page for any reason, pass a variable as prop and selectively hide the element (similar to [1])
App.vue
<Waiting :hide="true"/>
Waiting.vue
<b-col lg="5" v-if="!hide">
WELCOME
</b-col>
<!-- other code -->
<script>
export default {
props: {
default: false,
type: Boolean,
required: false
}
}
</script>

VueJS, Vuetify, data-table - expandable, performance problem

I've got a problem with my VueJS and Vuetify project.
I wanna create a table with expandable rows. It'll be a table of orders with possibility to see bought products for each one. For one page it should show at least 100 rows of orders.
For this, I used <v-data-table> from the Vuetify framework.
What is the problem?
After preparing everything I realized that it works, but for expansion for each row, I have to wait a few seconds (it's too long - it must be a fast system). And for expanding all visible records it is necessary to wait more than 20 seconds with whole page lag.
What I've tried?
I started with standard Vuetify <v-data-table> with a show-expand prop and with an expanded-item slot - it was my first try - the slowliest.
Secondly, I tried to create on my own - but with Vuetify:
<v-data-table>
<template v-slot:item="{item}">
<td #click="item.expanded = !item.expanded">expand / hide</td>
<!--- [my table content here - too long to post it here] -->
<tr v-if="item.expanded">
<td :colspan="headers.length">
<v-data-table>
<!--- [content of the nested table - also too long to post it here] -->
</v-data-table>
</tr>
</template>
</v-data-table>
What's interesting - I realized that v-if works faster than v-show, which is a weird fact, because I thought that changing display: none to nothing should be less problematic than adding/removing whole objects to DOM.
This method was a little faster than first, but it is still too slow.
I found a hint to set :ripple="false" for every v-btn in my tables and I did it - helped, but only a bit.
Everything was tested on Chrome and Firefox, on three devices with Windows and Linux Fedora and two android smartphones.
What should else I do?
Thank you in advance!
This excellent article suggests that the raw number of DOM nodes has the biggest impact on performance. That said, I didn't experience any real performance bottlenecks in the sample app that I built to learn more about your problem. The entire page with the table loaded in about 1.25s (from localhost), regardless of whether it was in dev mode or it was a production build. The JavaScript console timer reported that expanding or contracting ALL 100 rows simultaneously only took an average of about 0.3s. Bottom line, I think you can achieve the optimizations you're looking for and not have to give up the conveniences of Vuetify.
Recommendations
Consider displaying fewer rows at one time (biggest expected impact)
Streamline your template to use as few elements as possible, only display data that's really necessary to users. Do you really need a v-data-table inside a v-data-table?
Streamline your data model and only retrieve the bare minimum data you need to display the table. As #Codeply-er suggested, the size and complexity of your data could be causing this strain
Testing Method
Here's what I did. I created a simple Vue/Vuetify app with a VDataTable with 100 expandable rows. (The data was pulled from the random user API). I used this method to count DOM nodes. Here are some of the parameters/info:
Rows: 100
Columns: 5 + the expansion toggler
Expansion row content: a VSimpleTable with the user's picture and address
Size of a single JSON record returned from the API: ~62 lines (about half the size of your sample object above)
Vue v2.6.11
Vuetify v2.3.0-beta.0
(I realize this just came out, but I don't think you'd have different results using v2.2.x)
App was built with vue create myapp and vue add vuetify
VDataTable actually adds/removes the expansion rows from the DOM whenever the rows are expanded/contracted
Here's some approximate stats on the result (these numbers fluctuated slightly in different conditions--YMMV):
773 (~7/row): number of DOM nodes in 100 rows/5 columns without expansion enabled
977 (+2/row): number of nodes with expansion enabled
24: number of nodes added to the table by expanding a single row
3378 (+26/row): total nodes with ALL rows expanded
~1.25s to load the entire page on a hard refresh
~0.3s to expand or contract ALL of the nodes simultaneously
Sorting the columns with the built-in sorting tools was fast and very usable
Here's code of the App.vue page of my app. The v-data-table almost the only component on the page (except the toggle button) and I didn't import any external components.
<template>
<v-app>
<v-btn
color="primary"
#click="toggleExpansion"
>
Toggle Expand All
</v-btn>
<v-data-table
:expanded.sync="expanded"
:headers="headers"
:items="items"
item-key="login.uuid"
:items-per-page="100"
show-expand
>
<template #item.name="{ value: name }">
{{ name.first }} {{ name.last }}
</template>
<template #expanded-item="{ headers, item: person }">
<td :colspan="headers.length">
<v-card
class="ma-2"
max-width="500px"
>
<v-row>
<v-col cols="4">
<v-img
:aspect-ratio="1"
contain
:src="person.picture.thumbnail"
/>
</v-col>
<v-col cols="8">
<v-simple-table>
<template #default>
<tbody>
<tr>
<th>Name</th>
<td class="text-capitalize">
{{ person.name.title }}. {{ person.name.first }} {{ person.name.last }}
</td>
</tr>
<tr>
<th>Address</th>
<td class="text-capitalize">
{{ person.location.street.number }} {{ person.location.street.name }}<br>
{{ person.location.city }}, {{ person.location.state }} {{ person.location.postcode }}
</td>
</tr>
<tr>
<th>DOB</th>
<td>
{{ (new Date(person.dob.date)).toLocaleDateString() }} (age {{ person.dob.age }})
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
</v-card>
</td>
</template>
</v-data-table>
</v-app>
</template>
<script>
import axios from 'axios'
export default {
name: 'App',
data: () => ({
expanded: [],
headers: [
{ text: 'Name', value: 'name' },
{ text: 'Gender', value: 'gender' },
{ text: 'Phone', value: 'phone' },
{ text: 'Cell', value: 'cell' },
{ text: 'Country', value: 'nat' },
{ text: '', value: 'data-table-expand' },
],
items: [],
}),
created () {
axios.get('https://randomuser.me/api/?seed=stackoverflow&results=100')
.then(response => {
this.items = response.data.results
})
},
methods: {
toggleExpansion () {
console.time('expansion toggle')
this.expanded = this.expanded.length ? [] : this.items
console.timeEnd('expansion toggle')
},
},
}
</script>
You can see a working demo in this codeply. Hope this helps!

VueJS V-For and bootstrap Cards in a Deck

Good Evening,
I've been working on a issue for about an hour or so, I am kinda new to VueJS (and front end Dev in general).
I am trying to have a deck of cards looping through and object (using Bootstrap-Vue).
When I manually post the cards in they align correctly in groups of 3. the issue is when I iterate through the json object it seems to create one solid column.
Ill post my code (dont mind some of the names this is in a test section, im going to refactor it into a comp once its complete) in case someone wants to take a look. I am going to poke at it some more and Update in case I figure it out before someone.
Thank you in advance
<div class="about">
<h1>Dawning Recipes</h1>
<br />
<b-container>
<b-card-group deck v-for="recipes in data.recipes" :key="recipes">
<b-card
bg-variant="dark"
:header="recipes.name"
class="text-center"
style="max-width: 23rem;"
>
<b-row>
<b-col sm="auto">
<b-img thumbnail fluid :src="recipes.icon" :alt="recipes.name" />
</b-col>
<b-col class="text-left">
<b-list-group>
<b-list-group-item
variant="dark"
v-for="ingredient in recipes.ingredients"
:key="ingredient"
>{{ingredient}}</b-list-group-item>
</b-list-group>
</b-col>
</b-row>
<b-row>
<b-row>
<b-col>
<b-card-text class="text-left ml-2 mt-3 mr-2">{{recipes.delivery}}</b-card-text>
</b-col>
</b-row>
</b-row>
</b-card>
</b-card-group>
</b-container>
</div>
</template>
<script>
import data from "../data/destiny/dawning.json";
export default {
data() {
return {
data: data
};
}
};
</script>```
Hard to say if this is the only issue, since I don't have your files, but one thing immediately sticks out:
<b-card-group deck v-for="recipes in data.recipes" :key="recipes">
:key has to be a unique string value. Since you're using recipes.name below that line, I assume recipes is an object. Vue will call toString() on this, which will always result in a String of "[object Object]", for any object, no matter the contents.
I bet the same key of [object Object] is being used, so Vue is only creating one object there from that loop.
Try using a unique value like :key="recipes.name" (assuming that is unique, ideally you have an id field)
At the very minimum to get it working, you could use index as a key, but that is essentially the same as not using a key at all:
<b-card-group deck v-for="(recipes, index) in data.recipes" :key="index">
Edit:
Just peeking at the Vue source code, it looks like you should be seeing a warning about this. It might be good to work through other warnings in there, if any.

Vue add one component before a component inside v-for loop dynamically

I have a question on how to add an unread component before a dynamic component (message, can be of a different type, which requires different rendering) inside a v-for loop.
Here is my sample code without the unread panel.
<template>
<div>
<Message v-for="(message, index) in messages" :key="index" :message="message></Message>
</div>
</template>
AIM:
I want to add an unread panel before the message that is unread. And clearly, it will be added once in the template. So I would like another developer when viewing the template, can easily know that it does the job.
How to add Unread Panel
Method 1:
Add it in template directly.
<template>
<div>
<template v-for="(message, index) in messages" :key="index">
<Unread v-if="message.getId() === firstUnreadId"></Unread>
<Message :message="message"></Message>
</template>
</div>
</template>
Problem:
There are lots of unnecessary checking to see if unread is needed to place to the DOM, not to mention, it is difficult for the developer to know that the unread component will only be loaded once.
Method 2:
Put unread component inside messages, and add one more message into messages, so that the v-for loop will mark it work.
<template>
<div>
<template v-for="(message, index) in messages" :key="index">
<Message :message="message"></Message>
</template>
</div>
</template>
<template>
<message :is="currentComponent"></message>
</template>
<script>
export default {
props: {
message
},
computed: {
currentComponent: function() {
switch (this.message.getType()) {
case "unread":
return "Unread";
// ...
}
}
}
}
</script>
Problem:
Unread is definitely not a message, it seems a trick to make it work well.
I don't think any of the methods suggested above is a good one, does anyone have any solution for this? Thanks in advance.
Method 1 looks fine to me. Method 2, as you say, might be tricky to understand later. Otherwise, you could set a firstUnread property directly on the appropriate message in the array, then test that. You could do this when you load the array, or you could make the array a computed property, so that firstUnread is recalculated whenever the array is changed. Does that help?

Categories

Resources