How can I set a controller's model without triggering an observer? - ember.js

Given I have this:
App.SomeController = Ember.ObjectController.extend({
autosave: function(){
//stuff
}.observes('content.text', 'content.colour', 'content.webfont')
});
How can I set SomeController's model in a route WITHOUT triggering autosave?

Emberjs doesn't have suport to suspend observers at least in public api.
My approach to that problems is to create a flag:
App.SomeController = Ember.ObjectController.extend({
_suspendAutosave: false,
suspendAutosave: function(fn) {
this.set('_suspendAutosave', true);
fn();
this.set('_suspendAutosave', false);
},
autosave: function(){
if (this.get('_suspendAutosave')) { return; }
// some code ...
}.observes('content.text', 'content.colour', 'content.webfont')
});
At some place:
controller.suspendAutosave(function() {
controller.set('content.text', 'foo');
});
This isn't cool, but is the only way that I know. I hope it helps.

Alternatively you can use: Ember.removeObserver() and Ember.addObserver() methods.

Related

Ember setupController fires observes, how can i prevent that

My code looks something like this
App.ItemRoute = Em.Route.extend({
setupController: function(controller) {
var model = this.modelFor('item');
controller.setProperties({
name : model.get('name'),
title: model.get('title')
});
}
});
App.ItemController = Em.ObjectController.extend({
saveOnChange: function() {
console.log('saveOnChange');
}.observes('name', 'title'),
});
From my understanding because i am using setProperties the observe should only fire once , but it fire two times
also wrapping the setProperties with beginPropertyChanges & endPropertyChanges still fires twice
what i ultimately is for it to not fire at all, so what i ended up doing was changing the controller code to be like this
App.ItemController = Em.ObjectController.extend({
load: false,
saveOnChange: function() {
if(!this.get('load')) {
this.set('load', true);
return;
}
console.log('saveOnChange');
}.observes('name', 'title'),
});
this code would work if the change is only fired once, but it won't work if its fired multiple times (that's my case)
The setProperties function doesn't coalesce your observers (unfortunately there's no way to do that), it just groups them into one operation. The source might help you to better see what it does:
Ember.setProperties = function(self, hash) {
changeProperties(function() {
for(var prop in hash) {
if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
}
});
return self;
};
So, back to your problem. The best way that I can think of is to debounce your function.
App.ItemController = Em.ObjecController.extend({
load: false,
saveOnChange: function() {
Em.run(this, 'debouncedSave', 150);
}.observes('name', 'title'),
debouncedSave: function() {
if(!this.get('load')) {
this.set('load', true);
}
}
});
If you're not familiar with debouncing, you can read about it here. There are probably some other solutions involving direct manipulation of the properties, but I'm not sure if that's a road you want to go down.

ember store.find promise not being fulfilled in time

I'm using an ArrayController with an ItemController but I'm having problems with getting my store.find promise fulfilled before the hotelName and hotelAddress properties in my ItemController are used. Any idea on how I can unsure the store find is fulfilled before returning? I know the store.find works. I'm able to see the results but the timing is off. Thank you.
export default Ember.ArrayController.extend({
itemController: ...,
});
export default Ember.ObjectController.extend({
init: function() {
this._super();
},
hotelName: function() {
var self = this;
self.store.find('hotel', this.get('hotelId')).then(function(hotel) {
self.set('hotelName', hotel.get('name'));
});
}.property('hotelId'),
hotelAddress: function() {
var self = this;
self.store.find('hotel', this.get('hotelId')).then(function(hotel) {
self.set('hotelAddress', hotel.get('street'));
});
}.property('hotelId')
});

How do I call an action method on Controller from the outside, with the same behavior by clicking {{action}}

