Vue Vuetify Performance/Freeze Issue - javascript

I'm building an vue2.js app, which gets data from an API and shows it within an vuetify data-iterator and on a leaflet map.
The App.vue component handles the communication between the other components (FilterPanel, ListPanel and MapPanel) ...
<template>
<div id="app">
<v-app id="inspire">
<v-app>
<v-navigation-drawer app width="26em" class="pb-16 pb-lg-0">
<FilterPanel #change="getData" />
</v-navigation-drawer>
<v-sheet id="widget-panel" app class="open" :elevation="8">
<v-container
class="grey lighten-5 scrollContainer"
style="display: flex; overflow-y: auto; flex: 1"
>
<ListPanel :loading="loading" :data="data">
<template #card="{ data }">
<DataCard :data="data" :id="'data' + data.id"></DataCard>
</template>
</ListPanel>
</v-container>
</v-sheet>
<v-main>
<MapPanel :loading="loading" :data="data" />
</v-main>
</v-app>
</v-app>
</div>
</template>
.. and the the API call to get the data.
...
methods: {
getData() {
this.loading = true;
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) => response.json())
.then((json) => (this.data = json))
.finally(() => (this.loading = false));
},
},
...
Within the ListPanel.vue component an slot is defined, so it can be used somewhere else with other data visualization.
<template>
<v-data-iterator
no-results-text="no - data"
loading="loading"
loading-text="loading - data"
no-data-text="no - data"
:items="data"
hide-default-footer
disable-pagination
>
<template v-slot:default="props">
<v-row>
<v-col v-for="item in props.items" :key="item.id" cols="12">
<slot name="card" :data="item">
no slot provided
</slot>
</v-col>
</v-row>
</template>
</v-data-iterator>
</template>
<script>
export default {
name: "ListPanel",
props: ["data", "loading"],
};
</script>
The MapPanel.vue component shows all data entries on the map with clickable markers, which will open popups to show some information about the data. The map also holds an loading indicator (v-progress-linear) to show if data is loading or not.
<template>
<l-map
id="map"
ref="myMap"
:style="styleObject"
v-if="showMap"
:zoom="zoom"
:center="center"
:options="mapOptions"
>
<v-progress-linear
style="z-index: 800 !important"
:indeterminate="loading"
:active="loading"
absolute
top
color="info"
></v-progress-linear>
<l-tile-layer :url="url" :attribution="attribution" />
<l-control-zoom position="bottomright"></l-control-zoom>
<l-marker
v-for="(item, index) in markers"
:key="'marker-' + index"
:lat-lng="item.location"
>
<l-popup :options="getPopupOptions(item.id)">
<DataCard :data="item" showRequest flat></DataCard>
</l-popup>
</l-marker>
</l-map>
</template>
For better visual representation I'm using an reusable component (DataCard) with v-card to show the necessary data.
I have setup a simplified version of the app. https://codesandbox.io/s/sleepy-hoover-s5w5l
Loading the app or getting the data, let the app freezes for some seconds. You'll see it at the progress bar on the top. The animation stops!
I figured out, that the v-card component is slowing down the whole app. If I remove the card component entirely, loading the data seems to work just fine.
Getting the data from the API isn't the problem, since getting the data from it is very fast.
Vue DevTools Extension within FireFox shows Problems with the Map Markes and the DataCard component.
So I'm asking, what I'm doing wrong with the DataCard Component ?

Related

Using axios to display an array in a Database

I'm having trouble using axios(my code will follow) but when i use {{response.data}} the site breaks and gives me a blank page as soon as i remove it, it works alright. I'm not too sure what I'm doing wrong, I'm quite new to using vue, vuetify and axios.
I've got a local server running for the database and the get request link works. it displays 3 columns of strings and 1 array and I'm looking to get the data from the array.
<template>
<v-app id="inspire"
style="background-color: #e4e4e4">
<!-- to be deleted-->
<v-app-bar-nav-icon style="padding-left: 80px">
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">Login</router-link>
<br>
<vbtn>Logout</vbtn>
</nav>
<router-view/>
</div>
</v-app-bar-nav-icon>
<!--end-->
<!-- profiles to login to-->
<v-main style="padding: 10rem">
<v-container>
<v-row>
<v-col
v-for="n in 10"
:key="n"
cols="5"
style="background-color: #e4e4e4;">
<v-card height="200" style="background-color: #a3a3a3; text-align: center">{{n}}</v-card>
</v-col>
</v-row>
</v-container>
</v-main>
<!-- profiles to login to end-->
</v-app>
</template>
<script>
import axios from 'axios'
export default {
name: 'HelloWorld' ,
mounted () {
axios
.get("http://localhost:8081/contractor?user_id=c65722")
.then((response) => {
this.posts = response.data
})
},
data: () => ({
ecosystem: [],
importantLinks: [],
}),
methods: {}
}
</script>
'

How to access data from a loop in a vuetify carousel?

