Durandal: At what stage in lifecycle should router.isNavigating deactivate? - durandal

sigh It's becoming the "daily question" routine with me, sorry!
I have a nav.html that contains:
<div class="loader pull-right" data-bind="css: { active: router.isNavigating }">
<i class="icon-spinner icon-2x icon-spin"></i>
</div>
Trouble is, it never deactivates! The spinner always stays rotating, basically.
I've put in debugs in the viewmodels like in the Durandal html samples page:
var vm = {
activate: activate,
//refresh: refresh,
//sessions: sessions,
title: 'My App Home Page',
activate: function () {
system.log('Lifecycle : activate : home');
},
binding: function () {
system.log('Lifecycle : binding : home');
return { cacheViews: false }; //cancels view caching for this module, allowing the triggering of the detached callback
},
bindingComplete: function () {
system.log('Lifecycle : bindingComplete : home');
},
attached: function (view, parent) {
system.log('Lifecycle : attached : home');
},
compositionComplete: function (view) {
system.log('Lifecycle : compositionComplete : home');
},
detached: function (view) {
system.log('Lifecycle : detached : home');
}
//viewAttached: viewAttached
};
and in my Chrome dev console I have:
Lifecycle : bindingComplete : home system.js:73
Lifecycle : attached : home system.js:73
Lifecycle : compositionComplete : home system.js:73
...and I would have thought that "compositionComplete" would have triggered the end of "router.isNavigating", no?
The nav.html is composed in my shell.html file as so:
<header>
<!-- ko compose: {view: 'nav'} -->
<!-- /ko-->
</header>
Basically the spinner just stays there on the top right and never disappears. I can navigate between my two pages and it just stays there, even though "compositionComplete" fires every time.
Entire shell.html file:
<div>
<header>
<!-- ko compose: {view: 'nav'} -->
<!-- /ko-->
</header>
<div>
<section id="content" class="main">
<!--ko router: { transition:'entrance', cacheViews:true }--><!--/ko-->
</section>
</div>
</div>
Edited shell.html file:
<div>
<header>
<nav class="navbar navbar-default">
<div class="navbar-header">
<a class="navbar-brand" href="/">
<span class="title">My test SPA</span>
</a>
</div>
<div>
<ul class="nav navbar-nav" data-bind="foreach: router.navigationModel">
<li data-bind="css: { active: isActive }">
<a data-bind="attr: { href: hash }, html: title"></a>
</li>
</ul>
<p class="navbar-text pull-right">Logged in as Bilbo Baggins</p>
<div class="loader pull-right" data-bind="css: { active: router.isNavigating }">
<i class="icon-spinner icon-2x icon-spin"></i>
</div>
</div>
</nav>
</header>
<div>
<section id="content" class="main">
<!--ko router: { transition:'entrance', cacheViews:true }--><!--/ko-->
</section>
</div>
</div>

This might be because the way you do the composition with the router in the shell.html.
In durandal 2.0 it should look like this:
<!--ko router: { transition:'entrance', cacheViews:true }--><!--/ko-->
Or if you want to keep it like the old way:
<!--ko compose: {model: router.activeItem,
attached: router.attached,
compositionComplete: router.compositionComplete,
transition: 'entrance'} -->
<!--/ko-->
Also take into consideration that the css class active might not work as expected in the divfor the loading gif. Instead you can use:
<div class="loader pull-right" data-bind="visible: router.isNavigating">
So you will hide the loader gif when it is not navigating.

Related

Scroll to section from child component in Vue.js

