I want to abort transition on a particular route and show a modal. This is how my route code looks like:
export default Ember.Route.extend({
model: {/* some code here */},
actions: {
willTransition: function(transition) {
if (!this.controller.get('model.name')) {
console.log('aborting transition');
transition.abort();
this.send('showModal', {
template: 'campaign/campaign-name-modal',
controller: this.controller,
model: this.controller.get('model')
});
}
else {
// Bubble the `willTransition` action so that
// parent routes can decide whether or not to abort.
return true;
}
}
}
});
and then in my application.hbs, I have:
{{outlet 'modal'}}
What I am observing is that transition aborts but my modal doesn't show up. When I switch the ordering to something like:
this.send('showModal', {
template: 'campaign/campaign-name-modal',
controller: this.controller,
model: this.controller.get('model')
});
console.log('aborting transition');
transition.abort();
the transition doesn't abort at all.
I am not exactly sure why this might be happening. Any pointers?
Maybe try editing your conditional to use firstObject:
if (!this.controller.get('model.firstObject.name')) {
Related
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.
My code looks something like this
App.ItemRoute = Em.Route.extend({
setupController: function(controller) {
var model = this.modelFor('item');
controller.setProperties({
name : model.get('name'),
title: model.get('title')
});
}
});
App.ItemController = Em.ObjectController.extend({
saveOnChange: function() {
console.log('saveOnChange');
}.observes('name', 'title'),
});
From my understanding because i am using setProperties the observe should only fire once , but it fire two times
also wrapping the setProperties with beginPropertyChanges & endPropertyChanges still fires twice
what i ultimately is for it to not fire at all, so what i ended up doing was changing the controller code to be like this
App.ItemController = Em.ObjectController.extend({
load: false,
saveOnChange: function() {
if(!this.get('load')) {
this.set('load', true);
return;
}
console.log('saveOnChange');
}.observes('name', 'title'),
});
this code would work if the change is only fired once, but it won't work if its fired multiple times (that's my case)
The setProperties function doesn't coalesce your observers (unfortunately there's no way to do that), it just groups them into one operation. The source might help you to better see what it does:
Ember.setProperties = function(self, hash) {
changeProperties(function() {
for(var prop in hash) {
if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
}
});
return self;
};
So, back to your problem. The best way that I can think of is to debounce your function.
App.ItemController = Em.ObjecController.extend({
load: false,
saveOnChange: function() {
Em.run(this, 'debouncedSave', 150);
}.observes('name', 'title'),
debouncedSave: function() {
if(!this.get('load')) {
this.set('load', true);
}
}
});
If you're not familiar with debouncing, you can read about it here. There are probably some other solutions involving direct manipulation of the properties, but I'm not sure if that's a road you want to go down.
I'm trying to set a model in a rendered template from a route. The model is being captured correctly via the template call:
{{#each itemController="user"}}
<div {{bind-attr class="isExpanded:expanded"}}>
<div><button {{action "sayHello" this}}>{{first_name}} {{last_name}}</button></div>
and my Route:
Newkonf.UsersRoute = Ember.Route.extend({
isExpanded: false,
actions: {
sayHello: function(myModel){
alert('first name: ' + myModel.first_name); // <- this works
// this.render('edit-user', {model: myModel}); <- doesn't work but not sure since this leads me to susupec
// it might https://github.com/emberjs/ember.js/issues/1820
// this.set('model', myModel}); <- doesn't work
this.render('edit-user');
}
}
}
the handlebars is:
going to edit user here first name: {{first_name}}
Edit 1
I can update the message in UsersRoute and it will update accordingly. I kind of though since I specified UserController that it would cause UserRoute to take precedence but I guess not.
Here is my router:
Newkonf.Router.map(function() {
// this.resource('posts');
this.route('about', { path: '/about'});
this.resource('users', { path: '/users'});
});
Edit 2
I had to do my modal.js.hbs like this:
within modal for you:
{{model.first_name}}
because this didn't work:
within modal for you:
{{first_name}}
and not sure why.
Here's my Route:
Newkonf.ApplicationRoute = Ember.Route.extend({
actions:{
somePlace: function(item){
alert('here i am in somePlace: ' + item.last_name); // <- this works
var modalController = this.controllerFor('modal');
modalController.set('model', item);
var myjt=modalController.get('model');
console.log('my value:' + myjt.first_name); // <- this works
this.render('modal', {
into: 'application',
outlet: 'modal3',
controller: modalController
});
}
}
});
and there is no controller.
Rendering to a particular outlet requires both the outlet be named, and the outlet to be in view when you attempt to render to it.
In the case of a modal, having it live in the application template is generally a good choice since the application is always in view.
actions: {
openModal: function(item){
var modalController = this.controllerFor('modal');
modalController.set('model', item);
this.render('modal', { // template
into: 'application', // template the outlet lives in
outlet: 'modal', // the outlet's name
controller: modalController // the controller to use
});
}
}
App.ModalController = Em.ObjectController.extend();
In the case above I'm rendering the template modal, into application, using the outlet named modal ( {{outlet 'modal'}}) and I created a controller to back the template I'm rendering. In the process I set the model on the controller, which would be used in the template.
You can read more about modals: http://emberjs.com/guides/cookbook/user_interface_and_interaction/using_modal_dialogs/
And here's the example:
http://emberjs.jsbin.com/pudegupo/1/edit
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"}}
I am writing a CRUD application using Ember.JS:
A list of “actions” is displayed;
The user can click on one action to display it, or click on a button to create a new action.
I would like to use the same template for displaying/editing an existing model object and creating a new one.
Here is the router code I use.
App = Ember.Application.create();
App.Router.map(function() {
this.resource('actions', {path: "/actions"}, function() {
this.resource('action', {path: '/:action_id'});
this.route('new', {path: "/new"});
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('actions');
}
});
App.ActionsIndexRoute = Ember.Route.extend({
model: function () {
return App.Action.find();
}
});
App.ActionRoute = Ember.Route.extend({
events: {
submitSave: function () {
this.get("store").commit();
}
}
});
App.ActionsNewRoute = Ember.Route.extend({
renderTemplate: function () {
this.render('action');
},
model: function() {
var action = this.get('store').createRecord(App.Action);
return action;
},
events: {
submitSave: function () {
this.get("store").commit();
}
}
});
The problem is that when I first display an action, and then come back to create a new one, it looks like the template is not using the newly created record, but use instead the one displayed previously.
My interpretation is that the controller and the template are not in sync.
How would you do that?
Maybe there is a simpler way to achieve this?
Here is a JSBin with the code: http://jsbin.com/owiwak/10/edit
By saying this.render('action'), you are not just telling it to use the action template, but also the ActionController, when in fact you want the action template, but with the ActionNewController.
You need to override that:
this.render('action', {
controller: 'actions.new'
});
Updated JS Bin.