In the following code, my click events delegate and all three 'click' handlers in my view hierarchy get fired.
However, I also want to fire 'edit' in my entire view hierarchy. 'edit' is simply the target of an element in my 'child' view.
Template
<script type="text/x-handlebars">
{{#view App.GrandparentView}}
{{#view App.ParentView}}
{{#view App.ChildView}}
<span {{action "edit" target="view"}}>Click Me</span>
{{/view}}
{{/view}}
{{/view}}
</script>
JavaScript
App.GrandparentView = Ember.View.extend({
click: function() {
console.log('Grandparent Click Fired!');
},
edit: function () {
console.log('GrandParent edit fired');
}
});
App.ParentView = Ember.View.extend({
click: function() {
console.log('Parent Click Fired!');
},
edit: function () {
console.log('Parent edit fired');
}
});
App.ChildView = Ember.View.extend({
click: function() {
console.log('Child Click Fired!');
},
edit: function () {
console.log('Child edit fired');
}
});
Is there no way to delegate the target handlers in the view hierarchy? What I dont want to do is this:
App.ChildView = Ember.View.extend({
click: function() {
console.log('Child Click Fired!');
},
edit: function () {
console.log('Child edit fired');
this.get('parentView').edit(); //DO NOT WANT TO DO THIS.
}
});
Here is a jsFiddle as an example to test.
It looks like the same question you've posted about a week ago. As far as I can see, such feature is not implemented in ember. The event is propagated to the view hierarchy, but the action name is lost, and the default click handler is triggered.
The only workaround I found is to reopen the Ember.View class itself, and override the click handler like this:
Ember.View.reopen({
click: function(event){
if(event.view !== this && this[event.actionName]){
return this[event.actionName](event);
}
}
})
See the fiddle here:
http://jsfiddle.net/Sly7/zZyCS/
Related
I have a component nested several levels down in other components. I'm trying to propagate an action all the way up to the AppController in order to open a modal.
The only way I know of doing this is to pass in the action to each component - but this seems extremely impractical. Is there a better way to access the AppController from a nested component?
See my jsbin for the code
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
App.AppController = Ember.Controller.extend({
actions: {
openModal: function(){
alert('this would open the modal')
}
}
})
App.MainComponentComponent = Ember.Component.extend({})
App.SubComponentComponent = Ember.Component.extend({
actions: {
triggerModal: function(){
// need to trigger the openModal action on the AppController
this.sendAction('openModal')
}
}
})
.
<script type="text/x-handlebars" data-template-name="index">
<h1>Index</h1>
{{main-component model=model}}
</script>
<script type="text/x-handlebars" data-template-name="components/main-component">
<h2>Main component</h2>
{{#each color in model}}
{{sub-component color=color}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="components/sub-component">
<button {{action "triggerModal"}}>{{color}}</button>
</script>
EDIT: I'm aware that I can render a template into the modal outlet:
this.render(modalName, {
into: 'application',
outlet: 'modal'
});
But I'm trying to access an action on the AppController.
You can utilize Ember.Instrumentation module, which can be used like a pub/sub.
Here is a working JS Bin example.
Solution outline:
1. On ApplicationController init, the controller subscribes to "openModal" event.
2. The neseted component instruments the event "openModal" within an action.
3. The instrumentation can be executed with a payload, so this would be the place to determine the modal content.
App.ApplicationController = Ember.Controller.extend({
actions: {
openModal: function(options) {
alert('this would open the modal with the content: ' + options.modalContent);
}
},
subscribeEvents: function() {
this.set('openModalSubscriber', Ember.Instrumentation.subscribe('openModal', {
before: Ember.K,
after: Ember.run.bind(this, function(name, timestamp, payload, beforeRet) {
this.send('openModal', payload);
}),
}, this));
}.on('init')
});
App.SubComponentComponent = Ember.Component.extend({
actions: {
triggerModal: function() {
Ember.Instrumentation.instrument('openModal.sub-component', {
modalContent: 'Inner content of modal'
}, Ember.K, this);
}
}
});
Components are supposed to be pretty isolated, therefore it probably doesn't make sense to be jumping over other components, going straight to their controllers... See the following discussion here
There is a targetObject property, which might be of use to you, although I am not 100% sure what you would set it to in this case.
I'm trying to work with a simple overlay component, and close this overlay if someone clicks outside of the overlay content:
<div class="overlay" {{action 'close' on='click'}}>
<div class="item">
<form {{action 'submit' on='submit'}}>
{{yield}}
{{#link-to closeRoute class="close"}}Close{{/link-to}}
</form>
</div>
</div>
The component looks like this:
import Ember from 'ember';
export default Ember.Component.extend({
actions: {
submit: function() {
this.sendAction();
},
close: function(param) {
console.log(param); // -> undefined
console.log(this); // -> complete component object, no reference to the event?
// this.$("a.close").click();
}
}
});
This works like advertised, however, I need to determine the target of the click event, because also clicks on the item and form will trigger this click(close) action.
Question: How can I access the (jQuery) event object which has a target from within the close action inside the component?
I am using EmberCLI, and Ember 1.9
I have found this to provide the required result:
export default Ember.Component.extend({
classNames: ['overlay-block'],
didInsertElement: function() {
var self = this;
self.$().click(function(e) {
if (self.$(e.target).hasClass("overlay-block")) {
self.$("a.close").click();
}
});
}
});
This does not use an ember action like I expected. I'll leave the question open for a while to see if somebody comes up with an more 'Ember way' of doing this.
More Ember way
export default Ember.Component.extend({
classNames: ['overlay-block'],
click: function(e) {
if (this.$(e.target).hasClass("overlay-block")){
this.$("a.close").click();
}
}
});
I have two problems with Foundation Reveal and Ember.js.
First, action "close" is not firing. I have any ideas why.
#application.js
App.ModalView = Ember.View.extend({
templateName: "modal",
title: "",
classNames: ["reveal-modal"],
didInsertElement: function () {
this.$().foundation('reveal', 'open');
},
actions: {
close: function () {
console.log('close action fired');
this.destroy();
}
},
});
App.ApplicationRoute = Ember.Route.extend({
actions: {
showModal: function () {
var view = this.container.lookup('view:modal', {title:'Test title'}).append();
}
}
});
#index.html
<script type="text/x-handlebars" data-template-name="test">
<a {{action showModal}}>show modal</a>
</script>
<script type="text/x-handlebars" data-template-name="modal">
<h2> {{title}}</h2>
<p>Im a cool paragraph that lives inside of an even cooler modal. Wins</p>
<a class="close-reveal-modal" {{action close target="view"}}>×</a>
<a {{action close target=view}}>Close</a>
</script>
And the second is that i cant set attributes of view while adding it this way:
App.ApplicationRoute = Ember.Route.extend({
actions: {
showModal: function () {
var view = this.container.lookup('view:modal', {title:'Test title'}).append(); //not setting title
}
}
});
For second i can't find in documentation how i can set view parameters while adding via lookup.
Fiddle: http://jsfiddle.net/L4m6v/
Ember doesn't set up the plumbing when you create a view in this manner.
You can build a popup that lives on the application (which is easy to edit and manipulate from anywhere within the application (controllerFor('application') from a route, or needs:['application'] and this.get('controllers.application') from controllers).
Here's a simple JSBin showing this (I didn't spend much time on making it pretty, CSS isn't really a strong suit of mine anyway).
http://emberjs.jsbin.com/eGIZaxI/1/edit
App.ApplicationController = Ember.ObjectController.extend({
title: "Popup Title",
description: "You should do something",
isVisible: true
});
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
},
actions: {
hidePopup: function(){
$(".popup").fadeOut();
this.controllerFor('application').set('isVisible', false);
},
showPopup: function(){
$(".popup").fadeIn();
this.controllerFor('application').set('isVisible', true);
}
}
});
I've created project on github for this problem with fixed foundation.reveal.js:
(i didnt find the way to fix foundation.js on jsbin)
I think other libs that making modal have the same problem, so if you'are using jquery-ui you may fix it too.
https://github.com/xjok3rx/ember-modal
I put my question in a code example here:
http://jsbin.com/urukil/12/edit
See, I can use a {{action}} (which is placed in a child view) with target option to trigger an event in ApplicationView or ApplicationController or ChildView, only except the ChildController which is the one I truly wanted.
According the document, if no target specified, the event itself should handled in corresponding controller, in my case, which is should be ChildController. But why this action always lookup in ApplicationController? Did I miss something obviously important?
You can use needs to call a action on different controller...
App.ApplicationController = Ember.Controller.extend({
needs: ['child'],
doSomething: function() {
alert("From ApplicationController");
}
});
And the target can be specified as "controllers.child" from the template
<p {{action doSomething target="controllers.child"}}>Blah blah</p>
Here is your working fiddle...
http://jsbin.com/agusen/1/edit
Use this.controllerFor('') to call different controller event. A working example is given below.
JS:
/// <reference path="Lib/ember.js" />
var app = Ember.Application.create()
app.Router.map(function () {
this.resource('post')
});
app.ApplicationRoute = Ember.Route.extend({
model: function () {
return { "firstName": "amit", "lastName": "pandey" }
}
});
app.ApplicationController = Ember.ObjectController.extend({
Address: "House no 93-B",
fullName: function () {
return this.get("model.firstName") + " " + this.get("model.lastName")
}.property("model.firstName", "model.lastName"),
actions: {
submit: function (name) {
this.controllerFor('post').send('handleclick')
},
makeMeUpper:function()
{
alert('calling application controller Event');
this.set("model.firstName",this.get("model.firstName").toUpperCase())
}
}
});
app.PostRoute = Ember.Route.extend({
model:function()
{
return user;
}
});
app.PostController = Ember.ObjectController.extend({
Hello: "afa",
handleclick: function ()
{
alert('calling post controller Event');
this.controllerFor('application').send('makeMeUpper');
}
});
var user = [
{
id: "1",
Name: "sushil "
},
{
id: "2",
Name: "amit"
}
];
//hbs
<script type="text/x-handlebars">
<button {{action submit firstName}}>CLICK HERE TO CALL Post controller event</button>
{{input type="text" action= "makeMeUpper" value=firstName }}
{{#if check}}
No Record Exist
{{else}}
{{firstName}}{{lastName}}
{{/if}}
{{#linkTo 'post'}}click {{/linkTo}}
{{outlet}}
</script>
<script type="text/x-handlebars" id="post">
<button {{action hanleclick}}>Click here to call application controller event</button>
</script>
As far as I know the view class does not change the current controller. Since you are calling the view from the Application template, it remains in the ApplicationController.
Emberjs.com guides on render:
{{render}} does several things:
When no model is provided it gets the singleton instance of the corresponding controller
Simply changing your code from a view to a render call seems to do the trick:
Trigger ApplicationController
</p>
{{render 'child'}}
Since controllerFor is getting deprecated, the correct way to do this now is to specify needs in the controller, retrieve it from the controllers list, and then send it there. Example:
App.SomeController = Em.Controller.extend({
needs: ['other'],
actions: {
sayHello: function () {
console.log("Hello from inside SomeController.");
this.get('controllers.other').send('helloAgain');
}
}
});
App.OtherController = Em.Controller.extend({
actions: {
helloAgain: function () {
console.log("Hello again from inside OtherController!");
}
}
});
EDIT: oops... Looks like someone already posted this answer in essence. Will revise if needed.
I have a submit button with a onClick view event. This event checks a flag and depending upon the condition it will allow form submission. I'd like the submit action on the controller to be called. What is the best way to do this?
Here another solution based on the example by albertjan for the case you have to perform some logic in your View and afterwards delegate to your controller. This is the way i understood your question:
HBS:
<script type="text/x-handlebars" data-template-name="index">
<button {{action submit target="view"}} >Sumbit</button>
</script>
View:
App.ThingView = Ember.View.extend({
submit : function(){
//do the view part of your logic
var object = //do whatever you may need
this.get("controller").send("submitInController", object); //you do not have to send object, if you do not need to
}
});
Controller:
App.ThingController = Em.ObjectController.extend({
submitInController: function(model) {
// do the controller part of your logic
}
});
Note: The call from your view will also bubble up to your current route. So this is basically the same code, that ember is executing when using the action helper.
I would handle the whole event on the controller:
HBS:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "submit"}}>Sumbit</button>
</script>
Controller:
App.ThingController = Em.ObjectController.extend({
submit: function() {
//handle things here!
//change the state of your object here to reflect the changes that
//the submit made so that the view shows these.
}
});
In Ember version 1.0.0, I've been having success adding actions to their own object literal in the controller.
IndexTemplate.html
<script type="text/x-handlebars" data-template-name="index">
<button {{action "submit"}}>Submit</button>
</script>
ThingController.js
App.ThingController = Ember.ObjectController.extend({
actions: {
submit: function() {
//handle things here!
//change the state of your object here to reflect the changes that
//the submit made so that the view shows these.
}
}
});
For more information, check out the {{action}} helper documentation from Ember Guides.
You can trigger an action from a view if the view uses the ViewTargetActionSupport mixin. The following example demonstrates its usage:
App.SomeController = Ember.Controller.extend({
actions: {
doSomething: function() {
alert('Doing something!');
}
}
});
App.SomeView = Ember.View.extend(Ember.ViewTargetActionSupport, {
someMethod: function() {
this.triggerAction({action: 'doSomething'});
}
});