How is possible, that binding have latency?
App.SomeRoute = Ember.Route.extend
actions:
someAction: ->
console.log #controllerFor('some').get('foo')
Ember.run.later (=> console.log #controllerFor('some').get('foo')), 10
App.SomeController = Ember.ObjectController.extend()
App.OtherController = Ember.ObjectController.extend
needs: ['some']
fooBinding: 'controllers.some.foo'
action:
changeIt: ->
#set('foo', 'bar')
#send('someAction')
If action changeIt is evoked (from view for example) console output will be undefined, but after a little delay it will be bar.
What I am doing wrong?
This is the expected behavior, when you change a bound property, the syncronization isn't performed immediatelly, it's just scheduled and called later.
This is important because if we have a fooBinding: 'controllers.some.foo'. Doesn't matter how many times the foo is changed, the controllers.some.foo property just need to be updated once with the final result. With this, we avoid to trigger uneeded observers, and save processing.
You can force the scheduled syncronization queue to flush using Ember.run.sync(), with the following code:
App.SomeRoute = Ember.Route.extend
actions:
someAction: ->
Ember.run.sync()
# now you can see the updated value
console.log #controllerFor('some').get('foo')
App.SomeController = Ember.ObjectController.extend()
App.OtherController = Ember.ObjectController.extend
needs: ['some']
fooBinding: 'controllers.some.foo'
action:
changeIt: ->
#set('foo', 'bar')
#send('someAction')
Keep in mind that this is just an example, for you see the updated value. The use of Ember.run.sync() isn't recommended.
Related
I'm trying to make use of _super in the handler of a Promise inside of a Controller action, but it doesn't work because it seems to lose the correct chain of functions.
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#_super()
I want to revert to the Mixin's default behavior on the else but I need to resolve user asynchronously before I can do a conditional statement. I tried:
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
_super = #_super
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
_super()
and
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#_super.bind(#)()
Neither works.
This answer claimed this should work as of 1.5.0, but I'm using 1.7.0-beta.5 and it's no go. Is there a way to get this to work, even in terms of approaching this differently?
Ember currently doesn't support calling _super asynchronously. In that example I'm not actually calling _super asynchronously, it's synchronous still.
http://emberjs.com/blog/2014/03/30/ember-1-5-0-and-ember-1-6-beta-released.html#toc_ever-present-_super-breaking-bugfix
In order to continue the bubbling you need to call this.target.send() with the name of the action.
see: How can I bubble up an Ember action inside a callback function?
Something like this should work:
ApplicationRoute = Ember.Route.extend SimpleAuth.ApplicationRouteMixin,
actions:
sessionAuthenticationSucceeded: ->
#get("session.user").then (user) =>
if #get("session.isTemporaryPassword") or not user.get "lastLogin"
#transitionTo "temp-password"
else
#target.send('sessionAuthenticationSucceeded')
I have a simple EmberJS application with 2 simple models (ember-model). Accounts and Items, while an Account hasMany Items.
So when i navigate to #/accounts/1/items with the links in the application it works perfectly fine. However when i directly reload #/accounts/1/items i get an error:
Assertion failed: The value that #each loops over must be an Array. You passed <App.Account:ember335> (wrapped in (generated items controller)) ember.js?body=1:382
Uncaught TypeError: Object [object Object] has no method 'addArrayObserver' ember.js?body=1:19476
Assertion failed: Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications. ember.js?body=1:382
This is how my App looks like:
App.Router.map ()->
#resource 'accounts', ->
#resource 'account', path: ':account_id', ->
#resource 'items'
App.AccountRoute = Ember.Route.extend
model: (params) ->
App.Account.find(params.account_id)
App.ItemsRoute = Ember.Route.extend
model: ->
#.modelFor('account').get('items')
App.Account = Ember.Model.extend
name: Ember.attr('string')
item_ids: Ember.attr(),
items: (->
App.Items.find(#.get('comment_ids'))
).property('comment_ids')
App.Item = Ember.Model.extend
name: Ember.attr('string')
Controllers are standard (empty).
In the JS console a call like this works fine and returns the correct results, even after the error is thrown (and nothing rendered):
App.Account.find(1).get('items')
I have no idea why this is happening and the code seems so straight forward that its really annoying not to have a clue. Anyone has an idea?
I am no ember-data expert, but it seems that it is returning a promise. Therefore you should try:
App.ItemsRoute = Ember.Route.extend({
model : function(){
var accountPromise = App.Account.find(1);
var itemsPromise = Ember.Deferred.create();
accountPromise.then(function(account){
itemsPromise.resolve(account.get("items"));
});
return itemsPromise;
}
});
Why does it have to be it that way?
App.Account.find(1); performs an asynchronous call and therefore returns a promise.
That's why you can't immediately return the items, you have to wait for accountPromise to be fulfilled.
You return a new promise (itemspromise) which gets fulfilled when the accountPromise gets fulfilled.
Because you return a Promise, Ember waits for it to be fulfilled and uses the result as the model for your Controller.
PS: Actually this seems a little bit complicated to me. I thinks this will work, but there might be a more elegant solution.
I am making an app with ember.js and ember-model
I have a model named Plugin defined as follows:
Eme.Plugin = Ember.Model.extend
id: Ember.attr()
name: Ember.attr()
description: Ember.attr()
downloads: Ember.attr()
tags: Ember.attr()
Eme.Plugin.url = "/api/v1/plugins"
Eme.Plugin.adapter = Ember.RESTAdapter.create()
Eme.Plugin.collectionKey = 'plugins'
I want show the most downloaded in index.hbs ( i use ember-rails)
And i fetch data in IndexRoute 's setupController hook:
Eme.IndexRoute = Em.Route.extend
setupController: (controller, model)->
console.log Eme.Plugin.findAll().toArray()
controller.set 'plugins', Eme.Plugin.findAll()
Output :
[nextObject: function, firstObject: undefined, lastObject: undefined, contains: function, getEach: function…]
But in chrome console i execute Eme.Plugin.findAll().toArray(), i got the results as follows:
[{
__ember1377710636537: "ember404"
__ember1377710636537_meta: Meta
_dirtyAttributes: Array[0]
_reference: Object
_super: undefined
get _data: function() {}
isLoaded: true
isNew: false
set _data: function(value) {}
__proto__: Object
}, {
...
}, {
...
}]
In my IndexController have a computed property:
Eme.IndexController = Em.Controller.extend
mostDownloads:(->
# console.log #get('plugins').slice(0, 3)
#get('plugins').slice(0, 3)
).property('plugins')
and i iterate the mostDownloads but there is nothing to show, however when i output {{plugins.length}}, i can't get the count of all my data
Who can give a hand to me?
Plugins looks like an array and would need to use the .#each iterator like so:
Eme.IndexController = Em.Controller.extend({
// Code
}).property('plugins.#each')
Here is documentation on #each http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/
Regarding your array length, I've never had much luck using .length, for length I usually do
plugins.get('length')
Hope that helps!
I propose two changes to make you app working.
First
I assume since it's called plugins (plural) the call to .findAll() returns an array of plugins for this to work you should change your controller type to be an ArrayController. Then because you are using # aka. this in your computed property you should use the fat arrow => to have the right reference to this, so the resulting IndexController should look like:
Eme.IndexController = Em.ArrayController.extend
mostDownloads:(=>
# console.log #get('content').slice(0, 3)
#get('content').slice(0, 3)
).property('content.[]')
Notice also that we observe content.[] this will trigger whenever the content array changes, items added or removed etc. you could also use content.#each but this is better suited for when you need to observe changes to the properties of a plugin record in the array, e.g. content.#each.name.
Second
Now change also how you set the plugins collection on your controller, you should rather set the controller's content property since this is what it is for:
Eme.IndexRoute = Em.Route.extend
setupController: (controller, model)->
# console.log Eme.Plugin.findAll().toArray()
controller.set 'content', Eme.Plugin.findAll()
This line console.log Eme.Plugin.findAll().toArray() will not work the way you expect because when you call it it will give you a promise back and not the array that is still underway (async...).
And a last change, to print out the plugins length, use the afterModel hook of your IndexRoute, since this is the right time when the model promise has being resolved (the async operation has given back control to your app).
Eme.IndexRoute = Em.Route.extend
...
afterModel: (plugins, transition) ->
console.log plugins.get 'length'
...
Hope it helps.
I have the following two routes for edit and new:
WZ.ExercisesNewRoute = Em.Route.extend
model: ->
WZ.Exercise.createRecord()
deactivate: ->
#_super.apply this, arguments
#get('currentModel.transaction').rollback()
WZ.ExercisesEditRoute = Em.Route.extend
model: (params) ->
WZ.Exercise.find(params.exercise_id)
serialize: (params, options) ->
exercise_id: params.get('id')
deactivate: ->
#_super.apply this, arguments
tx = #get('currentModel.transaction')
tx.rollback() if tx
I would like to know what the correct code should be in each deactivate so the store is in a correct state if the user does not save, does save or whatever.
Currently if I route to the edit route and then directly to the new route without saving, I get the following error:
Uncaught Error: Attempted to handle event willSetProperty on
while in state rootState.deleted.saved.
Called with {reference: [object Object], store: ,
name: name}
This question is for an older version of ember data, but answer would have been to first check the state for isDeleted and only rollback if the record is not already deleted.
In the newer ember data there is no concept of a transaction, but you can still run into a similar issue if you are trying to rollback a record that is not yet persisted.
I would probably do this in the routers willTransition event as you can do things like abort the transition if you want to give the user the option to save the changes.
willTransition: function(transition) {
controller = this.get('controller')
if( controller.get('content.isDirty') ) {
if( controller.get('content.isNew') && confirm('Closing this window will revert all unsaved changes.') ){
controller.get('content').deleteRecord();
} else {
controller.get('content').rollback()
}
}
}
Given a controller like:
App.SignInController = Ember.Controller.extend
authenticated: false
authenticatedDidChange: (() =>
console.log #get('authenticated')
).observes('controller.authenticated')
This doesn't seem to work so I must not understand how observers works. I think it is supposed to created an observer on controller.authenticated. However when I call #set("authenticated", true) nothing is logged.
Updated:
I did try replacing controller.authenticated with App.signInController.authenticated to no avail.
What am I missing?
Eventually I stumbled upon an answer this Yehuda Katz on Quora.
App.friendsController = Ember.ArrayProxy.extend({
contentDidChange: function() {
// stuff here
}.observes('content')
});
After reviewing this answer I noticed that the observes call only specifies the name of the property with no controller or App.signInController prefix. Changing my solution above to just observes('authenticated') works.
App.SignInController = Ember.Controller.extend
authenticated: false
authenticatedDidChange: (() ->
console.log #get('authenticated')
).observes('authenticated')