I am trying to understand how to emit an event from a child component to the parent that scrolls to the correct section via a ref or id. So for example the navigation menu is a child component with links. when I click a link, the corresponding section in the parent scrolls into view. I am coming from React so I am a bit thrown off. How do I pass parameters up to scroll to correct ref or id? basically the way anchorlinks act.
Here is what I got so far.. I am not sure I should be using links , I might need buttons since i'm not navigating pages.
Navigation.vue (Child)
<template>
<div class="navigation">
<img class="logo" alt="Vue logo" src="../assets/logo.png" />
<nav>
<ul>
<li>
<a #click="goSection" href="/">Contact</a>
</li>
<li>
Projects
</li>
<li>
Skills
</li>
<li>
Education
</li>
</ul>
</nav>
</div>
</template>
<script>
export default {
name: "Navigation",
//I want to emit an event in the parent where the page section scrolls in to view//
methods: {
goSection() {
this.$emit("go-section", this.value);
},
},
};
</script>
App.vue (Parent)
<template>
<div id="app">
<Navigation #go-section="goToSection" />
<section class="section" ref="projects">
<p>Projects</p>
</section>
<section class="section" ref="skills">
<p>Skills</p>
</section>
<section class="section" ref="contact">
<p>Contacts</p>
</section>
</div>
</template>
<script>
import Navigation from "./components/Navigation.vue";
export default {
name: "App",
components: {
Navigation,
},
methods: {
goToSection(event, value) {
var element = this.$refs[(event, value)];
var top = element.offsetTop;
window.scrollTo(0, top);
},
},
};
</script>
You can use a tag but prevent default link behavior, and pass ref to your goSection method :
Vue.component('navigation', {
template: `
<div class="navigation">
<img class="logo" alt="Vue logo" src="../assets/logo.png" />
<nav>
<ul>
<li>
<a #click.prevent="goSection('contact')" href="/">Contact</a>
</li>
<li>
Projects
</li>
<li>
Skills
</li>
<li>
Education
</li>
</ul>
</nav>
</div>
`,
methods: {
goSection(val) {
this.$emit("go-section", val);
},
},
})
new Vue({
el: '#demo',
methods: {
goToSection(value) {
const top = this.$refs[(value)].offsetTop;
window.scrollTo({
top: top,
left: 0,
behavior: 'smooth'
});
},
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<navigation #go-section="goToSection"></navigation>
<section class="section" ref="projects">
<p>Projects</p>
</section>
<section class="section" ref="skills">
<p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p><p>Skills</p>
</section>
<section class="section" ref="contact">
<p>Contacts</p>
</section>
</div>
You can emit an event with parameter (section ref for example) in the child component:
<a #click="$emit('go-section', 'contact')" href="/">Contact</a>
<a #click="$emit('go-section', 'projects')" href="/">Projects</a>
And then use this parameter in the parent's listener method
// template
<Navigation #go-section="goToSection" />
// js
goToSection(sectionRef) {
var element = this.$refs[sectionRef];
var top = element.offsetTop;
window.scrollTo(0, top);
},

How to send props in Vue

I am quite new in vue so I haven't understood the logic completely. My question is I have ticket and ticketlist components. So I am not on my ticket list component I am creating some tickets data and I want to show them according to the ticket component. To make it more clear here is my ticketlist component:
<template>
<section class="tickets">
<div class="container">
<div class="row">
<div class="col-12 col-md-3 mb-3">
<Ticket v-for="ticket in tickets" :key="ticket.id" :product="ticket"/>
</div>
</div>
</div>
</section>
</template>
<script>
import Ticket from './Ticket'
export default {
components: {
Ticket
},
data() {
return {
tickets: [
{
id: 0,
category: "Einzelkarte",
price: "€3,50",
tariff: [
"Wählen Sie eine Option",
"Erwachsene",
"Erwachsener erm.",
"Kinder / Jugendliche",
"Kinder / Jugendliche erm.",
],
available_amount: 23,
article_number: "2021.05.04-2673990197-1",
},
],
};
},
}
</script>
And also ticket component:
<template>
<widget type="ticket" class="--flex-column">
<div class="top --flex-column">
<div class="bandname -bold">Ghost Mice</div>
<div class="tourname">Home Tour</div>
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/199011/concert.png" alt="" />
<div class="deetz --flex-row-j!sb">
<div class="event --flex-column">
<div class="date">3rd March 2017</div>
<div class="location -bold">Bloomington, Indiana</div>
</div>
<div class="price --flex-column">
<div class="label">Price</div>
<div class="cost -bold">€{{ ticket.price }}</div>
</div>
</div>
</div>
<div class="rip"></div>
<div class="bottom --flex-row-j!sb">
<a class="btn button" href="#">ADD TO CART</a>
</div>
</widget>
</template>
<script>
export default {
props: ['ticket'],
}
</script>
<style scoped>
#import 'https://i.koya.io/flex/1.1.0.css';
*, ::after, ::before {
box-sizing: unset;
}
</style>
So, I am showing TicketList component in one of the page but the thing is it doesnt show anything. So I wonder how can I connect them together and show tickets data according to ticket component. I hope I am clear but if I am not I can answer you in the comments.
The probleme is the props name, you need to pass ticket as props and not product
...
<Ticket v-for="ticket in tickets" :key="ticket.id" :ticket="ticket"/>
...
or the contrary inside you Ticket component set :
props: ['product']

How to transfer data between components (siblings) Vue?

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!

Vue JS Component reattach or Cache

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.

Durandaljs shell is not rendering corectly

I have following shell.js
define(function (require) {
var router = require('durandal/plugins/router');
return {
router: router,
activate: function () {
return router.activate('movies/show');
}
};
});
and following shell.html
<div>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<ul class="nav" data-bind="foreach: router.visibleRoutes">
<li data-bind="css: { active: isActive }">
<a data-bind="attr: { href: hash }, html: name"></a>
</li>
</ul>
</div>
</div>
<div class="container-fluid page-host">
<!--ko compose: {
model: router.activeItem, //wiring the router
afterCompose: router.afterCompose, //wiring the router
transition:'entrance', //use the 'entrance' transition when switching views
cacheViews:true //telling composition to keep views in the dom, and reuse them (only a good idea with singleton view models)
}--><!--/ko-->
</div>
</div>
But when I run my application, first div in the shell.html having class="navbar navbar-fixed-top" never renders. What am I missing?
This turned out to be a silly mistake. I use ReSharper quite heavily. So I created a shell.html and then hit F6 to move it to view folder. Resharper instead of moving, copied the file thus leaving the old file behind. All this while I was editing the wrong file. Now I have one shell.html and everything works just ok.