Calling controller action from view in Ember - ember.js

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

Related

observer fire action to early. before action is initialize observes fired the action

Template
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{input type="text" value=model.name}}
</ul>
<p {{action 'test'}}>test</p>
</script>
ember code
App = Ember.Application.create();
App.Router.map(function () {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function () {
return Ember.Object.create({name:'dilip'});
},
actions:{
test:function(){
alert('route test');
console.log('test')
}
}
});
App.IndexController = Ember.Controller.extend({
handleChange:function()
{
console.log('handle')
this.send('test')
}.observes('model.name')
})
action 'test' is already defined in route but its showing below error.This error comes only when i am using observes.
Error while loading route: Error: Nothing handled the action 'test'.
The observer does fires before the route is ready to handle the action. This kinda, sorta makes sense because the controller is "built" as part of the route. I've solved this by setting a property within the routes setupController hook to let the controller know that it's ready for action!
// Route
setupController: function(controller, model) {
this._super(controller, model);
controller.set('routeIsReadyForAction', true);
}
// Controller
routeIsReadyForAction: false,
someObserver: function() {
if (!this.get('routeIsReadyForAction')) {
return;
}
this.send('someRouteAction');
}.observes('someProperty'),
You need to handle the action in the controller, not in the route. Check out this jsbin: http://emberjs.jsbin.com/yubowi/edit?html,js,output
You should define your action within controller.
this.send('test') Looks for actions in this extended class (in this case controller). If u make this.sendAction('test') It goes out and looks for actions but these have to be predefined like so `actionName="actionName". This is mostly used when you have a component that needs to interact with a controller
{{my-component actionName="actionName"}} < sendAction now looks for controllers action

How to prevent double clicks with ember.js?

I'm trying to figure out the idiomatic way to prevent a button from being clicked multiple times.
Imagine I have a simple controller action like so ...
var FooController = Ember.ObjectController.extend({
actions: {
go: function() {
console.log("done!");
}
}
});
and in my template I have a button defined like so ...
<button {{action go}}>Click Me Fast</button>
Does the action have an option to disable it immediately / making it so only once true event will be handled by the controller (until it's disabled for example)
Edit
I'm looking for a long term / multi use solution. One idea I'm thinking about is creating a special ember-component called "button-disable" that would allow me to create a custom button type that generally disables after a single click -but will still allow me to bubble up events to a parent controller. This feels a little heavier weight than I'd like so if another option exists, or if someone has created an addon for just this - let me know
As a one-off, if you bind the disabled attribute on your button like so
<button {{action go}} {{bind-attr disabled=actionPerformed}}>
and then set up your controller like
var FooController = Ember.ObjectController.extend({
actionPerformed: false,
actions: {
go: function() {
this.set("actionPerformed", true);
console.log("done!");
}
}
});
then the button will become disabled after you click it once
If you want a reusable component I'd borrow the spinner button from http://emberjs.com/guides/cookbook/helpers_and_components/spin_button_for_asynchronous_actions/ and tweak it as you need.
So your JS would be along the lines of
window.SpinEg = Ember.Application.create({});
SpinEg.ApplicationController = Ember.Controller.extend({
isLoading:false,
buttonText:"Submit",
actions:{
saveData:function(){
var self = this;
var saveTime = Ember.run.later(function(){
self.set('isLoading', false);
}, 1000);
}
}
});
SpinEg.SpinButtonComponent = Ember.Component.extend({
classNames: ['button'],
buttonText:"Save",
isLoading:false,
actions:{
showLoading:function(){
if(!this.get('isLoading')){
this.set('isLoading', true);
this.sendAction('action');
}
}
}
});
The template for your component would be
<script type='text/x-handlebars' id='components/spin-button'>
<button {{bind-attr id=id}} {{action 'showLoading'}}>
{{#if isLoading}}
<img src="http://i639.photobucket.com/albums/uu116/pksjce/spiffygif_18x18.gif"></img>
{{else}}
{{buttonText}}
{{/if}}
</button>
</script>
and you would then just include the following where you need the button to appear
<script type='text/x-handlebars' id='application'>
{{spin-button id="forapplication" isLoading = isLoading buttonText=buttonText action='saveData'}}
</script>

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

Dynamic value in application template

I tried to implement user name displaying after log in. It displays in top menu. But top menu is getting displayed before log in, so it user name is getting cached.
I tried many approaches, and using volatile() is seems the best option, but it doesn't work. In this simple example currentTime calculates only once:
<script type="text/x-handlebars" data-template-name="application">
{{currentTime}}
</script>
App.ApplicationController = Ember.Controller.extend({
currentTime: function() {
console.log('computing value');
var time = new Date();
return time;
}.property().volatile()
});
Ember version 1.3
P.S. I prepared the gist to illustrate this issue: http://jsbin.com/OPUSoTaF/1
Actually, I can't find ANY way do display dynamic value in Ember's application template. Tried to display value from another controller using {{render}} helper, value still gets cached.
It seems that I just need to update value on ApplicationController from some other controller, and to do it in a proper way. Like this:
App.LoginController = Ember.Controller.extend({
needs: 'application',
setTime: function() {
this.get('controllers.application').set('currentTime', new Date());
}
});
The application to illustrate: http://jsbin.com/OPUSoTaF/4/edit
You can change ember properties and thus views using Handlebars {{action 'actionName'}} helper. You can add action helper to almost any UI element in your handlebars template an it is usually triggered on click. When triggered it calls actionName method on the controller.
Example:
Handlebars template:
<script type="text/x-handlebars" data-template-name="application">
<button {{action 'login'}}>Login</button>
{{loginTime}}
</script>
Controller:
App.ApplicationController = Ember.Controller.extend({
loginTime: 'User not logged in yet',
actions: {
login: function() {
// ... Do some login stuff ...
this.set('loginTime', new Date());
}
}
});
Working jsbin example is here: http://jsbin.com/udUyOXaL/1/edit

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

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.