How to get the parent controller for a custom TextField - ember.js

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

Related

Ember.js persist classNameBindings on transition to different routes

I'm fairly new to ember and I've been trying to tackle this problem for a couple of days but I can't seem to find a solution anywhere online.
I have a page with a list of all posts, each post has one tag (like a hashtag), either 'Fitness', 'Knowledge' or 'Social'. At the top of the page I have 3 view helpers and each view helper represents a tag (fitness, knowledge or social). These will be used to filter out the posts with that particular tag name.
My problem is that when I click on a view helper I toggle the "isSelected" property to true, which adds the "isSelected" class via classNameBindings. But when I transition to a different route on the site and come back, the "isSelected" property is reset back to false and the "isSelected" class has been removed. How do I keep these values persistent and in-tact for when I revisit the route?
Here's my code:
<script type="text/x-handlebars" data-template-name="global">
<ul class="categories">
<li>{{view App.Tag class="label fitness" text="fitness"}}</li>
<li>{{view App.Tag class="label knowledge" text="knowledge"}}</li>
<li>{{view App.Tag class="label social" text="social"}}</li>
</ul>
</script>
View:
"use strict";
App.Tag = Ember.View.extend({
tagName: 'span',
template: Ember.Handlebars.compile('{{view.text}}'),
classNames: ['label'],
classNameBindings: ['isSelected'],
isSelected: false,
click: function () {
this.toggleProperty('isSelected');
}
});
I have also tried using a controller with actions but that way persisted the "isSelected" property but didn't preserve the addition of the class when I revisited the route.
This may not be ideal, but to save the state of the application, you can put the state in the controller. You probably had a simple implementation, but maybe did not specify the isSelected as a property. The below works and you can view the jsbin here
App = Ember.Application.create();
App.Router.map(function() {
this.route('global');
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
App.GlobalController = Ember.Controller.extend({
activeTags: Ember.A()
})
App.Tag = Ember.View.extend({
tagName: 'span',
template: Ember.Handlebars.compile('{{view.text}}'),
classNames: ['label'],
classNameBindings: ['isSelected'],
isSelected: function () {
console.log("ON CHANGE", this.get('controller.activeTags'));
return this.get('controller.activeTags').contains(this.text);
}.property('controller.activeTags.#each'),
click: function () {
var tagArray = this.get('controller.activeTags');
if (tagArray.contains(this.text))
this.set('controller.activeTags', tagArray.without(this.text))
else
tagArray.pushObject(this.text);
}
});

How do I programmatically add child views to an Ember view at specific DOM selectors?

I have a view that uses a 3rd party library to render additional DOM elements in the didInsertElement hook. After these new elements are added, I need to add some child views inside them, so that they can render dynamic data.
Here's what I tried:
App.MyView = Ember.View.extend({
didInsertElement: function() {
create3rdPartyDomElements();
var element = this.$('someSelector');
childView = this.createChildView(App.SomeViewClass, attributesDict);
childView.appendTo(element);
}
});
(jsbin: http://jsbin.com/idoyic/3)
This renders my views as expected, but gives the following assertion error with Ember RC 7: "You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead."
I have tried extending ContainerView, as advised here and that works, but I have no way of inserting the child views at specific DOM selectors. It just inserts the child views at the beginning of the parent view.
Can someone please help me? Thanks a lot!
This is how I created:
An implementation where you have the main view, in that case codemirror, in the middle. And it's possible add more views, in the top or bottom.
App.EditorView = Ember.View.extend({
templateName: 'editor-view',
topView: Ember.ContainerView.extend(),
bottomView: Ember.ContainerView.extend(),
CodeMirrorView: Ember.TextArea.extend({
didInsertElement: function() {
this.codeMirror = CodeMirror.fromTextArea(this.get('element'));
}
})
});
The template:
<script type="text/x-handlebars" data-template-name="editor-view">
{{view view.topView viewName="topViewInstance"}}
{{view view.CodeMirrorView}}
{{view view.bottomView viewName="bottomViewInstance"}}
</script>
A view to represent a custom component:
App.MyComponent = Ember.View.extend({
templateName: 'click-here',
message: null,
click: function() {
alert(this.get('message'));
}
});
The implementation:
App.MyEditorView = App.EditorView.extend({
didInsertElement: function() {
this._super();
this.get('topViewInstance').pushObject(App.MyComponent.create({ message: "Hello" }));
this.get('bottomViewInstance').pushObject(App.MyComponent.create({ message: "World" }));
}
});
With this is possible to create a new instance, or extend App.EditorView and insert more views in top or bottom. Because the topView and bottomView are Ember.ContainerViews, all views added will have the bindings, events, and other ember features.
Give a look in that jsbin to see it working http://jsbin.com/ucanam/686/edit
You can render child views into parent view's hidden div, and then detach and append them to arbitrary DOM elements in didInsertElement hook.
http://jsbin.com/qaqome/1/
For related issue (components instead of views) see also this question.
try adding a property in your view, something like this:
App.MyView = Ember.View.extend({
childViewsContainer: Em.ContainerView.create({}),
didInsertElement: function() {
create3rdPartyDomElements();
var element = this.$('someSelector');
childViewsContainer.createChildView(App.SomeViewClass, attributesDict);
childView.appendTo(element);
}
});
then, you can access your childViewsContainer and do what ever you want with it

How to debug a missing view

From my router, I'm rendering a view:
App.MonthSummaryRoute = Ember.Route.extend({
events: {
selectTab: function(name) {
this.render(name, { into: 'month/summary', outlet: 'tab' });
}
}
});
As an example, name is "summaryCompany". If I add a
<script type="text/x-handlebars" data-template-name="summaryCompany">
<h2>Test template</h2>
</script>
this template displays. But I tried to add a view to handle the events:
App.SummaryCompanyView = Ember.View.extend({
didInsertElement: function() {
console.log('here');
}
});
and I'm not getting anything. What am I missing?
Could you provide your entire code selection, or a JSBin / JSFiddle?
Possible approaches:
What's in your month/summary template / route / view?
Maybe you can't call render from an event. What happens when instead of doing the render from inside selectTab you do it from the route's renderTemplate hook?
renderTemplate: function() { this.render("summaryCompanyView", { into: 'month/summary', outlet: 'tab' }); }
You can try seeing if the view is inserted at all: in web inspector, find the ember-id of the div corresponding to view (somethign like <div id="ember310" ...>, then access the actual view object via Ember.Views.views.ember310 (or whatever id). You can check the view's class and see if it's App.SummaryCompanyView or a generic Ember.View
Lastly, what happens if you remove the inlined-template and specify the template on the View object via templateName?

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.

How to add css classes to a child view from handlebar template

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.