I am following the documentation on emberjs.com, but can't get the first bindings example to work.
I created a jsfiddle to demonstrate. What am I missing?
Ember.js uses the concept of a RunLoop to allow bindings, observers and so on.
The problem with the example is that by setting a (bound) property and immediately getting the value via console.log no event is fired which would trigger the RunLoop and therefore synchronize the changes. There are 2 excellent blog posts about the RunLoop: Part 1 and Part 2. Although they target Sproutcore, the concept is about the same for Ember.js.
There are two ways to make your example work.
Force synchronisation via Ember.run.sync()
As the docs state, invoking Ember.run.sync() ... is a useful way to immediately force all bindings in the application to sync. This leaves the code like this, see http://jsfiddle.net/pangratz666/cwR3P/
App = Ember.Application.create({});
App.wife = Ember.Object.create({
householdIncome: 80000
});
App.husband = Ember.Object.create({
householdIncomeBinding: 'App.wife.householdIncome'
});
// force bindings to sync
Ember.run.sync();
console.log(App.husband.get('householdIncome')); // 80000
// Someone gets raise.
App.husband.set('householdIncome', 90000);
// force bindings to sync
Ember.run.sync();
console.log(App.wife.get('householdIncome')); // 90000
Or the second option is to ...
Show the values in a view
Showing the properties in a view handles all the RunLoop stuff for you, see http://jsfiddle.net/pangratz666/Ub97S/
JavaScript:
App = Ember.Application.create({});
App.wife = Ember.Object.create({
householdIncome: 80000
});
App.husband = Ember.Object.create({
householdIncomeBinding: 'App.wife.householdIncome'
});
// invoke function in 3000ms
Ember.run.later(function() {
// someone gets a raise
App.husband.set('householdIncome', 90000);
}, 3000);
Handlebars (the view):
<script type="text/x-handlebars" >
Wifes income: {{App.wife.householdIncome}}<br/>
Husbands income: {{App.husband.householdIncome}}
</script>
You'll need to call Ember.run.sync(); after setting up your bindings to give Ember's run loop a chance to sync before your log statements. This is a handy technique for testing with Ember as well, but isn't typically needed in Ember apps themselves.
Related
Often, we'll need to schedule an update after rendering completes in reaction to a property change. Here is an example:
page_changed: function() {
Ember.run.scheduleOnce('afterRender', this, this.scroll);
}.observes('current_page')
This doesn't work for testing since there is no runloop at the time scheduleOnce is called. We can simply wrap scheduleOnce in an Ember.run
page_changed: function() {
Ember.run(function() {
Ember.run.scheduleOnce('afterRender', that, that.scroll);
});
).observes('current_page')
..but I'm being told that's not the right way to go about it. I thought I'd reach out and get some other ideas.
For reference here is the issue that I opened up in ember.js#10536
Looks like this is the way to do it according to #StefanPenner's comment. Instead of modifying the app code itself just wrap the render call with an Ember.run
test('it renders', function() {
expect(2);
var component = this.subject();
var that = this;
equal(component._state, 'preRender');
// Wrapping render in Ember.run
Ember.run(function() {
that.render();
});
equal(component._state, 'inDOM');
});
the entry point into your application in the test needs the Ember.run. For example
test('foo', function() {
user.set('foo', 1); // may have side-affects
});
test('foo', function() {
Ember.run(function() {
user.set('foo', 1);
});
});
Why is this needed? Ember.run wraps the root of all call-stacks that are triggered by click/user-actions/ajax etc. Why? The run-loop is what allows ember to batch
When writing unit-tests, we are "faking" the user or network actions, it isn't obvious to ember what groups of changes you want to "Batch" together.
We can think of Ember.run as a way to create a batch or transactions of changes. Ultimately we use this, to batch DOM reads/writes to interact with the DOM in an ideal way.
Although maybe frustrating until you get the hang of it, it is a great way to create concise and deterministic tests. For example:
test('a series of grouped things', function() {
Ember.run(function() {
component.set('firstName', 'Stef');
component.set('lastName', 'Penner');
// DOM isn't updated yet
});
// DOM is updated;
// assert the DOM is updated
Ember.run(function() {
component.set('firstName', 'Kris');
component.set('lastName', 'Selden');
// DOM isn't updated yet, (its still Stef Penner)
});
// DOM is once again updated, its now Kris Selden.
});
Why is this cool? Ember gives us fine-grained control, which lets us not only very easily test aspects of our app in isolation, but also test sequences of what may be user or ajax pushed based actions, without needing to incorporate those aspects.
As time goes on, we hope to improve the test helpers and clarity around this.
..but I'm being told that's not the right way to go about it.
The reason is that you need the Ember.run call when the callback is executed, not when it's scheduled. Like this:
Ember.run.scheduleOnce('afterRender', function() {
Ember.run(function() {
that.scroll();
});
});
In your code, Ember.run is being called when the callback is scheduled. This is likely unnecessary as you're probably already in the run loop. My version ensures that when the callback is called, even if the run loop finished long ago, that another run loop is started and that.scroll() is called within the run loop. Does that makes sense?
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 :(
tl;dr: Data can be sent in and out of a component, but I only know how to send actions out. Is there a way to send actions in?
In my Ember application, I have something like the following UI from Google Maps:
The background map corresponds to a PinsRoute/PinsView/PinsController, and it shows many pins. When you click one, you enter the PinRoute, which renders the overlay to {{outlet}}. Both the big map and the thumbnail (in the Google Maps image, the picture that says "Street View") are components: FullscreenMapComponent and ThumbnailMapComponent, respectively.
In Google maps, when you click "Street view", it pans and zooms the main map to the selected point. This is essentially what I'm trying to figure out how to wire up.
When the user clicks "streeth view" on my ThumbnailMapComponent, I can send out an action, which the PinsRoute can handle. The question is, how can I then reach down to my FullscreenMapComponent and invoke the appropriate method (.panToSelected(), in this case)?
Here's a good solution. Have the component register itself to whatever controller it's being rendered in, and that way you can access the component in action handlers.
In my case, I added a method to my component
App.FullscreenMapComponent = Ember.Component.extend({
...
_register: function() {
this.set('register-as', this);
}.on('init')
});
and a property to my controller:
App.SensorsController = Ember.ArrayController.extend({
fullscreenMap: null,
...
});
In my template, I bind the two with my new register-as property:
// sensors.hbs
{{fullscreen-map data=mapData
selectedItem=currentSensor
action='selectSensor'
deselect='deselectSensor'
register-as=fullscreenMap }}
And now, let's say I click the thumbnail above and it sends an action that bubbles up to my ApplicationRoute, I can now do this:
// ApplicationRoute.js
actions: {
panTo: function(latLong, zoom) {
this.controllerFor('sensors').get('fullscreenMap').panTo(latLong, zoom);
}
}
Presto! Components responding to actions.
Update (10/6/2016)
Whew, I've learned a lot in two years.
I would no longer recommend this solution. In general, you should strive for your components to be declarative, and have their output and behavior depend solely on their state. Instead, my original answer here solved the problem using an imperative method, which is brittle, harder to understand, and not very easy to extend (what if we wanted to tie the the map's position to the URL?).
If I was refactoring this specific component, I'd probably make {{fullscreen-map}} accept latLong and zoom params. If someone from the outside sets those, the map would respond and update itself. You can use didReceiveAttrs from within the component to respond to param changes, and then from there call the imperative panTo method from Google's API.
The takeaway is that, even if you have to interact with imperative methods (maybe because of a third-party lib), strive to make your own components' APIs as declarative as possible. What first seems like "sending an action down" can usually be expressed as some function of state, and that state is what you want to identify and make explicit.
This is working example but I am not 100% sure that this approach is best
Here what you can do:
When calling action pass your component as parameter:
App.PinController = Ember.Controller.extend({
actions: {
actionThatComponentCalls: function(){
// different component will be called
new App.MyOtherComponent().send('differentAction'):
}
}
});
App.FullScreenMapComponent = Ember.Component.extend({
click: function(){
this.sendAction();
}
});
App.MyOtherComponent = Ember.Component.extend({
actions: {
differentAction: function(){
console.log('different action called');
}
}
});
Hope this helps
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();
},
});
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.