Listening to events in Durandal - durandal

I'm reviewing the Durandal documentation, and I can't find a concrete implementation of listening for Durandal events, e.g, router events.
Can someone point me to the docs, or (if there is no documentation on this) an example?

In your view model you should listen to activator events. Link. check this example from Durandal starter template. It is listening to activate and canDeactivate events:
define(['plugins/http', 'durandal/app', 'knockout'], function (http, app, ko) {
//Note: This module exports an object.
//That means that every module that "requires" it will get the same object instance.
//If you wish to be able to create multiple instances, instead export a function.
//See the "welcome" module for an example of function export.
return {
displayName: 'Flickr',
images: ko.observableArray([]),
activate: function () {
//the router's activator calls this function and waits for it to complete before proceding
if (this.images().length > 0) {
return;
}
var that = this;
return http.jsonp('http://api.flickr.com/services/feeds/photos_public.gne', { tags: 'mount ranier', tagmode: 'any', format: 'json' }, 'jsoncallback').then(function(response) {
that.images(response.items);
});
},
select: function(item) {
//the app model allows easy display of modal dialogs by passing a view model
//views are usually located by convention, but you an specify it as well with viewUrl
item.viewUrl = 'views/detail';
app.showDialog(item);
},
canDeactivate: function () {
//the router's activator calls this function to see if it can leave the screen
return app.showMessage('Are you sure you want to leave this page?', 'Navigate', ['Yes', 'No']);
}
};
});

Here's some example code from the project I have worked in:
//authentication.js
define(['durandal/events'], function(events){
var authentication = {};
events.includeIn(authentication);
//perform login then trigger events to whoever is listening...
authentication.trigger('logged:on',user);
//perfom logoff then trigger events to whoever is listening...
authentication.trigger('logged:off');
return {
authentication: authentication
}
});
//logon.js
//pull in authenticaion
define(['authentication'], function(authentication){
authentication.on('logged:on',loggedOn);
//callback that gets called when the logged:on event is fired on authentication
function loggedOn(user){
console.log(user);
}
});

Related

Few questions about Vue SSE(Server Sent Event)

I'm going to use SSE to implement real-time notifications.
Please look at my method and tell me what the problem is and how to solve it.
in vuex login action method
// SSE EvnetSource Connect
let url = process.env.VUE_APP_API_URL + "subscribe";
let eventSource = new EventSource(url, {
withCredentials: true
});
eventSource.addEventListener("notification", function (event) {
console.log(event.data);
commit('setNotification', event.data) // => set event's data to vuex 'notification' state as array
});
and then
in top nav component's watch method
watch: {
notification(val) {
if(val) {
const notiData = JSON.parse(val)
if(notiData.id) {
// show notification alert component
this.$notify("info filled", "notification", notiData.content, null, {
duration: 7000,
permanent: false
});
}
}
}
}
This is my current situation.
And this is my questions.
Currently, when logging in through vuex, I create an EventSource, but how to delete the EventSource when logging out? (EventSource is not defined globally, so I don't know how to approach it when logging out).
How to reconnect EventSource after page refresh? (I think main.js can handle it.)
Is there a way to put in Custom Header when creating EventSource?
As any other event bus, EventSource needs to be unsubscribed when events shouldn't be received. This requires to keep a reference to listener function. If a listener uses Vuex context that is available inside an action, it should be defined inside login action and stored in a state:
const notificationListener = (event) => {...};
eventSource.addEventListener("notification", notificationListener);
// can be moved to a mutation
state._notificationEventSource = eventSource;
state._notificationListener = notificationListener;
Inside logout action:
let { _notificationEventSource: eventSource, _notificationListener: notificationListener } = state;
eventSource.removeEventListener("notification", notificationListener);
It's no different when a page is initially loaded and reloaded.

How to detect the user comes back to a page rather than starts browsing it?

I've got a grid component that I use in many routes in my app. I'd like to persist its state (ie. paging, search param) and restore it when the user comes back to the grid (ie. from editing a row). On the other hand, when the user starts a new flow (ie. by clicking a link) then the page is set to zero and web service is called with the default param.
How can I recognise the user does come back rather then starts a new flow?
When I was researching the problem I've come across the following solutions.
Unfortunatelly they didn't serve me
1/ using router scroll behaviour
scrollBehavior(to, from, savedPosition) {
to.meta.comeBack = savedPosition !== null;
}
It does tell me if the user comes back. Unfortunately the scroll behaviour runs after grid's created and mounted hooks are called. This way I have no place to put my code to restore the state.
2/ using url param
The grid's route would have an optional param. When the param is null then the code would know it's a new flow and set a new one using $router.replace routine. Then the user would go to editing, come back and the code would know they come back because the route param != null. The problem is that calling $router.replace re-creates the component (ie. calling hooks etc.). Additionally the optional param mixes up and confuses vue-router with other optional params in the route.
HISTORY COMPONENT
// component ...
// can error and only serves the purpose of an idea
data() {
return {
history: []
}
},
watch: {
fullRoute: function(){
this.history.push(this.fullRoute);
this.$emit('visited', this.visited);
}
},
computed: {
fullRoute: function(){
return this.$route.fullPath
},
visited: function() {
return this.history.slice(-1).includes(this.fullRoute)
}
}
the data way
save the information in the browser
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return JSON.parse(local.storage.gridData)
},
set: function(dataObj){
local.storage.gridData = JSON.stringify(dataObj)
}
}
}
//...
use statemanagement
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return this.$store.state.gridData || {}
},
set: function(dataObj){
this.$store.dispatch("saveGrid", gridData)
}
}
}
//...
use globals
// component ...
// can error and only serves the purpose of an idea
computed: {
gridData: {
get: function() {
return window.gridData || {}
},
set: function(dataObj){
window.gridData = dataObj
}
}
}

