Ember.Instrumentation.subscribe: Trigger event from handlebars - ember.js

In the init of my view I have setup the event listening as follows:
Ember.Instrumentation.subscribe("inlineEdit.makeBlue", {
before: function (name, timestamp, payload) {
alert(name, timestamp,payload);
},
after: function () {
}
});
From handlebars I would like to trigger the event with an action:
<a {{action inlineEdit.makeBlue on="mouseDown"}} class="btn ">Blue</a>
Unfortunately this does not trigger the above event listener. Can an instrumenation event be triggered from handlebars? If yes how?

At the moment isn't avaliable in the ember core, but it's possible to implement.
When you use the actions internally ember will use the Ember.ActionHandler#send method to dispatch these events. So you can reopen that class, and proxy the method, wrapping the invocation in a Ember.instrument:
Ember.ActionHandler.reopen({
send: function(actionName) {
var orininalArguments = arguments,
args = [].slice.call(arguments, 1),
result;
Ember.instrument('action.' + actionName, args, function() {
result = this._super.apply(this, orininalArguments);
}, this);
return result;
}
});
So you can subscribe:
// Specific action
Ember.Instrumentation.subscribe("action.inlineEdit.makeBlue", { ... })
I added the action prefix in the subscription, so you can take advantage of the instrumentation api, and listen all action events with:
// All actions
Ember.Instrumentation.subscribe("action", { ... })
Give a look in that fiddle to see this working http://jsfiddle.net/marciojunior/8P46f/
I hope it helps

Related

ember: promise for a controller action that calls a service method

I have an action on my controller that calls a service method. The service method is an ember-data query. I need to return a message contained in that ember-data payload back to the controller (print it to the screen).
I am having a hard time figuring out how to get the controller action (function) to "wait" for the service method to finish.
The controller action:
// controller action
processCoupon() {
// the service method I want to wait for the response for
let messageObject = DS.PromiseObject.create({
promise: this.get('cart').processCoupon()
});
// the message
messageObject.then(response => {
let promo_message = messageObject.get('promo_message');
if (promo_message.message && promo_message.alert) {
if (!promo_message.success) {
// show error message in alert with ember-cli-notifcations
} else {
// show success message in alert with ember-cli-notifcations
}
}
});
},
Method in the service I want to wait for the response for:
// service method syncs cart info (containing promo) with the backend
// promo_message is in the response payload
processCoupon() {
return this.get('store').findRecord('cart', get(this, 'cartObj.id')).then(cart => {
cart.save().then(newCart => {
set(this, 'cartObj', newCart); // sets response to property on service
return newCart.get('promo_message');
});
});
},
the 'response' in the promise is empty, and the MessageObject itself has no content. So I'm doing something wrong here (and it's likely misunderstanding promises).
I messed around with RSVP promises and didn't do well there either. What am I missing, OR is there a better way to do this?
Your service method should return a promise. Then you can use it like this: this.get('cart').process().then((response) => {/*your code working with service's response*/});
You also should be aware that if you use ember data, it will return a model instance, not an original response from your back-end.
And, in order to return promise, you need to wrap your service's method in new Promise((resolve, reject) => {/*asynchronous code here*/});
I've done something similar with Ember.RSVP.Promise() is that what you want?
// controller
myMessage = null,
actions:
cartCoupon() {
let msg = this.get('cart').processCoupon();
msg.then(myDataMessage => this.set('myMessage', myDataMessage);
}
//service
processCoupon() {
return new Ember.RSVP.Promise( resolve => {
let data = this.get('store').findRecord('cart', get(this, 'cartObj.id')).then(cart => {
cart.save().then(newCart => {
set(this, 'cartObj', newCart); // sets response to property on service
return newCart.get('promo_message');
});
});
resolve(data);
});
}

How to remove event listener in Ember

I would like to set up an event listener in a component on keydown event in Ember. The event listener gets properly added, but I can't seem to remove it as the removeEventListener passes in a different function. Any help on how to properly set this up would be appreciated.
onEscPress(e) {
if (e.which === 27) {
alert('test');
this.send('dismissModal');
}
},
willInsertElement() {
window.addEventListener('keydown', this.onEscPress(e), false);
},
willDestroyElement() {
// Uses a different function, so doesn't properly remove the listener
window.removeEventListener('keydown', this.onEscPress(e), false);
},
Its important to understand that in your code you passed the result of onEscPress to the event handler. This is not what you want.
Also you can't directly pass the function to the event handler if you want to use this, because it will be executed in a different dynamic context. So the correct way to do this is to
create a bound version of your function in the init hook,
add an event listener to that bound function in the didInsertElement hook, and
remove this bound function in the willDestroyElementhook.
So here is some code:
onEscPress(e) {
if (e.which === 27) {
alert('test');
this.send('dismissModal');
}
},
init() {
this._super(...arguments);
this.set('boundOnEscPress', this.get('onEscPress').bind(this));
},
willInsertElement() {
this._super(...arguments);
window.addEventListener('keydown', this.get('boundOnEscPress'), false);
},
willDestroyElement() {
this._super(...arguments);
window.removeEventListener('keydown', this.get('boundOnEscPress'), false);
},
Also never forget calling this._super(...arguments);! Its really important!

Ember route has action

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.

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

linkTo and action helpers default events

Is there a way to replace the default event that triggers the linkTo and action helpers?
I want to respond to mousedown rather than click...
It is to make the interface work a little faster.
thanks
Action helper:
The answer can be found in official guide:
Specify the type of event: http://emberjs.com/guides/templates/actions/
<p>
<button {{action "select" post on="mouseUp"}}>✓</button>
{{post.title}}
</p>
List of available event: http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_adding-new-events
LinkTo helper:
As {{linkTo}} doesn't have an on option like {{action}}, this helper can simulate the same result by define the eventManager property.
Ember.Handlebars.registerHelper('linkAction', function (name) {
var options = [].slice.call(arguments, -1)[0];
var params = [].slice.call(arguments, 1, -1);
var hash = options.hash;
hash.namedRoute = name;
hash.currentWhen = hash.currentWhen || name;
hash.parameters = {
context: this,
options: options,
params: params
};
var LinkView = Ember.LinkView.extend({
didInsertElement: function () {
this.eventManager = Ember.Object.create({});
this.eventManager.click = function () {
return false;
};
this.eventManager[hash.on] = function (e, v) {
Ember.LinkView.prototype.click.call(v, e);
}
}
})
return Ember.Handlebars.helpers.view.call(this, LinkView, options);
});
How to use:
{{#linkAction route on="mouseEnter"}}Text{{/linkAction}}
Demo: http://jsfiddle.net/indream/mKVU8/1/
Reference: Action helper LinkTo helper