Asynchronous Call in Meteor Router Flickering Other Template - javascript

Ok, maybe I am going about this the wrong way. I am new to meteor and am used to building my apps in express. I have a route to /myaccount. Before rendering any templates I am calling an asynchronous method that checks if the user is an admin. The method is on the server. I tried putting the admin check in onBeforeAction, but I got the same result. What happens is that the login template flickers quickly on the screen before the admin template is loaded. Anyone know how to fix this so the login screen does not flicker on screen before the admin template is loaded? Here's my route:
Router.route('/myaccount', function() {
var _this = this;
var user = Meteor.user();
if(user) {
Meteor.call('checkIfAdmin', user.username, function(err, isAdmin){
if(isAdmin) {
_this.render('admin');
} else{
_this.render('myaccount');
}
});
} else{
this.render('login');
}
});
What I find strange is that in the iron router docs it says this about onBeforeAction in regards to their example of checking if a user is an admin:
"If the user is not logged in, the route function will never get called"
But the flickering that happened when I used onBeforeAction lead me to believe that it was calling the route function.

Well I am not sure I have the solution but I have still some remarks.
First if you are running this code on the server, your meteor.call is effectively asynchronous. And I do not know what happens at the end of your function for the rendering template. Maybe it renders your precedent template, that is to say login ?
But if you want to avoid the flickering of a template the time iron-router is working, the best thing to do is, I think, add a loading template :
Router.configure({
loadingTemplate: 'loading'
});
<template name='loading'>
// some css to render a 'loading feature'
</template>
So when you want to go on admin you will have this loading screen instead of login.
For the verification, it is true that I would use a hook or a plugin with onBeforeAction to do such thing as verification, I think it is cleaner with iron-router than a method call.

Related

Passing unsaved record to Ember.js route

Inside an application we allow users to create new records, related to an existing record. To achieve this, we use actions something like this:
createUser() {
var route = this;
var model = this.store.createRecord('user', {
client: route.modelFor('client'),
});
route.transitionTo('user.update', model);
},
The user.update route renders a user-form component, using the model that was passed in the transition. The same route is also used to update existing users.
The issue with this approach is as follows; when refreshing the page, the page errors because the route fails to find the respective record when querying the store (at this point, the URL is /users/null/update). Ideally I'd pass the client (or client.id) argument in the URL so that:
The page can be reloaded without issue.
The client associated with the user is set correctly.
How can I achieve this in Ember.js? I know that this can easily be done using nested routes (by nesting the user.update route inside a client route), but this doesn't make sense visually.
The relevant parts of the router are as follows:
this.route('clients');
this.route('client', {path: 'clients/:id'}, function() {
this.route('users');
});
this.route('user', {path: 'users/:id'}, function() {
this.route('update');
});
All I do in the user/update.hbs template is {{user-form user=model}}
The problem is that the model you just created has no id at that point because it is not saved, ember can´t route to a model without an id, if possible save the model before you try to transition to the route, if you don´t want to save the model because the user can cancel the action check this thread where a user had the same problem (if I understand you problem correctly), I provided a solution for that problem that I´m using in my own project
https://stackoverflow.com/a/33107273/2214998

How to display a "not found" page with parameterised route in Durandal

