How to add css classes to a child view from handlebar template - ember.js

I would like to add a css class to a child view from a handlebars template. I am currently getting an error as follows:
Uncaught TypeError: Object [object DOMWindow] has no method 'get'
My code is as follows:
App = Ember.Application.create()
App.ParentView = Ember.View.extend({
foo: 'bar',
content: 'Hello',
ChildView: Ember.View.extend({
classNames: this.get('parentView').get('classesToAdd')
})
});
I have a jsfiddle here: http://jsfiddle.net/sohara/PKMTn/2/
Is there something special about the classNames property that shifts the context from the Ember.View to a dom element? Or perhaps there's some other way that I can achieve this?

You can overwrite the init on the view to access classesToAdd, see http://jsfiddle.net/WCjda/1/
Handlebars:
<script type="text/x-handlebars">
{{#view App.ParentView}}
{{#view ChildView classesToAdd="my-class"}}
hello
{{/view}}
{{/view}}
</script>​
JavaScript:
App = Ember.Application.create()
App.ParentView = Ember.View.extend({
foo: 'bar',
content: 'Hello',
ChildView: Ember.View.extend({
init: function() {
this._super();
var classes = this.get('classesToAdd');
this.set('classNames', Ember.makeArray(classes));
}
})
});​

I don't think you can use this in your classNames declaration of your ChildView because the view object has not been instantiated.
However, you can use bindings. See the documentation at Emberjs.com under the "Customizing the HTML Element" section.

Related

itemControllers and custom Views

I am working on a small app that animates different iframes in and out of view. Right now I am just trying to start simple with two iframes for my data.
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{current: true, url:'http://www.flickr.com'},
{url:'http://bing.com'}
];
}
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'iframe',
now: function() {
return this.filterBy('isCurrent').get('firstObject');
}.property('#each.isCurrent')
});
App.IframeController = Ember.ObjectController.extend({
isCurrent: Ember.computed.alias('current')
});
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'isCurrent'],
templateName: 'iframe'
});
And my templates:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "next"}}>Next</button>
{{#each}}
{{view "iframe"}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="iframe">
<iframe {{bind-attr src=url}}></iframe>
</script>
Why can't my IframeView access my isCurrent property of my itemController? I am also unsure if this is the right way to do this, or if there is an easier way to have my each use my IframeView
Here is a jsbin: http://emberjs.jsbin.com/vagewavu/4/edit
isCurrent lives on the controller. The controller property will be in scope in the view, but the properties under the controller aren't in scope of the view. You just need to reference controller first.
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'controller.isCurrent'],
templateName: 'iframe'
});
Additionally your next action isn't doing anything, just creating some local variables, maybe you weren't finished implementing it. Either way I tossed together an implementation.
next: function() {
var now = this.get('now'),
nowIdx = this.indexOf(now),
nextIdx = (nowIdx + 1) % this.get('length'),
next = this.objectAt(nextIdx);
now.toggleProperty('current');
next.toggleProperty('current');
}
http://emberjs.jsbin.com/vagewavu/10/edit

Can the text of a Button be set directly in the ember View definition

