How do I access component properties in a transcluded view in ember? - ember.js

I am passing some html to a components in ember. The html is yielded.
But the yielded html is unable to access properties defined in the component. However the properties do work on the component template.
component
import Ember from 'ember';
export default Ember.Component.extend({
user: undefined,
replyText: undefined,
onInitialization: function(){
this.set('replyText', '#' + this.user.get('username') + ' ');
}.on("init"),
remainingTweetChars: function () {
var length = 140 - this.get('replyText').length;
return length;
}.property('replyText')
});
component template
{{remainingTweetChars}} {{!-- this works --}}
{{yield}}
component usage with html which is yielded into the component template above
{{#action-reply class="item-actionables__reply"
user=user
}}
<span>{{remainingTweetChars}}</span> {{!-- this does NOT works --}}
<span>{{view.remainingTweetChars}}</span> {{!-- this does NOT works --}}
{{/action-reply}}

To overcome this you can assign a viewName to the component and use it to reference any property defined.
Example,
http://emberjs.jsbin.com/bihuzupogi/1/edit?html,js,output
hbs
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
<h3>Component in block form example accessing props</h3>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{#test-comp propInTmpl="test-prop-in-tmpl" viewName="the-test-comp"}}
<span style="color:gray">
this is content of the block content <b>without</b> using <b>viewName</b>
(<b>props:</b> {{propInTmpl}}, {{propInClass}})
</span>
<br/>
<span style="color:gray">
this is content of the block content using the <b>viewName</b>
(<b>props:</b> {{view.the-test-comp.propInTmpl}}, {{view.the-test-comp.propInClass}})
</span>
{{/test-comp}}
</script>
<script type="text/x-handlebars" data-template-name="components/test-comp">
<i>This is content of test-compo component template! (<b>props:</b> {{propInTmpl}}, {{propInClass}})</i>
<br/>
{{yield}}
</script>
js
App = Ember.Application.create();
App.TestCompComponent = Em.Component.extend({
propInClass:"test-prop-in-class"
});

Related

How to render template other than application.hbs in EmberJS?

In EmberJS, the main template file is the application.hbs. Any template rendered from the routes goes the the {{outlet}} of this main template file.
Now, I have another main template file, say print.hbs wherein the template design is very different from the application.hbs. How do I do this?
In the router file, I have:
App.Router.map(function() {
this.resource('print', function() {
this.route('display1');
this.route('display2');
});
this.route('dashboard', {path: '/'});
this.route('anything');
});
The routes dashboard and anything uses the application.hbs.
What should I do to use print.hbs on the print route? Please help.
You can't easily change application template. Ember doesn't listen on templateName property changes and responds poorly when you try to re-render the template yourself.
A nice way to do this would be to use different partials within your application template, based on whether you are in the 'screen' or 'print' mode.
<script type="text/x-handlebars">
{{#if isPrint}}
{{partial "application-print"}}
{{else}}
{{partial "application-normal"}}
{{/if}}
</script>
<script type="text/x-handlebars" data-template-name="application-normal">
<div id="app-normal">
<h2>Normal template</h2>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="application-print">
<div id="app-print">
<h2>Print template</h2>
{{outlet}}
</div>
</script>
App.ApplicationController = Ember.Controller.extend({
isPrint: false,
currentPathChange: function () {
var currentPath = this.get("currentPath");
var isPrint = currentPath ? currentPath.indexOf("print") === 0 : false;
this.set("isPrint", isPrint);
}.observes('currentPath').on("init")
});
This JSBin will demonstrate why this, unfortunately, doesn't work either. According to this bug report, Ember's handlebars gets confused when there are multiple outlet directives in the same page, even if they are in different #if scopes.
Until this gets fixed, I propose the following, slightly modified, solution.
Application template is empty. One template each for normal and print section.
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="normal">
<div id="app-normal">
<h2>Normal template</h2>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="print">
<div id="app-print">
<h2>Print template</h2>
{{outlet}}
</div>
</script>
In router, everything goes into normal and print resources. Normal resources are placed at /, so that all the links remain the same. No need for special coding in ApplicationController.
App.Router.map(function() {
this.resource("print", function () {
this.route("a");
this.route("b");
});
this.resource("normal", {path: "/"}, function () {
this.route("a");
this.route("b");
});
});
Working jsbin here.

Nothing rendered into outlet

I am manually rendering some outlets:
Dashboard.IndexRoute = Ember.Route.extend({
renderTemplate : function ( ) {
this.render('index');
this.render('disposition-legend', {outlet : 'dispositionLegend'} );
},
});
The templates:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<div class="container">
{{outlet dispositionLegend}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="disposition-legend">
<div class="row">
<div class="col-lg-12 well">Legend:
{{#each controller.content}}
<span class="label" style="background-color:{{unbound color}};">{{label}}</span>
{{/each}}
</div>
</div>
</script>
Ember says that is is indeed rendering the outlet:
Rendering index with default view <Ember._MetamorphView:ember320> Object {fullName: "view:index"}
Rendering disposition-legend with <Dashboard.DispositionLegendView:ember328> Object {fullName: "view:disposition-legend"}
There are no error messages, but neither view ember320 nor view ember328 are in the DOM. The only view present in the DOM is ember299, related to the application template.
Why could that be?
(I am running ember-1.4.0)
The IndexRoute is trying to render into application template, but you want to render into index, so you need to pass the into: 'index' option.
App.IndexRoute = Ember.Route.extend({
renderTemplate : function ( ) {
this.render('index');
this.render('disposition-legend', {outlet : 'dispositionLegend', into: 'index'} );
},
});

Break up my application.handlebars into separate templates using Ember.js and Ember.Router

I'm building a front-end (on top of Ruby on Rails) using ember.js and the ember-rails gem.
My (ember) application consists of Models, Views, Controllers and an application.handlebars template which describes my UI.
Whats the best practice to break up this application.handlebars file so that I can manage the UI? For example, I'd like to have Navigation at the top of the page.
I've tried using the Ember.Router, a separate navigation.handlebars (with NavigationView and NavigationController) the {{outlet}} helper to no avail. Here's what the Router looks like:
App.Router = Ember.Router.extend(
enableLogging: true
root: Ember.Route.extend(
index:
route: '/'
connectOutlets: (router, context) =>
router.get('applicationController').connectOutlet('navigation')
)
and the application.handlebars
<h1>Lots of HTML that I want to break up</h1>
{{outlet}}
Let me know if you need more info...thanks.
As per my Understanding, Let's suppose you want 3 sections(can be any number) Header, Content & Footer, You can do something as follows
<script type="text/x-handlebars" data-template-name="application">
{{view MyApp.HeaderView}}
{{#view MyApp.ContentView}}
{{outlet}}
{{/view}}
{{view MyApp.FooterView}}
</script>
<script type="text/x-handlebars" data-template-name="app-header">
All your Header related HTML
</script>
<script type="text/x-handlebars" data-template-name="app-content">
HTML related to content
{{yield}} //this will be explained at the end
More HTML if you want
</script>
<script type="text/x-handlebars" data-template-name="app-footer">
HTML related to footer
</script>
MyApp.HeaderView = Ember.View.extend({
templateName: 'app-header'
})
MyApp.ContentView = Ember.View.extend({
templateName: 'app-content'
})
MyApp.FooterView = Ember.View.extend({
templateName: 'app-footer'
})
MyApp.ApplicationView = Ember.View.extend({
templateName: 'application'
})
explaining {{yield}} In a nutshell, whatever is between in the block helper of a given view goes in there, In the above example for the MyApp.ContentView, the {{outlet}} defined in the {{#view MyApp.ContentView}} handlebars gets inserted at the {{yield}}
On the similar lines let me show the difference between layoutName property & templateName property,
App.someView = Ember.View.extend({
tagName: 'a',
templateName: 'a-template',
layoutName: 'a-container'
})
<script type="text/x-handlebars" data-template-name="a-template">
Hi There
</script>
<script type="text/x-handlebars" data-template-name="a-container">
<span class="container">
{{yield}}
</span>
</script>
Will result in following HTML
<a class="ember-view" id="ember235">
<span class="container ember-view" id="ember234">
Hi There
</span>
</a>
Use these concepts to split the application handlebars in your case it would be something like
{{view App.NavigationView}}
{{outlet}}
Update as per latest ember
The new ember supports partials similar to rails, now we can modify the above to use {{partial}} as follows:
{{partial "header"}}
{{outlet}}
{{partial "footer"}}
Ember when encountered this template will look for the template whose name is _header(similar to rails) and inserts the template(same goes for footer)
And If want to associate a controller we can use {{render}} helper
{{render "sidebar"}}
inserts the template whose name is sidebar at specified location in handlebars besides it also associates App.SidebarController to it,
Note: we cannot use {{render 'sidebar'}} more than once in same handlebars file.
But again if you want to use a widget like view multiple places in a given page then use {{view}} helper
For this problem, what you need to do is think about what views change and where that changes happen. If for example you have a navigation section and a main section, then think about how each of these sections change with the state of your application. Be sure to only create an {{outlet}} for dynamic content, otherwise things will get messy and the application will be slower. Then setup your templates and your router similar to the example below.
Templates:
<script type="text/x-handlebars" data-template-name="application">
<!--Your application template goes here-->
{{outlet navigation}}
{{outlet body}}
</script>
<script type="text/x-handlebars" data-template-name="navigation">
<!--Your navigation template goes here-->
</script>
<script type="text/x-handlebars" data-template-name="main-one">
<!--Your mainOne template goes here-->
</script>
<script type="text/x-handlebars" data-template-name="main-two">
<!--Your mainTwo template goes here-->
</script>
Note: You can have {{outlet}} in any of your view templates to change in more sub-states
Javascript:
window.App = Em.Application.create({
ApplicationView: Em.View.extend({
templateName: "application"
}),
ApplicationController: Em.Controller.extend({
}),
NavView: Em.View.extend({
templateName: "navigation"
}),
NavController: Em.Controller.extend({
}),
MainOneView: Em.View.extend({
templateName: "main-one"
}),
MainOneController: Em.Controller.extend({
}),
MainTwoView: Em.View.extend({
templateName: "main-two"
}),
MainTwoController: Em.Controller.extend({
})
Router: Em.Router.extend({
root: Em.Route.extend({
index: Em.Route.extend({
route: '/',
connectOutlets: function(router,context) {
router.get("applicationController").connectOutlet("navigation","nav");
router.get("applicationController").connectOutlet("body","mainOne");
}
}),
otherState: Em.Route.extend({
route: '/other-state',
connectOutlets: function(router,context) {
router.get("applicationController").connectOutlet("navigation","nav");
router.get("applicationController").connectOutlet("body","mainTwo");
}
}),
})
})
});
App.initialize();
Note: The applicationController must extend Controller and not ObjectController or ArrayController

Using multiple named outlets and a wrapper view with no content in Emberjs

I'm trying to use multiple named outlets with Ember.js. Is my approach below correct?
Markup:
<script type="text/x-handlebars" data-template-name="application">
<div id="mainArea">
{{outlet main_area}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="home">
<ul id="sections">
{{outlet sections}}
</ul>
<ul id="categories">
{{outlet categories}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="sections">
{{#each section in controller}}
<li><img {{bindAttr src="section.image"}}></li>
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="categories">
{{#each category in controller}}
<img {{bindAttr src="category.image"}}>
{{/each}}
</script>​
JS Code:
Here I set the content of the various controllers to data grabbed from a server and connect outlets with their corresponding views. Since the HomeController has no content, set its content to an empty object - a hack to get the rid of this error message:
Uncaught Error: assertion failed: Cannot delegate set('categories'
) to the 'content' property of object
proxy : its 'content' is undefined.
App.Router = Ember.Router.extend({
enableLogging: false,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
connectOutlets: function(router){
router.get('sectionsController').set('content',App.Section.find());
router.get('categoriesController').set('content', App.Category.find());
router.get('applicationController').connectOutlet('main_area', 'home');
router.get('homeController').connectOutlet('home', {});
router.get('homeController').connectOutlet('categories', 'categories');
router.get('homeController').connectOutlet('sections', 'sections');
}
})
})
});
​
If it's any help, I got this error because I was connecting to an Ember.ObjectController instead of Ember.Controller.

handlebars view not refreshing when {{#if}} {{else}} condition changes

(edit: simplified things a bit below)
I have a handlebars template with an {{#if etc}} conditional, and when I change the associated data the view updates the first time, but then does not continue to update on subsequent changes. I am toggling a boolean that is in the condition, and I know the toggle is running and switching the property because I can watch it do so on the console, but the actual view, a I said, only refreshes once. In my html file this looks like this:
<script type="text/x-handlebars" data-template-name="application">
<body>
<div class="row">
{{#if App.Nav.show}}
{{outlet nav}}
{{/if}}
<div class="span10">
<h1>Hello</h1>
{{outlet}}
</div>
</div>
</body>
</script>
and a bit further on, to toggle:
<script type="text/x-handlebars" data-template-name="people">
<a {{action toggleMenu}}> toggle </a>
</script>
and in the javascript:
App.NavController = Ember.Controller.extend();
App.NavView = Ember.View.extend({
templateName: 'nav'
});
App.Nav = Ember.Object.create({
show: true
});
and finally the relevant bits of the router:
App.Router = Ember.Router.extend({
enableLogging: true,
root: Ember.Route.extend({
index: Ember.Route.extend({
route: '/',
showPerson: Ember.Route.transitionTo('aName'),
toggleMenu: function(){
console.log('Changing the toggle!');
App.Nav.toggleShow();
},
connectOutlets: function(router){
router.get('applicationController').connectOutlet('nav', 'nav');
router.get('applicationController').connectOutlet('allPeople', unrelated_function())
}
})
})
});
I would say to put your if into your view. Something like this:
<script type="text/x-handlebars" data-template-name="people">
<a {{action toggleMenu}}> toggle </a>
{{#if App.Nav.show}}
<div>navigation</div>
{{/if}}
<div class="span10">
<h1>Hello {{name}}</h1>
</div>
</script>
Then your application view should look something like this:
<script type="text/x-handlebars" data-template-name="application">
<body>
<div class="row">{{outlet}}</div>
</body>
</script>
Let me know if you tried it and it worked.
I did not use connectOutlet to display the view, let me know if there is some purpose behind using connectOutlet method, Meanwhile here is my implementation of toggling the view visibility