Ember JS: Trigger Loading Substate for slow loading templates - ember.js

According to the Ember docs on loading substates you can optionally create a loading route and/or loading template for Promises that might take a long time to process.
I have a situation where I am rendering a large template using lots of components and the rendering time is over 2 seconds on average.
I am sure there is a solution to the slow render time, but while I work on it, I would like to see if I can call the loading template to display while the slow template is loading.
Is there a way to do this?
I have a trivial loading page all set up (just a simple message with some animated css) and saved in my templates root folder (so it could potentially be used everywhere when needed).
Ember should automatically display this loading template- just by virtue of it being there- but again, by default, it is only when a Promise is being loaded, and not a template.
So is there a workaround (i.e hack) to get this to work?

I think you may be leading yourself off-course here with the loading substate.
My best guess at this point is that you need to address your {{view Ember.Select}} helpers within that template. These are terribly slow to render. I ran into this problem recently when rendering a few thousand options across 15-20 selects on a page.
Unfortunately, the DOM is usually the bottleneck of big SPAs, and you'll probably need to get creative if you want that snappy async feel everywhere in your app.
Try to reduce the number of options that are rendered, and you will speed things up. Or, you could try another form library... Or, roll your own.
Update::
If I were you, I would consider adding a method to the ArrayController that dynamically creates and slowly loads the options in chunks. Here's some pseudo-code:
App.CollectionController = Ember.ArrayController.extend({
generatedOptions: null,
generateOptions: function () {},
destroyOptions: function () {
this.set('generatedOptions', null);
}
});
App.CollectionRoute = Ember.Route.extend({
setupController: function (controller, model) {
controller.generateOptions(); //slowly render
},
willTransition: function () {
this.controllerFor('controller').destroyOptions(); //destroy so that when we return it will need to generate again
}
});
{{#each generatedOptions}}
{{partial 'collection_form'}}
{{else}}
<p>loading, for kicks</p>
{{/else}}

Related

Loading several data sources into an Ember UI

I'm trying to understand how to compose a page with multiple data sources and loading spinners in Ember.
Let's you have a UI like this:
http://postimg.org/image/6i1ko340f/
(sorry, don't have reputation to embed image)
And let's say each screen 'module' [books, shows, movies, tweets] has a separate data source at a remote API url.
somesite.com/api/books.json
somesite.com/api/shows.json
somesite.com/api/books.json
twitter.com/api/tweets.json
In order to simplify, and get to the essence of the question, I ask that you don't use:
1) Components
2) A data library (e.g. Ember Model or Ember Data) unless that's essential to the answer.
How would you approach architecting this in Ember so that each module has its own spinner and they load at their own pace (separate route hooks?).
Please help me understand how you would compose your routes, controllers, and models, or whatever else to elegantly solve this extremely common problem. I'll attach some of my approaches and thoughts in the comments :)
Well, I don't know if this is the best way by any means, but this is how I approach that problem. Dummy example using timeouts instead of async calls.
Write some sort of async wrapper. I use ic-ajax wrapper around jQuery ajax to make ajax calls that return promises in a class I call my rest-client. I use initializers to inject this object into all of my routes as restClient as a convenience. You can always just import when you need it.
I would normally block for my model retrieving via Ember.RSVP.hash() calls, but lets assume I did not as you've asked (this is our index route):
import bookModel from 'app/models/book-model';
setupController: function(controller, model){
var self = this;
//do this for each "module" call
this.restClient.get('books/url').then(function(response){
var booksController = this.controllerFor('booksTemplate');
//data is json for the properties of your Book model
var books = model.create(response.data);
booksController.set('isLoading', false);
booksController.set('model', books);
});
}
Make an asynchronous call, and on its success, set the model for the template's controller. Your template books.hbs:
{{#if isLoading}}
<img src="spinner.gif">
{{else}}
{{#each}}
{{this.title}}
{{/each}}
{{/if}}
This template shows a spinner or while loading happens. Notice in the setupController call that we set isLoading to false on complete of the ajax call. This causes the else block to display.
And your books.js controller:
export default Ember.ArrayController.extend({
isLoading: true;
});
And finally, your index.hbs:
{{render 'books'}}
{{render 'otherModel'}}
{{render 'anotherModel'}}
Caveat, if you render the same template twice with a render partial and do not specify the model, the two templates share a singleton instance of the controller meaning isLoading is shared across instances. Holler if you have any questions

Timing Issues with Layouting via didInsertElement view function

I encounter ugly timing problems (race conditions) with putting code which carries out layout based on height calculations with jQuery in didInsertElement().
For example, I calculate the height of a header via $('header.someClass').outerHeight(true); then I use the result to offset the content area from the top. If I render the view completely new via reloading the whole page it works (60px in my example) but if I navigate to the view from another one, it fails because the wrong height is returned (6px in my example).
To prove that it is a timing issue: If I wrap the code in:
Em.run.later(function() {
...do layout
}, 50);
It works.
I consider this a serious issue because there are not other hooks in Ember, I can attach to.
Instead you should schedule your jQuery logic to run after the rendering:
App.YourView = Ember.View.extend({
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, function(){
// perform your jQuery logic here
});
}
});
Find more infos and explanations in my blog.
It sounds like you might have a bit of a misunderstanding of how ember works.
didInsertElement is fired when that particular view is injected into the dom, not when all of the elements are in the dom. Note: If the model behind it changes, ember won't fire didInsertElement again, because it wasn't reinserted into the dom.
If it works when you delay, then it sounds like your logic for calculating the size of something is depending on something that may not be there yet.
Feel free to show an example of it using emberjs.jsbin.com.

Updating loaded models with JS

To explain my exact use-case:
So I have a news-model with a 'created_at' field. Displaying all the dates with an ArrayController works fine and as expected. But what I want to do now, is converting these dates into fuzzy times with http://pragmaticly.github.io/smart-time-ago/ (or http://timeago.yarp.com/).
The problem here is, I need to call $('.timeago').timeago('refresh'); once the timestamps are loaded and displayed. That seems to work fine as long as I navigate within the ember-app. But when I refresh a site the plugin somehow can't convert it.
So I suppose the command doesn't get called at the right time from the app.
I am doing that in the following two ways right now:
in the View:
didInsertElement: function(){
$('body').timeago('refresh');
}
and in the Controller:
updateFuzzyTime: function(){
$('body').timeago('refresh');
}.observes('content')
When I do it in either place with the setTimeout() command set to 1000ms it obviously does work.
So how can I get ember to perform that command when a model is fully loaded and displayed on a refresh?
(In another project I had a similar issue and there I used the rerender() command which did work, but in this use-case I simply can not do that)
Edit: Just to post it here as well. The plugin looks at the datetime-attribute of a time element. So my hbs code looks like this:
<time class="timeago" {{bindAttr datetime="date"}}>{{date}}</time>
And I suppose the reason why most hooks don't work is because even though the DOM is rendered I suppose Ember hasn't updated the attribute yet.
Try calling it in an afterRender callback. That usually executes after everything else.
init: function() {
this._super();
Ember.run.scheduleOnce('afterRender', this, 'updateFuzzyTime');
}
updateFuzzyTime: function() {
$('body').timeago('refresh');
}

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.