I am trying to access the data context of my Template from an event handlers, but it is null.
Here is my Template:
<template name="calendar">
<div class="calendar">
{{#with calendar}}
<h1>{{name}}</h1>
{{#each days}}
<div class="calendar-day">
{{this.date.getDate}}
<!-- I want to access the data context when this div is pressed -->
</div>
{{/each}}
{{/with}}
</div>
</template>
Here is where I want to get the data context.
Template.calendar.events({
'click .calendar-day': function(e, template) {
console.log(Template.currentData()); // null
console.log(Template.parentData()); // null
}
});
Update: I am able to access the current data context through this, but I also want to access the data context of the parent, namely, the data context of calendar
OK. Let me make it clear;
Template.calendar.events({
'click .calendar-day': function(e, template) {
//FYI: template === Template.instance()
//will give you your data context
console.log(this);
//will give you your data context
console.log(template.data);
//will give you your data context
console.log(Template.currentData());
//will give you your data context also
console.log(Template.parentData(0));
//will give you your parent template data context. Equivalent of Template.parentData()
console.log(Template.parentData(1));
}
});
If it turns out that your parentData is null then IT IS null, double check it.
you can get the context from this.
Template.calendar.events({
'click .calendar-day': function(e, template) {
console.log(this);
}
});
It's possible there is a bug in Blaze - github has 3 or 4 open issues which relate to this, so I haven't raised a specific issue myself.
The issue is with having multiple data contexts within a single template; this fix is to split the contexts by splitting the template:
<template name="calendar">
<div class="calendar">
{{#with calendar}}
<h1>{{name}}</h1>
{{#each days}}
{{> calendarDay}}
{{/each}}
{{/with}}
</div>
</template>
<template name="calendarDay">
<div class="calendar-day">
{{this.date.getDate}}
<!-- I want to access the data context when this div is pressed -->
</div>
</template>
And then just move your event to the new template:
Template.calendarDay.events({
'click .calendar-day': function(e, template) {
console.log(Template.currentData()); // no longer null!
console.log(Template.parentData()); // no longer null!
}
});
Within this event, you can now get to the following data contexts:
console.log(this); // the {{#each days}} context
console.log(template.data); // the {{#each days}} context
console.log(Template.currentData()); // the {{#each days}} context
console.log(Template.parentData(0)); // the {{#each days}} context
console.log(Template.parentData(1)); // the {{#with calendar}} context
By using this pattern as described I've managed to solve an identical problem to the OP, but only after 2-3 hours of investigation!
Related
Sorry if this is a really obvious questions but I have the following routes:
Web.Router.map(function () {
this.resource('orders', function(){
this.resource("order", {path:":order_id"});
});
});
And for my orders template I have something like:
<div class="someclass">
{{outlet}}
</div>
And what I want todo is:
{{#if onOrderRoute}}
<div class="someclass">
{{outlet}}
{{else}}
<div class="someotherclass">
{{/if}}
</div>
I was wondering what the best way of doing this is, or am I mising something?
There are multiple ways to accomplish this. The view has a layoutName property you can use to specify your layout. Another option is to specify a property on your child view, and then your template can bind to that by using the view property.
For example:
Web.OrderView = Ember.View.extend({
childView: true
);
Then, in your template you bind to view.childView
{{#if view.childView}}
<!-- code goes here -->
{{/if}}
Further, you can even create a mixin and then just inject that mixin into every view.
Web.ChildViewMixin = Ember.Mixin.create({
childView: true
});
Web.ChildView = Ember.View.extend(ChildViewMixin, {
});
In my meteor project, I have added iron:layout, iron:dynamic-template along with iron:router.
My question is, how can you prevent the dynamic template from rendering if there is no data available in the Session? The reason is, the dynamic template is currently being rendered with all html content within it except for data context. This is the problem when the user initially arrives onto the page.
I have a list of names on 'postlist' template. These are 'usernames' of the person who created the post. When a user clicks on the name, the template 'viewpost' is rendered with the relevant data passed...that is fine. But as stated earlier, there is no data context when the user first arrives onto the page. So the see all the content except for the dynamic content.
The following is my current code, with help received from my previous post. Meteor: Render template inside a template
HTML:
<template name="postlist">
<div class="container">
<div class="col-sm-3">
{{#each post}}
<li>{{fullname}}</li>
{{/each}}
</div>
</div>
{{> Template.dynamic template='viewpost' data=currentPost}}
</template>
Click event to capture post _id / helper file:
Template.postlist.helpers({
currentPost: function(){
return Posts.findOne(Session.get('currentPost'));
}
});
Template.postlist.events({
'click li': function(e){
e.preventDefault();
Session.set("currentPost", this._id);
}
});
This is one alternative hack method but understand is not good practice. This is what I am effectively wanting to achieve so you get an idea. But I would like non-hack suggestions for this issue. Thank you.
html:
<template name="viewpost">
{{#if hasData}}
<div class="container">
Post creator is : {{username}} - Info: {{body_text}}
</div>
{{/if}}
</template>
js:
Template.viewpost.helpers({
"hasData":function(){
return Session.get("currentPost");
}
});
I'm new to Ember and am finding some of their concepts a bit opaque. I have a app that manages inventory for a company. There is a screen that lists the entirety of their inventory and allows them to edit each inventory item. The text fields are disabled by default and I want to have an 'edit item' button that will set disabled / true to disabled / false. I have created the following which renders out correctly:
Inv.InventoryitemsRoute = Ember.Route.extend({
model: function(params) {
return Ember.$.getJSON("/arc/v1/api/inventory_items/" + params.location_id);
}
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" data-id=id}}>edit item</button>
<button {{action "saveInventoryItem" data-id=id}}>save item</button>
</div>
{{/each}}
</script>
So this renders in the UI fine but I am not sure how to access the specific model to change the text input from disabled/true to disabled/false. If I were just doing this as normal jQuery, I would add the id value of that specific model and place an id in the text input so that I could set the textfield. Based upon reading through docs, it seems like I would want a controller - would I want an ArrayController for this model instance or could Ember figure that out on its own?
I'm thinking I want to do something like the following but alerting the id give me undefined:
Inv.InventoryitemsController=Ember.ArrayController.extend({
isEditing: false,
actions: {
editInventoryItem: function(){
var model = this.get('model');
/*
^^^^
should this be a reference to that specific instance of a single model or the list of models provided by the InventoryitemsRoute
*/
alert('you want to edit this:' + model.id); // <-undefined
}
}
});
In the Ember docs, they use a playlist example (here: http://emberjs.com/guides/controllers/representing-multiple-models-with-arraycontroller/) like this:
App.SongsRoute = Ember.Route.extend({
setupController: function(controller, playlist) {
controller.set('model', playlist.get('songs'));
}
});
But this example is a bit confusing (for a couple of reasons) but in this particular case - how would I map their concept of playlist to me trying to edit a single inventory item?
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled="true"}}</p>
<p>{{input type="text" value=detail disabled="true"}}</p>
<button {{action "editInventoryItem" this}}>edit item</button>
<button {{action "saveInventoryItem" this}}>save item</button>
</div>
{{/each}}
</script>
and
actions: {
editInventoryItem: function(object){
alert('you want to edit this:' + object.id);
}
}
Is what you need. But let me explain in a bit more detail:
First of all, terminology: Your "model" is the entire object tied to your controller. When you call this.get('model') on an action within an array controller, you will receive the entire model, in this case an array of inventory items.
The {{#each}} handlebars tag iterates through a selected array (by default it uses your entire model as the selected array). While within the {{#each}} block helper, you can reference the specific object you are currently on by saying this. You could also name the iteration object instead of relying on a this declaration by typing {{#each thing in model}}, within which each object would be referenced as thing.
Lastly, your actions are capable of taking inputs. You can declare these inputs simply by giving the variable name after the action name. Above, I demonstrated this with {{action "saveInventoryItem" this}} which will pass this to the action saveInventoryItem. You also need to add an input parameter to that action in order for it to be accepted.
Ok, that's because as you said, you're just starting with Ember. I would probably do this:
<script type="text/x-handlebars" data-template-name="inventoryitems">
{{#each}}
<div class='row'>
<p>{{input type="text" value=header disabled=headerEnabled}}</p>
<p>{{input type="text" value=detail disabled=detailEnabled}}</p>
<button {{action "editInventoryItem"}}>edit item</button>
<button {{action "saveInventoryItem"}}>save item</button>
</div>
{{/each}}
</script>
with this, you need to define a headerEnabled property in the InventoryitemController(Note that it is singular, not the one that contains all the items), and the same for detailEnabled, and the actions, you can define them also either in the same controller or in the route:
App.InventoryitemController = Ember.ObjectController.extend({
headerEnabled: false,
detailEnabled: false,
actions: {
editInventoryItem: function() {
this.set('headerEnabled', true);
this.set('detailEnabled', true);
}
}
});
that's just an example how you can access the data, in case the same property will enable both text fields, then you only need one, instead of the two that I put . In case the 'each' loop doesn't pick up the right controller, just specify itemController.
I'm having trouble understanding exactly what is available as my context in a template invoked by Meteor with Iron-Router – and how these things inherit.
Here are all the potential sources for "stuff I can refer to inside double curly braces" that I can think of:
Built-in helpers
Handlebars.registerHelper(...)
Template.myTemplate.myVar/myHelper = ...
Template.myTemplate.helpers({ ... })
data: { ... } inside route (Router.map)
Something to do with #each?
Something to do with #with?
Have I forgotten anything? Are there any global variables?
I guess I'm a bit confused about what the standard way is of giving the template a context in the first place. Also about what happens inside control structures such as #each and #with.
A clarification would be great!
IronRouter renders your template with the result of RouteController.data as the current data context.
<template name="viewPost">
<div>
<h1>{{title}}</h1>
<p>{{content}}</p>
</div>
</template>
var PostsController=RouteController.extend({
template:"viewPost",
waitOn:function(){
return Meteor.subscribe("postsById",this.params._id);
},
data:function(){
return Posts.findOne(this.params._id);
}
});
this.route("viewPost",{
path:"/posts/:_id",
controller:PostsController
});
In this example, IronRouter will render the "viewPost" template with the post having this.params._id as data context.
What is the standard way of giving a template a context in the first place ?
There is 2 ways :
{{#with myContext}}
{{> myTemplate}}
{{/with}}
{{> myTemplate myContext}}
As you can see, the #with control structure sets the current data context.
The #each structure iterates over a Cursor (or an Array) and sets the current data context to the current fetched document (or the current cell).
<template name="postsList">
{{#each posts}}
<h1>{{title}}</h1>
{{/each}}
</template>
Template.postsList.helpers({
posts:function(){
// cursor
return Posts.find();
// array
return [{title:"Title 1"},{title:"Title 2"},{title:"Title 3"}];
}
});
UPDATE : Could you possibly add a note about inheritance ? For instance, if I have nested #each blocks, do variables cascade ?
I came up with this example :
<template name="parent">
<ul>
{{#each parentContexts}}
{{> child}}
{{/each}}
</ul>
</template>
<template name="child">
<li>
<ul>
{{#each childContexts}}
{{> content}}
<p>../this.parentProperty = {{../this.parentProperty}}</p>
{{/each}}
</ul>
</li>
</template>
<template name="content">
<li>
<p>this.childProperty = {{this.childProperty}}</p>
<p>this.parentProperty = {{this.parentProperty}}</p>
</li>
</template>
Template.parent.helpers({
parentContexts:function(){
return [{
parentProperty:"parent 1"
},{
parentProperty:"parent 2"
}];
}
});
Template.child.helpers({
childContexts:function(){
return [{
childProperty:"child 1"
},{
childProperty:"child 2"
}];
}
});
If you run this example, you'll notice that you can't access the parentProperty in "content" because the default #each helper OVERRIDES the parent data context with the new context provided.
You can access the parentProperty in the nested #each block using this syntax : ../this.parentProperty, which is reminiscent of the UNIX parent directory access syntax.
However you cannot use this syntax in the "content" template, because it is agnostic of the nested each structures it was called from : you can only use the ../../parent syntax in a template where the actual nesting happens.
If we want to access the parentPropery in the content template, we must augment the current data context with the parent context.
To do so, we can register a new #eachWithParent helper like this :
Handlebars.registerHelper("eachWithParent",function(context,options){
var parentContext=this;
var contents="";
_.each(context,function(item){
var augmentedContext=_.extend(item,parentContext);
contents+=options.fn(augmentedContext);
});
return contents;
});
Now if you replace the nested #each with this new helper, you will have access to parentProperty in "content".
I added the example application.
http://jsfiddle.net/Sly7/amG56/
Js:
App = Ember.Application.create();
App.ApplicationController = Ember.ArrayController.extend({
selectedBook: null
});
App.ApplicationView = Ember.View.extend({
actions: {
selectBook: function(book) {
this.get('controller').set("selectedBook", book);
},
cancel: function(book) {
alert(book);
}
}
});
App.Book = Em.Object.extend({
name: null
});
Template:
<script type="text/x-handlebars">
{{#each book in books}}
<a {{action "selectBook" book target="view"}} href="#">select {{book.name}}</a><br />
{{/each}}
<hr />
Selected Book: {{selectedBook.name}}
<br />
<a {{action "cancel" selectedBook target="view"}} href="#">cancel selected book</a>
</script>
Select one of the books. You will see that name of the book will be displayed. But the "cancel selected book" link does not work.
I think the problem is context of the action helper does not change when a book is selected.
How do I implement an action helper which has a changing context? Or is it a bug?
The answer is in the guides
http://emberjs.com/guides/templates/actions/#toc_action-parameters
And the context is lazily evaluated, so the problem does not occur anymore
DEPRECATED ANSWER BELOW
The problem here is that the action helper is interpreted with the selectedBook context. But at this time, selectedBook is null. So when clicking on the link, even if you previously select a book, it's too late, for the registered action, the context is still null.
As a workaround, you can enclose this with a {{with}} block.
{{#with selectedBook}}
Selected Book: {{name}}
<br />
<a {{action cancel this target="view"}} href="#">cancel selected book</a>
{{/with}}
see: http://jsfiddle.net/x82dr/17/
BTW, you can see the code of the ApplicationView, where I access the application controller, using the controller property. With Ember.js convention, the controller is injected to the view when the application initialize
UPDATE: The use of the {{with}} helper seems to be not mandatory now, see: https://github.com/emberjs/ember.js/issues/1150