In a method on an Ember.View subclass, I would like to make changes to the DOM only if the view element has already been inserted into the DOM. How can I check that?
I know I could create an auxiliary property like so:
didInsertElement: function() {
this.set('elementIsInserted', true);
}
willDestroyElement: function() {
this.set('elementIsInserted', false);
}
But is there some canonical, built-in way?
I didn't find anything skimming view.js, but perhaps I'm missing something.
Every view has a _state property, which is set to "inDOM" when the element is inserted.
if (this._state=="inDOM") doStuff();
should work. Make sure you have the correct this!
If you want to avoid having to set an auxiliary flag, you can extend Ember.View:
Ember.View.reopen({
didInsertElement: function() {
this.set('elementIsInserted', true);
this._super();
},
willDestroyElement: function() {
this.set('elementIsInserted', false);
this._super();
}
});
Now every View that extends Ember.View will get the above.
Also a member of the core team suggested that you avoid referring to inDOM as it is an internal variable and not intended to be used in code.
Related
I want to access a property, say selectedItem defined in a component from a parent controller. How to achieve this? I want to access this item so that I can open a modal defined as a partial with this. If anyone can suggest any better solution that is also welcome.
You could bind the property to a property of the controller. Something like this:
App.FooBarComponent = Ember.Component.extend({
selectedItem: null,
// set the property somewhere in your component
});
In your controller
App.SomeController = Ember.Controller.extend({
fooBarSelectedItem: /* empty (null) or a value */
});
In your template
{{foo-bar selectedItem=controller.fooBarSelectedItem}}
I have a "count-down" component, which renders a clock, and when the count-down ends, I need to disable some buttons on the view around the component. The solution is similar to #splattne's answer, but it's newer Ember 3.1 syntax and the shared value is not part of the model.
Component:
export default Component.extend({
'remaining_time': computed('timer_end', 'dummy', function() {
let now = new Date();
let remaining = this.get('timer_end') - now;
if (remaining < 0) {
scheduleOnce('afterRender', this, function(){
this.set('event_listener.expired', true);
});
this.set('should_run', false);
return "Countdown Closed";
}
...
}),
});
Template:
{{count-down timer_end=model.timer_end event_listener=countdown_status}}
Controller:
export default Controller.extend({
countdown_status: Object.create({'expired': false}),
controls_enabled: computed('countdown_status.expired', function() {
return !this.get('countdown_status.expired');
}),
...
});
Note the scheduleOnce('afterRender': it was necessary for https://github.com/emberjs/ember.js/issues/13948. You will only need it if your component changes the shared value before the whole view is rendered, which is unfortunately what mine did.
in ember application i want to call my custom function which does some modifications of dom elements, the only solution i found is to repeat below code as many times as many views/routes i have
for example
rendering indexView
indexView = Ember.View.Extend({
didInsertElement:function(){
//my custom function call goes here.. myFunction();
}
});
rendering OtherView
OtherView = Ember.View.Extend({
didInsertElement:function(){
//my custom function call goes here.. myFunction();
}
});
rendering MoreView
MoreView = Ember.View.Extend({
didInsertElement:function(){
//my custom function call goes here.. myFunction();
}
});
Is there a way of calling myfunction globaly whenever any view is rendered? I really do not want to repeat code for every single view i render.
thanks!
You can create a Mixin:
App.SomeMixin = Ember.Mixin.create({
didInsertElement: function() {
this._super();
//do your common stuff here
}
});
And use it in your views:
App.SomeView = Ember.View.Extend(App.SomeMixin, {
didInsertElement: function() {
this._super();
//do your custom stuff here
}
});
I would use a mixin to do this. If, however, you find that you are using this mixin into every single view that you create, it might be better to reopen the Ember.View class and add this functionality there. Also,if you reopened the class, what you could do is, depending upon the use case, create a static function inside Ember.View.reopenClass which you would pass to a
Ember.run.scheduleOnce()
utility that ember provides so that, if the function that you need doesn't need any state (for example, just does something to the page after the content has loaded or something.), it will just run the function once after all the views are rendered in the page.
There are two ways to handle this. Create a base class, or reopen the global View class and insert your code. I prefer the first, since it's easier to maintain and track down issues.
Reopen the View class and tack on an additional method that triggers on didInsertElement
Ember.View.reopen({
doStuff:function(){
//myFunction();
}.on('didInsertElement')
});
In the case of debouncing
Ember.View.reopen({
doStuff:function(){
Ember.run.debounce(this.doRealStuff, 200);
}.on('didInsertElement'),
doRealStuff: function(){
console.log('foo');
}
});
http://emberjs.jsbin.com/gecoziwe/1/edit
Create a base class and extend from it (My preferred method, since it's easier to track down issues, can't say this enough)
App.BaseView = Ember.View.extend({
doStuff:function(){
//myFunction();
}.on('didInsertElement')
});
App.FooView = App.BaseView.extend({
});
App.BarView = App.BaseView.extend({
});
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.
I am trying to access data from DashboardIndexController in DashboardIndexView
JP.DashboardIndexController = Ember.Controller.extend({
users: []
});
Is it possible to access users in JP.DashboardIndexView in didInsertElement?
didInsertElement : function(){
console.log(this.get("controller.users").objectAt(0));
}
This is my DashboardIndexRoute:
JP.DashboardIndexRoute = Ember.Route.extend({
setupController: function(controller, model) {
controller.set('users', JP.User.find());
}
});
Thank you
EDIT
console.log(this.get("controller.users").objectAt(0));
Returns data only when I go to UsersIndex and then back to DashboardIndex... I think it's something with initialization, but I don't know, how to fix it.
Yes, this is how to access the list of users.
This happens because the DashboardIndexView is inserted into the DOM before the controller.users is populated with data from the server.
So when the view is rendered, controller.users is still empty, and will asynchronously be populated after the ajax request finishes, problem is, didInsertElement already fired.
In order to solve this, you need to access controller.users from another hook. DidInsertElement is the wrong place, because it will fire irrespective of whether JP.User.find() finished loading.
You just need to make sure that it re-fires when JP.User.find() is loaded even if view is already in the DOM. Something like this:
JP.DashboardIndexView = Ember.View.extend({
didInsertElement: function() {
this.usersChanged();
},
usersChanged: function() {
if (!this.$()) { return; } // View not in DOM
console.log(this.get('controller.users'));
}.observes('controller.users.#each')
});
I have an ember object and i'd like to know if it is in a dirty state.
var App.Post = Ember.Object.create({
title: "Test",
isDirty: false
});
App.Post.set("title", "Test2");
App.Post.get("isDirty") // Should === true
For the moment, I have tried overloading the set for the object
App.Post = Ember.Object.create({
set: function(path, value) {
this._super(path, value);
this._super("isDirty", true);
}
})
It works when I am calling directly myObject.set but it doesn't seem to use that set function when using embers binding. I added logs and this method isn't called by the regular emberjs bindings workflow.
Another thing I've tried is to add an observers to toggle the dirty flag.
App.Post = Ember.Object.create({
hasBeenModified: function() {
this.set("isDirty", true);
}.observes("title")
})
For a reason still unknown, when I use observes at the model level my bindings do not work anymore in the UI.
I believe you may also need to override setUnknownProperty. The UI is using Ember.set(object, key, value). If you look at the implementation
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/property_set.js#L60
It doesn't call your setter, but will call setUnknownProperty if it exists.
Actually, at
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/property_set.js#L52
It looks like they will call your setter if you have predefined the field in your App.Post class.