Ember.View is deprecated, in favor of Components. That's great but I'm having trouble making sense of the 2.0 release.
Most often, I used the didInsertElement hook to run some jQuery code etc. But now that the Em.View class has been deprecated, how can I achieve the same thing? I don't want to create a component or anything like that. It doesn't make sense to create components for normal pages(routes). Simply because its not a re-usable thing plus component's scopes are isolated.
Say we have a about route, and when the template is rendered I just want to sun some jQuery code. How can I do this in Ember 2.0+?
You could take advantage of didTransition hook and Ember.run.next. Check my solution:
export default Ember.Route.extend({
actions: {
didTransition() {
Ember.run.next(this, 'initParticles');
}
},
initParticles() {
let ammount = (window.matchMedia('(max-width: 456px)').matches) ? 40 : 100;
particlesJS('particles-js', {
// my options
});
}
});
While it doesn't seem like the best approach, creating a component is probably your best option. Soon we'll have routeable components which will take over much of what controllers and views use to do. Creating a component and just inserting it into your template should put you on a good path to be ready for routeable components.
Related
I have an app with many similar views which I instantiate programmatically to "DRY-up" my app.
The problem is that controllers instantiated programmatically do not delegate actions in the actions hash further. This is clear because there is nothing from which the controller can derive the hierarchy. There should be a way, however, to tell a controller which parent controller it has to use for event bubbling. Does anyone know it?
You shouldn't be initializing controller's on your own. All controller initialization should be handled by Ember itself. Another interesting note, controller's are intended to be singletons in the application. The only exception to this being the itemController when looping over an ArrayController. You can read more about it in the guides. Quote from the guides:
In Ember.js applications, you will always specify your controllers as
classes, and the framework is responsible for instantiating them and
providing them to your templates.
This makes it super-simple to test your controllers, and ensures that
your entire application shares a single instance of each controller.
Update 1:
An example of how to do routing for a wizard:
App.Router.map(function() {
this.resource('wizard', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
This way, you can have a separate controller/view/template per step of the wizard. If you have logic around how much of each step should be completed prior to transitioning to the next one, you can handle that in the individual routes.
Update 2:
In the event that the number of steps aren't predetermined, but are based on the data being fed to the app, you can make a WizardController that is an ArrayController where each item in the array is a step in the wizard. Then, use the lookupItemController hook on the ArrayController, kind of like this:
App.WizardRoute = Ember.Route.extend({
model: function() {
return [
{controllerName: 'step1', templateName: 'step1'},
{controllerName: 'step2', templateName: 'step2'},
{controllerName: 'step3', templateName: 'step3'}
];
}
});
App.WizardController = Ember.ArrayController.extend({
lookupItemController: function(modelObject) {
return modelObject.controllerName;
}
});
{{#each step in controller}}
{{view Ember.View templateName=step.templateName}}
{{/each}}
As another, probably better, alternative, you can override the renderTemplate hook in the route where you're pulling down the model for the next step in the wizard and pass in the appropriate templateName and controller in the render call, kind of like you see here.
Point being, I think it should be possible to do this without having to instantiate controllers yourself.
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 have a question about Ember routing and controllers. I've just written a small App to get familiar with the new router. Therefore I've built a button that transitions to another state by clicking on it.
App.PostsView = Em.View.extend({
click: function() {
var router;
this.get('controller').transitionTo('about');
}
});
My question now is: what does the get method return?. Obviously an instance of the PostController but on the one hand the controller doesn't have a transitionTo() method and on the other hand that would not make any sense.
this.get('foo') returns a property of your Ember object. Since Views can have a "controller" property, this.get('controller') returns the controller bound to your view's controller property (by default the postsController).
this.get('controller').transitionTo() works because as sly7_7 mentioned, transitionTo() is also defined on the controller and delegates to the router. Note, it's probably going to be deprecated and one should use
this.get('controller').transitionToRoute('about');
instead.
You should not do this at the view level, this is what the router is designed for so instead of capturing a click event in the view, rather implement an action on the button and let the router handle it. I suggest you learn the best practices from the get-go, chances are your application will evolve, requiring more elaborate concepts such as handling transactions commits/rollbacks, creating/updating records. So here's my suggestion to you
In your view
<button type="button" {{action onSaveClick}} />Save</button>
In your router
App.FooRoute = App.Route.extend({
events: {
onSaveClick: function() {
this.transitionTo('bar');
}
}
})
If for any other reasons, say styling or animation, you find yourself forced to capture the click event in the view, then i suggest to, capture the event, perform your styling and finally send an event to the controller. The event can then be handled the same way in the router
App.FooView = Ember.View.extend({
click: function() {
// Do some styling
this.get('controller').send('onSaveClick')
}
})
Finally speaking of best practices, try to learn when working with ember to think of your application as a series of states interacting with each other. You'll find yourself bound to implement the concept right. Have a look at this
the controller has a transitionTo: https://github.com/emberjs/ember.js/blob/master/packages/ember-routing/lib/ext/controller.js#L36, it basically delegate to it's target (which is the router)
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.
How do I run a function after an Ember View is inserted into the DOM?
Here's my use-case: I'd like to use jQuery UI sortable to allow sorting.
You need to override didInsertElement as it's "Called when the element of the view has been inserted into the DOM. Override this function to do any set up that requires an element in the document body."
Inside the didInsertElement callback, you can use this.$() to get a jQuery object for the view's element.
Reference: https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js
You can also use afterRender method
didInsertElement: function () {
Ember.run.scheduleOnce('afterRender', this, function () {
//Put your code here what you want to do after render of a view
});
}
Ember 2.x: View is deprecated, use component instead
You have to understand the component's lifecycle to know when does certain things happen.
As components are rendered, re-rendered and finally removed, Ember provides lifecycle hooks that allow you to run code at specific times in a component's life.
https://guides.emberjs.com/v2.6.0/components/the-component-lifecycle/
Generally, didInsertElement is a great place to integrate with 3rd-party libraries.
This hook guarantees two (2) things,
The component's element has been both created and inserted into the DOM.
The component's element is accessible via the component's $() method.
In you need JavaScript to run whenever the attributes change
Run your code inside didRender hook.
Once again, please read the lifecycle documentation above for more information
Starting with Ember 3.13, you can use components that inherit from Glimmer, and this example below shows what that could look like:
import Component from '#glimmer/component';
import { action } from '#ember/object';
/* global jQuery */
export default class MyOctaneComponent extends Component {
#action configureSorting(element) {
jQuery(element).sortable();
}
}
<div {{did-insert this.configureSorting}}>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
These view style components don't have lifecycle hooks directly, instead, you can use render-modifiers to attach a function. Unofficial introduction to modifiers can be found here
The benefit of this is that, it's clearer what the responsibilities of the template are and become.
Here is a runnable codesandbox if you want to play around with this:
https://codesandbox.io/s/octane-starter-ftt8s
You need to fire whatever you want in the didInsertElement callback in your View:
MyEmberApp.PostsIndexView = Ember.View.extend({
didInsertElement: function(){
// 'this' refers to the view's template element.
this.$('table.has-datatable').DataTable();
}
});