I'm using ember-simple-auth along with ember-validations to validate my user login credentials
in order to validate I "override" the login route's login action in controller.
The problem is that after validation I now wanna bubble up the action; however, since validate returns a promise I can't just simply return true.
I tried calling my route with this.get('target').send('login') but apparently it doesn't work.
I tried this.send('login') but this creates an infinite loop as the controller calls itself recursively.
Just use a different action name in the controller and call login there
actions: {
validate: function() {
var that = this;
return this.validate().then(function() {
that.send('login');
}, function() {
// report errors in an array
var errors = that.get('errors');
var fullErrors = [];
Object.keys(errors).forEach(function(val) {
if(errors[val] instanceof Array)
errors[val].forEach(function(msg) {
fullErrors.push([val, msg].join(" "));
});
});
that.set('fullErrors',fullErrors);
});
},
loginFailed: function(xhr) {
this.set('errorMessage', xhr.responseText);
}
}
Related
I'm loading a route. Its model hook loads some models. Some are fetch from ember store and some are promises requested through AJAX:
model: function () {
return Em.RSVP.hash({
//the server data might not be loaded if user is offline (application runs using appcache, but it's nice to have)
someServerData: App.DataService.get(),
users: this.store.find('user')
});
}
The App.DataService.get() is defined as:
get: function () {
return new Ember.RSVP.Promise(function(resolve, reject) {
//ajax request here
});
}
Obviously if the request is rejected, the flow is interrupted and I cannot display the page at all.
Is there a way to overcome this?
Ember.RSVP.hashSettled is exactly meant for this purpose.
From tildeio/rsvp.js Github repository:
hashSettled() work exactly like hash(), except that it fulfill with a hash of the constituent promises' result states. Each state object will either indicate fulfillment or rejection, and provide the corresponding value or reason. The states will take one of the following formats:
{ state: 'fulfilled', value: value }
or
{ state: 'rejected', reason: reason }
Here is an example for using it (working JS Bin example):
App.IndexRoute = Ember.Route.extend({
fallbackValues: {
firstProperty: null,
secondProperty: null
},
model: function() {
var fallbackValues = this.get('fallbackValues');
return new Ember.RSVP.Promise(function(resolve, reject) {
Ember.RSVP.hashSettled({
firstProperty: Ember.RSVP.Promise.resolve('Resolved data despite error'),
secondProperty: (function() {
var doomedToBeRejected = $.Deferred();
doomedToBeRejected.reject({
error: 'some error message'
});
return doomedToBeRejected.promise();
})()
}).then(function(result) {
var objectToResolve = {};
Ember.keys(result).forEach(function(key) {
objectToResolve[key] = result[key].state === 'fulfilled' ? result[key].value : fallbackValues[key];
});
resolve(objectToResolve);
}).catch(function(error) {
reject(error);
});
});
}
});
fallbackValues can be useful for managing resolved hash's properties' fallback values without using conditions inside the promise function.
Taking into account that Ember.RSVP.hashSettled is not available in my Ember version. I come up with the following solution:
model: function(params) {
var self = this;
return new Em.RSVP.Promise(function(resolve, reject){
// get data from server
App.DataService.get().then(function(serverData) { //if server responds set it to the promise
resolve({
serverData: serverData,
users: self.store.find('user')
});
}, function(reason){ //if not ignore it, and send the rest of the data
resolve({
users: self.store.find('user')
});
});
});
}
I am building a mixin for different routes to handle a save action. Sometimes after saving I want to do extra things, so I want to send a saveCallback action back to the route.
Is it possible to ask to a route if a action is defined? Or else: can I retrieve all functions of a route, so I could check myself if a route is defined?
Code mixin:
export default Ember.Mixin.create({
actions: {
save: {
var self = this;
this.get('model').save()
.then(function(result) {
//do stuff
//Something like if(self.hasDefined('saveCallBack')) {
self.send('saveCallback')
}
}, function(errorReason) {
//error handling
});
}
}
}
I can't just fire the send action, as a Nothing handled the action 'saveCallback' error is thrown if the action is not defined.
I can't find the issue now, but I read an issue on Github at some point about adding a canSendAction (or something similar) function for this reason. For now, just define the same action on your mixin, and have it bubble.
export default Ember.Mixin.create({
actions: {
save: function() {
var self = this;
this.get('model').save().then(function() {
self.send('saveCallback');
});
},
saveCallback: function() {
return true;
}
}
});
EDIT: I'm being dumb. Just return true and the action should bubble up the hierarchy and will be caught by another action if it exists.
As I currently have it set up, my validations are not being shown on the submit on an ember easyForm form. the current setup for the mixin only allows for validations to be shown on focusOut of an input. Does anybody have a workaround for this?
easyForm is calling validate in the context.
Ember.EasyForm.Form = Ember.EasyForm.BaseView.extend({
// rest of the code removed for brevity
submit: function(event) {
var _this = this, promise;
if (event) {
event.preventDefault();
}
if (Ember.isNone(this.get('context.validate'))) {
this.get('controller').send(this.action);
} else {
if (!Ember.isNone(this.get('context').validate)) {
promise = this.get('context').validate();
} else {
promise = this.get('context.content').validate();
}
promise.then(function() {
if (_this.get('context.isValid')) {
_this.get('controller').send(_this.action);
}
});
}
}
});
Could you verify it's calling your model's validate method? If that's not the case could you raise a bug on easyForm if this isn't the case?
It seems to be that isn't calling validate in the content since it will call the controller if context.validate isNone
I have a controller that lists all the units-of-measure in the system. When a user chooses a specific record in the Uom model I want to be able to delete it. I'm using Ember-Data beta-2. Here's what I have so far:
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.deleteRecord();
});
}
}
});
The action deleteRecord is called passing in a valid ID and a promise is returned. Using the then() functionality of the promise I then call Ember's deleteRecord() when the promise has been fulfilled and it appears to work locally. I say that because this record immediately disappears from the screen and the Ember Debugger. Unfortunately the delete has not been persisted to the backend and a reload of hte page immediately brings back the locally "deleted" record.
My questions are:
Is this a reasonable way to execute a local delete?
How do I persist a delete to the backend?
You will have to call uom.save() to persist the change to the backend after calling uom.deleteRecord().
What you are doing could work, but seems a bit complicated (for example this.store.find('uom',id) will result into an unnecessary request to the backend). Try this:
App.UomsItemController = Ember.ObjectController.extend({
actions: {
deleteRecord: function() {
this.get('model').destroyRecord();
// .destroyRecord() only exists in recent versions of ED
// for previous versions use .deleteRecord() followed by .save()
// (though you should really consider upgrading :))
}
}
);
App.UomsController = Ember.ArrayController.extend({
itemController: 'uoms_item'
});
and in your template you will have something like this:
{{#each content}}
{{name}} <a href="#" {{action "deleteRecord" this}}>Delete</a>
{{/each}}
EDIT to answer comment below: If this.get('model') is returning a promise, the following should work.
deleteRecord: function() {
this.get('model').then(function(item) {
item.destroyRecord();
})
}
In ember-data v1.0.0-beta.4 they added a destroyRecord method which does the delete and save in one call. Which you can use like this:
this.get('model').destroyRecord().then(function() {
router.transitionTo('users');
});
The deleteRecord method can be called on any instance of DS.Model class. It removes the record form the Store But it will not persist in the backend.
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.deleteRecord();
});
}
}
});
For the deletion to persist in the backend, we have to call save method on that record as( same as createRecord() followed by save() to save the record in backend) :
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.deleteRecord();
uom.save(); //The deletion will persist now
});
}
}
});
Alternatively, you can also use destroyRecord() method of DS.Model class which persists deletion.
App.UomsController = Ember.ArrayController.extend({
actions: {
deleteRecord: function(id) {
console.log("deleting: " + id);
var promisedDelete = this.store.find('uom',id).then(function(uom){
uom.destroyRecord();
});
}
}
});
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"}}