<button class="ember-view delete-tier-view">Click Me</button>
Can be done using
{{#view RateEditor.DeleteTierView}}Click Me{{/view}}
Is it possible in any latest version of ember to define the text in view definition and just use it like this
{{view RateEditor.DeleteTierView}}
If you want static text, you can do just this:
RateEditor.DeleteTierView = Ember.View.extend({
//...
template: function () {
return "Click Me";
}
//...
});
If you want text bound to a property on view, try this:
RateEditor.DeleteTierView = Ember.View.extend({
//...
text: "Click Me",
template: Ember.Handlebars.compile("{{view.text}}")
//...
});
EDIT:
Or you can define a template with {{view.text}} inside and set that under templateName property, like in the other answer.
I think u should use a component!
But a simple answer for your question:
yes! use templateName on the View, and define the handlebars template.
Handlebars/HTML:
<script type="text/x-handlebars" data-template-name="my-cool-template">
TEST
</script>
JS:
RateEditor = {};
RateEditor.TestView = Em.View.extend({
templateName: 'my-cool-template'
});
Usage:
{{view RateEditor.TestView}}

How to get the parent controller for a custom TextField

I have a simple controller
App.UploadController = Ember.Controller.extend({
toUpload: Ember.A([])
});
I have a template backing this w/ a custom text field
<div>
{{view App.UploadFileView name="file" contentBinding="content"}}
</div>
My custom text field in JS is below. The problem I'm having is that in the change event, I need to push an object into the parent controllers "toUpload" array but when I do a get on the parentView.controller it's undefined. How can I get the parent in this scenario?
App.UploadFileView = Ember.TextField.extend({
type: 'file'
change: function() {
var foo = Ember.Object.create();
this.get('parentView.controller').get('toUpload').pushObject(foo);
}
});
The TextField is a component, so the parent controller doesn't exist, you'd need to use sendAction to get things out of it.
Here's my implementation of the upload button that's just a view.
App.UploadFileView = Ember.View.extend({
tagName: 'input',
attributeBindings: ['type'],
type: 'file',
change: function() {
console.log(this.get('controller'));
}
});
http://emberjs.jsbin.com/oQaReMi/1/edit
If you are using an Ember Component (like TextField for example) you would do this like so
App.UploadFileView = Ember.TextField.extend({
change: function() {
console.log(this.get('targetObject'));
}
});
Note- this is in the current version of ember 1.3.x

How do I bind to the active class of a link using the new Ember router?

I'm using Twitter Bootstrap for navigation in my Ember.js app. Bootstrap uses an active class on the li tag that wraps navigation links, rather than setting the active class on the link itself.
Ember.js's new linkTo helper will set an active class on the link but (as far as I can see) doesn't offer any to hook on to that property.
Right now, I'm using this ugly approach:
{{#linkTo "inbox" tagName="li"}}
<a {{bindAttr href="view.href"}}>Inbox</a>
{{/linkTo}}
This will output:
<li class="active" href="/inbox">Inbox</li>
Which is what I want, but is not valid HTML.
I also tried binding to the generated LinkView's active property from the parent view, but if you do that, the parent view will be rendered twice before it is inserted which triggers an error.
Apart from manually recreating the logic used internally by the linkTo helper to assign the active class to the link, is there a better way to achieve this effect?
We definitely need a more public, permanent solution, but something like this should work for now.
The template:
<ul>
{{#view App.NavView}}
{{#linkTo "about"}}About{{/linkTo}}
{{/view}}
{{#view App.NavView}}
{{#linkTo "contacts"}}Contacts{{/linkTo}}
{{/view}}
</ul>
The view definition:
App.NavView = Ember.View.extend({
tagName: 'li',
classNameBindings: ['active'],
active: function() {
return this.get('childViews.firstObject.active');
}.property()
});
This relies on a couple of constraints:
The nav view contains a single, static child view
You are able to use a view for your <li>s. There's a lot of detail in the docs about how to customize a view's element from its JavaScript definition or from Handlebars.
I have supplied a live JSBin of this working.
Well I took what #alexspeller great idea and converted it to ember-cli:
app/components/link-li.js
export default Em.Component.extend({
tagName: 'li',
classNameBindings: ['active'],
active: function() {
return this.get('childViews').anyBy('active');
}.property('childViews.#each.active')
});
In my navbar I have:
{{#link-li}}
{{#link-to "squares.index"}}Squares{{/link-to}}
{{/link-li}}
{{#link-li}}
{{#link-to "games.index"}}Games{{/link-to}}
{{/link-li}}
{{#link-li}}
{{#link-to "about"}}About{{/link-to}}
{{/link-li}}
You can also use nested link-to's:
{{#link-to "ccprPracticeSession.info" controller.controllers.ccprPatient.content content tagName='li' href=false eventName='dummy'}}
{{#link-to "ccprPracticeSession.info" controller.controllers.ccprPatient.content content}}Info{{/link-to}}
{{/link-to}}
Building on katz' answer, you can have the active property be recomputed when the nav element's parentView is clicked.
App.NavView = Em.View.extend({
tagName: 'li',
classNameBindings: 'active'.w(),
didInsertElement: function () {
this._super();
var _this = this;
this.get('parentView').on('click', function () {
_this.notifyPropertyChange('active');
});
},
active: function () {
return this.get('childViews.firstObject.active');
}.property()
});
I have just written a component to make this a bit nicer:
App.LinkLiComponent = Em.Component.extend({
tagName: 'li',
classNameBindings: ['active'],
active: function() {
return this.get('childViews').anyBy('active');
}.property('childViews.#each.active')
});
Em.Handlebars.helper('link-li', App.LinkLiComponent);
Usage:
{{#link-li}}
{{#link-to "someRoute"}}Click Me{{/link-to}}
{{/link-li}}
I recreated the logic used internally. The other methods seemed more hackish. This will also make it easier to reuse the logic elsewhere I might not need routing.
Used like this.
{{#view App.LinkView route="app.route" content="item"}}{{item.name}}{{/view}}
App.LinkView = Ember.View.extend({
tagName: 'li',
classNameBindings: ['active'],
active: Ember.computed(function() {
var router = this.get('router'),
route = this.get('route'),
model = this.get('content');
params = [route];
if(model){
params.push(model);
}
return router.isActive.apply(router, params);
}).property('router.url'),
router: Ember.computed(function() {
return this.get('controller').container.lookup('router:main');
}),
click: function(){
var router = this.get('router'),
route = this.get('route'),
model = this.get('content');
params = [route];
if(model){
params.push(model);
}
router.transitionTo.apply(router,params);
}
});
You can skip extending a view and use the following.
{{#linkTo "index" tagName="li"}}<a>Homes</a>{{/linkTo}}
Even without a href Ember.JS will still know how to hook on to the LI elements.
For the same problem here I came with jQuery based solution not sure about performance penalties but it is working out of the box. I reopen Ember.LinkView and extended it.
Ember.LinkView.reopen({
didInsertElement: function(){
var el = this.$();
if(el.hasClass('active')){
el.parent().addClass('active');
}
el.click(function(e){
el.parent().addClass('active').siblings().removeClass('active');
});
}
});
Current answers at time of writing are dated. In later versions of Ember if you are using {{link-to}} it automatically sets 'active' class on the <a> element when the current route matches the target link.
So just write your css with the expectation that the <a> will have active and it should do this out of the box.
Lucky that feature is added. All of the stuff here which was required to solve this "problem" prior is pretty ridiculous.

Dynamically load javascript inside Ember View

Is it possible to run Javascript inside an Ember View Handlebars template? I want to run the Javascript when the template is added to the DOM.
window.App = Ember.Application.create()
App.TestView = Ember.View.create
tagName: 'div'
template: Ember.Handlebars.compile '<script>console.log("some javascript");</script><div>This is a view</div>'
App.TestView.append()
The didInsertElement function on a view is invoked when it has been added to DOM, see documentation.
You can then access the added view via this.$(), see http://jsfiddle.net/NuaA6/
Handlebars:
<script type="text/x-handlebars" data-template-name="tmpl" >
hello from template
</script>​
JavaScript:
App = Ember.Application.create({});
Ember.View.create({
templateName: 'tmpl',
didInsertElement: function() {
console.log('view has been added to dom', this.$());
}
}).append();