VueJS component doesn't get cached or atleast reattached after navigation. On refresh or launch everything gets attached and rendered well but after navigating to another page then back. The First Component - Carousel - component in my case doesn't get rendered but the API call is made.
<template>
<div class="rel">
<div id="homeCarousel" class="owl-carousel owl-slider">
<div class="item" v-for="product in featured">
<div class="bg-holder top-area-half" >
<div class="bg-mask-lighten"></div>
<img class="bg-img" v-bind:src="product.feature_image_url">
<div class="hero-caption">
<div class="container">
<h3 class="hero-title">{{product.feature_title}}</h3>
<p class="hero-subtitle">{{product.feature_subtitle}}</p>
<a class="btn btn-white btn-ghost btn-lg hero-btn" href="#">Shop now</a>
</div>
</div>
</div>
</div>
</div>
<div id="hero-slider-nav" class="hero-slider-nav">
<div class="container">
<div class="pull-right"></div>
</div>
</div>
</div>
</template>
<style>
</style>
<script>
export default{
data(){
return{
featured:[]
}
},
ready(){
},
mounted(){
this.getFeaturedProducts();
},
components:{
},
methods: {
getFeaturedProducts: function () {
Vue.http.get('/api/product/filter/featured=1').then(
(response) => {
this.featured = response.body;
}
)
}
}
}
</script>
`
<template>
<div class="global-wrapper clearfix ">
<keep-alive>
<Carousel></Carousel>
</keep-alive>
//The rest of the code which is just importing the Component
I found out what i was doing wrong. I had a separate JS/JQuery file and on the document ready i was initializing an owl carousel by id #('homeCarousel').owlCarousel({}) . What worked was, since i had already bootstrapped owl carousel -> on the mounted lifecycle callback i was now targeting the element and making it an owl carousel.
Related
In the HeaderComponent method 'clickPrices' is called on click
<template>
<header>
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal">Company name</h5>
<nav class="my-2 my-md-0 mr-md-3">
<a class="p-2 text-dark" href="#">Features</a>
<a class="p-2 text-dark">Enterprise</a>
<a class="p-2 text-dark" #click="clickPrices()">Get pricing</a>
</nav>
<a class="btn btn-outline-primary " href="#">Sign up</a>
</div>
</header>
</template>
<script>
export default {
name: "HeaderComponent",
methods: {
clickPrices() {
...
},
},
}
</script>
<style scoped>
</style>
and there is a Pricing Component in which I make a request to the server in the method 'getPricing'
<template>
<div class="wrap-pricing">
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="display-4">Pricing</h1>
<p class="lead">Quickly build an effective pricing table for your potential customers with this Bootstrap example. It’s built with default Bootstrap components and utilities with little customization.</p>
</div>
<div class="container">
<div class="card-deck mb-3 text-center">
<div class="card mb-4 shadow-sm">
<div class="card-header">
<h4 class="my-0 font-weight-normal">Lorem</h4>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">$10 <small class="text-muted">/ mo</small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>Lorem</li>
<li>Lorem</li>
<li>Lorem</li>
<li>Lorem</li>
</ul>
<button type="button" class="btn btn-lg btn-block"></button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import router from '../routes.js';
import { axios } from 'axios';
export default {
name: "PriceComponent",
methods: {
getPricing() {
axios.get('api/pricing').then((response) => {
//some actions
router.push('prices');
});
},
},
}
</script>
<style scoped>
</style>
How should I process the result of the 'сlickPrices' HeaderComponent method?
Or am I waiting for your ways, how can I get data in another by clicking in one component
You can emit an event and let the parent component handle the fetching and pass the data as props to the child component.
Or another way is to directly listen to the event as follows
Component 1:
this.$root.$emit('clickPrices', data);
Component 2:
mounted() {
this.$root.$on('clickPrices', data => {
this.getPricing();
});
}
Check out the answer by #alex
Since you are using 2 independent components (one is not included in the other), you cannot pass props
Things you can do -
Instead of fetching all the prices on click, just create a created hook like so, which will fetch all the pricing details whenever your component is created -
created () {
this.getPricing()
},
methods: {
getPricing() {
axios.get('api/pricing').then((response) => {
//some actions
router.push('prices');
});
},
},
Use State and when a user clicks on the button, pricing details are fetched and added in the state. And you can just use the state anywhere in your application like so -
this.$store.state.prices
Let me know if it works, if not we will find some other solution for you!
I have a component like this:
<test></test>
I declare this as follows:
Vue.component('test', {
data: {
showModal: true
},
methods: {
displayComponentModalDialog: function() {
this.showModal = true;
}
},
template: `<button #click='displayComponentModalDialog'>test</button>`
});
The <test></test> component is then placed somewhere inside the <div id="#app"> wrapper.
var app = new Vue({
router,
el: '#app',
// etc.
})
Now, I want to display another component inside the test component. So in this case I want a dialog to appear after I click the button in test component. I am not able to achieve this.
What I did is adding a new component:
Vue.component('dialog', {
template: '#dialog-template'
});
And then the following code, although I do not know for sure where to put it.
<!-- template for the modal component -->
<script type="text/x-template" id="dialog-template">
<transition name="dialog">
<div class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
default header
</slot>
</div>
<div class="modal-body">
<slot name="body">
default body
</slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button class="btn btn-primary" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</div>
</transition>
</script>
<!-- use the modal component, pass in the prop -->
<dialog v-if="showModal" #close="showModal = false">
<h3 slot="header">header</h3>
<p slot="body">
test
</p>
</dialog>
I tried putting this code inside the <test></test> but doesn't work. If I put it inside the template attribute in the component structure, it complains about only one root element.
So it is clear I miss some basic conception how this actually works in VueJS. Someone can help me clarify? Thanks.
As far as I can see your component indeed doesn't have a root tag. Templates have to have a root tag.
This is NOT a valid Vue template:
<div>
<h1>Stuff</h1>
</div>
<h2>Other stuff</h2>
This IS a valid Vue template:
<div>
<div>
<h1>Stuff</h1>
</div>
<h2>Other stuff</h2>
</div>
Note that in the second version we have a single root element for the template, a <div>, whereas in the first one we do not.
You have both a <script></script> and a <dialog></dialog> in your component template.
if you want to add another component in your test component . you can use slot on it.
You can refer to this documentation: https://v2.vuejs.org/v2/guide/components-slots.html
Example:
//component 1
<template>
<div id="modal">
// do something for your modal here.
<slot></slot> // reserve area for your another html or component.
</div>
</template>
// component 2
<template>
<your-modal-component>
<another-component>
</your-modal-component>
</template>
My requirement is something like this.
I got the list of images from the back-end.
I want to pass those image names to the carousel to display images.
This is my code.
<template>
<div class="">
<div id="carouselExampleSlidesOnly" class="carousel slide" data-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active" v-for="banner in banners">
<img :src="`./assets/${banner}`" alt="" class="img-fluid" >
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
banners:["logo.png","index.png","Capture.png"]
}
},
methods:{
}
}
</script>
But this method doesn't work. How do I pass images to my carousel element?
The problem is that you're setting all carousel slides to active, so they will all display at once. Use :class to conditionally set the active slide...
<div class="carousel-item" v-for="(banner,idx) in banners" :class="{ active: idx==0 }">
<img :src="banner" alt="" class="img-fluid">
</div>
Also, make sure the :src="'./assets/${banner}'" reference is actually working to find the images.
Working demo on Codeply
Note: You don't want to use jQuery $('.carousel').carousel(); to load the Carousel since you're already using the data-ride="carousel" attribute. As stated in the Bootstrap 4 docs...
The data-ride="carousel" attribute is used to mark a carousel as
animating starting at page load. It cannot be used in combination with
(redundant and unnecessary) explicit JavaScript initialization of the
same carousel.
From https://www.w3schools.com/bootstrap/bootstrap_carousel.asp
The data-ride="carousel" attribute tells Bootstrap to begin animating the carousel immediately when the page loads.
Vue hasn't mounted this component yet on page load. So you gotta initialize the slider only after it has mounted.
So you gotta remove data-ride="carousel", and add $('.carousel').carousel() in the mounted hook (assuming that the jquery $ is available as a global variable). Make sense ?
<template>
<div class="">
<div id="carouselExampleSlidesOnly" class="carousel slide">
<div class="carousel-inner">
<div class="carousel-item active" v-for="banner in banners">
<img :src="`./assets/${banner}`" alt="" class="img-fluid" >
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'app',
data () {
return {
banners:["logo.png","index.png","Capture.png"]
}
},
methods:{
},
mounted(){
$('.carousel').carousel();
}
}
</script>
I use $refs.height[index].offsetTop in the 1st part to computed same values, but $refs.height is in the 2rd part ,that render after the 1st part,so reload page has error TypeError: Cannot read property '0' of undefined.
I try comment <div>{{showNumber(index)}}</div>,then run npm dev,then recomment , it's work.
How render the 2rd part at first?
<template>
<div id="app">
//1st part
<div id="catalog">
<div v-for="(baike,index) in baikes">
<div class="catalog">
<h2>{{index+1}}.{{baike.name}}</h2>
<hr style="border:1px dashed #000; flex-grow: 1">
<div>{{showNumber(index)}}</div>
</div>
</div>
</div>
//2rd part
<div id="content">
<div v-for="(baike,index) in baikes" ref="height">
<h3 >{{index+1}}.{{baike.name}}</h3>
<p>{{baike.abstract}}</p>
<ul v-for="(info,key,index) in baike.info">{{key}}-{{info}}</ul></ul>
</div>
</div>
</div>
</template>
<script>
import baikeJSON from '../../items.json'
export default {
name: 'app',
data () {
return {
baikes: baikeJSON,
}
},
methods:{
showNumber(i){
return Math.ceil(this.$refs.height[i].offsetTop/1000)
}
},
}
</script>
Switch 2rd part and 1st part of position then add css
#app{
display:flex;
flex-direction:column-reverse;
}
There is no console log error but anything that I put in updated() hook [in the current code getCartItems/] will be rendered infinitely for some reasons that I do not know. even I set it as alert('hi') and it alert it infinitely. So I suspect something makes the project keeps updating values or something but I do not know where. Can anyone give me a suggestion to check where the problem is?
Cart-dropdown.vue
<template>
<div class="row">
<div class="col-md-12">
<div class="row cart-dropdown-row" v-for="cart in carts">
<div class="col-md-3">
<div class="cart-dropdown-img-wrapper">
<img class="d-flex align-self-center cart-dropdown-img" :src="cart.product_choice.img1" alt="Generic placeholder image">
</div>
</div>
<div class="col-md-6 cart-dropdown-info-wrapper">
<h6 class="cart-dropdown-info">{{cart.product.name}}</h6>
</div>
<div class="col-md-3 cart-dropdown-qty-wrapper">
<div class="cart-dropdown-qty">x{{cart.qty}}</div>
</div>
</div>
</div>
<div class="row">
</div>
<div class="row cart-dropdown-checkout-wrapper">
<button type="button" class="btn btn-success btn-sm cart-dropdown-checkout" #click.prevent="goCheckout()">Check Out</button>
</div>
</div>
</template>
<script>
export default {
data(){
return{
carts:{},
}
},
props:[
],
mounted(){
this.getCartItems()
},
updated(){
this.getCartItems()
},
methods:{
getCartItems(){
var vm = this
vm.$http.get('/getCartItems/').then((response)=>{
vm.carts = response.data
});
},
goCheckout(){
window.location.href = 'http://localhost:8000/cart'
}
},
computed:{
}
}
</script>
You are updating the vue instance data variable carts in the updated hooks and as docs says: updated hook is called after a data change causes the virtual DOM to be re-rendered and patched. So you are in a infinite loop: you change the vue data it updates the the DOM and call the updated block which again change the data.
This is also mention in the docs:
you can perform DOM-dependent operations in this hook. However, in most cases you should avoid changing state in this hook, because it may lead to an infinite update loop.
You can see this circle in the below vue instance lifecycle diagram.