I've noticed that a route doesn't re-render when the model changes (i.e. transitionTo the same route with a different model). I have some jQuery plugins set up on a particular page and I need them to re-render when the model changes, so it appears as a fresh page.
Is there a way to do this? Perhaps by observing the model's ID and firing a re-render of the route somehow?
Thanks in advance
I have an ember twiddle that, I believe, does what you're looking for, but first I would like to argue there are no straightforward ways to do what you're asking because it is the opposite of what an SPA is designed to do.
Data binding (without refreshing the view) is typically a boon of an SPA, and the SPA works hard to avoid brute force reloading/refreshing/rerendering the view at all costs. It took me a while to find a solution to your question as it is stated because it seems to go against Ember design principles. Even hooks like route refresh() are meant to update the model and bound data, not reload the template.
Although other people have asked the same question before, it seems that most answers guide users towards not refreshing the whole view. More often than not, the ideal of refreshing the view is an incorrect assumption.
Following previous examples, I would suggest that your goal shouldn't be to refresh the template completely, but rather, figure out how you can make your jQuery plugin better fit in to a Single Page App/client-side JS friendly design and have it reload as a natural part of the route lifecycle.
For instance, maybe the plugin can be reloaded/reset/re-run in afterModel() or somewhere similar.
That said, I was able to accomplish what you asked for using (in my opinion, a hack) Ember.run.later() so that I could invalidate an if block and force the content to rerender. Note, this is typically not what users want since (aside from design principle reasons) it causes UI flicker.
I have a component like so.
/* will-rerender.hbs */
{{#if show}}
{{yield}}
{{/if}}
And it has a JS file like so.
/* will-rerender.js */
import Ember from 'ember';
export default Ember.Component.extend({
show: false,
didReceiveAttrs() {
this._super(...arguments);
/*
Ugly hack, but we need to reset `show` in
a separate run loop in order to force the view
to rerender.
*/
this.set('show', false);
Ember.run.later(() => {
this.set('show', true);
});
}
});
You can invoke it like this...
/* your template */
{{#will-rerender cacheKey=model.id}}
... your content to rerender ...
{{/will-rerender}}
Whenever the model.id changes the component will invoke didReceiveAttrs() causing show to invalidate and the view to refresh.
As an aside, I think the behavior of switching between models would be much more natural with {{link-to}} rather than calling transitionTo yourself.
Related
I am really new to Ember, which I am asked to do and well, love to learn. Basically, the current project uses Gentelella Admin Dashboard. I being trying to get the dashboard to load properly but failed.
After I login, I get redirected to /dashboard/ route, which basically loads the main dashboard interface. Now the problem is I can't click-expand the menus on the sidebar nor toggle the sidebar menu. And the main page is not extended to fill the space, as in our current application.
I know the function init_start() takes care of the resize and the click, which is already added to vendor.js from custom.js but I can't seem to call the function from ember at all.
My latest attempt was using mixins but it failed too:
import Ember from 'ember';
export default Ember.Mixin.create({
activate: function() {
this._super();
init_sidebar();
}
});
then from dashboard.js route:
import HandleTempLoadMixin from '../mixins/handle-temp-load';
export default Route.extend(AuthenticatedRouteMixin,HandleTempLoadMixin, {
});
but still the function is not executed.
I have read that it is best to avoid working with jquery inside ember in SO but I have pretty much many JQuery functions that I cant transfer right now (nor sure why exactly since it says somewhere in the documentation jquery is built into ember itself).
Anyway, what is the best way to initailize the dashboard interface?
From my understanding you have some jQuery stuff that you would like to utilise. I suggest looking into Component's didInsertElement hook and triggering your custom code from there.
You can find more details in here https://guides.emberjs.com/v2.17.0/components/the-component-lifecycle/#toc_integrating-with-third-party-libraries-with-code-didinsertelement-code
In general, try avoid working with view related stuff in Routes. Ember's power comes from strong conventions. Learning where to place your code is crucial.
I need to rerender whole page application after language has switched. I don't like to use observers for it because of performance issues.
Language switch is done by a Ember.View. I tried to rerender parentView after changing Ember.I18n.translations but running into a known bug. It works one time but Ember Inspector shows parentView has dutzend of children views of it's own afterwards. After another switch parentView got destroyed. Just like demonstrated in this JSFiddle.
Here is simplified code of my view:
export default Ember.View.extend({
templateName: 'language-switch',
languageChanged: function() {
// change language
var language = this.get('controller.language.selected');
Ember.I18n.translations = translations[language];
// rerender page
this.get('parentView').rerender();
}.observes('controller.language.selected')
});
There is also a discussion about that problem on discuss.emberjs.com. I tried the suggestions there but they don't work. Last post suggest a work-a-round to map over all views, rerender them and afterwards use a transition. I don't like that one since I am afraid getting side problems by that hack.
I am pretty sure there must be a way to do a language switch in Ember with ember-i18n but what's the right way to do that?
Update:
I tried to implement the hack from discuss.emberjs.com. It's working but it's very limited. I implement it like this in the view:
var self = this;
var currentRoute = self.get('controller.currentRouteName');
this.get('controller.target').transitionTo('loading').then(function(){
self.get('controller.target').transitionTo(currentRoute);
});
Problem is that all data stored in model is lost. I did not find a way to get the current model and use it in transition. Also query parameters are lost. That limitation makes this work-a-round unacceptable for my application.
Update App.reset():
As suggested in comment I tried to use App.reset(). It works better than transitionTo work-a-round but doesn't match all needs. If calling App.reset() queryParams aren't lost but model is reseted. I'm looking for a way to rerender application while keeping current model and queryParams.
It seems that mostly these problem is handled by a full page reload on locale change. E.q. one of the main developers of ember-i18n said so in this discussion.
I have a Route with dynamic segment :id. When I change only the dynamic segement part manually in browser url input field, then goes a transition, and all model hooks are called as expected.
The problem is: none of the views are rerendered. I guess it is because only model changed - not the UI. But I have some UI logic in views' didInsertElement handler - reinitialize UI plugins and so on.
How to force ember to rerender view after dynamic segment change?
I agree with the josh's comment. if you are rerendering views with the change of dynamic segment means your code is not written properly. But still if you want to go like that i am gonna give is a part of code.
In your route:
model: function(params){
model.set('id', params.id);
}
In your view which needs to be rerendered:
_modelIdChange: function(){
this.rerender();
}.observes('controller.model.id')
But i don't suggest this. I would rather prefer proper bindings. But i did use rerendering in some cases which mostly when you end up using jquery plugins.
I'm using the {{bind-attr}} helper to bind a class name to an element for the purposes of transitions (in css), and setting the values in the controller on 'didInsertElement' seems to short circuit the transition, even with delays in my CSS to prevent it. Though, the end-state of the transition is rendered.
I've tried Ember.run.scheduleOnce('afterRender') which seems to do the trick, but isn't quite what I want from a 'clarity' standpoint.
Is there a hook (or a way to create one, outside of an uggo setTimeout call) that fires AFTER an element has been inserted into the dom and is effectively finished rendering?
You have to use Ember.run.scheduleOnce('afterRender'). But you can do it more elegantly by doing the following. Now all of your Views have the hook afterRenderEvent. This should suffice from a clarity standpoint, right?
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
}
});
PS: This snippet is taken from a post on my blog, where i explain the approach more in detail.
Bit late to the party, but we needed css animations in ember-paper, so I created an ember addon that makes it possible to transition components (just like ng-animate and React Animation) with only css.
https://github.com/peec/ember-css-transitions
I found a lot of questions about how to initialize jQuery plugins with dynamic data etc and usually the answer is that you should use the didInsertElement hook. My problem is that when using ember-data, initially the view is inserted empty and the template vars are filled afterwards through the bindings. Thus in didInsertElement the elements I need are not there.
I found a solution that works for me but I think there must be a better solution.
In addition to the didInsertElement method I also observe the attributes in the controller and call my code from there, too, wrapped in a Ember.run.next so the child view is rendered before.
Is there a better solution to react to updated child views?
It depends a bit on the size of your application, and the number of elements your view is rendering.
Edit: You might successfully observer the isLoaded property of the RecordArray. Some details here: https://github.com/emberjs/data/blob/master/packages/ember-data/lib/system/record_arrays/adapter_populated_record_array.js#L21
One option that I have used for views that have a single object as their content is something like:
MyApp.CustomView = Ember.View.extend({
content: null,
contentObserver: function() {
this.rerender();
}.observes('content')
}
If your view contents is a list, it makes little sense to re render the view for each item in the list, though.
However, I think this approach is fairly similar to what you are already doing, though.
Another approach is to implement your own adapter with Ember Data. This way, you can tell the rest of your application that it's finished loading your data. Something like:
MyApp.Adapter = DS.Adapter.create({
//Finding all object of a certain type. Fetching from the server
findAll: function(store, type, ids) {
var url = type.url;
jQuery.getJSON(url, function(data) {
store.loadMany(type, data);
});
//Perform some custom logic here...
}
}
This should work, but its not as generic as it should/could be though.
Also, you might want to take a look at this commit, which allows for registering a one-time event.
https://github.com/emberjs/ember.js/commit/1809e65012b93c0a530bfcb95eec22d972069745#L0R19
I had this problem and came up with a solution: make a handlebars helper that tells you when the sub-section has rendered. Hope that helps some other folks!