Meteor template data context not available in template.created upon refresh - javascript

I use the template data context inside a template's created function.
Template.temp.created = function() { console.log('this.data'); };
When I go to the page normally--i.e. click the link to the page--I see the console log the correct data object. When I hit the refresh button on the page, this.data is null.
Why?
Also, I am using iron-router to set the data context:
...
this.route('temp', {
...
data: function() { return MyCollection.findOne(someId); },
...
}
...

If you want to wait until data come then use waitOn.
this.route('temp', {
waitOn:function(){
return this.subscribe("nameOfPublishFunction");
},
data: function() { return MyCollection.findOne(someId); },
...
}
Remember to activate loading hook (thanks #Peppe L-G):
Router.onBeforeAction("loading");
IronRouter docs #waitOn
Update
Here you can find sample meteor app with iron:router package which shows how turning loading hook on and off ( Router.onBeforeAction("loading")) changes availability of data to created and rendered methods.

Related

Get script file outside of vue2 scope

I need to detect if adblocker is being used on my site, from googling around the most common method of checking this is to create a ads.js or adverts.js file and then display a message if that has been blocked.
This file has been set up however I am having trouble accessing this file when it needs to be outside of the vue scope in a vue componenet, any suggestions?
You might need to register independent js file's to the window object, then inside your component, you can call the object or function. For example, if your ads.js contain like as follow snippet
function block(){
// console.log('i am gonnna show this ')
}
In your vue component
var newData = new Vue({
el: '#app',
data: {
demo:'',
},
methods: {
fetchData: function (data) {
window.block()
}
},
created: function () {
window.block()
}
})
Like above the code you can achieve.

Trouble accessing data-context data in template from Iron Router

I have a template that has data getting passed into it through the Iron Router params here, it might be obvious what the template is being designed to do:
lib/routes.js
// custom reset password page for child user
Router.route('/reset-password/child/:_id', {
name: 'reset-child-password',
template: 'childResetPassword',
layoutTemplate: 'appLayout',
data: function() {
return Users.findOne({ _id: this.params._id });
}
});
But, when I try to access this child user data in the template I get errors saying this.data is undefined. or cannot read property 'profile' of undefined. Here are my helpers and template use of the helper.
client/templates/childResetPassword.html
...
<h3>Reset Password for {{childFirstName}}</h3>
<form id='childResetPassword'>
<div class="form-group">
<input type="password" name="new-child-password" class="form-control" value=''>
</div>
...
client/templates/helpers/childResetPassword.js
Template.childResetPassword.helpers({
childFirstName: function() {
console.log("Template helpers:");
console.log(this.data);
return this.data.profile.firstname;
}
});
Any thoughts on how to access the data-context passed with the iron router data callback? Am I using it incorrectly?
UPDATE (STILL NOT ANSWERED): I have verified that this particular user that I am passing into the template data-context is being found and that their profile is populated with a firstname property, and I'm still getting the same error.
Based on another question I found I tried this. I added a Template rendered callback function like this:
client/templates/helpers/childResetPassword.js
Template.childResetPassword.rendered = function() {
console.log(this);
};
I do see this.data containing the correct user object in the browser console, but my this.data.profile.firstname still fails, yet again, with the same console output error. If there something I need to do between the template rendering and the template helper?? SO CONFUSED!!!
You don't have to mention data ... you can just call this.profile.firstname. Your application already understands 'this' as the data object that is returned.
Template.childResetPassword.helpers({
childFirstName: function() {
return this.profile.firstname;
}
});
So, #Joos answer is not wrong, however after a lot more trial and error I found the solution for the meteor project I am working on.
My project (unbeknownst to me until I looked around more) has the meteor package autopublish removed. Therefore in order to access data in my collections I have to be subscribed to them. So the best place I put this subscription line is within my Router.route declaration for this template:
Router.route('/reset-password/child/:_id', {
name: 'reset-child-password',
template: 'childResetPassword',
layoutTemplate: 'appLayout',
waitOn: function() { // this is new new line/option i added to my route
return Meteor.subscribe('users');
},
data: function() {
if (this.ready()) {
var childUser = Users.findOne({_id: this.params._id});
if (childUser)
return childUser;
else
console.error("Child User not found", childUser);
}
else {
this.render("Loading");
}
}
});
So with that being said, if you still have autopublish package in your project and you intending to keep it, then #Joos answer is all you need to do.
However, if you DO intend to remove it, you need my router solution above, combined with making sure that you have published your users collection like this somewhere on the server:
server/publications.js
Meteor.publish("users", function () {
return Meteor.users.find();
});

Loading template not showing in Ember app

I am developing an Ember-cli app with ember 2.1.
I created a templates/loading.hbs and several other templates.
My foo template has a input that I use to send a queryParam to foo/bar.
foo/bar uses the query param to find its model:
export default Ember.Route.extend({
queryParams: {
q: {refreshModel: true}
},
model: function (params) {
if (params.q) {
return this.store.query('client', { q: params.q });
} else {
return {};
}
}
});
When I go from foo to foo/bar, the model gets loaded and foo/bar gets rendered correctly, but, during the loading time, I don't get the loading template. If I hit refresh in foo/bar, then I see the loading template.
Can you help me to understand this, and how can I make sure I always get the loading template.
Thanks!
If you are using liquid-fire, the current version breaks all loading substates. I have PR'd the main repository.
https://github.com/ef4/liquid-fire/pull/384
Until then, you can add the fork to your package.json:
"liquid-fire": "git#github.com:erkie/liquid-fire.git#breaking-loading-substates"

Meteor Iron router hooks being run multiple times

EDIT: Here is the github repo. And you can test the site here.
On the homepage, just open the browser console and you will notice that WaitOn and data are being run twice. When there is no WaitOn, then the data just runs once.
I have setup my pages by extending RouteController and further extending these controllers. For example:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldTemplates: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
// Authentication
onBeforeAction: function() {
if(_.isNull(Meteor.user())){
Router.go(Router.path('login'));
} else {
this.next();
}
}
});
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
console.log("from controller waitOn");
return Meteor.subscribe('userProfileVerification');
},
data: function() {
// If current user has verified email
console.log("from controller data start");
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
console.log("from controller data end");
return data;
}
});
On observing the console in the client, it seems the hooks are being run 2-3 times. And I also get an error on one of the times because the data is not available. The following is the console on just requesting the page once:
from controller waitOn
profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:44 from controller data start
debug.js:41 Exception from Tracker recompute function: Cannot read property 'phoneNumber' of undefined
TypeError: Cannot read property 'phoneNumber' of undefined
at ProfileController.extend.data (http://localhost:3000/lib/router/profileController.js?966260fd6629d154e38c4d5ad2f98af425311b71:46:62)
at bindData [as _data] (http://localhost:3000/packages/iron_controller.js?b02790701804563eafedb2e68c602154983ade06:226:50)
at DynamicTemplate.data (http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:219:50)
at http://localhost:3000/packages/iron_dynamic-template.js?d425554c9847e4a80567f8ca55719cd6ae3f2722:252:25
at null.<anonymous> (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2445:26)
at http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1808:16
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:2043:12)
at viewAutorun (http://localhost:3000/packages/blaze.js?efa68f65e67544b5a05509804bf97e2c91ce75eb:1807:18)
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:296:36)
at Tracker.Computation._recompute (http://localhost:3000/packages/tracker.js?517c8fe8ed6408951a30941e64a5383a7174bcfa:310:14)
from controller data start
from controller data end
from controller waitOn
from controller data start
from controller data end
Have I not used the controllers properly?
Without being able to see the rest of the code that you have defined that uses these route controllers (such as templates or route definitions), I cannot accurately speak to the reason for the data function being called multiple times. I suspect that you may be using the ProfileVerificationsController with multiple routes, in which case the data definition for this controller would be executed multiple times, one for each route that uses the controller. Since the data definition is reactive, as you browse through your application and data changes, this might be resulting in the code defined to be rerun.
As for your controller definitions, I would suggest making a few modifications to make the code more robust and bulletproof. First, the ProfileController definition:
ProfileController = RouteController.extend({
layoutTemplate: 'UserProfileLayout',
yieldRegions: {
'navBarMain': {to: 'navBarMain'},
'userNav': {to: 'topUserNav'},
'profileNav': {to: 'sideProfileNav'}
},
onBeforeAction: function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
this.redirect('login'); // Could do this as well
this.render('login'); // And possibly this is necessary
} else {
this.next();
}
}
});
Notice the first thing that I changed, yieldTemplates to yieldRegions. This typo would prevent the regions from your templates using this route controller to be properly filled with the desired subtemplates. Second, in the onBeforeAction definition, I would suggest checking not only whether or not the Meteor.user() object is null using Underscore, but also checking for whether or not it is undefined as well. The modification that I made will allow you to check both states of the Meteor.user() object. Finally, not so much a typo correction as an alternative suggestion for directing the user to the login route, you could use the this.redirect() and this.render() functions instead of the Router.go() function. For additional information on all available options that can be defined for a route/route controller, check this out.
Now for the ProfileVerificationsController definition:
ProfileVerificationsController = ProfileController.extend({
waitOn: function() {
return Meteor.subscribe('userProfileVerification');
},
data: function() {
if(this.ready()) {
var verifiedEmail = Meteor.user().emails && Meteor.user().emails[0].verified ? Meteor.user().emails[0].address : '';
var verifiedPhoneNumber = Meteor.user().customVerifications.phoneNumber && Meteor.user().customVerifications.phoneNumber.verified ? Meteor.user().customVerifications.phoneNumber.number : '';
var data = {
verifiedEmail: verifiedEmail,
verifiedPhoneNumber: verifiedPhoneNumber
};
return data;
}
}
});
Notice the one thing that I changed, which is to wrap all of your code defined in the data option for your controller with a if(this.ready()){}. This is critical when using the waitOn option because the waitOn option adds one or more subscription handles to a wait list for the route and the this.ready() check returns true only when all of the handles in the wait list are ready. Making sure to use this check will prevent any cases of data unexpectedly not being loaded yet when you are building up your data context for the route. For additional information on defining subscriptions for your routes/route controllers, check this out.
As a final suggestion, for your onBeforeAction option definition in your ProfileController, I would suggest moving this out into its own global hook like so:
Router.onBeforeAction(function() {
if(!Meteor.user()) {
Router.go(Router.path('login'));
} else {
this.next();
}
});
Defining this check in the global hook ensures that you don't have to worry about adding your ProfileController to all of your routes just to make sure that this check is run for all of them. The check will be run for every route every time that one is accessed. Just a suggestion, though, as you may have reasons for not doing this. I just wanted to suggest it since I make sure to do it for every Meteor app that I develop for additional security.