Nuxt Js Event Fires Twice

I am using Nuxt js SSR for an app that am build, I installed Vue Event plugin but when i emit an event it runs twice at the listener. Created hook runs twice too.
Modules am using:
Axios, Auth, Toast
Child Component
methods: {
initPlaylist(songs) {
console.log(songs)
}
},
mounted () {
this.$events.$on('playAll', data => {
this.initPlaylist(data.songs) //runs twice
})
}
Parent Component
method () {
playAll (songs) {
this.$events.$emit('playAll', songs)
}
}
How can i resolve this issues guys? I need your help.
Maybe you have to call that parent's method on client side only.
you can write code like this to prevent emit on server side:
methods: {
playAll(songs) {
if (process.server) return
this.$events.$emit('playAll', songs)
}
}
or do not call playAll method on server side. (eg: created, mounted...)
You need to off that event first before.
this.$events.$off("playAll");
this.$events.$on('playAll', data => {
this.initPlaylist(data.songs) //runs twice
})

Run method before route

I have a login modal that I activate by setting .is-active to it. For this, I have a method like this:
methods: {
toggleModal: function (event) {
this.isActive = !this.isActive
}
}
that I run onclick. Depending on the boolean value of isActive, my modal gets the class .is-active.
Thing is, in my modal I have a button that takes the user to a new view which means it's rendering a new component, with this code:
<router-link class="control" #click="toggleModal()" to="/register">
As you can see, it's routing to /register. Before doing this, I need to run toggleModal() so that the modal gets closed. Right now it's routing without running the method which means that the new view has my modal overlay which is... not optimal.
Is there any good way to do this in Vue? Could I maybe create a method, that first calls toggleModal(), and then routes from the method?
Thanks.
I would define a method that calls toggleModal first, then navigates. Like so:
methods: {
navigateAway () {
this.isActive = !this.isActive
this.$router.push('/register')
}
}
You don't need the event argument unless you intend on capturing more data from the event or event target. You could also wrap the router push in a setTimeout if you so desire, for perhaps cleaner looking view changes.
methods: {
navigateAway () {
let vm = this
vm.isActive = !vm.isActive
setTimeout(function () {
vm.$router.push('/register')
}, 50)
}
}
Of course, there are hooks that you can use from vue-router that make this easy. Example (assuming you're using single file components and Vue.js 2.x):
export default {
data () {
return {
isActive: false
}
},
beforeRouteLeave (to, from, next) {
this.isActive = false // I assume that you would not want to leave it open upon navigating away
next()
}
}
Link to vue router hooks: https://router.vuejs.org/en/advanced/navigation-guards.html

Vue $emit in beforeDestroy

Vue - I want to track user changes on a page and send those updates if they navigate away. The basic idea is
//child
beforeDestroy: function() {
var that = this;
axios.post('gate/cart.php', userUpdates)
.then(function(res) {
if (res.data.success) {
that.$emit('updateCart', res.data.cart);
//parent (App.vue)
<router-view
#updateCart="updateCart"
...
methods: {
updateCart: function(newCart) {
alert('caught');
this.cart = newCart;
The dev tools show me the emit is emitted and the correct payload (res.data.cart) is sent, but the parent method isn't called. (That alert doesn't trigger.) I know the updateCart method in the parent is working, as another component uses it fine like this with a regular method:
addToCart: function() {
var that = this;
axios.post('gate/cart.php', this.dataToSend)
.then(function(res) {
if(res.data.success === true) {
that.$emit('updateCart', res.data.cart)
that.$router.push({ path: '/product/' + that.product.id})
}
If the ajax is working, I get a correct $emit, and the target method is ok, what lifecycle hook caveat is stopping me from executing the parent method? Do you know a better way to do this? (I want to check the success of the ajax call before updating the parent data.)