Something like document ready, but after all Ember views rendering
I am doing this right now with an override on ApplicationView didInsertElement, which seems to be working so far:
App.ApplicationView = Em.View.extend({
didInsertElement: function() {
// Do your magic.
}
});
I am wondering if this is the right way for an Ember document ready, or if Ember has a more native support for this simple and very common thing.
You can easily add a "post render" hook by reopening the base View class and adding it into the render queue.
Here's some code to show you how:
Ember.View.reopen({
didInsertElement : function() {
this._super();
Ember.run.scheduleOnce('afterRender', this, this.didRenderElement);
},
didRenderElement : function() {
// Override this in your View's
}
});
The didInsertElement is the right place, but if you want to be completely sure your render queue is completely flushed you could also listen to the afterRender event, something like this:
App.ApplicationView = Ember.View.extend({
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, 'processChildElements');
},
processChildElements: function() {
// do here what you want with the DOM
}
});
Hope it helps.
App.ApplicationView = Ember.View.extend({
afterRender: function () {
Ember.run.next(this, function () {
// This will run one time, after the full initial render.
});
}
});
Related
I have written a view:
export default Ember.View.extend({
select: null,
modify: null,
createSelect: function() {
return new ol.interaction.Select();
},
onMapCreated: function() {
this.select = this.createSelect();
this.modify = this.createModify();
},
init: function() {
this._super();
this.get('controller').addObserver('olMap', this.onMapCreated);
},
});
The view is added in a template related to a controller which has an olMap property.
I need to wait for the olMap property to be instantiated before doing some work in my view.
The code above is kind of working, except that the this referenced in the onMapCreated function is the controller's instance and not the view's instance.
I'm quite sure I am doing something wrong in my application's design. I would like to separate the concerns and get the drawing part outside of the main controller. Should I use a component? Not sure because it's not going to be reusable...
I would love to have some directions here.
After reading the manual in the API section, I came up with the solution. I post it here is someone needs it someday. Actually, all I had to do is add a parameter this to the addObserver method in order to change the context.
export default Ember.View.extend({
select: null,
modify: null,
createSelect: function() {
return new ol.interaction.Select();
},
onMapCreated: function() {
this.select = this.createSelect();
this.modify = this.createModify();
},
init: function() {
this._super();
this.get('controller').addObserver('olMap', this, this.onMapCreated);
},
});
Here is what I've ended up with:
My setup:
Ember : 1.10.0
Ember Data : 1.0.0-beta.16
jQuery : 1.11.2
Directory structure:
controllers:
map.js
map-draw.js
templates
map.hbs
map-draw.hbs
views
map.js
map-draw.js
in the template templates/map.js I use a render helper like this:
{{render "mapDraw" mapDraw}}
the renderer uses the controller controllers/map-draw.js and the view views/map-draw.js
content of the view map-draw.js:
export default Ember.View.extend({
templateName: "mapDraw",
classNames: ["map-draw"]
});
in the controller map-draw.js I am binding the map.js controller.
export default Ember.Controller.extend({
needs: ['map'],
olMap: null,
//...//
init: function() {
this._super();
this.get('controllers.map').addObserver('olMap', this, function(sender) {
this.set('olMap', sender.get('olMap'));
});
}
//...//
});
From my understanding, one way to work with CSS transitions is to use Ember.run.scheduleOnce('afterRender')
However, for me it is not working without adding a timeout. This is in Ember 1.0.0
View = Em.View.extend({
didInsertElement: function() {
Ember.run.scheduleOnce('afterRender', this, 'animateModalOpen');
},
animateModalOpen: function() {
// this does not work - modal gets styles from class "in" with no transition
$('.modal').addClass('in');
// this does work, the transition is fired
setTimeout(function() {
$('.modal').addClass('in');
}, 1);
}
},
});
Is this something that used to work and just doesn't anymore, or am I missing something?
Ember.run.next has worked very well for me on this type of thing.
didInsertElement: function() {
Ember.run.next(this, this.animateModalOpen);
}
I have an application that uses masonry and Ember JS I attempt to search DOM an element by selector, but it retrieves null It seems I do it early than template was rendered. Please, help me resolve it.
#GJK answer is correct, I just want to provide a working example: http://jsbin.com/enijad/3/edit
App.IndexView = Ember.View.extend({
didInsertElement: function() {
var $container = $('#container');
$container.masonry({
columnWidth: 150,
itemSelector: '.item'
});
}
});
The didInsertElement function will be called when the view was inserted into the DOM, so it will be safe to initialize additionally libraries.
Also worth mentioning is that if you need some clearing up after the view was removed from the DOM you would do this in didInsertElement's counterpart hook willDestroyElement.
Example:
App.IndexView = Ember.View.extend({
didInsertElement: function() {
// do initialization here
},
willDestroyElement: function() {
// and here you can remove stuff safely
}
});
Hope it helps.
Create a corresponding View for your Route and Template, and then override the didInsertElement method.
hi i have the following route:
MB3.PlaylistRoute = Ember.Route.extend({
model: function(params) {
return MB3.Playlist.find(params.playlist_id);
}
});
The playlist has a hasMany realtion with tracks. in the playlist view i want do do some logic with an attribute of the first track of the playlist.
so i added this code:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
console.log(this.get("controller.tracks").objectAt(0).get("title"));
}
});
The problem is title is undefined (i think because it is not yet loaded. the second thing i tried is waiting for the didLoad event:
MB3.PlaylistView = Ember.View.extend({
didInsertElement: function() {
var self=this;
this.get("controller.tracks").on("didLoad", function() {
console.log(self.get("controller.tracks").objectAt(0).get("title"));
});
}
});
but this logges null as well. How do i accomplish that?
Like Adrien said in the comments, it seems you are running into issue 587. That said, I don't think you actually need the "didLoad" callback in this case. Instead, try using a computed property to get the video_id or track title. For example:
MB3.PlaylistView = Ember.View.extend({
firstTrackTitle: function() {
return this.get('controller.tracks.firstObject.title');
}.property('controller.tracks.firstObject.title')
});
Then in your template, embed the player if this property is defined:
{{#if view.firstTrackTitle}}
embed code here
{{/if}}
FWIW I would put this logic in controller instead of view, but same idea.
When the main view of my application is switched (new route that reconnects the main outlet of my application controller) I want the page to be scrolled to the top. Otherwise it's a bit strange that I navigate to another page-like view and the viewport is still lost somewhere where I left off.
I hacked a solution and wonder if there's a better way or if anyone has the same thing.
Here's what I do:
App.ApplicationController = Ember.Controller.extend({
connectOutlet: function(){
window.scrollTo(0, 0);
this._super.apply(this, arguments);
}
});
#Baruch's solution is good, but when I implemented it I had render on elements within my application state and would cause a scrollTop when it was not needed.
I found this to be much more effective as it only runs on the path change:
App.ApplicationController = Ember.Controller.extend({
currentPathChanged: function () {
window.scrollTo(0, 0);
}.observes('currentPath')
});
I achieved this with the following code:
Ember.Route.reopen({
render: function(controller, model) {
this._super();
window.scrollTo(0, 0);
}
});
Coffee Script:
Ember.Route.reopen
activate: ->
#_super()
window.scrollTo(0, 0)
Javascript:
Ember.Route.reopen({
activate: function() {
this._super();
window.scrollTo(0, 0);
}
});
You should probably try and extend Ember.Route and add your window.scrollTo in the enter callback. Then instead of using Ember's Route for your leaf routes, you call your route .extend(), so they'll automatically scroll up when you enter a route/state. Something similar to this:
// define your custom route and extend "enter"
var MyRoute = Em.Route.extend({
enter: function(router) {
// for now on, all the routes that extend this,
// will fire the code in this block every time
// the application enters this state
// do whatever you need to do here: scroll and whatnot
}
});
App.Router = Em.Router.extend({
enableLogging: true,
location: 'hash',
index: Em.Route.extend({
route: '/',
connectOutlets: function(router) {
...
},
// on your leaf routes, use your own custom route that
// does your scroll thing or whatever you need to do
home: MyRoute.extend({
route: '/',
connectOutlets: function (router, context) {
...
}
}),
// other routes...
})
});
does it make sense?
It's now render(name, options), and if you are specifically calling render (ie with a modal) you want to pass that to super()
Ember.Route.reopen({
render: function(name, options) {
if (name != null) {
return this._super(name, options);
} else {
return this._super();
}
}
});
Ember 3.12+ (this is technically 3.20 code listed here)
import EmberRouter from '#ember/routing/router';
const Router = EmberRouter.extend({
init() {
// call event everytime route changes
this.on('routeDidChange', () => {
this._super(...arguments);
window.scrollTo(0, 0); // scrolls to top
});
}
});
Router.map(function () {
// your mapping code goes here
});
export default Router;
Prior to 3.12 (this is technically 3.4 but the key code should be the same)
import EmberRouter from '#ember/routing/router';
const Router = EmberRouter.extend({
didTransition() {
this._super(...arguments);
window.scrollTo(0, 0);
}
});
Router.map(function () {
// your mapping code goes here
});
export default Router;
We have handled this problem serveral times and the way we've found that is the easiest and most straight-forward way is to configure this once in the router.js file using a 'route transition' event function. We used didTransition before it got deprecated in Ember 3.12 in lieu of routeDidChange. I've posted both examples below. Some syntax may differ slightly depending on which version of Ember you are on but this core code should be the same.