I've just begun using the templates:tabs package for Meteor. By default, this generates a custom template that is inserted as {{#basicTabs tabs=tabs}}.
I have made a simple app to understand how this kind of custom template works, and in particular to understand how this is different from using {{> custom}}
HTML:
<body>
{{> parent1}}
{{> parent2}}
</body>
<template name="parent1">
<h1>Parent 1</h1>
{{> child}}
</template>
<template name="parent2">
<h1>Parent 2</h1>
{{#child}}
<h2>Stuff inside "child" tag</h2>
{{/child}}
</template>
<template name="child">
<button type="button">Child button</button>
</template>
JS:
if (Meteor.isClient) {
Template.child.events({
'click button': function (event, template) {
console.log("child", event, template)
}
});
Template.parent1.events({
'click button': function (event, template) {
console.log("parent1", event, template)
}
});
Template.parent2.events({
'click button': function (event, template) {
console.log("parent2", event, template)
}
});
}
I don't see any of the Stuff inside the "child" tag appearing. When I click on a button, I can see in the browser console that both the parent and the child templates can react to the input, but this happens in both cases.
How should I be using the {{#custom}} ... {{/custom}} syntax?
EDIT
Here are updated files that show how the {{#child}} block works, in connection with events and helpers:
<body>
{{> parent1}}
{{> parent2}}
</body>
<template name="parent1">
<h1>Parent 1</h1>
{{> child}}
</template>
<template name="parent2">
<h1>Parent 2</h1>
{{#child}}
<h2>Stuff inside "child" tag</h2>
{{else}}
<h3>Other stuff</h3>
{{/child}}
</template>
<template name="child">
{{extras}}
{{> Template.contentBlock}}
{{> Template.elseBlock}}
<button type="button">Child button</button>
</template>
JS:
if (Meteor.isClient) {
Template.child.events({
'click button': function (event, template) {
console.log("child", event, template)
}
});
Template.parent1.events({
'click button': function (event, template) {
console.log("parent1", event, template)
}
});
Template.parent2.events({
'click button': function (event, template) {
console.log("parent2", event, template)
}
});
Template.child.helpers({
extras: function () {
return "Child extras"
}
})
Template.parent1.helpers({
extras: function () {
return "Parent 1 extras"
}
})
Template.parent2.helpers({
extras: function () {
return "Parent 2 extras"
}
})
}
Output with Meteor 1.2.0.2:
Parent 1
Child extras [Child button]
Parent 2
Child extras
Stuff inside "child" tag
Other stuff
[Child button]
It seems that the only thing you're lacking here is Template.contentBlock, i.e.
<template name="child">
<button type="button">Child button</button>
{{> Template.contentBlock}}
</template>
You can think of it as a helper capable of rendering whatever the user of your child template puts within the {{#child}}..{{/child}} block.
Back to your question, the main difference between {{> child}} and {{#child}} is that the former has Template.contentBlock equal to null. For your information, there is also another helper Template.elseContentBlock which represents the part of markup placed after the {{else}} "tag", e.g.
{{#child}}
this content goes to Template.contentBlock
{{else}}
this content goes to Template.elseContentBlock
{{/child}}
You can use it to let your child template choose what it is going to render based on some context, as shown in the example here.
It was not directly pointed out in your question, but another thing to keep in mind is if you use the block syntax {{#child}}, then the block's content has access to your parent template's helpers and does not have access to the helpers of child template.
Related
Is there any way to dynamically load a template with onCreated method of Meteor.js? I have different template and one display area (main template).
<template name="main">
</template>
default loaded template
<template name="default">
</template>
Loaded templates via links
<template name="page1">
</template>
<template name="page2">
</template>
Is there a way I can use oncreated function to load the default, and remove (default) and load other template in the same main template when they are clicked?
Check out Template.dynamic.
It allows you to load a template only by its name. Easy thing here because you can let your template handle dynamic names by using a Reactive var or Reactive Dict.
Declare some templates:
<template name="main">
{{> Template.dynamic template=getTemplateName }}
<button class="loadTemplate" data-target="page1">Load Page 1</button>
<button class="loadTemplate" data-target="page2">Load Page 2</button>
</template>
<template name="page1">
</template>
<template name="page2">
</template>
<template name="default">
</template>
In your main template you can set in onCreated the default template name default:
Template.main.onCreated(function(){
this.state = new ReactiveDict();
this.state.set("targetTemplate", "default");
})
Get the template to load via helper:
Template.main.helpers({
getTemplateName() {
return Template.instance().state.get("targetTemplate");
},
})
And set the new template name by button click event:
Template.main.events({
'click .loadTemplate'(event, templateInstance) {
event.preventDefault();
const targetName = $(event.currentTarget).attr('data-target');
templateInstance.state.set("targetTemplate", targetName);
}
})
You can of course do this also by other events than only by clicking a button, since it depends on a ReactiveDict / ReactiveVar it will render the new template if the variable updates.
Note: Very important is to handle the data that is passed to the template, too. Once your dynamic template becomes more complex you have to be more aware of that, too.
I need assistance in executing action methods defined in ember components from outside. (even though ember follows DATA Down and Actions Up approach). My usecase is as follows
Application Template
<script type="text/x-handlebars-template" data-template-name="application">
<h2> Dialog Component </h2>
{{outlet}}
</script>
Index Template
<script type="text/x-handlebars-template" data-template-name="index">
<button {{action "showDialog1"}}>Open Dialog 1</button>
<button {{action "showDialog2"}}>Open Dialog 2</button>
{{#if openFirst}}
{{#modal-dialog name="dlg1" title="Modal 1" auto-open="true" onOpen=(action "handleDialogOpen" "dlg1") }} Content of the Dialog ...{{/modal-dialog}}
{{/if}}
{{#modal-dialog name="dlg2" title="Modal 2" onOpen=(action "handleDialogOpen" "dlg2")}} Content of the dialog ... {{/modal-dialog}}
</script>
Modal Dialog Template
<script type="text/x-handlebars-template" data-template-name="components/modal-dialog">
<div class="titlebar-title">
<span> {{title}} </span>
<a class="closeBtn" {{action "close"}}>X</a>
</div>
<div class="content">
{{yield}}
</div>
</script>
Index Controller
App.IndexController = Ember.Controller.extend({
openFirst : false,
actions : {
showDialog1 : function(){
this.toggleProperty("openFirst"); // open and close the first dialog when clicking the button.
},
showDialog2 : function(){
// want to trigger "open" action of modal-dialog component without using any conditionals(if-else) and without observing "auto-open" attribute
......
},
handleDialogOpen : function(dialogName){
if(dialogName === "dlg1"){
// do something.
}else if(dialogName === "dlg2"){
// do something
}
}
}
});
Modal Dialog Component
App.ModalDialogComponent = Ember.Component.extend({
tagName : 'div',
classNames : ['ui-dialog'],
attributeNames : ['title','name','auto-open'],
didInsertElement : function(){
if(this.get("auto-open")){
this.send("open");
}
},
actions : {
open : function(){
$(this.element).show();
this.onOpen()
},
close : function(){
$(this.element).hide();
}
}
});
Css Style Definition
ui-dialog{
display : none;
}
Is there any way to achieve this ? Kindly guide me.
Executing actions from controller inside component it not recommended approach. Why does your modal send Save and Cancel actions to the controller and router instead.
Ember has sendAction which you use to send action from component to controller or router.
Inside component you can define an action like this Ember 1.x
{{my-component action="doSomething"}}
which you can send to controller in router
MyComponent = Ember.Component.extend({
click: function() {
this.sendAction();
}
});
This will trigger doSomething from component to controller. In ember 2.x you can check them here, they are pretty much the same
https://guides.emberjs.com/v2.6.0/components/handling-events#toc_sending-actions
Lets say that you want to open modal. Inside component you would define a property called isOpen that will show hide modal content
{{my-modal isOpen=isOpen}}
There is a prop on controller that also has this field which you pass from controller to component. Inside your template you could say:
{{#if isOpen}}
{{my-modal ....this will trigger didInertElement on component that you can use to handle backdrop ...etc
{{/if}}
App Labs has a great example on how to do the modals which integrates with ember-cli
https://github.com/yapplabs/ember-modal-dialog
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!
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, {
});
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".