Ember.js: when using {{action}}, how to target a specific controller? - ember.js

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.

Related

Propagate action from nested component to AppController

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.

How to add search results from an action to the user interface in Emberjs

I have adopted a simple Ember app. Currently, I load a set of locations via the model method on the route like this:
Hex.LocationsbysectionsRoute = Ember.Route.extend({
model: function(params) {
return $.getJSON("/arc/v1/api/all-locations").then( function(response){
return response.map(function (child) {
return Hex.Location.create(child);
});
});
}
});
I would like to add a search button at the bottom to add locations to a specific section. I understand that I could use transitionTo but I'd just like to place this into the DOM somehow - this seems really simple but having a hard time finding a working example online.
Something like:
<script type="text/x-handlebars" id="locationsbysections">
<input id='hex-ember-location-val' /><button {{action 'searchForLocations'}}>search</button>
</script>
But I'm not really sure how to handle the searchForLocations action and get the results into the UI. Would I use the model on the Route? I was thinking something like this but how would I deliver the Promise to the template?
Hex.LocationsbysectionsController = Ember.ArrayController.extend({
actions: {
searchForLocations: function() {
var query=$('#hex-ember-location-val').val();
$.getJSON("/arc/v1/api/locations/query_by_sections/" + query).then( function(response){
var items=[];
$.each(response, function(idx, val){
var location=Hex.Location.create(val);
items.push(location);
console.log(location);
});
});
}
}
});
I'm able to put this into the items array but how would I render that into the original locationsbysections template? It doesn't seem like the model method of the Router is the place to do this but how would I get this to work?
I have tried something like this:
{{#if hasSearchItems}}
<div>there are {{items.length}} search resutls!!!</div>
{{#each items}}
<div>{{name}} <button {{action "addToSection" this}}>add to section</button></div>
{{/each}}
{{else}}
<div>there are no search results</div>
{{/if}}
and then manage the hasSearchItems variable in the Controller, but no luck.
If you don't use real ember-data model you can eventually leave model empty and set your property in setupController:
Hex.LocationsbysectionsRoute = Ember.Route.extend({
model: function(params) {return null},
setupController: function(controller, model) {
$.getJSON("/arc/v1/api/all-locations").then(function(response) {
var locations = response.map(function (child) {
return Hex.Location.create(child);
});
controller.set("locations", locations)
});
}
}
<script type="text/x-handlebars" id="locationsbysections">
{{#each location in locations}}
<div>{{location.name}}</div>
etc...
{{/each}}
</script>
In this manner you can overwrite your locations property without problems.
<script type="text/x-handlebars" id="locationsbysections">
...
{{input type='text' value=searchInput}}
<button {{action 'searchForLocations'}}>search</button>
</script>
Hex.LocationsbysectionsController = Ember.ArrayController.extend({
searchInput: "",
actions: {
searchForLocations: function() {
var that = this;
$.getJSON("/arc/v1/api/locations/query_by_sections/" + that.get("searchInput")).then(function(response) {
var locations = response.map(function (child) {
return Hex.Location.create(child);
});
that.set("locations", locations)
});
});
}
});

Ember.js and Foundation 4 modal window

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

Calling controller action from view in Ember

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

Global Notifications View using Ember

I have a notification view responsible for displaying global messages at the top of the page (info, warning, confirmation messages ...)
I created a NotificationView for the purpose, defined its content property and provided two handlers to show and hide the view.
APP.NotificationView = Ember.View.extend({
templateName: 'notification',
classNames:['nNote'],
content:null,
didInsertElement : function(){
},
click: function() {
var _self = this;
_self.$().fadeTo(200, 0.00, function(){ //fade
_self.$().slideUp(200, function() { //slide up
_self.$().remove(); //then remove from the DOM
});
});
_self.destroy();
},
show: function() {
var _self = this;
_self.$().css('display','block').css('opacity', 0).slideDown('slow').animate(
{ opacity: 1 },
{ queue: false, duration: 'slow' }
);
}
});
Ideally, i should be able to send an event from any controller or route to show the view with the proper content and styling. What would be the best way to architect this
I thought of using a named outlet in my application's template, however outlets are not quite suited for dynamic views.
<div id="content">
{{outlet notification}}
{{outlet}}
</div>
I was also thinking of architecting the notification view to be a response to "The application" or "A Module" state.
Because you have animations you want to run when the notifications change, you will want to create a subclass of Ember.View (a "widget"):
App.NotificationView = Ember.View.extend({
notificationDidChange: function() {
if (this.get('notification') !== null) {
this.$().slideDown();
}
}.observes('notification'),
close: function() {
this.$().slideUp().then(function() {
self.set('notification', null);
});
},
template: Ember.Handlebars.compile(
"<button {{action 'close' target='view'}}>Close</button>" +
"{{view.notification}}"
)
});
This widget will expect to have a notification property. You can set it from your application template:
{{view App.NotificationView id="notifications" notificationBinding="notification"}}
This will gets its notification property from the ApplicationController, so we will create a couple of methods on the controller that other controllers can use to send notifications:
App.ApplicationController = Ember.Controller.extend({
closeNotification: function() {
this.set('notification', null);
},
notify: function(notification) {
this.set('notification', notification);
}
});
Now, let's say we want to create a notification every time we enter the dashboard route:
App.DashboardRoute = Ember.Route.extend({
setupController: function() {
var notification = "You have entered the dashboard";
this.controllerFor('application').notify(notification);
}
});
The view itself manages the DOM, while the application controller manages the notification property. You can see it all working at this JSBin.
Note that if all you wanted to do was display a notification, and didn't care about animations, you could just have done:
{{#if notification}}
<div id="notification">
<button {{action "closeNotification"}}>Close</button>
<p id="notification">{{notification}}</p>
</div>
{{/if}}
in your application template, with the same ApplicationController, and everything would just work.
I don't agree that Notifications should be a View, I think they should be a Component. Then they are also more flexible to be used across your application.
You could a Notification Component instead as answered here: How can I make an Alert Notifications component using Ember.js?