Please look at this code...
```
App.BooksRoute = Ember.Route.extend({
model: return function () {
return this.store.find('books');
}
});
App.BooksController = Ember.ArrayController.extend({
actions: {
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomething…
}
}
}
});
```
I want to call the updateData action on BooksController from the outside.
I tried this code.
App.__container__.lookup("controller:books").send('updateData');
It works actually. But, in the updateData action, the this is different from the one in which updateData was called by clicking {{action 'updateData'}} on books template.
In the case of clicking {{action 'updateData'}}, the this.filter() method in updateData action will return books models.
But, In the case of calling App.__container__.lookup("controller:books").send('updateData');, the this.filter() method in updateData action will return nothing.
How do I call the updateData action on BooksController from the outside, with the same behavior by clicking {{action 'updateData'}}.
I would appreciate knowing about it.
(I'm using Ember.js 1.0.0)
You can use either bind or jQuery.proxy. bind is provided in JS since version 1.8.5, so it's pretty safe to use unless you need to support very old browsers. http://kangax.github.io/es5-compat-table/
Either way, you're basically manually scoping the this object.
So, if you have this IndexController, and you wanted to trigger raiseAlert from outside the app.
App.IndexController = Ember.ArrayController.extend({
testValue : "fooBar!",
actions : {
raiseAlert : function(source){
alert( source + " " + this.get('testValue') );
}
}
});
With bind :
function externalAlertBind(){
var controller = App.__container__.lookup("controller:index");
var boundSend = controller.send.bind(controller);
boundSend('raiseAlert','External Bind');
}
With jQuery.proxy
function externalAlertProxy(){
var controller = App.__container__.lookup("controller:index");
var proxySend = jQuery.proxy(controller.send,controller);
proxySend('raiseAlert','External Proxy');
}
Interestingly this seems to be OK without using either bind or proxy in this JSBin.
function externalAlert(){
var controller = App.__container__.lookup("controller:index");
controller.send('raiseAlert','External');
}
Here's a JSBin showing all of these: http://jsbin.com/ucanam/1080/edit
[UPDATE] : Another JSBin that calls filter in the action : http://jsbin.com/ucanam/1082/edit
[UPDATE 2] : I got things to work by looking up "controller:booksIndex" instead of "controller:books-index".
Here's a JSBin : http://jsbin.com/ICaMimo/1/edit
And the way to see it work (since the routes are weird) : http://jsbin.com/ICaMimo/1#/index
This solved my similar issue
Read more about action boubling here: http://emberjs.com/guides/templates/actions/#toc_action-bubbling
SpeedMind.ApplicationRoute = Ember.Route.extend({
actions: {
// This makes sure that all calls to the {{action 'goBack'}}
// in the end is run by the application-controllers implementation
// using the boubling action system. (controller->route->parentroutes)
goBack: function() {
this.controllerFor('application').send('goBack');
}
},
};
SpeedMind.ApplicationController = Ember.Controller.extend({
actions: {
goBack: function(){
console.log("This is the real goBack method definition!");
}
},
});
You could just have the ember action call your method rather than handling it inside of the action itself.
App.BooksController = Ember.ArrayController.extend({
actions: {
fireUpdateData: function(){
App.BooksController.updateData();
}
},
// This is outside of the action
updateData: function () {
console.log("updateData is called!");
var books = this.filter(function () {
return true;
});
for(var i=0; i<books.length; i++) {
//doSomething…
}
}
});
Now whenever you want to call updateData(), just use
App.BooksController.updateData();
Or in the case of a handlebars file
{{action "fireUpdateData"}}

How to make a computed property that depends on a global class attribute?

I wanna create a property that depends on a global attribute:
App.Test= Em.Object.extend();
App.Test.reopenClass({ all: Em.A() });
App.Other = Em.object.extend({
stuff: function() {
return "calculated stuff from this.get('foo') and App.Test.all";
}.property('foo', 'App.Test.all.#each.bar')
});
As a workarround I could create a observer and always set a dummy property with a new random value to trigger the property change, but is there a better way to do this?
I need this for some caching. I've a really crazy, and single threaded backend. So I write my own Model classes. So I try to reimplement a bit of the logic in the client for a better caching.
Ive an Item class (App.Item) and another class where each instance has a calculated reduced list of Items.
App.Model = Em.Object.extend({
});
App.Model.reopenClass({
all: Em.A(),
load: function(hash) {
return this.get('all').pushObject(this.create(hash));
}
});
App.Item = App.Model.extend({
});
App.List = App.Model.extend({
loadedInitItems: false,
items: function() {
if(!this.get('loadedInitItems')) { this.set('loadedInitItems', true); Backend.call('thelist', function(item) { App.Item.load(this); }); }
return App.Item.all.filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
});
}.property('someprops', 'App.Item.all.#each.foo')
});
Backend.call represents some AJAX stuff
the point is, that now any item could change so that the filter will return something diffrent. And there are other places om the application, where the user can add Items. I dont want to call the backend again, because its very slow! And I know that the backend will not modify the list! So I wanna cache it.
This is just a reduced example of my use case, but I think've described the point. In reallity I have this dozend of times, with over 25000 objects.
have you tried adding 'Binding' to your property and then the value you want to bind to ?, something like this:
App.PostsController = Em.ArrayController.extend({
nameOfYourVariableBinding: "App.SomeObject.propertyYouWantToBindTo"
})
It looks like the problem is the double uppercase letter. So App.test ist working, but not App.Foo.test.
But I was able to find a Solution with the ArrayProxy.
Its about this:
App.Model = Em.Object.extend({
});
App.Model.reopenClass({
all: Em.A(),
load: function(hash) {
return this.get('all').pushObject(this.create(hash));
}
});
App.Item = App.Model.extend({
});
App.List = App.Model.extend({
loadedInitItems: false,
items: function() {
var self = this;
if(!this.get('loadedInitItems')) {
this.set('loadedInitItems', true);
Backend.call('thelist', function(item) {
App.Item.load(this);
});
}
return Em.ArrayProxy.extend({
content: App.Item.all,
arrangedContent: function() {
return this.get('content').filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
// use self.get('someprops')
})
}.property('content.#each.foo')
});
}.property('someprops')
items: function() {
if(!this.get('loadedInitItems')) { this.set('loadedInitItems', true); Backend.call('thelist', function(item) { App.Item.load(this); }); }
return App.Item.all.filter(function(item) {
// heavy filter stuff, depends on a lot of propertys on the current list instance
});
}.property('someprops', 'App.Item.all.#each.foo')
});

