Ember.js - currentViewBinding and stop re-rendering on every view transition - ember.js

I have a statemachine and I am using the new currentViewBinding to swap out parts of an overall containerView whenever a new state is entered using currentViewBinding:
index: Ember.State.create({
enter: function(manager) {
App.get('appController').set('feedView', Ember.View.create({
templateName: 'dashboard_feed',
contentBinding: 'App.feedController.content',
controller: App.get('App.feedController')
}));
}
})
At this moment in time, the rendering of these view is quite slow. Is there a way I could keep the view in memory and avoid the re-rendering every time I enter the state?

I actually provided a solution to this for another question on StackOverflow, but it's super relevant here too. Avoiding re-rendering of a flash object from scratch when view is reactivated
Here's the jsFiddle: http://jsfiddle.net/EE3B8/1
I extend ContainerView with a flag to stop it from destroying the currentView upon it's destruction. You'll want to stash the view instance somewhere that it won't be destroyed.
App.ImmortalContainerView = Ember.ContainerView.extend({
destroyCurrentView: true,
willDestroy: function() {
if (!this.destroyCurrentView) { this._currentViewWillChange(); }
this._super();
}
});
App.immortalView = Ember.View.create({
template: Ember.Handlebars.compile(
'I WILL LIVE FOREVER!'
)
});
​

You could extend Ember.ContainerView to show/hide its currentView view like so:
App.FastContainerView = Ember.ContainerView.extend({
toggleCurrentViewFast: true,
_currentViewWillChange: function() {
var childViews = this.get("childViews"),
currentView = this.get("currentView");
if (this.toggleCurrentViewFast && childViews.indexOf(currentView) >= 0) {
currentView.set("isVisible", false);
} else {
this._super();
}
},
_currentViewDidChange: function() {
var childViews = this.get("childViews"),
currentView = this.get("currentView");
if (this.toggleCurrentViewFast && childViews.indexOf(currentView) >= 0) {
currentView.set("isVisible", true);
} else {
this._super();
}
}
});

Related

Ember loading state not triggered on transitionTo

If I use a transitionTo on a route with a slow model hook, the loading.hbs state never gets triggered (I have loading.hbs files at all of the levels -- cluster, cluster.schedule and cluster.schedule.preview_grid). I tried renaming the one at cluster.schedule preview_grid-loading.hbs with no luck.
On the transitionTo, there is no model or model id passed in, just the route:
viewPreviewGrid: function() {
this.transitionTo('cluster.schedule.preview_grid');
},
I also have a loading action defined as follows:
loading(transition) {
var controller = this.controller;
if (!Ember.isNone(controller)) {
this.controller.reset();
}
transition.promise.finally(function() {
NProgress.done();
});
}
During the transitionTo call the page just stays on the previous route until the promises in the model hook resolve, and then it transitions to the other route. If I refresh the page, the loading state gets triggered just fine. Is this a known behaviour for transitionTo?
This is my model hook:
model: function (/*params*/) {
var socialProfile = this.modelFor('cluster.schedule').get('firstObject');
if (!socialProfile.get('isInstagram')){
throw new Error("Attempted to access preview with non-ig profile: " + socialProfile.get('id'));
}
var accessToken = socialProfile.get('token');
var self = this;
return Ember.RSVP.hash({
igPosts: new Ember.RSVP.Promise(function(resolve) {
self.getUsersRecentMedia(accessToken).then(function(response) {
var igPosts = Ember.A([]);
response.data.forEach(function(data) {
igPosts.pushObject(self.igPostFromResponse(data, socialProfile));
});
resolve(igPosts);
});
}),
posts: new Ember.RSVP.Promise(function(resolve) {
self.store.query('gram', { type: 'preview', social_profile_id: socialProfile.get('id'), limit: self.get('postLimit') }).then(function(grams) {
var filteredGrams = grams.filter(function(gram) {
return (gram.get('scheduledInFuture')) && (gram.belongsTo('socialProfile').id() === socialProfile.get('id')) && (gram.get('active'));
});
resolve(filteredGrams);
});
}),
igUser: new Ember.RSVP.Promise(function(resolve) {
self.getSelf(accessToken).then(function(response) {
resolve(self.igUserFromResponse(response.data, socialProfile));
});
})
});
},
You need to return true at the end of the loading() hook to tell Ember to go ahead and show the default loading route (loading.hbs).
loading(transition) {
var controller = this.controller;
if (!Ember.isNone(controller)) {
this.controller.reset();
}
transition.promise.finally(function() {
NProgress.done();
});
return true;
},

