How in Vue.js could I load images once the route loads without displaying them yet? - css

I am trying to add a transition of 2 png into an png in the background, and be able to switch between different scenarios ( like living-room, dining-room etc). Once I click the element that select which area of the house I am choosing, the images gets fetched but because it takes some time to load, the transition doesn't happen and they just appear on top of the image on the background without transition smoothly from the side. I need a way to load the all the images once the route gets rendered, or maybe if you could give me another solution for the problem I'm happy to hear it.
This is the template
<template>
<div>
<div v-for="item in painting" :key="item.id" class="showroom">
<div class="setting">
<h1>{{ item.name }}</h1>
<h3>Choose a room:</h3>
<div v-for=" (room, idx) in rooms" :key="idx" id="roomSelection">
<img id="roomSelector" :src="room.roomview" #click="showRoom(room)">
<p>{{ room.name }}</p>
<br>
</div>
<p>Choose a wall color: <span id="colorPicker" :style="{backgroundColor: color}"><input type="color" id="base" v-model="color"></span></p>
<router-link to="/painting"><i class="fas fa-arrow-left"></i></router-link>
</div>
<div class="display">
<div :style="{backgroundColor: color}" class="wall">
<img :src="item.img">
</div>
<div class="forniture">
<transition name="slideIn">
<div v-for=" room in roomToShow" :key="room" class="setForniture">
<img class="left" :id="room.space1" :src="room.forniture1">
<img class="left" :id="room.space2" :src="room.forniture2">
</div>
</transition>
</div>
</div>
</div>
</div>
</template>
This is the script
<script>
import { mapGetters } from 'vuex'
import sofa from '../assets/sofa.png'
import lamp from '../assets/lamp.png'
import table from '../assets/table.png'
import cabinet from '../assets/cabinet.png'
import chair from '../assets/chair.png'
import sidetable from '../assets/sidetable.png'
import livingroom from '../assets/livingroom.png'
import diningroom from '../assets/diningroom.png'
import lounge from '../assets/lounge.png'
export default {
data(){
return{
color: '#243E36',
rooms: [
{ name: "lounge", space1: 'lounge1', space2: 'lounge2', forniture1: chair, forniture2:sidetable, roomview: lounge},
{ name: "livingroom" , space1: 'livingroom1', space2: 'livingroom2', forniture1:sofa, forniture2:lamp, roomview: livingroom },
{ name: "diningroom" , space1: 'diningroom1', space2: 'diningroom2', forniture1: table, forniture2: cabinet, roomview: diningroom }
],
roomToShow: [],
}
},
computed: {
...mapGetters([
'showPainting',
'forSale',
'painting'
]),
},
methods: {
showRoom(room) {
setTimeout( () => {
this.roomToShow.shift();
this.roomToShow.push(room);
}, 100)
}
}
</script>

The easiest way to solve this issue is to just hide the images with CSS but nevertheless load all from the beginning (render the elements). You can do that by dynamically adding a class to the currently visible image with :class="".
A more complex solution, which gives you also a callback for when the images are loaded, is to create a new Image() for all images and react to onload/onerror. Like this, you can load all the images in the beginning with JS abd without changing the HTML. Here is an example for this use case: How to create a JavaScript callback for knowing when an image is loaded?

Related

Vue - requests and watch data on unmounted components

I'm new to vue and strugling with some props and attributes.
I have a vue application where the main app is calling three different components:
Navbar,
Sidebar
MapContainer
In Navbar user will fill a selector with information about city and states. After user presses search, the list of results will then show up in the Sidebar menu.
Sidebar itself is a simple component carrying only a router-view for it's children components which are Results and Details
Results will receive the result of the search performed in the Navbar component. When user clicks any item in the Results component, Sidebar will then load Details taking the place of Results with detailed information about that place.
the problem is that the data used to make the request(city and states) comes from the first component navbar. I'm passing this data to sub-components using vue-router params option. When component Results gets unmounted, I lost all the data that was passed, even adding a Watch couldn'd fix the problem and thus can't return back to the previous page. Even adding a Watch couldn'd fix the problem. What's the proper way to handle data across components that area unmounted?
Navbar.vue
<template>
<div class="navbar">
<div class="logo">
Logo
</div>
<div class="middle">
<div class="flex-selectors">
<div class="navbar-options">
<select v-model="state_id" class="main-selectors" #click="load_cities">
<option v-for="state in states" :value="state.state_id" :key="state">
{{ state.state }}
</option>
</select>
<select v-model="city_id" class="main-selectors">
<option v-for="city in cities" :value="city.city_id" :key="city">
{{ city.city }}
</option>
</select>
</div>
<div class="search">
<button #click="seach">
<router-link :to="{name: 'results', params: {state: state_id, city: city_id} }" aria-current="page" title="Resultados">
<div>Search</div>
</router-link>
</button>
</div>
</div>
</div>
</template>
Sidebar.vue
<template>
<div class="sidebar">
<router-view></router-view>
</div>
</template>
Results.vue
<template>
<div v-if="areas.length">
<router-link :to="{name: 'details' }" tag="div" class="container" #click="load(area)" v-for="area in areas" :key="area" :value="area">
<!-- BUNCH OF DIVS AND V-FOR -->
</router-link>
</div>
<div v-else>
NA
</div>
</template>
<script>
export default {
data() {
return {
state_id: this.$route.params.state,
city_id: this.$route.params.city,
areas: [],
}
},
methods: {
search(state_id, city_id) {
load_areas.get(state_id, city_id).then(
result => {
this.areas = result.data
}
)
}
},
mounted() {
this.emitter = inject('emitter')
this.searchAreas(this.state_id, this.city_id)
},
created() {
this.$watch(
() => this.$route.params,
(toParams, previousParams) => {
this.searchAreas(toParams.state, toParams.city)
}
)
},
watch: {}
}
</script>
What's the proper way to handle data across components that are
unmounted?
You cannot access data from an unmounted component.
When multiple components need to access or modify the same data, a good option to look into is state management. Pinia is the new official library recommendation for state management.
Instead of passing data through vue-router params, create a store for it. Set results of your search query from Navbar component and access it in Results component. When Results component gets unmounted, you won't lose the data.

VUE3.JS - Modal in a loop

I have a GET request in my home.vue component.
This query allows me to get an array of objects.
To display all the objects, I do a v-for loop and everything works fine.
<div class="commentaires" v-for="(com, index) of coms" :key="index">
My concern is that I want to display an image by clicking on it (coms[index].imageUrl), in a modal (popup).
The modal is displayed fine but not with the correct image, i.e. the modal displays the last image obtained in the loop, which is not correct.
Here is the full code of my home.vue component
<template>
<div class="container">
<div class="commentaires" v-for="(com, index) of coms" :key="index">
<modale :imageUrl="com.imageUrl_$this.index" :revele="revele" :toggleModale="toggleModale"></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="toggleModale">
</div>
</div>
</template>
<script>
//import axios from "axios";
import axios from "axios";
import Modale from "./Modale";
export default {
name: 'HoMe',
data() {
return {
coms: [],
revele: false
}
},
components: {
modale: Modale
},
methods: {
toggleModale: function () {
this.revele = !this.revele;
},
</script>
Here is my modale.vue component
<template>
<div class="bloc-modale" v-if="revele">
<div class="overlay" #click="toggleModale"></div>
<div class="modale card">
<div v-on:click="toggleModale" class="btn-modale btn btn-danger">X</div>
<img :src=""" alt="image du commentaire" id="modal">
</div>
</div>
</template>
<script>
export default {
name: "Modale",
props: ["revele", "toggleModale", "imageUrl"],
};
</script>
I've been working on it for 1 week but I can't, so thank you very much for your help...
in your v-for loop you're binding the same revele and toggleModale to every modal. When there is only one revele then any time it's true, all modals will be displayed. It's therefore likely you're actually opening all modals and simply seeing the last one in the stack. You should modify coms so that each item has it's own revele, e.g.:
coms = [
{
imageUrl: 'asdf',
revele: false
},
{
imageUrl: 'zxcv',
revele: false
},
{
imageUrl: 'ghjk',
revele: false
}
];
then inside your v-for:
<modale
:image-url="com.imageUrl"
:revele="com.revele"
#toggle-modale="com.revele = false"
></modale>
<img class="photo" :src=""" alt="image du commentaire" #click="com.revele = true">
passing the same function as a prop to each modal to control the value of revele is also a bad idea. Anytime a prop value needs to be modified in a child component, the child should emit an event telling the parent to modify the value. Notice in my code snippet above I replaced the prop with an event handler that turns the revele value specific to that modal to false. Inside each modal you should fire that event:
modale.vue
<div class="btn-modale btn btn-danger" #click="$emit('toggle-modale')">
X
</div>
This way you don't need any function at all to control the display of the modals.

How do I display multiple cards of the data i fetched in vue?

So i have my beers component and beer detaillist component in de second one i used a card with bootstrap to display image title etc. My problem is that i want to display the cards in multiple rows and column. i have tried a few ways but failed so can some one tell me how to go about it?
beers
<template>
<div>
<div v-bind:key="beer.id" v-for="beer in beers">
<BeerDetailsList v-bind:beer="beer" />
</div>
</div>
</template>
<script>
import BeerDetailsList from "./BeerDetailsList";
export default {
components: { BeerDetailsList },
name: "Beers",
props: ["beers"],
};
</script>
<style scoped></style>
beer details list
<template>
<div class="container">
<b-card-group deck>
<b-card
:title="beer.name"
:img-src="beer.image_url"
:alt="beer.name"
img-top
tag="article"
style="max-width: 20rem ;"
class="mb-2"
>
<b-card-text>
{{ beer.tagline }}
</b-card-text>
<b-button href="#" variant="primary">View Beer details</b-button>
</b-card>
</b-card-group>
</div>
</template>
<script>
export default {
name: "BeerDetailsList",
props: ["beer"],
};
</script>
<style scoped></style>
You have to put your for loop on the b-card-group component to at first specify the rows with at most 4 cards.
Then you have to put another for loop on the b-card component to print 4 cards for each card group.
You do not need any second component. eg code:
<template>
<div class="container">
<b-card-group deck v-for="i in Math.ceil(beers.length/4)" :key="i">
<b-card
v-bind:key="beer.id"
v-for="beer in beers.slice((i-1)*4, (i-1)*4 + 4)"
:title="beer.name"
:img-src="beer.image_url"
:alt="beer.name"
img-top
style="max-width: 20rem ;"
class="mb-2"
>
<b-card-text>
{{ beer.tagline }}
</b-card-text>
<b-button href="#" variant="primary">View Beer details</b-button>
</b-card>
</b-card-group>
</div>
</template>
<script>
export default {
name: "Beers",
props: ["beers"],
};
</script>
<style scoped></style>

Passing props that is an array to 2 different components

This is my 3rd day working with Vue CLI, and I want to pass props that is an array to 2 different components. The components are called Products and Modal. The Modal component is dependent on the Products component. When I pass props to both components inside my App.vue, I don't want both components to render in my App.vue. Like I said, my Modal component is dependent on my Products component.
My overall goal is to display the product title, photo, and description in my modal.
The issue I have is I make a get request in my App.vue, which i fill an empty array and then pass that to Products component. If i pass the array as props to both Products and Modal, I will get an extra modal to render at app level which does make sense, but I don't want that.
Im still getting used to how Vue works and tips or help would be much appreciated.
This could be a very obvious answer that i'm just overlooking, but i'm learning, so please understand that.
App.Vue (I make my get request here and fill an empty array to then have that array passed down as props to the two components):
<template>
<div id="app">
<all-products v-bind:newProductsArray="newProductsArray"></all-products>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import AllProducts from './components/AllProducts.vue'
import Modal from './components/Modal.vue'
//import so I can use vue resource for an http request
import VueResource from 'vue-resource'
Vue.use(VueResource)
//export components to be rendered by main.js then by the DOM
export default {
components: {
'all-products': AllProducts
},
data() {
return {
//empty products array to be filled after get request
products: [],
//declare new array to be set to products array, then passed as props. I do this because without another array,
//all products component will receiev an empty array before it can be filled by the http request.
newProductsArray: []
}
},
//request to retrieve all products from API using Created
created() {
//http request using vue resource from dependencies
this.$http.get('https://tap-on-it-exercise-backend.herokuapp.com/products').then(function(data) {
//fill products array by grabbing the data, slicing ot, then setting it to products array
this.products = data.body.slice(0, data.body.length)
//set products array to the new array to be passed down as props to all products component.
this.newProductsArray = this.products
})
}
}
</script>
Product component (This component receives props from app.vue):
<template>
<div class="products">
<h1 class="all-products">All Products</h1>
<div v-for="product in newProductsArray" :key="product.id" class="single-product">
<div class="product-container">
<div class="row">
<div class="col-md-8 center-block">
<div class="title">{{product.title}}</div>
<img class="images" :src="product.photo_url">
<div class="price">${{product.price}}</div>
<modal/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//imports
import Vue from 'vue'
import Modal from './Modal.vue'
//import bootstrap to be used to style buttons and modal.
import BootstrapVue from 'bootstrap-vue'
Vue.use(BootstrapVue)
export default {
//receive new products array as props from app
props: ['newProductsArray'],
components: {
'modal': Modal
},
data() {
return {
//set modal show to false by default
modalShow: false
}
},
methods: {
showModal() {
//toggle modal to show
this.modalShow = true
},
closeModal() {
//toggle modal to close
this.modalShow = false
}
},
}
</script>
Modal component (I want to receive the same props that products received)
<template>
<div>
<b-button #click="modalShow = !modalShow">Show Product Info</b-button>
<b-modal v-model="modalShow" title="title">
<div class="modal-body">
<img class="img-responsive" style="margin:0 auto;" src="http://placehold.it/300x340" alt="">
</div>
<p class="product-description">Description</p>
<div slot="modal-footer" class="w-100">
<b-button size="md" class="float-right" variant="warning" #click="modalShow=false">Close</b-button>
<b-button size="md" class="float-right" variant="primary" #click="modalShow=false">Like</b-button>
</div>
</b-modal>
</div>
</template>
<script>
export default {
props: ['newProductsArray'],
data() {
return {
modalShow: false
}
}
}
</script>

Vue 2 Display All Content After Specfic Time

Using Vue 2 on Laravel 5.3
I would like to add a loading icon which covers the whole page for 3 seconds allowing all rendering happened, then showing the content after 3 seconds. However, without or with adding the div v-show="!loading" as shown below, the content actually flashed for around half second before it gone disappeared, then after 3 seconds it appears again. So I think there is an unexpected flash at the start when the page is loaded. I read a bit about v-cloak online but it seems only solve short flash of {{}} ?
<template>
<div>
<transition name="fade">
<div v-if="loading" class="loading">Loading…</div>
</transition>
<div v-show="!loading">
<slot>
<!-- Main Content -->
</slot>
</div>
</div>
</template>
<script>
export default {
data(){
return{
loading:true
}
},
props:[
],
mounted(){
var vm = this
vm.loaded()
LoadingEvent.$on('loading', function(loading){
vm.loading = loading
})
},
updated(){
},
methods:{
loaded(){
setTimeout(function(){
LoadingEvent.$emit('loading',false);
}, 1000);
},
}
}
</script>
Try putting both v-if and v-else inside transition like following:
<template>
<div>
<transition name="fade">
<div v-if="loading" class="loading">Loading…</div>
<div v-else>
<slot>
<!-- Main Content -->
</slot>
</div>
</transition>
</div>
</template>

Resources