Closure actions in Ember - ember.js

I'm rewriting some old Ember code to use closure actions, but I'm new to Ember and I can't seem to figure out how to change a sendAction that hasn't got parameters.
This is the code in the component row.js:
click: function() {
this.sendAction();
}
The row.hbs as well as the parent templates are just
{{yield}}
so I can't add anything there it seems. Does anyone know how to solve this?

If it's just {{yield}} all the way up is it possible that the click didn't actually do anything?
Usually the calling template would have something like {{row action="doSomething"}} Ember Docs
It's also possible there was some behavior here that kept sending the action up when you have a {{yield}} in which case you would want to just keep going up the tree until you found (somewhere) an action="doSomething" declaration. That is where you will start sending the action back down.
e.g.
{{top action="doSomething"}}
{{row}}
{{/top}}
Would become
{{top}}
{{row doSomething=(action "doSomething")}}
{{/top}}
Then row.js
click() {
this.doSomething();
}

Related

Cannot get Ember action to bubble

I have spent a lot of time trying to implement what ought to be a simple thing in Ember: bubbling an action from a component to the application controller. The docs say that in order to propogate an action then all I need to do is declare the action when I declare the template:
{{#if isLoggedIn}}
{{menu-bar action="authenticate"}}
{{/if}}
...and then send the action from the component (which is nested in the login route):
authenticate: function() {
//code omitted for brevity
this.sendAction('authenticate');
}
...which should be received by the application route:
user: Ember.inject.service(),
isLoggedIn: false,
actions: {
authenticate: function(){
console.log('authenticated!');
this.set('isLoggedIn', this.get('user').isLoggedIn());
}
}
but it isn't. And I'm wasting a lot of time trying to work out why.
I'm sure it's really obvious but if someone can tell me what I'm doing wrong I would greatly appreciate it.
Your issue is that the application controller isn't supposed to catch bubbled actions, the application route is. For the life of me I can't find the relevant section in the guides any more, but actions don't bubble through controllers, they bubble through routes. The first place an action usually goes (and is true in your case) is the relevant controller. In your case, you don't want the controller to catch it you want it to bubble. However, if an action isn't caught by a controller it bubbles to that controller's route. From there, it does not bubble to parent controllers, it only bubbles to parent routes.
For instance, let's say you had the following routes:
this.route('books', function() {
this.route('book', { path: '/:book_id' });
});
The bubbling order would be as such:
Component
Book controller
Book route
Books route
Application route
TL;DR: Catch your action in the application route, not controller. If I can find the guide on action bubbling, I'll edit my answer and link to it.
The mistake was that I had put the action="authenticate" on the wrong component! Apologies to GJK and Daniel Kmak for wasting your time trying to solve that one but it seems that I had just got mixed up.

Emberjs: how to reach the action of a parentView within an itemsViewClass

Cannot get my head around the following problem. I got a Uncaught TypeError: Cannot read property 'send' of undefined whatever I try. I think it must be the controllerBinding in the itemsViewClass, however I think it is defined correctly.
In the code below there are two showMenu actions. The first one works, but the last one in the itemsViewClass does not.
Please take a look at my code below (I show only the relevant code):
//views/menu.js
import Ember from "ember";
var MenuitemsView = Ember.View.extend({
template: Ember.Handlebars.compile('<div{{action "showMenu" target="view"}}>this works already</div>\
much more code here'),
contentBinding: 'content',
itemsView: Ember.CollectionView.extend({
contentBinding: 'parentView.subCategories',
itemViewClass: Ember.View.extend({
controllerBinding: 'view.parentView.controller', // tried to add controllerBinding but did not help
// this is where the question is all about
template: Ember.Handlebars.compile('<div {{action "showMenu" target="parentView"}}>dummy</div>')
}),
actions: {
showMenu: function(){
// dummy for testing
console.log('showmenu itemsView');
}
}
}),
actions: {
showMenu: function() {
console.log('showMenu parentView!'); // how to reach this action?
}
}
});
export default MenuitemsView;
I have tested with {{action "showMenu" target="view"}} and without a target. It seems not to help.
Do someone have a clue why the second showMenu action cannot be reached?
OK, so this is by no means the only way to do logic separation in Ember, but it's the method I use, and seems to be the method generally used in the examples across the web.
The idea is to think of events and actions as separate logic pools, where an event is some manipulation of the DOM itself, and an action is a translatable function that modifies the underlying logic of the application in some way.
Therefore, the flow would look something like this:
Template -> (User Clicks) -> View[click event] -> (sends action to) -> Controller[handleLogic]
The views and the controllers are only loosely connected (which is why you can't directly access views from controllers), so you would need to bind a controller to a view so that you could access it to perform an action.
I have a jsfiddle which gives you an idea of how to use nested views/controllers in this way:
jsfiddle
If you look at the Javascript for that fiddle, it shows that if you use the view/controller separation, you can specifically target controllers to use their actions, utilising the needs keyword within the controller. This is demonstrated in the LevelTwoEntryController in the fiddle.
At an overview level, what should happen if your bindings are correct, is that you perform an action on the template (either by using a click event handler in the view, or using an {{action}} helper in the template itself, which sends the action to the controller for that template. Which controller that is will depend on how your bindings and routing are set up (i've seen it where I've created a view with a template inside a containerView, but the controller is for the containerView itself, not the child view). If the action is not found within that controller, it will then bubble up to the router itself (not the parent controller), and the router is given a chance to handle the action. If you need to hit a controller action at a different level (such as a parent controller or sibling), you use the needs keyword within the controller (see the fiddle).
I hope i've explained this in an understandable way. The view/controller logic separation and loose coupling confused me for a long time in Ember. What this explaination doesn't do, is explain why you are able to use action handlers in your view, as I didn't even know that was possible :(

Ember.js route hook to be called on every transition

Is there a route hook in Ember.js that is called on every transition, even if the new route is the same as the old route (for example, clicking a top-level navigation link to the same route).
I tried activate, but it's only being called once, and is not being called again when I use the top-level navigation to go to the same route I'm already in.
Example jsFiddle: When I click "Test" the first time, the activate hook is called, but when I click it a second time, it does not.
You can setup a didTransition in the router, exactly how Ember does it for Google Analytics.
App.Router.reopen({
doSomething: function() {
// do something here
return;
}.on('didTransition')
});
See example here: http://emberjs.com/guides/cookbook/helpers_and_components/adding_google_analytics_tracking/
UPDATE: (new link since old is broken)
https://guides.emberjs.com/v1.10.0/cookbook/helpers_and_components/adding_google_analytics_tracking/
Activate is not being called a second time because This hook is executed when the router enters the route... And when you click on that link-to a second time, the router isn't doing anything... As in, no transition is being made (although it is "attempted").
http://emberjs.com/api/classes/Ember.Route.html#method_activate
The method that I have found to work best is observing the currentPath from within a controller. I use this for animations between routes.
In your application controller you can do something like the following:
currentPathChange: function () {
switch(this.get('currentPath')){
case 'test.index':
this.doSomething();
break;
case 'test.new':
this.doSomethingElse();
break;
}
}.observes('currentPath')
You should be able to access almost any part of your app from the application controller, so it's a nice "root hook," I suppose.
Example: http://jsfiddle.net/mattblancarte/jxWjh/2/
Did you already consider the hook willTransition?
http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
App.SomeRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
// do your stuff
}
}
});
Alter/Hack EmberJS code and add a jQuery event trigger inside the doTransition() Function. This is Best but kind of defeating the point.
As of today, 1 year later and Ember 2.0 kind of out, there is NO OTHER WAY :(
Ember does not provide a way to track route-change attempts! This includes URLattemts(history), link-to attempts, hash change attempts etc..

emberjs "loading screen" at the beginning

I don't know, if you have seen this demo-app yet: http://www.johnpapa.net/hottowel/ but once you start it, you see a really nice loading screen at the beginning like you would in any bigger desktop application/game.
So I haven't had the chance to go through the code properly myself, but I have started recently with Emberjs and I have the feeling that loading all the js-code for the whole SPA that I am building could be in the seconds area.
My question now, how would such a loading screen be possible with emberjs?
Or would there be a better way to go about that? (I somehow don't think requirejs would be a solution, though I could be wrong)
I'd like to contribute an alternate answer to this. By default, ready fires when the DOM is ready, and it may take some time to render your application after that, resulting in (possibly) a few seconds of blank page. For my application, using didInsertElement on ApplicationView was the best solution.
Example:
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
$("#loading").remove();
}
});
Please note that Ember also offers the ability to defer application readiness, see the code for more information.
Maybe it's my lazy way of doing things, but I just solved this by adding a no-ember class to my div.loading and in my CSS I added
.ember-application .no-ember {
display: none;
}
(Ember automatically adds the ember-application to the body.)
This way, you could also add CSS3 animations to transition away from the loading screen.
you can do something like this:
App = Ember.Application.create({
ready: function () {
$("#loader").remove();
}
});
in your body you set something like this
<img src="img/loading.gif" id="loader">
Alternative to using didInsertElement, the willInsertElement is a better event to perform the loading div removal since it will be removed from the body tag "before" the application template is rendered inside it and eliminates the "flicker" effect ( unless using absolute positioning of the loading div ).
Example:
App.ApplicationView = Ember.View.extend({
willInsertElement: function() {
$("#loading").remove();
}
});
Ember has an automagic loading view logic.
By simply setting App.LoadingView and its template, Ember will show that view while application loads.
This feature is likely to change in next release, in favor of a nested loading route feature which looks promising. See below:
Draft documentation
Feature proposal and discussion
In Ember 2.0 there is no more View layer, but you can do the same with initializers:
App.initializer({
name: 'splash-screen-remover',
initialize: function(application) {
$('#loading').remove();
},
});