Delay ember view render till $getJSON isLoaded

The problem with this code is that the render code is entered twice, and the buffer is not where I expect it. Even when I get the buffer, the stuff I push in is not rendered to the screen.
App.FilterView = Ember.View.extend({
init: function() {
var filter = this.get('filter');
this.set('content', App.ViewFilter.find(filter));
this._super();
},
render: function(buffer) {
var content = this.get('content');
if(!this.get('content.isLoaded')) { return; }
var keys = Object.keys(content.data);
keys.forEach(function(item) {
this.renderItem(buffer,content.data[item], item);
}, this);
}.observes('content.isLoaded'),
renderItem: function(buffer, item, key) {
buffer.push('<label for="' + key + '"> ' + item + '</label>');
}
});
And the App.ViewFilter.find()
App.ViewFilter = Ember.Object.extend();
App.ViewFilter.reopenClass({
find: function(o) {
var result = Ember.Object.create({
isLoaded: false,
data: ''
});
$.getJSON("http://localhost:3000/filter/" + o, function(response) {
result.set('data', response);
result.set('isLoaded', true);
});
return result;
}
});
I am getting the data I expect and once isLoaded triggers, everything runs, I am just not getting the HTML in my browser.
As it turns out the answer was close to what I had with using jquery then() on the $getJSON call. If you are new to promises, the documentation is not entirely straight forward. Here is what you need to know. You have to create an object outside the promise - that you will return immediately at the end and inside the promise you will have a function that updates that object once the data is returned. Like this:
App.Filter = Ember.Object.extend();
App.Filter.reopenClass({
find: function(o) {
var result = Ember.Object.create({
isLoaded: false,
data: Ember.Object.create()
});
$.getJSON("http://localhost:3000/filter/" + o).then(function(response) {
var controls = Em.A();
var keys = Ember.keys(response);
keys.forEach(function(key) {
controls.pushObject(App.FilterControl.create({
id: key,
label: response[key].label,
op: response[key].op,
content: response[key].content
})
);
});
result.set('data', controls);
result.set('isLoaded', true);
});
return result;
}
});
Whatever the function inside then(), is the callback routine that will be called once the data is returned. It needs to reference the object you created outside the $getJSON call and returned immediately. Then this works inside the view:
didInsertElement: function() {
if (this.get('content.isLoaded')) {
var model = this.get('content.data');
this.createFormView(model);
}
}.observes('content.isLoaded'),
createFormView: function(data) {
var self = this;
var filterController = App.FilterController.create({ model: data});
var filterView = Ember.View.create({
elementId: 'row-filter',
controller: filterController,
templateName: 'filter-form'
});
self.pushObject(filterView);
},
You can see a full app (and bit more complete/complicated) example here

Ember How fire observes when create object(observe was set already in model)

I cannot fire observes function when was created object in Controller Array
My code:
Model
App.Meeting = Em.Object.extend({
id: null,
name: null,
type: null,
proposes: null
});
App.Meeting.reopen({
proposedChanged: function() {
//some do
}.observes('proposes')
});
Controller
App.meetingsController = Ember.ArrayController.create({
content: [],
loadList: function(){
var me = this;
$.getJSON(url,function(data){
if(data.status == 0){
$(data.meetings).each(function(index,value){
var m = App.Meeting.create(value)
me.pushObject(m);
});
}else{
alert('Error loading content');
}
});
},
});
App.meetingsController.loadList();
When i run application Controller has get JSON data and created App.Meeting with that data, but observer not fire
While I was creating a jsbin to play with #Darshan Sawardekar got it right, so now you have to answers to play with :)
The important code:
App.meetingsController = Ember.ArrayController.create({
content: [],
loadList: function(){
var me = this;
$.getJSON(url, function(data){
if(data.status == 0){
$(data.meetings).each(function(index, value){
var m = App.Meeting.create();
m.set('id', value.id);
m.set('name', value.name);
m.set('type', value.type);
m.set('proposes', value.proposes);
me.pushObject(m);
});
} else {
alert('Error loading content');
}
});
}
});
Hope it helps.
EDIT
See here for a working jsbin that shows the concept.
I think observers fire when you do meeting.set('proposes', 'value'). They don't fire inside a create call. You could modify your create to retouch proposes. This might work,
var m = App.Meeting.create(value);
m.set('proposes', value.proposes);

