Accepted practice to reset a bunch of controllers - ember.js

In several situations in my App I need to reset a bunch of controllers (in ajax replies, ...). This happens at different points in the application, so I do not have always a route/controller/view available. I want to have a single easy-to-use function to reset everything, no parameters needed.
This is what I am trying to replace (very old version of ember):
function resetApp() {
Dashboard.router.totalCallsController.set("content", 0);
Dashboard.router.totalMinutesController.set("content", 0);
Dashboard.router.callDurationAvgController.set("content", 0);
};
And now I am doing:
function resetApp() {
var container = Dashboard.__container__;
container.lookup('controller:totalCalls').set("content", 0);
container.lookup('controller:totalMinutes').set("content", 0);
container.lookup('controller:callDurationAvg').set("content", 0);
}
But according to this answer the container should not be used for that. How can I get a controller if I only have access to the application object? Before it was just easy: in Dasboard.router we could access all controllers directly. Now only via the container, but this is not advised, and probably will change (API is n flux). So what can I do?

If you're going for the most Emberish way, but want the function to live outside of Ember, I would suggest the observer pattern. In an essence anytime a controller is created you subscribe it to the global reset function. There are a few ways to accomplish this, I'd probably do it with a mixin.
Here's a small example:
App.ResettableMixin = Em.Mixin.create({
init: function(){
this._super();
App.Resettable.join(this.get('resetMe'), this);
}
});
App.IndexController = Em.ArrayController.extend(App.ResettableMixin,{
resetMe: function(){
console.log('reset index controller');
}
});
App.FooController = Em.ObjectController.extend(App.ResettableMixin,{
resetMe: function(){
console.log('reset foo controller ' + this.get('model'));
}
});
App.Resettable = {
resetters: [],
join: function(methodToCall, context){
this.resetters.push({method: methodToCall, context:context});
},
reset: function(){
var item;
console.log('resetting ' + this.resetters.length + ' items');
for(var i = 0, len = this.resetters.length; i<len;i++){
item = this.resetters[i];
item.method.apply(item.context);
}
},
remove: function(methodToFind){
// iterate through find, and remove it from resetters
}
};
http://emberjs.jsbin.com/hudugene/4/edit

Related

counter that counts on page loads

I am trying to build a simple counter that will count from 0 to 100 on page load.Here is some that i came out with but does not work. I'll appreciate all the help thanks
App.AboutController = Ember.ObjectController.extend({
count: 0,
content: 0,
counterUp: function() {
var count = this.get('count') + 1;
this.set('count', count);
this.set('content', count % 100);
}
});
This seems like a great candidate for a component:
component
App.TimerCounterComponent = Ember.Component.extend({
tick: function() {
this.incrementProperty('count');
if (Ember.isEqual(this.get('count'), 100)) {
this.killCounter();
} else {
this.scheduleTick();
}
},
scheduleTick: function() {
this._counter = Ember.run.later(this, 'tick', 600);
},
setup: function() {
this.set('count', 0);
this.scheduleTick();
}.on('didInsertElement'),
killCounter: function() {
Ember.run.cancel(this._counter);
}.on('willDestroyElement')
});
component template
Count: {{count}}
then in any template you want to use the component:
{{timer-counter}}
EDIT: tidied up the jsbin code a bit
Bonus round: full js-bin example
You cannot make the counting on controller's property because it would always re-initialize and you would have always zero. You can access the application controller however and this way the property will not reset:
// better use Controller because ObjectController is getting deprecated
App.AboutController = Ember.Controller.extend({
needs: ['application'],
count: Ember.computed.alias('controllers.application.count'),
content: Ember.computed.alias('controllers.application.content'),
counterUp: (function() {
count = this.get('count') + 1 || 1;
this.set('count', count);
this.set('content', count % 100);
}).on('init')
});
If you want, you can initialize count and content properties like this:
App.ApplicationController = Ember.Controller.extend({
count: 0,
content: 0
})
If you have used the proxying behavior of ObjectController use can replace Controller with it.
As things are, your counterUp method has been declared and defined, but is never called. You have to call it in an appropriate hook. In http://emberjs.jsbin.com/wuxesosadi/1/edit?html,css,js,output you find an example that counts whenevere you click on the number.
You want to count "on page load" - a full page loade which restarts the application will always reset the counter. If you mean a transition into the route, http://emberjs.jsbin.com/wuxesosadi/4/edit?html,css,js,output would be a working example.
That said, for real world problems you would probably have a counter service or the data in a model. The controller should only hold presentation logic. And even though the controller is held in memory and re-used (as a singleton), this feels very wrong. Then again, as an example of how things work, this is perfectly valid.

Do something when Ember component is instantiated?

I call a component like this:
{{Gd-text-input label="Specify" name="Specify" key="entry.810220554" triggerKey="tada" hideIf="Client"}}
I would like to run some javascript-code that sets an additional property to this component.
What I'm trying to run is something like this
//Convert string ot array.
GdRadioInput = Em.Component.extend({
init: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}
});
But it doesn't run. If someone could just provide a sample that console.logs a the value of a property of a component whenever that component is created, that would be very helpful.
You can run this code in the init method
init:function(){
this._super();
hideIf = this.get('hideIf');
key = this.get('key')
if(hideIf === key){
this.set('class', 'hide');
}
}
Good luck
PD: now this method is private: http://emberjs.com/api/classes/Ember.Component.html#method_init
I know this is an old question, but I wanted to update it with the new way to do things. Instead of overriding the init function, you can now cause a function to run on initialization with .on('init'). Example:
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('init')
});
A follow-up: Just in case you are depending on a fully loaded DOM tree, you can use .on('didInsertElement') instead of on('init'):
GdRadioInput = Em.Component.extend({
setupFunc: function(){
var cs = this.get('contentString');
console.log('doesthiswork?');
if(cs){
this.set('content', eval(cs));
}
}.on('didInsertElement')
});
That event is fired when the view's element has been inserted into the DOM: http://emberjs.com/api/classes/Ember.Component.html#event_didInsertElement

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 call Controllers method by name in new (ember-1.0.0-pre.4) version

I used Ember.ready function to add some keyboard binding like this https://github.com/greggyNapalm/firebat-overlord/blob/master/web_static/static/js/app/app.js#L24 is there any proper way to do this in new version, asking because can't anymore call method by its name?
The best place to do this sort of thing is from the view. Based on your example the appropriate view in this case is probably TestsView. From there you can bind and unbind keyboard bindings when the view is inserted/removed. For example:
var TestsView = Ember.View.extend({
templateName: 'testsTemplate',
didInsertElement: function() {
console.log("Controller: " + this.get('controller').toString());
var self = this;
Mousetrap.bind('ctrl+right', function() {
self.get('controller').goToPage('next');
});
},
wilLRemoveElement: function() {
Mousetrap.unbind('ctrl+right');
}
});
To see what controller is set to, try adding console.log("Controller: " + self.get('controller').toString());