Run controller event after render in Ember without View - ember.js

I'm new with Ember and what I want to do is execute some initialization with jQuery like this: Ember.$("select").selectpicker(); to customize default select (it actually inserts div that represents the hidden select). I used to have this code in my Controller:
init() {
this._super();
Ember.run.schedule("afterRender", this, function() {
this.send("initializeJQuery");
}
});
actions: {
initializeJQuery() {
Ember.$("select").selectpicker();
}
}
It really initializes my select tags, but when I transition to another route and go back – it rerenders and doesn't want to call initializeJQuery method despite the run method (not runOnce). I use Ember v1.13 and Views are deprecated so I'm looking for alternative way to do this.
Hope for your help.

init is only called once, because controllers are singletons. Typically these things are put in didInsertElement, preferably in a component. But to show you what's happening consider this:
App.IndexController = Ember.Controller.extend({
init: function(){
console.log("I get called once");
}
});
App.IndexView = Ember.View.extend({
didInsertElement: function(){
console.log("I get called every time Index is rendered");
}
});
JSBin: http://emberjs.jsbin.com/xacosekuku/edit?html,css,js,output
Now what you actually want to do is make a component. For instance you can do this:
App.SelectPickerComponent = Ember.Component.extend({
tagName: 'select',
didInsertElement: function(){
this.$().selectpicker();
}
});
Then in your template do:
{{#select-picker}}
<option value=1>1</option>
<option value=2>2</option>
<option value=3>3</option>
{{/select-picker}}
Something like this (but with our jQuery call added): http://emberjs.jsbin.com/xacosekuku/2/edit?html,css,js,output

Related

Subscribe to DOM events within Ember app

I've got a Ember CLI based blog. I currently resize all <img> inside posts upon clicking them using this function inside the application-controller.
I rely on on('init') and Ember.run.later and this feels just dirty.
How can I improve the subscription code below?
import Ember from 'ember';
export default Ember.ArrayController.extend({
imageScaling: function() {
Ember.run.later((function() {
//Subscribe to the actual event
Ember.$(".post-content img").on("click", function(){
// RESIZE HERE
})
}), 3000)
}.on('init')
});
If you're inserting all of your images inside Ember templates, you can always use the action helper:
<img {{action 'scaleImages' on='click'}} src='' />
I realize that's probably not the case though. The way I've dealt with DOM interactions in the past is to override the application view then set up and tear down my event handler there. You can also avoid the Ember.run.later call by modifying your jQuery event listener. Here's how I would do it:
// app/views/application.js
export default Ember.View.extend({
setupImageScaling: function() {
$('body').on('click.image-scaling', 'img', function() {
// RESIZE HERE
});
}.on('didInsertElement'),
teardownImageScaling: function() {
$('body').off('click.image-sacling');
}.on('willDestroyElement')
});
In my opinion, the above is the 'most Ember' way of handling this kind of situation.

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.

why element Id as a computed property in ember view not working in ember 1.0.0-PRE.4?

Below is the jsfiddle describing the issue. First view has elementid as a computed property and second one has a explicit element id. First view's id has not got changed while the second one has the id.
http://jsfiddle.net/LZjEx/
App = Ember.Application.create();
App.MultiView = Ember.View.extend({
templateName : 'appl',
textInput: Ember.TextField.extend({
elementId : function(){
return "disk";
}.property()
})
})
App.MultiView.create().append();
<script type="text/x-handlebars" data-template-name="appl">
{{view view.textInput}}
{{view Ember.TextField elementId="answer"}}
</script>
After some digging and experiments, here is what I found:
In this thread it is explained when the elementId is set: https://github.com/emberjs/ember.js/issues/1549
Since the view registers itself with Ember.View.views on init it needs the elementId defined before init is run. It doesn't have to do with 'inDOM' state.
Here is a fiddle confirming that: http://jsfiddle.net/LLSQD/
App.MultiView = Ember.View.extend({
templateName : 'appl',
textInput: Ember.TextField.extend({
init: function() {
// This view's id will not be set to 'disk'.
return this._super();
this.set('elementId', 'disk');
}
}),
textInput2: Ember.TextField.extend({
init: function() {
// This view's id will be set to 'answer'.
this.set('elementId', 'answer');
return this._super();
}
})
})
In this thread it is explained, that computed properties are not computed before initialization: https://github.com/emberjs/ember.js/issues/777
This has been discussed before. create extends prototypes, it's not for setting computed property values. This isn't something that is going to change. You can consider using setProperties if you want this.
Overall, because the elementId is used at prototype initialisation, it cannot be changed after the object is constructed, thus using a computed property to determine the id is wrong. The best you can do is set the id in the init method and then call this._super();