I use vuetify to make a kind of carousel to display recipes that are stored in the database.
But I would like when I click on a recipe the carousel opens below a space with all the elements of the recipe in question (the one we clicked on) So I found a component on vuetify that corresponds exactly to what I'm looking for: Here is Vuetify Carousel
But in my v-slide-item I use a loop that retrieves the recipe data but suddenly from the v-expand-transition I no longer have access to this loop how can I display the recipe data suddenly?
Here is the code :
<template>
<v-sheet
class="mx-auto"
elevation="8"
max-width="100%"
style="box-shadow: none !important;"
>
<v-slide-group
v-model="model"
class="pa-4 slider"
show-arrows
>
<v-slide-item
v-for="n in Object.keys(recipes)"
:key="recipes[n].id"
v-slot="{ active, toggle }"
>
<v-card
:color=" 'grey lighten-1'"
class="ma-4 card-recipe"
height="200"
width="200"
style="border-radius: 10px;"
v-bind:style="recipes[n].recipe[0].first_recipes_image != null ? { backgroundImage: 'url(' + recipes[n].recipe[0].first_recipes_image + ')' } : { backgroundImage: 'url(https://cdn.vuetifyjs.com/images/cards/sunshine.jpg)' }"
#click="toggle"
>
<p class="card-text" ><span class="black--text">{{ recipes[n].recipe[0].name }}</span></p>
<v-row
class="fill-height"
align="center"
justify="center"
>
<v-scale-transition>
<v-icon
v-if="active"
color="white"
size="48"
v-text="'mdi-close-circle-outline'"
></v-icon>
</v-scale-transition>
</v-row>
</v-card>
</v-slide-item>
</v-slide-group>
<v-expand-transition>
<v-sheet
v-if="model != null"
height="200"
tile
style="background-color: #FFF8F0 !important;"
>
<v-row
class="fill-height"
align="center"
justify="center"
>
<h3 class="text-h6">
Selected {{ model }}
</h3>
</v-row>
</v-sheet>
</v-expand-transition>
</v-sheet>
</template>
<script>
import { mapGetters } from "vuex";
export default {
props: {
},
data: () => ({
model: null,
recipes: [],
openedCards: [],
}),
computed: {
console: () => console,
...mapGetters({
plantActive: 'permatheque/getPlant',
}),
},
methods: {
async getPlantRecipes() {
this.$axios
.$get("/lnk/plant/recipes?plant_id=" + this.plantActive.id + "")
.then((response) => {
this.recipes = response;
console.log(this.recipes);
})
.catch((error) => {
console.log(error);
});
},
},
mounted() {
this.getPlantRecipes()
}
}
</script>
Hope I was clear enough, thanks!
You can use the v-model of the v-slide-group which is basically the index from recipes array of the selected item in the carousel.
This way you know which recipe is selected, so you can go grab the recipe info from the array or make another api call to get that info.
Example
Check this codesandbox I made: https://codesandbox.io/s/stack-71474788-recipes-carousel-6vm97o?file=/src/components/Example.vue
If you already have the recipe info within your recipes array, all you need to do is use the v-model variable of the v-slide-group which I renamed to recipeIndex to access that data directly from your array.
<v-expand-transition>
<v-sheet v-if="recipeIndex != null" tile style="background-color: #FFF3E0 !important;">
<v-container fluid class="pa-12">
<v-row>
<v-col cols="12" sm="6">
<span class="text-h6">{{ `${recipes[recipeIndex].name}'s Recipe` }}</span> <br>
<span class="text-subtitle-1">{{ recipes[recipeIndex].description }}</span>
<p class="text-justify mt-4">
{{ recipes[recipeIndex].steps }}
</p>
</v-col>
<v-col cols="12" sm="6" class="d-flex align-center justify-center">
<div class="thumbnail">
<img :src="recipes[recipeIndex].image" alt="Beach Scene" style="width: 100%;" />
</div>
</v-col>
</v-row>
</v-container>
</v-sheet>
</v-expand-transition>
If you need to get the info from a secondary api call. You can set up a watcher on the recipeIndex variable to obtain the recipe info everytime it changes.

Create static left navbar component with Vue