I have a Durandal application, and I use router.mapUnknownRoutes to display a user-friendly error page if the URL does not correspond to a known route. This works fine -- if I go to, say /foo, and that doesn't match a route, then the module specified by mapUnknownRoutes is correctly displayed.
However I cannot find any way to display that same error page when I have a parameterised route, and the parameter does not match anything on the backend.
For example, say I have a route like people/:slug where the corresponding module's activate method looks like this:
this.activate = function (slug) {
dataService.load(slug).then(function () {
// ... set observables used by view ...
});
};
If I go to, say /people/foo, then the result depends on whether dataService.load('foo') returns data or an error:
If foo exists on the backend then no problem - the observables are set and the composition continues.
If foo doesn't exist, then the error is thrown (because there is no catch). This results in an unhandled error which causes the navigation to be cancelled and the router to stop working.
I know that I can return false from canActivate and the navigation will be cancelled in a cleaner way without borking the router. However this isn't what I want; I want an invalid URL to tell the user that something went wrong.
I know that I can return { redirect: 'not-found' } or something similar from canActivate. However this is terrible because it breaks the back button -- after the redirect happens, if the user presses back they go back to /people/foo which causes another error and therefore another redirect back to not-found.
I've tried a few different approaches, mostly involving adding a catch call to the promise definition:
this.activate = function (slug) {
dataService.load(slug).then(function () {
// ... set observables used by view ...
}).catch(function (err) {
// ... do something to indicate the error ...
});
};
Can the activate (or canActivate) notify the router that the route is in fact invalid, just as though it never matched in the first place?
Can the activate (or canActivate) issue a rewrite (as opposed to a redirect) so that the router will display a different module without changing the URL?
Can I directly compose some other module in place of the current module (and cancel the current module's composition)?
I've also tried an empty catch block, which swallows the error (and I can add a toast here to notify the user, which is better than nothing). However this causes a lot of binding errors because the observables expected by the view are never set. Potentially I can wrap the whole view in an if binding to prevent the errors, but this results in a blank page rather than an error message; or I have to put the error message into every single view that might fail to retrieve its data. Either way this is view pollution and not DRY because I should write the "not found" error message only once.
I just want an invalid URL (specifically a URL that matches a route but contains an invalid parameter value) to display a page that says "page not found". Surely this is something that other people want as well? Is there any way to achieve this?
I think you should be able to use the following from the activate or canActivate method.
router.navigate('not-found', {replace: true});
It turns out that Nathan's answer, while not quite right, has put me on the right track. What I have done seems a bit hacky but it does work.
There are two options that can be passed to router.navigate() - replace and trigger. Passing replace (which defaults to false) toggles between the history plugin using pushState and replaceState (or simulating the same using hash change events). Passing trigger (which defaults to true) toggles between actually loading the view (and changing the URL) vs only changing the URL in the address bar. This looks like what I want, only the wrong way around - I want to load a different view without changing the URL.
(There is some information about this in the docs, but it is not very thorough: http://durandaljs.com/documentation/Using-The-Router.html)
My solution is to navigate to the not-found module and activate it, then navigate back to the original URL without triggering activation.
So in my module that does the database lookup, in its activate, if the record is not found I call:
router.navigate('not-found?realUrl=' + document.location.pathname + document.location.hash, { replace: true, trigger: true });
(I realise the trigger: true is redundant but it makes it explicit).
Then in the not-found module, it has an activate that looks like:
if (params.realUrl) {
router.navigate(params.realUrl, { replace: true, trigger: false });
}
What the user sees is, it redirects to not-found?realUrl=people/joe and then immediately the URL changes back to people/joe while the not-found module is still displayed. Because these are both replace style navigations, if the user navigates back, they go to the previous entry, which is the page they came from before clicking the broken link (i.e. what the back button is supposed to do).
Like I said, this seems hacky and I don't like the URL flicker, but it seems like the best I can do, and most people won't notice the address bar.
Working repo that demonstrates this solution

Meteor LogginIn() shows loading when not needed

i am following Discover meteor book and in one of the chapter author taught me to use Meteor.logginIn() method.
The goal is Simple, there is a submit page for new post and if there are no users logged in, it should display access denied template, else it should display post submit template. But if the user is loggin in or in wait state it should display loading template. I followed the tutorial and did what was told, the code looks exactly the same as the book. But, when user is logged in it rightfully display the submit page and when user is logged out it should display the access denied page but instead shows the loading template, while showing loading template if i log in, then it also display the submit page. The only trouble is while it should display the access denied, it is showing loading template.
Here is the routing code
Router.route('/submit', {name: 'postSubmit'});
var requireLogin = function(){
if(!Meteor.user()){
if(Meteor.logginIn()){
this.render(this.loadingTemplate);
}else{
this.render('accessDenied');
}
}else {
this.next();
}
}
Router.onBeforeAction('dataNotFound', {only:'postPage'});
Router.onBeforeAction(requireLogin, {only:'postSubmit'});
Eh, that code could use a little help. Like for this instance, we are going to separate concerns and call them in different hooks
First, keep in mind that every time you call onBeforeAction it adds that function to an array of and called sequentially. Although it's always a good practice not to actually depend on the sequential order, but it's great to keep in mind.
var userLoggingIn = function() {
if(Meteor.loggingIn()) {
this.render(this.loadingTemplate);
} else {
this.next(); // Done logging in? You may pass.
}
}
var requireLogin = function() {
if(!Meteor.user()) {
this.render('accessDenied');
} else {
this.next(); // User is logged in. I'll allow it.
}
}
// finally, use it.
Router.onBeforeAction([userLoggingIn, requireLogin], {only: 'postSubmit'});
Router.onBeforeAction('loading'); // built-in hook, just like dataNotFound. use in Routes with subscriptions.
Router.onBeforeAction('dataNotFound', {only:'postPage'});
Isn't that way better, and manageable? I'm not sure which order it's going to execute as I'm travelling and just typing this out at the airport.. but you can try swapping the hooks order around if it's not behaving accordingly.

prevent users to navigate back to specific routes with backbone

I am new to backbone and I want to implement a very simple auth using backbone router.
I am actually using only the router from backbone in my app. When I start the app I render a login view and I also init the backbone router (Backbone.history.start();)
If login succeeded I call router.navigate('mainmenu', { trigger: true, replace: false }); to navigate to a new route where I render the main menu, but when I click on the browser's back button I navigate back to the login view.
Before navigating to the previous view (the login view) I want to ask the user if he wants to logout first, and if logout process goes well, then he is redirected to the login view.
How can I achieve that? I checked few other questions, but the answer is too complicated for my use case. I just want to prevent users to navigate back to specific views if they're logged in.
#Dethariel thanks for the answer. I successfully implemented some kind of session, using the built-in Backbone router. I started with their small example snippet from the Backbone.Router execute method backbone router execute snippet and did something similar to bellow:
var Router = Backbone.Router.extend({
// define routes and calkbacks
// ....
// define routes and calkbacks
execute: function(callback, args) {
// execute will be called before the callback for each specific route
// get the next route in here
var nextRoute = Backbone.history.fragment;
if(user.LoggedIn()){
// check if nextRoute is '#login*'. I could make other checks as well
if(nextRoute.indexOf('login')>-1)
prompt('Log out?');
// else continue routing
else if (callback) callback.apply(this, args);
}
else if (callback) callback.apply(this, args);
}
});
This is very minimal, and I don't think it's the best or secure way, but it's a good starting point for me.
You can add a backbone route which will handle the login page (if you haven't done that yet). Once this route is hit, you do (pseudo-code follows):
if (user.isLoggedIn()) {
if (showLogoutPrompt().decision === "logout") {
user.logout();
}
}
Hope this helps.

Ember Route gets stuck after error loading model

I've run into an annoying issue when loading data asynchronously in an ember route's model callback. The issue seems to be that if the model method of my route returns a promise which is rejected then the route will never attempt to re-evaluate that route model. It just automatically returns the same rejected promise the next time it tries to go to that route without even trying to re-fetch the data!
I understand from this answer that an ember route will only call it's model method when trying to convert the url into a model. I'm guessing that in the case of routes with dynamic segments it may be called if it has never encountered that particular dynamic segment before.
Here is what I've got in my router setup.
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Router.map(function() {
this.route('login');
this.resource('users', { path: '/users' }, function() {
this.resource('user', { path: '/:user_id' });
this.route('create', { path: '/create' });
});
});
And this is my route.
App.UserRoute = Ember.Route.extend({
model: function(params) {
// This returns a promise
return App.User.fetch(params.user_id);
}
});
I have some special handling for errors in my application route so that routes which fail due to authentication exceptions redirect the user to the login screen.
App.ApplicationRoute = Ember.Route.extend({
actions: {
sessionExpired: function() {
this.controllerFor('login').set("tokenExpired", true);
this.transitionTo('login');
},
error: function(err) {
if (err.type === "TokenException") {
this.send('sessionExpired');
}
}
}
});
The Problem
I navigate to the /users route
For some reason my token expires (inactivity, whatever...)
I navigate to the /users/1 route
The route's model method returns a promise which rejects and I am kicked out to the login screen
I log back in and try to navigate back to the /users/1 route
The route automatically just returns the same failed promise it did last time and I'm kicked out to the login screen. :(
I'm thinking that what I want is some way to clear all the evaluated route models after a user logs in. If this was a multi-user system and one user logs out and another user logs in on the same computer without refreshing the page then that new user shouldn't have routes automatically resolved from the previous user's session.
This seems to me like it would be a common problem yet I can't find any sort of app-wide invalidate cache method. How should I solve this?
I'm not sure where ember data stands on the cache clearing feature, but here is one way to do it
clearCache: function (type) {
var map = App.store.typeMapFor(type);
map.idToCid = {};
map.clientIds = [];
map.recordArrays = [];
map.findAllCache = null;
}
And here is an example as to how the ember firebase library handles a fail find using cache clearing.
delete store.typeMapFor(store.modelFor('user')).idToRecord[username];
Full example here:
https://github.com/firebase/emberFire/blob/master/examples/blog/js/app.js
For anyone else who finds this - I never found a way to reset the ember application and cause it to forget all resolved routes. I did find a few other work-arounds.
In the end, I opted to just window.reload() any time that a user logged out of the system or had their authentication token expire.
Authenticated URLs
Another reasonable approach would be to put a random unique id in the hash state. Essentially just do this.
Instead of a route like:
#/contacts/1
prefix every authenticated route with some kind of unique id
#/PyUE4E+JEdOaDAMF6CwzAQ/contacts/1
App.reset
I tried tried a number of things. One of the more promising things I tried was redirecting to the login screen and using the Application's reset method on my global App object. http://emberjs.com/api/classes/Ember.Application.html#method_reset
That didn't work though, it seems that even a reset Application remember's the models of any routes that it has resolved - weird.

Categories

Resources