{{#if currentUser}} too slow - javascript

I have created an Admin area on my website, which is protected by a login, that is powered by the account-package.
My Admin Template currently looks like this:
<template name = "Admin">
{{#if currentUser}}
{{> SideNav}}
{{> Template.dynamic template = getCurrent}}
{{else}}
{{> Login}}
{{/if}}
</template>
It works, but when i change the website, it always shows the login page for a second, before it changes to the dynamic template. It is short, but you can notice it and it doenst look very nice. So how should i go about this? I am not sure how to fix this.

Bringing your logging-in logic on the view might be an easy way to do, but as you can see it, it is not worth.
The logging-in related tests must be done asap in your application. You should do it in the router, as it will allow you to efficiently manage the accesses and start subscriptions before the view start rendering (this last point depends on your package and yout way of managing the renders though).
In addition, several packages provides very relevant tools to improve your app perfs and rendering in this kind of situation.
Here are some of them :
meteorhacks:fastRender
meteorhacks:subscription-manager
kadira:flow-router (rather than Iron:router, which is way more random in rerunning the route and renders.)
Here is some example of how you would handle it with Flow Router.
The following example architecture is built according to The Meteor Chef model.
In this example, I assume you use alaning:roles package and code according to last version of Ecmascript.
/both/routes/__triggers.js
// Let's declare some namespace for our routing triggers
Triggers = {};
Triggers.mustBe = {};
Triggers.mustNotBe = {};
// Here, we check for the state of the client
Triggers.mustBe.loggedIn = ( ) => {
if (!(Meteor.loggingIn() || Meteor.userId()))
// If he is not logged in or logging in, we handle the redirection
{
FlowRoute = FlowRouter.current();
if (FlowRoute.route.name != "home")
Session.set("redirectAfterLogin", FlowRoute.path);
FlowRouter.go('/splash');
}
};
Triggers.mustBe.admin = ( ) => {
// Same here. If the user is not an admin, we should redirect him somewhere or prevent the routing to be executed
if (!Roles.userIsInRole(Meteor.user(), ['admin']))
FlowRouter.go(FlowRouter.current().path);
};
// Just an example of what if would looks like if we wanted to be sure the client is not connected (for accessing the login screen for example)
Triggers.mustNotBe.loggedIn = ( ) => {
if (Meteor.loggingIn() || Meteor.userId())
FlowRouter.go('/');
};
/both/routes/_configuration.js
// We set the rendering root to the 'body' tag. Check for the doc links I give below
if (Meteor.isClient) Meteor.startup(function() { BlazeLayout.setRoot('body'); });
exposed_Routes = FlowRouter.group({
name: "exposed",
triggersEnter: []
});
loggedIn_Routes = FlowRouter.group({
name: "loggedIn",
triggersEnter: [
Triggers.mustBe.loggedIn
]
});
// You might see that we declare the admin routes group from the logged_in group. Doing so, we will execute all the logged_in triggers before executing the one we define here. It will allow us to check if the user is connected before checking he is an admin
admin_Routes = loggedIn_Routes.group({
name: "admin",
triggersEnter: [
Triggers.mustBe.admin
]
});
/both/routes/admin.js
admin_Routes.route('/admin/reports', {
name: "reports",
action: function (params, queryParams) {
// We use kadira:BlazeLayout package to manage the rendering
BlazeLayout.render('adminLayout', { main: "someTemplate", menu: "SideNav" });
// Any other logic you would execute each time you create this route.
}
});
/client/templates/layouts/admin.html
<template name="adminLayout">
{{> Template.dynamic template=menu }}
{{> Template.dynamic template=main }}
</template>
BlazeLayout & FlowRouter docs (by Kadira)

Probably you need {{loggingIn}} helper which is reactive and is true while login method is currently in progress.
http://docs.meteor.com/#/full/template_loggingin
<template name = "Admin">
{{#if loggingIn}}
Loading...
{{else}}
{{#if currentUser}}
{{> SideNav}}
{{> Template.dynamic template = getCurrent}}
{{else}}
{{> Login}}
{{/if}}
{{/if}}
</template>

Related

Partially render a screen in Ember.js when some data doesn't load

I have an Ember route with a model that loads data from a few different places, using Ember.RSVP.hash. Each of these results in a call to a different API route in the backend:
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return Ember.RSVP.hash({
profile: this.store.queryRecord('profile', {}),
subscriptions: this.store.findAll('subscription'),
packages: this.store.findAll('package'),
});
},
});
The problem I'm having is that when any of the data calls throws an error, the entire template fails to load. What I would like to do instead is display as much data as is available even in case of an error, with the portions that couldn't be loaded displayed as an empty model of the appropriate type (with some additional error information). However, I don't seem to be able to do this. I tried adding an error handler to the route, but from the error handler there doesn't seem to be any way to continue the transition despite the error.
One possibility is to use service for obtaining such information. Model will be completely loaded but data from service will show after they will be loaded.
Take a look at http://emberigniter.com/render-promise-before-it-resolves/
I would suggest passing data down from the route, whether it uses one or more other services to retrieve the data (also from the same author http://emberigniter.com/should-components-load-data/ ). The template can be rendered partially by populating the model provided to the components with separate requests. A good place for this kind of code is in setupController. Usually I only add a call to a service from a component if there is a logical coupling between the two eg a component showing a specific ad.
rough example,
http://emberjs.jsbin.com/netesehasi/1/edit?html,js,output
js
...
App.IndexRoute = Ember.Route.extend({
model:function(){
return {dataForA:null,dataForB:null,dataForC:null};
},
setupController:function(controller,model) {
this._super.apply(arguments);
// simulate fetching data
setTimeout(function(){
Ember.set(model,'dataForA', 'this is data for component a');
controller.set('model', model);
},2000);
setTimeout(function(){
Ember.set(model,'dataForB', 'this is data for component b');
controller.set('model', model);
},1000);
setTimeout(function(){
Ember.set(model,'dataForC', {error:'error for component c'});
controller.set('model', model);
},3000);
}
});
...
hbs
...
<script type="text/x-handlebars" data-template-name="components/comp-a">
{{#if data}}
{{data}}
{{else}}
loading...
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="components/comp-b">
{{#if data}}
{{data}}
{{else}}
loading...
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="components/comp-c">
{{#if data}}
{{#if data.error}}
{{data.error}}
{{else}}
{{data}}
{{/if}}
{{else}}
loading...
{{/if}}
</script>
...

Show certain elements when using common template based on url in meteor

I am using on a common template in two pages. I have to show some lines in one page. So i have added an if function which will check the current url . It is working for me only once. After coming back to the same url i am getting previous url's value. Then this it is not changing . Below is a sample code. How to achieve this?
Below is my code
Url- /template1
Template1.html
<template name="template1">
{{>commontemplate}}
</template>
Url - /template2
Tempalate2.html
<template name="tempalte2">
{{>commonTemplate}}
</template>
CommonTemplate.html
<template name="commonTemplate">
{{#if isAdmin 'template1'}}
<div> hello</div>
{{else}}
<div> hai</div>
{{/if}}
</template>
CommonTemplate.js
Template.CommonTemplate.helpers({
isAdmin: function (val) {
var path = window.location.pathname;
var str = path.split("/");
return val === str[1];
}
})
The reason it's not rerunning is that your helper function has no reactive dependencies. Normal Javascript variables (like window.location.pathname) don't instruct reactive computations (like helper functions) to rerun when their values change.
The two easiest possibilities are:
Name your routes and use FlowRouter.getRouteName() in the helper function, which is reactive.
Add the line FlowRouter.watchPathChange() to your helper, which doesn't return anything, but does ensure the containing reactive function reruns whenever the path changes. FlowRouter API.
The other alternative is to simply use CSS. Have a look at meteor-london:body-class, which appends the route name as a class to the body, allowing you to selectively show or hide stuff based on route in your CSS.

Getting Exception in template helper: quickFormContext with aldeed:autoform

I'm having an issue with aldeed:autoform which I can't solve, nor understand what is the cause. The template:
<template name="staffCaseEdit">
{{> quickForm collection=Cases id="inserNewItem" type="insert"}}
</template>
I use aldeed:collection2 and aldeed:simple-schema to manage collections. So, I have the Case schema and Cases collection, both defined in /lib so they should be available on the client side, too.
Next, there's the route:
FlowRouter.route('/staff/case/:id', {
triggersEnter: [
AccountsTemplates.ensureSignedIn
],
subscriptions: function (params) {
this.register('theCase', Meteor.subscribe('theCase', params.id));
},
action: function (params, queryParams) {
return BlazeLayout.render('container', {
main: 'staffCaseEdit',
id: params.id
});
}
});
And of course, theCase is published:
Meteor.publish('theCase', function (id) {
return Cases.find({
id: Number(id)
});
});
In browser console, Cases are present:
> Cases
< Object
> Cases.find().count()
< 1
Which I suggest to be sufficient for the quickForm to consume the collection properly (it requires one as one of the arguments).
The problem is that, on the client side, I'm having an error
Exception in template helper: quickFormContext#http://localhost:3000/packages/aldeed_autoform.js?b0918af3b0bb0ae387d80c71f4230daca94cae11:6851:34
which I cannot trace. As a result, no form is being shown (actually, the whole DOM is left empty.
What should I look for? What could be the source of this particular problem?
A bit shooting in the dark here, I don't know if it will fix your problem but it will certainly help... Use this:
<template name="staffCaseEdit">
{{> quickForm collection="Cases" id="inserNewItem" type="insert"}}
</template>
You are passing the variable Cases to the collection parameter instead of passing it the name of the target collection "Cases"

Meteor #each loop ready

I wonder is there any way to know if #each loop is "ready". By "ready" I mean it rendered all nodes and inserted into the DOM. I don't even speak about onRendered callback (old rendered). I tried
<template name="myTemplate">
<div class="some-class">
{{#if Template.subscriptionsReady}}
{{#each messages}}
<div>{{text}}</div>
{{/each}}
<script>
$(".some-class").trigger("LOOP_READY")
</script>
{{/if}}
</div>
</template>
Template.myTemplate.onRendered(function(){
this.$(".some-class").on("LOOP_READY", doSomething)
})
But it does not work either. I don't want to use timers.
UPDATE
messages is a collection of text messages for current dialog, dialogId is a reactive var stored in the Session.
Template.myTemplate.onCreated(function(){
var self = this;
self.autorun(function(){
self.subscribe("messages", Session.get("dialogId"))
})
})
So if someone changes dialogId, myTemplate loads another dialog messages, and I want to know when this messages are ready, to scroll to a specific message. Simple onReady does not work, since I can not call scrollTop accurately before all messages are rendered and got its own height.
There's no easy way to get notified when a Spacebars {{#each}} block has done rendering into the DOM every item its spanning.
The best solution is to use another reactive computation (Tracker.autorun) to observe your messages cursor changes.
Everytime your messages list is modified, you can run arbitrary code after every other reactive computations are done performing whatever their job is, using Tracker.afterFlush.
The {{#each}} block is one of those computations, whose role is to listen to the reactive data source you give it as argument and rerender its Template.contentBlock as many times as items fetched from the source being iterated over, with the current item as current data context.
By listening to the exact same reactive data source as the {{#each}} block helper and running your code AFTER it has finished its own reactive computation, you can get the actual requested behavior without relying on some weird setTimeout tricks.
Here is the full implementation of this pattern :
HTML
<template name="myTemplate">
<div class="some-class">
{{#if Template.subscriptionsReady}}
{{#each messages}}
<div class="message">{{text}}</div>
{{/each}}
{{/if}}
</div>
</template>
JS
// declare your reactive data source once to reuse the same in multiple places
function messagesCursor(){
return Messages.find();
}
Template.myTemplate.helpers({
messages: messagesCursor
});
Template.myTemplate.onRendered(function(){
this.autorun(function(){
// we need to register a dependency on the number of documents returned by the
// cursor to actually make this computation rerun everytime the count is altered
var messagesCount = messagesCursor().count();
//
Tracker.afterFlush(function(){
// assert that every messages have been rendered
console.log(this.$(".messages") == messagesCount);
}.bind(this));
}.bind(this));
});
#saimeunt provided elegant solution, but I implemented it other way, maybe someone find it helpful too.
Since I don't want my code been executed everytime a new message arrives, I can not trigger scrolling after autorun is finished. (You would say, OK, do it in autorun that depends only on dialogId. - then I can not get exact message count of this dialog since subscription should take some time)
HTML
<template name="myTemplate">
<div class="chat">
{{#if Template.subscriptionsReady}}
<div class="messages">
{{#each messages}}
<div class="message">{{text}}</div>
{{/each}}
</div>
<script>$(".chat").trigger("MESSAGES_READY_EVENT")</script>
{{/if}}
</div>
</template>
JS
Template.chat.onCreated(function () {
var self = this;
self.messages = function() {
return Messages.find({dialogId:Session.get("dialogId")})
}
self.autorun(function(){
self.subscribe("messages", Session.get("dialogId"));
})
});
//helpers skipped
Template.chat.onRendered(function () {
var self = this;
self.$(".chat").on("MESSAGES_READY_EVENT", function () {
var computationNumber = 0;
var $messages = self.$(".messages");
//how many massages we have to render
var total = self.messages().count();
//max tries
var maxTries = 10;
var intervalId = Meteor.setInterval(function () {
var totalNodes = $messages.children().length;
if (computationNumber++ >= maxTries || total <= totalNodes) {
Meteor.clearInterval(intervalId);
$messages.scrollTop(someValue);
}
}, 100);
});
});

Meteor: use 2 different layouts (iron:router)

On the client I want to render two different layouts.
Router.route('/', {
template: 'register',
layoutTemplate: 'home'
});
Router.route('/main', {
layoutTemplate: 'layout'
});
In this case only the first Router.route function works. What seems to be the problem in this code example?
In the console, this error pops up:
Exception in defer callback: TypeError: Cannot read property 'handler' of undefined
Additionally I don't want users to access the 'main'-template if they are not logged in. The iron:router documentation doesn't provide me enough information on how to handle both these issues.
This is how your routes should look.
//Layout Configuration.
Router.configure({
layoutTemplate:"register"
});
Router.configure({
layoutTemplate:"layout"
});
//Simple Routes Config.
Router.route('/', {
layoutTemplate: 'register'
});
Router.route('/main', {
layoutTemplate: 'layout'
});
And The /client/views/layout.html
<template name="layout">
{{> yield}}
</template>
<template name="register">
{{> yield}}
</template>
I definitely think that the issue with your routing code above is the fact that you have not specified a value for the template attribute of your route with the path '/main'. At the very least, I would suggest that you define that attribute if nothing else for a given route. The first thing that iron-router will attempt to do to determine which template to use for the route is to look at the path of the URL, and that is not always obvious to determine for a given route. I would suggest defining your routes above like so:
Router.route('/', function() {
template: 'register',
layoutTemplate: 'home'
});
Router.route('/main', function() {
template: 'main',
layoutTemplate: 'layout'
});
With this routing logic defined, you would then also want to make sure to define your templates properly.
For your layout templates, do something like the following:
<template name="home">
{{> yield}}
</template>
<template name="layout">
{{> yield}}
</template>
The {{> yield}} parts are necessary here in the layout templates because that is where iron-router will render the template that you have defined for the route through the template attribute.
For your regular templates, do something like the following:
<template name="register">
<h1>Register</h1>
</template>
<template name="main">
<h1>Main</h1>
</template>
After doing this, do your routes work? Alternatively, you could use Route.configure to set global route attributes, or a series of RouteController objects to define your routes so that common routes can take advantage of common attribute definitions.
As for your second issue, I would suggest using one of the two following methods:
In order to define your described logic for just the '/main' route, do the following:
Router.route('/main', function() {
template: 'main',
layoutTemplate: 'layout',
onBeforeAction: function() {
if(!Meteor.userId()) {
this.render('register'); // Use either this one
this.redirect('register'); // or this one
} else {
this.next();
}
}
});
Going with this option will require that you add an additional attribute to your '/' route, name: 'register'. Alternatively you can directly reference the actual URL path of the desired route in the this.render() or this.redirect() function calls and then you do not have to define the name attribute for your templates. Personally, I prefer being obvious and open with my route definitions by giving them all names and referencing them using their names.
The second option is to define your desired logic globally so that it applies not only to the '/main' route, but to any other routes that you happen to define in the future as well. To do this, do the following:
Router.onBeforeAction(function() {
if(!Meteor.userId()) {
this.render('register'); // Use either this one
this.redirect('register'); // or this one
} else {
this.next();
}
},
{
except: ['register']
});
Once again, this option requires the definition of the name attribute for your '/' route, but you can do what I described above if you prefer not to do that. Finally, notice the except attribute definition at the end of this option. This notifies iron-router to not run this globally-defined logic for the array of specified routes. Obviously, you would not want to run this logic on the very page to which you are redirecting users who are not currently logged in, hence my except attribute definition above.
My guess is you need to define the template for the '/main' route as well--that would be what is undefined in your error. If you have a template with the name="main" attribute, I would need to see more code, particularly any template handlers.
A redirect could be accomplished with the onBeforeAction callback:
onBeforeAction: function() {
if (!Meteor.userId()) {
this.redirect('/');
} else {
this.next();
}
}

Categories

Resources