I need a vertical navbar to the left of my app's layout, and with the content all across the right.
As it stands, I can't do that. My navbar pushes all the content down. How can I fix this?
This is what my App.vue's template looks like:
<template>
<div id="app">
<v-app>
<navbar/>
<v-main>
<v-container class="main-container">
<router-view/>
</v-container>
</v-main>
</v-app>
</div>
</template>
For the navbar component I am using vuetify, specifically the mini navbar
As per the example code, here is what it currently looks like:
<template>
<v-navigation-drawer
v-model="drawer"
:mini-variant.sync="mini"
permanent
>
<v-list-item class="px-2">
<v-list-item-avatar>
<v-img src="https://randomuser.me/api/portraits/men/85.jpg"></v-img>
</v-list-item-avatar>
<v-list-item-title>John Leider</v-list-item-title>
<v-btn
icon
#click.stop="mini = !mini"
>
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
</v-list-item>
<v-divider></v-divider>
<v-list dense>
<v-list-item
v-for="item in items"
:key="item.title"
link
>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';
#Component<Navbar>({})
export default class Navbar extends Vue {
private value: string = 'home';
private drawer: boolean = true;
private items: Array<Object> = [
{ title: 'Home', icon: 'mdi-home-city' },
{ title: 'My Account', icon: 'mdi-account' },
{ title: 'Users', icon: 'mdi-account-group-outline' }
]
private mini: boolean = true;
}
</script>
As mentioned on the top, this is still just pushing all the app's content down. This is what it currently looks like:
What can I do so that on the left I have my navbar but on the right
Edit: For the sake of clarification, I do want the navbar to be vertical, but I just don't want it to push down all the content. Basically I want my app to have a side navigation
Wrap the navbar and main in a <v-row>:
<template>
<div id="app">
<v-app>
<v-row>
<navbar/>
<v-main>
<v-container class="main-container">
<router-view/>
</v-container>
</v-main>
</v-row>
</v-app>
</div>
</template>
By default, v-app has an internal wrapper with flex-direction: column CSS, meaning that its child elements are placed vertically. By wrapping these 2 child elements in a v-row, you place them in a wrapping element with flex-direction: row.
I guess you are misunderstanding the concepts. You are wrapping a v-navigation-drawer inside a navbar component.
In Vuetify v-navigation-drawer is a component used to build Sidebars and that's why your component is on vertical.
You should try to use v-app-bar instead. Here's the documentation: https://vuetifyjs.com/en/components/app-bars/
Edit 1
You can configure an app sidebar or an app navbar using the app option inside the component like that:
<v-navigation-drawer
app
>
...
</v-navigation-drawer>
Just like in the official documentation. If you do it that way, the component will work properly and your content will not be pushed down.

Tell Vue to look at all the pictures in a folder without changing the names of the images

I made a way to tell Vue to look in a file and assign images to the corresponding amount of cards. It's working fine.
But I was wondering if there is a better way to do this. The reason why I am asking is that currently, you need to rename your images in numerical order (Eg. 1.png,2.png,3.png etc) and as you know it's very bad for SEO to not name your Image files properly and its a tad bit annoying if you want to add more images or remove any
This is not a complete deal breaker but I would like it if I do not lose on my SEO ranking capabilities.
Here is the code
<v-container fluid>
<v-row>
<v-col
v-for="n in 38"
:key="n"
class="d-flex child-flex"
cols="4"
>
<v-lazy
:options="{
threshold: 0.5,
}"
transition="fade-transition"
>
<v-card elevation="8" shaped draggable="false" class="d-flex">
<v-img
:src="require('#/assets/skillsLogo/' + n + '.png')"
:lazy-src="require('#/assets/skillsLogo/' + n + '.png')"
class="grey lighten-4"
aspect-ratio="3"
>
<template v-slot:placeholder>
<v-row
class="fill-height ma-0"
align="center"
justify="center"
>
<v-progress-circular
indeterminate
color="grey lighten-5"
></v-progress-circular>
</v-row>
</template>
</v-img>
</v-card>
</v-lazy>
</v-col>
</v-row>
</v-container>
Thanks in advance
images.js
const context = require.context("#/assets/skillsLogo/", false, /\.png$/);
const images = context.keys().map(context);
export default images;
Docs
Component:
<script>
import images from "./images.js";
export default {
data() {
return {
images: images,
};
},
};
</script>
Iterate over images array in your template...

Show mutiple v-dialog boxes with different content in vue.js

Hii I am working on the Vue.js template and I stuck at a point where I need to show dynamic v-dialog using looping statement but now it shows all.
Dom:
<template v-for="item of faq">
<div :key="item.category">
<h4>{{ item.heading }}</h4>
<div v-for="subitems of item.content" :key="subitems.qus">
<v-dialog
v-model="dialog"
width="500"
>
<template v-slot:activator="{on}">
{{subitems.qus}}
</template>
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
Privacy Policy
</v-card-title>
<v-card-text>
{{ subitems.ans }}
</v-card-text>
<v-divider></v-divider>
</v-card>
</v-dialog>
</div>
</div>
</template>
Script:
export default {
data: () => ({
faq,
dialog:false,
}),
}
I do not understand how I can do this. If I click on one button then it shows all.
There must a design a pattern for this one but a quick solution would be to create array of booleans for v-models of dialogs. something like below
export default {
data: () => ({
faq,
dialog: [] // Array instead of Boolean.
}),
}
and
<template v-for="item of faq">
<div :key="item.category">
<h4>{{ item.heading }}</h4>
<div v-for="(subitems, index) of item.content" :key="subitems.qus">
<v-dialog
v-model="dialog[index]"
width="500"
>
<template v-slot:activator="{on}">
{{subitems.qus}}
</template>
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
Privacy Policy
</v-card-title>
<v-card-text>
{{ subitems.ans }}
</v-card-text>
<v-divider></v-divider>
</v-card>
</v-dialog>
</div>
</div>
</template>
Brother, you are doing a very small mistake, you should not keep your v-dialog component inside your loop, take this out from loop block and don't take dialog as empty array keep it false.

Categories

Resources