meteor iron-router and dynamic template rendering - meteor

today i decided to migrate my main app from package router to iron-router.
My app was able to do dynamic template rendering :
some template are rendered programatically because it depends of some info from Mongo
That was quite easy with router package but with iron-router i fall into problems.
When i do a
Template.myTplName(data)
it always return a
TypeError: Object #<Object> has no method 'fn'
To get more logs i did this in the console :
try{
Template.myTplName(data);
} catch(e) {
console.trace();
}
And i get this :
You called Router.path for a route named undefined but that that route doesn't seem to exist. Are you sure you created it?
console.trace() utils.js:169
Utils.warn utils.js:169
IronRouter.path router.js:178
(anonymous function) helpers.js:49
apply evaluate-handlebars.js:241
invoke evaluate-handlebars.js:266
(anonymous function) evaluate-handlebars.js:330
Spark.labelBranch spark.js:1124
branch evaluate-handlebars.js:320
(anonymous function) evaluate-handlebars.js:329
_.each._.forEach underscore.js:79
template evaluate-handlebars.js:323
Handlebars.evaluate evaluate-handlebars.js:377
(anonymous function) evaluate-handlebars.js:11
(anonymous function) deftemplate.js:157
Spark.isolate spark.js:859
(anonymous function) deftemplate.js:154
Spark.createLandmark spark.js:1163
(anonymous function) deftemplate.js:135
Spark.labelBranch spark.js:1124
partial deftemplate.js:134
(anonymous function) VM28316:2
InjectedScript._evaluateOn VM28186:603
InjectedScript._evaluateAndWrap VM28186:562
InjectedScript.evaluate VM28186:481
console.trace() VM28316:2
(anonymous function) VM28316:2
InjectedScript._evaluateOn VM28186:603
InjectedScript._evaluateAndWrap VM28186:562
InjectedScript.evaluate
So in fact when i run that command, iron-router seems to try to handle it. But this template is not named in its stack so it might be a problem. But i donit want the router to manage that part.
Maybe i should change the conception of that part of the app but i would prefer not to have to.
Here is the code of the parent template:
<template name="resultsList">
{{#if selectedSearch}}
{{#if itemCount}}
<div class="row">
{{#each items}}
<div id="item_{{_id}}">{{> myitem}}</div>
{{/each}}
</div>
{{> loadMore}}
{{/if}}
</template>
Now the code of the template :
<template name="myitem">
{{#if currentUser}}
<div class="btn-toolbar">
<div class="btn-group" style="margin-left: 20px;">
{{#if hasDetail}}
<button class="btn btn-sm btn-success"><span class="glyphicon glyphicon-zoom-in"></span></
button>
{{else}}
<!-- fake button that just take the place of the detail button -->
<button class="btn btn-sm btn-link" style="cursor:default;text-decoration:none;margin-left:12px;"></button>
{{/if}}
<button {{#if starSelected}}disabled="disabled"{{/if}} class="btn btn-sm btn-info StarItem" title="A
appeler"><span class="glyphicon glyphicon-phone"></span></button>
<button {{#if visitedSelected}}disabled="disabled"{{/if}} class="btn btn-sm btn-info VisitItem" title="A
visiter"><span class="glyphicon glyphicon-road"></span></button>
<button {{#if removedSelected}}disabled="disabled"{{/if}} class="btn btn-sm btn-warning DelItem"
title="Supprimer"><span class="glyphicon glyphicon-remove icon-white"></span></button>
</div>
</div>
{{/if}}
<div class="infos">
{{{getItemInfo}}} <!-- This one will call the Template.myItem(data) -->
</div>
<div class="centerContentImage">
{{#if image}}
<img src="/img/annonces/{{_id}}" />
{{#if notCurrentUserAndHasDetail}}<button class="btn btn-info Openitem"><span class="glyphicon glyphicon-
zoom-in"></span></button>{{/if}}
{{else}}
{{else}}
<img src="/img/no_photo_icon.jpg" />
{{#if notCurrentUserAndHasDetail}}<button class="btn btn-info OpenDetailItem"><span class="glyphicon glyphicon-
zoom-in"></span></button>{{/if}}
{{/if}}
</div>
</template>
And the code of the JS :
So how can i do this ?
Thanx

ok, thanks to #ChristianFritz i looked more at the template, and in fact i found that Iron-Router provide a {{link}} helper. The problem is that my Json Object has a property named 'link', Iron-Router is not able to make the difference and it generates an error.
The Error is not really explicit
You called Router.path for a route named undefined but that that route doesn't seem to exist. Are you sure you created it?
But when using Iron Router we should have a look at the provided helpers because their naming is not using any kind of Namespace that would prevent that kind of problem.

Related

Nested #Each values are not available outside of the context

I'm having a nested each pair like this:
{{#each goal in goals}}
<template name="task">
{{#each goal in goals}}
{{#each task in relatedTasks goal}}
<li>
<span class="text task"><strong>{{task.taskName}}</strong> to {{goal.goalName}}<br> taskid: {{task._id}}
{{task.taskPostpone}}</span>
{{#afModal class="btn btn-primary" collection="Tasks" operation="update" doc=task._id}}
Update {{task.taskName}}
{{/afModal}}
</li>
{{/each}}
{{/each}}
</template>
and would like to get the value of the task._id in my client.js like here:
Template.task.events({
'click .task': function() {
Session.set("selectedTask", this._id);
//console.log(this._id);
//console.log(goal._id);
console.log(task._id);
//console.log('Click event happened: this._id saved as selectedItem Session variable.');
}
});
When I click on a task I receive this error on the console: "undefined" and I don't really understand the reason behind. I did some research and found a possible solution: Maybe 'click .task': function(task) should receive the task context or input so it could be able to grasp the meaning of this._id.
I have a {{#afModal doc=task._id}} which also should receive the value of task._id and does not seem to work, although it is placed in the right context I think.
I have a feeling that the two issues are related somehow.
The problem is that the {{#each goal in goals}} loop syntax does not change the data context within the loop (see docs). It simply adds a goal variable for use in your spacebars template.
One solution would be to move the contents of the {{#each task in ...}} loop to another template like so.
<template name="task">
{{#each goal in goals}}
{{#each task in relatedTasks goal}}
{{> goalTask goal=goal task=task}}
{{/each}}
{{/each}}
</template>
<template name="goalTask">
<li>
<span class="text task">
<strong>{{task.taskName}}</strong>
to {{goal.goalName}}<br>
taskid: {{task._id}} {{task.taskPostpone}}
</span>
{{#afModal class="btn btn-primary" collection="Tasks" operation="update" doc=task._id}}
Update {{task.taskName}}
{{/afModal}}
</li>
</template>
Your event handler would then look like this.
Template.goalTask.events({
'click .task': function() {
console.log(this.goal._id);
console.log(this.task._id);
}
});
This is a common problem with events in nested objects, how to get the data context of the object that was clicked on?
The simplest way to approach this problem is to create a template for each level of nesting. The proper context is then provided automatically.
<template name="goals">
{{#each goals}}
{{#each task}}
{{> task}}
{{/each}}
{{/each}}
</template>
<template name="task">
<li>
<span class="text task"><a href="#modal-taskedit" data-toggle="modal">
<strong>{{task.taskName}}</strong></a> to {{goal.goalName}}<br>
taskid: {{task._id}}{{task.taskPostpone}}</span>
{{#afModal class="btn btn-primary" collection="Tasks" operation="update" doc=_id}}
Update {{task.taskName}}
{{/afModal}}
</li>
</template>
Then your event handler can be defined on the inner task template with the data context automatically being the individual task and not the data context of the outer template.
Template.task.events({
'click .task': function(){
Session.set("selectedTask", this._id);
}
});

Meteor Flow Router issue with update

I have a Meteor app and I'm transitioning from IronRouter to FlowRouter. So far so good, but there are aspects I don't understand yet.
I have a route as follows:
FlowRouter.route('/documents/:docId/edit', {
name: 'documentEdit',
subscriptions: function (params, queryParams) {
this.register('documentEdit', Meteor.subscribe('documentSingle', params.docId));
},
action: function (params, queryParams) {
BlazeLayout.render('layout', { top: 'header', main: 'documentEdit' });
},
});
First option:
Then I also have a template:
<template name="documentEdit">
<div class="container">
<h1>Edit document</h1>
{{#if isReady 'documentEdit'}}
{{#autoForm collection="Documents" doc=this id="documentForm" type="update" meteormethod="documentUpdateMethod"}}
<fieldset>
{{> afQuickField name='title'}}
{{> afQuickField name='content' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Update</button>
<a class="btn btn-link" role="button" href="{{pathFor 'documentsList'}}">Back</a>
{{/autoForm}}
{{/if}}
</div>
</template>
with a template helper as follows:
Template.documentEdit.helpers({
isReady: function(sub) {
if(sub) {
return FlowRouter.subsReady(sub);
} else {
return FlowRouter.subsReady();
}
}
});
This is as it is mentioned here, but I'm not getting the values pre-filled in the textboxes on the UI (which is normal when editing fields).
Second option:
When I do the following it works and I don't really understand why it works (found it browsing in different forums and tried it out):
<template name="documentEdit">
<div class="container">
<h1>Edit document</h1>
{{#with getDocument }}
{{#autoForm collection="Documents" doc=this id="documentForm" type="update" meteormethod="documentUpdateMethod"}}
<fieldset>
{{> afQuickField name='title'}}
{{> afQuickField name='content' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Update</button>
<a class="btn btn-link" role="button" href="{{pathFor 'documentsList'}}">Back</a>
{{/autoForm}}
{{/with}}
</div>
</template>
and the helper:
Template.documentEdit.helpers({
getDocument: function () {
return Documents.findOne();
}
});
So the questions are:
for the 1st option: any idea why it does not work. I would prefer that one as it's the documented way of doing things
for the 2nd option: not sure why I need (in the template helper) to do a Document.findOne() without even having to pass the id of the doc I want to edit:
You want to do template level subscriptions with Flow Router, that's one of the primary pattern changes.
So you'd do:
Setup the subscription at the template level. Autorun so it'll resubscribe on route changes.
Template.documentEdit.onCreated(function() {
var self = this;
this.autorun(function() {
var docId = FlowRouter.getParam('docId');
self.subscribe('documentSingle', docId));
};
};
Setup the template helper to pick up from the route, and grab the id and populate the helper/document.
Template.documentEdit.helpers({
getDocument: function () {
var docId = FlowRouter.getParam('docId');
var doc = Documents.findOne(docId) || {};
return doc;
}
});
Do a template level load check and if it's there render it, otherwise show loading...
<template name="documentEdit">
<div class="container">
<h1>Edit document</h1>
{{#if Template.subscriptionReady}}
{{#with getDocument }}
{{#autoForm collection="Documents" doc=this id="documentForm" type="update" meteormethod="documentUpdateMethod"}}
<fieldset>
{{> afQuickField name='title'}}
{{> afQuickField name='content' rows=6}}
</fieldset>
<button type="submit" class="btn btn-primary">Update</button>
<a class="btn btn-link" role="button" href="{{pathFor 'documentsList'}}">Back</a>
{{/autoForm}}
{{/with}}
{{else}}
Loading...
{{/if}}
</div>
</template>
Not knowing how this worked, seeing that all the tutorials I have read that deals with update used Iron router, I have spent 7 days worth of trying retrying, looking through others code, reading tutorials. Happy it now works.

Template helper not invoked on update aldeed autoform

I am using meteoric-autoform and doing an update to my values, The form is not getting populated with existing values and also the update is not happening.
I could also see that the template helper method that I have for fetching the id is not getting invoked at all.
assesmentEdit.js
Template.assesmentEdit.helpers({
assesment: function () {
alert("entered helper");
console.log(template.data.id);
var template = Template.instance();
return Assesments.findOne({_id: template.data.id});
}
});
assesmentEdit.html
<template name="assesmentEdit">
{{#ionModal customTemplate=true}}
{{# autoForm collection="Assesments" id="assesments-edit-form" type="update"}}
<div class="bar bar-header bar-stable">
<button data-dismiss="modal" type="button" class="button button-clear">Cancel</button>
<h2 class="title">Edit Assesment</h2>
<button type="submit" class="button button-positive button-clear">Save</button>
</div>
<div class="content has-header overflow-scroll">
{{> afQuickField name="name" }}
{{> afQuickField name="email"}}
{{> afQuickField name="category"}}
{{> afQuickField name="location"}}
</div>
{{/autoForm}}
{{/ionModal}}
</template>
As you can see in the docs, update forms require a doc attribute.
{{# autoForm doc=assesment collection="Assesments" id="assesments-edit-form" type="update"}}

How to reload (re render) template in Meteor

I am working on a chat app that supports multiple languages. The basics of the app is working fine however I have small problem when changing chat rooms.
Each room is setup to support 2 languages. When sending a chat message you can choose which language you are sending the message in. This is done by clicking on a small icon of a flag which toggles between the 2 flag icons. The template for this is:
<template name="chatBar">
<div id="chatBar" class="input-append input-prepend">
<form id="chatForm">
<span class="add-on"><img src="/images/flags-iso/flat/16/{{chatMessageLang}}.png" id="lang"></span>
<input id="chatMessageLanguage" type="hidden" value="{{chatMessageLang}}">
<input id="chatMessageTarget" type="hidden" value="{{chatMessageTargetLang}}">
<input class="input-xxlarge" id="chatMessage" type="text" placeholder="← choose your language and then type your message here…">
<button class="btn" id="sendNewMessage" type="submit">Send</button>
</form>
</div>
and is called from another template:
<template name="chat">
{{> chatroom}}
<div id="chatWindow">
<ul>
{{#each chats}}
<li class="{{messageClass}} {{_id}}">
<img src="/images/flags-iso/flat/16/{{langOriginal}}.png">
{{langTo.flagicon}}
{{user}}:
<span class="message" lang="{{langOriginal}}">{{original}}</span>
{{#if translated}}
<span class="message" lang="{{langTranslated}}">{{translated}}</span>
{{/if}}
{{#if correction}}
<i class="icon-remove"></i>
<span class="friendCorrection">{{correction}}</span>
{{/if}}
<span class="messageOptions">{{{messageOptions}}}</span>
</li>
{{/each}}
</ul>
</div>
{{> chatBar}}
</template>
The problem I have is when I change chatrooms the language options are being retained from the previous chatroom I was in. The code for setting the languages for the chatbar is:
Template.chatBar.helpers({
chatMessageLang: function() {
//return getCurrentChatroomInfo().langFrom.iso;
return Meteor.user().profile.langNative;
},
chatMessageTargetLang: function() {
var chatroomLang1 = getCurrentChatroomInfo().langFrom.iso;
var chatroomLang2 = getCurrentChatroomInfo().langTo.iso;
if(Meteor.user().profile.langNative == chatroomLang1){
return chatroomLang2;
} else {
return chatroomLang1;
}
}});
function getCurrentChatroomInfo(){
return Chatrooms.findOne({_id : Router.current().params['_id']})
}
How do I make the variable chatMessageTargetLang reactive to the loaded chatroom (each chatroom has their own route).
Thanks
Adam

Getting the outer context of a Meteor Template

I have the following use-case: There are rooms which have beds inside. (Bummer...)
There is a loop of rooms which uses a template "room".
<template name="rooms">
{{#each availableRooms}}
{{> room}}
{{/each}}
</template>
This template gets for each iteration a room. This is accessible by this.
<template name="room">
<div class="room-outer">
<button type="button" class="btn" data-toggle="collapse" data-target="#list-{{_id}}">
{{name}} : {{getBeds this}} beds free.
</button>
<div id="list-{{_id}}" class="collapse in room-inner">
{{#each guests_id}}
<div class="bed">
<div class="blanket">
{{showUser this}}
</div>
</div>
{{/each}}
</div>
</div>
</template>
Now I like to calculate some special value which I do by extending the template. I need now to pass the this variable to the getBeds function. Is it possible to do this by grabing outside the template and get the room into the function?
Template.room.getBeds = function (room) {
if (room.guests_id)
return room.beds - _.size(room.guests_id);
else
return room.beds;
};
Basically I don't want to have to write {{getBeds this}} but only {{getBeds}}
Shouldn't this work?
Template.room.getBeds = function () {
if (this.guests_id)
return this.beds - _.size(this.guests_id);
else
return this.beds;
};
See the docs:
Helpers can take arguments, and they receive the current template data in this:

Resources