I want to execute JavaScript when all content (e.g. images) has been loaded in my Ember application.
I already tried using didInsertElement() and didRender() hooks, but it looks like they do not wait for background images to load.
Here's what my component's code snippet looks like:
export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
Ember.run.scheduleOnce('afterRender', this, function() {
var home =$('#main-outlet')[0];
home.className += " homePage";
startTimer(5);
});
},
});
Any solution or an alternative approach for this?
Ember does not have an event that is equivalent to onload.
However, regarding an alternate approach, you could leverage Ember’s alias for jQuery, in combination with the didInsertElement hook within your component, to achieve the order of execution that you are looking for. Try this:
export default Ember.Component.extend({
didInsertElement() {
Ember.$(window).on('load', this.executeCssAnimations);
},
executeCssAnimations() {
// your CSS and animation logic would go here
Ember.$('.big-background')
.text('NOW READY FOR CSS and ANIMATION UPDATES.')
.css('color', 'yellow');
},
willDestroyElement(...args) {
this._super(...args);
Ember.$(window).off('load', 'window', this.executeCssAnimations);
},
});
The willDestroyElement hook has been included as well, to show proper teardown and removal of the load event listener from window.
I’ve created an Ember Twiddle example to demonstrate this for you.
Related
I have very small web page with emberjs, where I want to show some item list and openlayers map for them, and another map for selected item.
Something like that:
<script type="text/x-handlebars" id="list">
<div class="list">
<div id="list_map"></div>
</div>
{{outlet}}
</script>
<script type="text/x-handlebars" id="list/item" >
<div class="item">
<div id="item_map"></div>
</div>
</script>
<script>
function showListMap() {
listMap = new ol.Map({target:'list_map'});
}
function showItemMap() {
itemMap = new ol.Map({target:'item_map'});
}
</script>
There is no problem to display map for list:
model: function(params) {
var content = [];
var url = 'http://localhost:8080/app/list';
$.ajax({
url: url,
success: function(surveys) {
content.pushObjects(surveys);
showListMap();
}
});
return content;
}
and I have action in item controller that is executed, when opening selected item, but if I try to create item map there (in controllers action) it fails (afaik because there is no such div at that moment in DOM).
So if I could execute action or my function after div is already add to DOM, it should work.
And question would be, how to execute something after template is added to DOM, or that's completely wrong way to do such stuff (than what would be correct ember way)?
I can't say much with seeing full code. But to execute some code after the DOM is rendered you schedule a function on the the run loops afterRender queue.
Ember.run.scheduleOnce('afterRender', this, function() {
//The div should be available now.
});
But if you really need to touch the DOM I recommend you wrap your map code in a component. A component gets a didInsertElement where you can write your maps initialization code.
var component = Em.Component.extend({
setup: function() {
//Do you map init here.
}.on('didInsertElement')
});
There unfortunately isn't a really good route or controller hook that fires off after a page has already rendered. I believe the reason for this is that the developers of Ember think it is an anti-pattern to directly talk to the DOM.
That being said, I think it sometimes is quite handy for complex UI on otherwise static web pages. If you want to do some sort of jquery or use the DOM API after a route has rendered, you can do the following in your route file (as #Dainius correctly points out)
routeName.js
import Route from '#ember/routing/route';
import jQuery from 'jquery';
export default class myRouteFile extends Route {
manipulateDom = function() {
$("#myDiv").css( "color", "red" );
}
init() {
this._super(...arguments)
Ember.run.scheduleOnce('afterRender', this, this.manipulateDom)
}
}
I'm building an Ember app that needs to size a container DIV to be full window height on load of the application, and then run the same resize function again when transitioning to a new route, and then also on window resize.
On a normal site, I'd do this:
var appUI = {
init: function(){
appUI.sizeContainer();
},
sizeContainer: function(){
var winHeight = jQuery(window).height();
jQuery('#container').height(winHeight);
},
onResize: function() {
appUI.sizeContainer();
}
}
jQuery(document).ready(function($){
appUI.init();
jQuery(window).resize(function(){
appUI.onResize();
});
});
But obviously this won't work in Ember.
This can't be a component, because the #container DIV wraps the entire current view. But with Ember moving away from views, how should I do this?
The only way I came up with was to use a view, and hook onto didInsertElement, but I couldn't figure out how can I do it without having to create a view.js file for every route, that contains the same resize code? And how about the resize event? I thought the application view didInsertElement might work for this, but it only runs once on load.
All my route templates basically follow this patten:
{{top-header}}
{{background-image image=backgroundImage}}
{{side-menu session=session menuOpen=menuOpen}}
<div id="container" class="vert-center route-name">
{{partial "_logo"}}
{{some-component}}
</div>
On loading the application and on window resize can be done pretty much the way you described.
One easy way is to override the renderTemplate hook inside the ApplicationRoute. Within this hook, you can render your application template and then initialize the resize listener on the window object:
// handles on document load and on window change events
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function(controller, model) {
this.render('application'); // render the application template
appUI.init(); // call the init event on application load
Ember.$(window).resize(function() { // setup resize listener on the window object that will be called when window resizes
appUI.onResize();
});
}
});
As far as resizing each time a route loads, you could implement a generic Ember.Route, let's call it ResizableRoute for example, that calls the appUI.resize() after its template is rendered. This can again be achieved with overriding the renderTemplate hook.
// calls onResize() each time the current route's template is rendered in the DOM
App.ResizableRoute = Ember.Route.extend({
renderTemplate: function() {
// render the template with the same name as the route (assumes you follow ember naming conventions)
this.render(this.routeName);
// call resize since the route is loaded
appUI.onResize();
}
});
Now you can make any other route extend this ResizableRoute and, every time that route's template is rendered, appUI.onResize() will be called.
App.AnyOtherRoute = App.ResizableRoute.extend({
// do other stuff
});
The reason all the calls are made AFTER the template is rendered is because that way the #container element is definitely inserted in the DOM already and can be grabbed using jQuery.
Here is a running jsFiddle example
EDIT
Instead of overriding the renderTemplate hook, another way you could achieve this is to create a ResizeUIComponent that will perform resizing each time your route is loaded. The flaw is that you have to remember to insert this component into each route's template.
App.ResizeUIComponent = Ember.Component.extend({
didInsertElement: function() {
this.$().hide(); // make the component invisible, probably better to do it with css but this is a quick example
appUI.onResize();
}
});
And add this component to all templates (including application) you want to call onResize() each time they load:
{{top-header}}
{{background-image image=backgroundImage}}
{{side-menu session=session menuOpen=menuOpen}}
<div id="container" class="vert-center route-name">
{{resize-ui}} {{!-- add the invisible resize component as the child of #container to ensure necessary rendering order --}}
{{partial "_logo"}}
{{some-component}}
</div>
And you can add a listener on the window object after the init event of the ApplicationController:
App.ApplicationController = Ember.Controller.extend({
onInit: function() {
Ember.$(window).resize(function() { // setup resize listener on the window object that will be called when window resizes
appUI.onResize();
});
}.on('init');
});
Handlebar conditionals actually delete the DOM elements inside of the conditionals, JQuery thinks that the newly generated DOM elements, despite their matching ID or class is something entirely different.
I need a simple solution for this. A solution that I can wrap my solutions in once per page. I don't want to have to tack .observes() after everything as that seems like a shoddy work-around
Right now I put my Jquery in the didInsertElement{} in my current view that is being used.
Simple Example:
exampleView -
didInsertElement {
$('#exampleButton').on('click', function() {
console.log('To Ember. Or to Angular. That is the question.')
}
}
example.hbs -
{{#if booleanTrue}}
<button id="exampleButton">Button go!</button>
{{/if}}
That approach is not best for Ember. For the future release of Ember they are planing to remove jQuery dependency. With Ember you don't have to use jQuery.
rewrite your code like this:
didInsertElement: function(){
this.$().hide().fadeIn('slow'); // or any animation that you want
},
actions: {
myButtonAction: function(){
//do something
}
}
{{#if booleanTrue}}
<button id="exampleButton" {{action "myButtonAction"}}>Button go!</button>
{{/if}}
willDestroyElement is not right hook for animation. So you have to trigger it yourself.
I use my custom action like
actions: {
deleteClicked: function(){
var self = this;
this.$().animate({ height: 'toggle' }, 300, function() {
self.set('booleanTrue', false);
});
}
}
due to your comment I changed my answer
What hook can I use in ember that will only run after all of the content has been loaded?
I'm using zurb foundation's top-bar fixed and once a view is rendered I want to do something like this to dynamically space my body:
$(window).load(function() {
$("body").css("padding-top", parseInt($(".top-bar").css("height")) - 2);
});
The closest solution I've found here is:
Ember.View.reopen({
didInsertElement : function(){
this._super();
Ember.run.scheduleOnce('afterRender', this, this.afterRenderEvent);
},
afterRenderEvent : function(){
// implement this hook in your own subclasses and run your jQuery logic there
}
});
This almost works except for the fact that all of the content is not yet loaded i.e. images have not yet been loaded and therefore calculation above is wrong.
Inside the afterRenderEvent you can use some jQuery logic that waits for the image to be fully loaded
afterRenderEvent : function(){
$(photo).bind('load',doSomething());
}
Ok, i'm a really newbie to Ember JS, but i'm having a play and working my way through things.
So far I'm really liking it, but…
I wanted to use something like: https://github.com/ed-lea/jquery-collagePlus, which created a masonry style layout.
I have create the route, and the navigation to that view, but… how do you apply the effect?
Normally a:
$(window).load(function () {
$('.Collage').collagePlus();
});
would do the job at the bottom of the page, but i'm guessing popping in a:
App.GridRoute = Ember.Route.extend({
afterModel: function(){
$('.Collage').collagePlus();
}
});
Might be better, but that's not working…
any help, pointers on this welcome, be gentle as i'm not understanding it quite yet!
PS. i'm also using bootstrap and bootstrap ember (probably doesn't matter…)
The place to do it it's the View, handling the didInsertElement event. I believe the View it's a good place to isolate any logic related to the DOM.
App.GridView = Ember.Route.extend({
classNames: ['Collage'],
didInsertElement: function(){
this.$().collagePlus();
}
});
A useful link:
Hope it helps!