How to pass a function to a Meteor template?

I want to insert a Meteor template (a simple login form) but I want to control what happens after the form is submitted. Ideally, I'd like to be able to pass a function afterLogin() to the template. But I'm quite unsure how to do this and if this is even possible.
I've recently seen an interesting package viewmodel and I'm not sure how related it is. But the goal in this context is basically to render a view with a different view model.
Any ideas? I'm currently using a session variable and then after login, I check that session variable to run the correct function but this is ugly and not easy to work with. Any ideas?
This is how I do it :
I assume that your login form is called from within a parent template, use the attributes syntax to pass the value of a custom helper to the data context of the login form.
<template name="parent">
{{> loginForm options=loginFormOptions}}
</template>
The helper returns an object encapsulating a function, the caller is responsible for setting this function to whatever they want.
Template.parent.helpers({
loginFormOptions:function(){
return {
afterLogin:function(){
// we assert that the context is correct :
// this will print Template.loginForm
console.log(this.view.name);
}
};
}
});
Your login form code, acting as a library, can read from its data context the function that was passed by the caller template, and then call the function with the proper this context.
Template.loginForm.events({
"submit":function(event,template){
// ...
Meteor.loginWithPassword(...,function(error){
if(error){
console.log(error);
return;
}
// guard against the existence of the custom afterLogin function
if(template.data.options && template.data.options.afterLogin){
// execute the custom function with proper context
template.data.options.afterLogin.call(template);
}
});
}
});
First of all, I would use Iron Router for navigating through different views of my application, you can get it here:
https://github.com/EventedMind/iron-router
meteor add iron:route
Then, check http://docs.meteor.com/#template_events. I would use something like:
Template.loginFormTemplate.events({
'click .loginButton': function() {
//... if success login ...
Router.go('nextScreen');
}
});
[Update 1]
I am afraid that trying to pass function to Route is an ugly approach in a sense of Meteor architecture.
You can try though, defining some Global variable, which is responsible for listening and forward-triggering events across the Routes
var eventHelper = (function () {
var self = _.extend({
afterLogin: function () {
self.trigger('forwardedEvent');
}}, Backbone.Events);
return self;
})();
Route1.events({
'click': function () {
//... Let's call after login
eventHelper.afterLogin();
}
});
eventHelper.on('forwardedEvent',function() {
// ...
});

Categories

Resources