Is there a way to get a callback when Ember.js has finished loading everything?

I am building an Ember.js app and I need to do some additional setup after everything is rendered/loaded.
Is there a way to get such a callback? Thanks!
There are also several functions defined on Views that can be overloaded and which will be called automatically. These include willInsertElement(), didInsertElement(), afterRender(), etc.
In particular I find didInsertElement() a useful time to run code that in a regular object-oriented system would be run in the constructor.
You can use the ready property of Ember.Application.
example from http://awardwinningfjords.com/2011/12/27/emberjs-collections.html:
// Setup a global namespace for our code.
Twitter = Em.Application.create({
// When everything is loaded.
ready: function() {
// Start polling Twitter
setInterval(function() {
Twitter.searchResults.refresh();
}, 2000);
// The default search is empty, let's find some cats.
Twitter.searchResults.set("query", "cats");
// Call the superclass's `ready` method.
this._super();
}
});
LazyBoy's answer is what you want to do, but it will work differently than you think. The phrasing of your question highlights an interesting point about Ember.
In your question you specified that you wanted a callback after the views were rendered. However, for good 'Ember' style, you should use the 'ready' callback which fires after the application is initialized, but before the views are rendered.
The important conceptual point is that after the callback updates the data-model you should then let Ember update the views.
Letting ember update the view is mostly straightforward. There are some edge cases where it's necessary to use 'didFoo' callbacks to avoid state-transition flickers in the view. (E.g., avoid showing "no items found" for 0.2 seconds.)
If that doesn't work for you, you might also investigate the 'onLoad' callback.
You can use jQuery ajax callbacks for this:
$(document).ajaxStart(function(){ console.log("ajax started")})
$(document).ajaxStop(function(){ console.log("ajax stopped")})
This will work for all ajax requests.
I simply put this into the Application Route
actions: {
loading: function(transition, route) {
this.controllerFor('application').set('isLoading', true);
this.router.on('didTransition', this, function(){
this.controllerFor('application').set('isLoading', false);
});
}
}
And then anywhere in my template I can enable and disable loading stuff using {{#if isLoading}} or I can add special jQuery events inside the actual loading action.
Very simple but effective.