Ember.js bind class change on click

How do i change an elements class on click via ember.js, AKA:
<div class="row" {{bindAttr class="isEnabled:enabled:disabled"}}>
View:
SearchDropdown.SearchResultV = Ember.View.extend(Ember.Metamorph, {
isEnabled: false,
click: function(){
window.alert(true);
this.isEnabled = true;
}
});
The click event works as window alert happens, I just cant get the binding to.
The class is bound correctly, but the isEnabled property should be modified only with a .set call such as this.set('isEnabled', true) and accessed only with this.get('isEnabled'). This is an Ember convention in support of first-class bindings and computed properties.
In your view you will bind to a className. I have the following view in my app:
EurekaJ.TabItemView = Ember.View.extend(Ember.TargetActionSupport, {
content: null,
tagName: 'li',
classNameBindings: "isSelected",
isSelected: function() {
return this.get('controller').get('selectedTab').get('tabId') == this.get('tab').get('tabId');
}.property('controller.selectedTab'),
click: function() {
this.get('controller').set('selectedTab', this.get('tab'));
if (this.get('tab').get('tabState')) {
EurekaJ.router.transitionTo(this.get('tab').get('tabState'));
}
},
template: Ember.Handlebars.compile('<div class="featureTabTop"></div>{{tab.tabName}}')
});
Here, you have bound your className to whatever the "isSelected" property returns. This is only true if the views' controller's selected tab ID is the same as this views' tab ID.
The code will append a CSS class name of "is-selected" when the view is selected.
If you want to see the code in context, the code is on GitHub: https://github.com/joachimhs/EurekaJ/blob/netty-ember/EurekaJ.View/src/main/webapp/js/app/views.js#L100
Good answers, however I went down a different route:
SearchDropdown.SearchResultV = Ember.View.extend(Ember.Metamorph, {
classNameBindings: ['isSelected'],
click: function(){
var content = this.get('content');
SearchDropdown.SelectedSearchController.set('content', content);
var loadcontent = this.get('content');
loadcontent.set("searchRadius", $("select[name=radius]").val());
SearchDropdown.LoadMap.load(content);
},
isSelected: function () {
var selectedItem = SearchDropdown.SelectedSearchController.get('content'),
content = this.get('content');
if (content === selectedItem) {
return true;
}
}.property('SearchDropdown.SelectedSearchController.content')
});
Controller:
SearchDropdown.SelectedSearchController = Ember.Object.create({
content: null,
});
Basically stores the data of the selected view in a controller,

Dependable views in Ember

I have an app which lists albums. When album is clicked on both AlbumView and App.overlay (also a view) are displayed.
App.overlay = Ember.View.create({...}) (Lightbox-like overlay).
and:
App.AlbumView = Ember.View.extend({
// close the selected album view by closing the overlay
close: function() {
App.overlay.close();
}
});
And here's the problem: I want to be able to close those both views by clicking on the overlay, but I want overlay to remain independent of AlbumView, so that I can use the overlay in other places (i.e. without introducing a coupling between the two). How can I do it?
Here is my current implementation, with tight coupling, which I really don't like:
App.overlay = Ember.View.create({
// handle clicking anywhere on the overlay
click: function() {
this.close();
},
// close the overlay (setting selectedAlbum's controller content to null hides the AlbumView)
close: function() {
App.selectedAlbumController.set('content', null); // this should not be here
this.remove();
}
});
I'm only just learning ember, so take this with a grain of salt...
You could add a 'visible' property to the overlay, and then observe it from the other AlbumView. Like this:
var overlay = Ember.View.create({
visible: true,
click: function() {
this.close();
},
close: function() {
this.set('visible', false);
this.remove();
}
});
App.AlbumView = Ember.View.extend({
overlayClosed: function() {
App.selectedAlbumController.set('content', null);
this.remove();
}.observes('overlay.visible')
});
What about extracting your close method in a mixin?
App.AlbumClosing = Ember.Mixin.create({
close: function() {
App.selectedAlbumController.set('content', null);
this.remove();
}
});
var overlay = Ember.View.create(App.AlbumClosing, {
click: function() {
this.close();
}
});