How to manually update an ArrayController

In my Application I have the following rawNodes property, which I am using as an application-wide cache:
var App = Ember.Application.createWithMixins({
...
/**
The rawNodes property is a nodes deposit to be used
to populate combo boxes etc.
**/
rawNodes: null,
getNodes: function () {
if (!this.rawNodes) {
this.rawNodes = this.Node.find();
}
},
...
});
In some of my controllers I am modifying data which should also be updated in this generic cache. I would like to implement a couple of functions, to update a given node, and to delete a given node. Something like:
updateNode: function(node_id, node) {
this.rawNodes.update(node_id, node);
},
deleteNode: function(node_id) {
this.rawNodes.delete(node_id);
}
But I do not really know how to work with an ArrayController, not even if those operations are at all possible. I see no examples of this kind of procedures in the ArrayController documentation. Could somebody offer an example, or point me in the right direction?
Rather than using a rawNodes property, I think it might be more useful to
maintain a Node model and a NodesController. Assign the model property
with setupController so you can be sure that nodes are always fetched.
Since this is an application-wide cache, use needs in ApplicationController so it can delegate to its methods.
App.ApplicationRoute = Em.Route.extend({
setupController: function() {
this.controllerFor("nodes").set("model", App.Node.find());
}
});
App.ApplicationController = Em.Controller.extend({
needs: "nodes",
});
App.NodesController = Em.ArrayController.extend({
getNodes: function() {
// ...
}
});
App.NodeController = Em.ObjectController.extend({
updateNode: function() {
// ...
},
deleteNode: function() {
// ...
}
});