Ember route has action - ember.js

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.

Related

Emberjs abort transition and show modal

I want to abort transition on a particular route and show a modal. This is how my route code looks like:
export default Ember.Route.extend({
model: {/* some code here */},
actions: {
willTransition: function(transition) {
if (!this.controller.get('model.name')) {
console.log('aborting transition');
transition.abort();
this.send('showModal', {
template: 'campaign/campaign-name-modal',
controller: this.controller,
model: this.controller.get('model')
});
}
else {
// Bubble the `willTransition` action so that
// parent routes can decide whether or not to abort.
return true;
}
}
}
});
and then in my application.hbs, I have:
{{outlet 'modal'}}
What I am observing is that transition aborts but my modal doesn't show up. When I switch the ordering to something like:
this.send('showModal', {
template: 'campaign/campaign-name-modal',
controller: this.controller,
model: this.controller.get('model')
});
console.log('aborting transition');
transition.abort();
the transition doesn't abort at all.
I am not exactly sure why this might be happening. Any pointers?
Maybe try editing your conditional to use firstObject:
if (!this.controller.get('model.firstObject.name')) {

Ember - How can I call the route's action from my controller

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);
}
}

Correct way of deleting a model record with ember-data

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();
});
}
}
});

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"}}

What is the proper way to subscribe to model events in a view?

I want to subscribe to the model's becameInvalid and becameError events in my view so I can set some error state. I can think of two ways, both have their drawbacks.
The first way I can think of is to use observers to create my own becameInvalid and becameError hooks on the view:
becameInvalid: function() {
var isValid = this.get('controller.model.isValid');
if (Ember.isNone(isValid) || isValid) {
return;
}
console.log('becameInvalid');
}.observes('controller.model.isValid')
becameError: function() {
var isError = this.get('controller.model.isError');
if (Ember.isNone(isError) || !isError) {
return;
}
console.log('becameError');
}.observes('controller.model.isError')
This works, but uses a lot of boilerplate code. The second way I can think of is to add handlers to the events after the model is loaded:
didLoad: function() {
var isLoaded = this.get('controller.model.isLoaded');
if (Ember.isNone(isLoaded) || !isLoaded) {
return;
}
var model = this.get('controller.model');
model.on('becameInvalid', function() {
console.log('becameInvalid');
});
model.on('becameError', function() {
console.log('becameError');
});
}.observes('controller.model.isLoaded')
The problem with this approach is that it still requires some boilerplate code and (correct me if I'm wrong) the event handlers won't be cleaned up automatically when the view is removed.
What I'd like to do is something similar to the observers protocol for events that would be cleaned up automatically by the framework. E.g.:
becameInvalid: function() {
console.log('becameInvalid');
}.eventHandler('controller.model.becameInvalid')
becameError: function() {
console.log('becameError');
}.eventHandler('controller.model.becameError')
Does something like this exist?
I think your first proposal is a feasible solution. Here is a version of it, that is a little bit less verbose:
isValid : Ember.Computed.alias("controller.model.isValid"),
becameInvalid: function() {
if(this.get("isValid"){
return;
}
console.log('becameInvalid');
}.observes('isValid')
See this question for some details on Ember.computed.alias.
Apart from that, i have found no possibilities of triggering observers just under certain circumstances (= just when isValid turns false) with the out-of-the-box functionality Ember. You would have to implement such an observer on your own.