How can I make custom events that bubble through the view hierarchy? - ember.js

I have button views that are part of a set of Ember.js form helpers I wrote. E.g. MyAddressFormView has the following template:
{{#form}}
{{textArea address}}
{{submitButton}}
{{cancelButton}}
{{/form}}
To handle the "Submit" button, I let the "submit" form event bubble, and handle it in the submit method of MyAddressFormView.
But how do I do the same for the "Cancel" button? For instance, is there any way I can trigger a custom "cancel form" event in a child view, let it bubble, and handle it in an ancestor view?

I would suggest triggering a jQuery special event and registering the event with a mapping on your Ember app. For example:
Ember.Application.create({
customEvents: {
// key is the jquery event, value is the name used in views
formcancel: 'formCancel'
}
});
And in your cancelButton view:
click: function(evt){
Ember.$().trigger('formcancel')
}
This will bubble in the same way that other mapped Ember DOM events do.

Ember.ActionHandler bubbles events through its "target" property. It's possible to override Ember.View's target to let events bubble through parentViews by default.
Include this mixin first:
(function() {
Ember.View.reopen({
// Let actions bubble to parentView by default.
target: function() {
return this.get('parentView');
}.property('parentView')
});
})();
Then just send the event like you'd normally do:
tap: function() { this.send('someEvent'); }

Related

Triggering an action in nested Components in Ember.js

I have a Component--which is essentially a form--displaying editable prices for a set of items. Each row in this form is also a Component, so there's a nested relationship (a parent Component and child Components).
At the bottom of this form is a button to Cancel any changes the user made to the form, meaning it will need to affect all of the child Components. Ideally, I'd like to trigger an action from the Cancel button and have the child Components react to that.
I know this is against the Data Down / Actions Up methodology of Ember, but I'm not sure how else to approach this.
Is there any way to do this?
Following the data-down, actions-up approach, you'll have to do something like this.
Parent component:
App.ParentComponent = Ember.Component.extend({
isCancelled: false,
actions: {
cancel: function() {
this.set('isCancelled', true);
}
}
});
Parent template:
{{child-component isCancelled=isCancelled}}
<button {{action 'cancel'}}>Cancel</button>
Child component:
App.ChildComponent = Ember.Component.extend({
isCancelled: false,
wasCancelled: function() {
if (this.get('isCancelled')) {
// Cancelled logic here
}
}.observes('isCancelled')
});
I'm not sure exactly what you want to happen to the child component when the cancelled button is pressed, but you can probably use computed properties instead of an observer (which I think is a bit cleaner).

Should this go in my Ember View or Ember Controller?

I have an ember view, called picker, which is responsible for detecting click and mouseMove events on a div. Those events, when detected, send the respective calls to the corresponding controller.
export default Ember.View.extend({
templateName: 'picker',
click: function (event) {
this.get('controller').send('addColor', event);
},
mouseMove: function (event) {
this.get('controller').send('updateColor', event);
}
});
My app requires that I perform some logic using the pageX and pageY properties of the event object and also determine some scaling factors using the properties of the div (offset, width, height etc.).
Where should that work be done? Should it all be contained within the view and just passed through to the action methods as parameters or should I pass the event object through and make it a responsibility of the controller? My gut says the former...
This sounds like it should be wrapped in a component.
You can wrap the mouseMove and click event handlers in the component and the controller can pass in the actions to call when those events fire. for example you can have a component like
export default Ember.Component.extend({
click: function (event) {
this.sendAction('click');
},
mouseMove: function (event) {
this.sendAction('mouseMove');
}
});
and the in the template you would use it like
<div>
{{#x-picker click="addColor" mouseMove="updateColor"}}
<span> Track mousemove and click in here </span>
{{/x-picker}}
</div>

Ember Component keyboard events

In my webapp I have a search field at the top (kinda like Facebook) which is an input field and a bootstrap dropdown for result suggestions. I would like to decouple it from the controller of its container into an Ember Component. But I don't understand how to pick up events from the input field. Right now the view picks up submit (there is no submit button) and keyUp/keyDownfor navigating the dropdown. How do I listen to these events on a Component?
You can use Component the same way as you would View.
App.SearchFieldComponent = Ember.Component.extend({
value: "",
keyUp: function (e) {
console.log(e);
alert("You pressed key code " + e.keyCode);
}
});
Full jsbin.

Right way for conditional transition depending on where view is rendered in emberjs

Hi I have a view call AutocompleteView its job is to put google places autocomplete on landing page.There is no map rendered on this page just a textbox.
what I am try to achieve is user should just use autocomplete textbox on landing page. On entering some place of choice. I will transition the user to a specific route where the same AutocompleteView will be rendered with map so tha user can change his choice of place.
When user chang the location on the page where map is rendered no transition is needed here
the approch I am trying is I check the parent view where
{{view AutocompleteView}}
has been rendered using this.get('ParentView") and based on the parent view
say if ParentView is Application(landing page) I will transition or else no transition will occur.
I have no good experienve of javascript mvc my doubt
Is it right to rely on ParentView for just transtions or is there a better way??
App.AutocompleteAddressView = Ember.View.extend({
tagName: 'input',
didInsertElement: function() {
var options = {
componentRestrictions: {country: "xx"}
};
var autocomplete = new google.maps.places.Autocomplete(this.$()[0], options);
console.log(this.get('parentView'));
}
});
It sounds like what you want is to handle the action of "entering text in the search box" differently based on which route is currently active -- the landing page or the results page.
The Ember way to do this is to trigger an action from your view using this.get('controller').send(#{actionName}). That action will bubble up from the view's controller to the current route. Then you can handle that action differently depending on which route is active, e.g.:
App.IndexRoute = Em.Route.extend({
...
actions: {
search: function() {
# transition to results page
}
}
});
App.ResultsRoute = Em.Route.extend({
...
actions: {
search: function() {
# update visible results
}
}
});
To make this easier, you might want to use a subclass of Ember.TextField for your AutocompleteAddressView. Then you can define an action property on your view that will automatically get triggered when the user types enter/return.

Specify action for view in template?

I would like to know if it is possible to assign an action to a view like I could assign an action to a HTML tag:
This works:
<button {{action "show2" }}>Test 1</button>
This doesn't:
{{#view NewApp.MenuButton }}
{{action "show3" target="controller"}}
{{/view}}
I know that I could implement the click function in the view. But I would like to use the button as some sort of reusable component.
You typically want to use the Handlebars action helper on an HTML element, not on an Ember.View.
Since you want to attach an event to the NewApp.MenuButton View you, define the event in your view class definition. For example, here we handle the click event:
NewApp.MenuButton = Ember.View.extend({
click: function(event){
// When the user clicks this view,
// this function will be called.
// ... handle the click
App.myController.menuButtonWasClicked();
}
});
If the event you want to attach is not one of the built-in events, you can register your own events. Find the built-in supported events and how to register custom events here: Ember.js - Events
Edit: You say you want to be able to reuse it. You can define a mixin for attaching arbitrary events and targeting arbitrary objects:
Ember.MyEventAttacher = Ember.Mixin.create({
init: function() {
var action = this.get('action');
target = this.get('target'),
targetObj = Ember.getPath(target);
if (action && targetObj) {
var targetEventFnc = targetObj[action];
if (typeof targetEventFnc === 'function') {
var actionFnc = function(event) {
targetEventFnc(event);
}
this.set(action, actionFnc);
}
this._super();
}
});
Include the Mixin in your View:
NewApp.MenuButton = Ember.View.extend(Ember.MyEventAttacher);
And then re-use this view in your templates, making sure to define the action and target properties. Example:
{{#view NewApp.MenuButton action="show3" target="NewApp.myController"}}
<!-- ... -->
{{/view}}
Targeting:
NewApp.myController = Ember.Controller.create({
show3: function(event) {
// the event is sent here!
}
});