I want to call a base action from an overriden action like in this jsbin.
I know if I return true then it will propagate to the action, the problem is that I want the action to execute and then do something in the overriden action. Is this possible?
As #fanta noted you aren't exactly overriding the action because you'd have to extend from your parent route, but aside from that:
If you return true from the action handler the action will bubble up to any parent routes. Simple as that you can specify to call the parent's route action handler or just let the action die in your handler
App.IndexRoute = Ember.Route.extend({
actions:{
doSomething: function(){
//do things
if (something) {
//I got this, no need for my parent
return true;
} else if (otherThing) {
//I can't do this, let my parent route handle it
return false;
}
return defaultOption;
}
}
});
I can't think of a scenario in which using action bubbling you couldn't (implicitly) call your parent's route action handler. Do you?
EDIT
If you were to wait for a promise in the remove method of the object you want to remove you can use something like this:
App.IndexRoute = Ember.Route.extend({
actions:{
removeConfirmed: function(){
// do some handling
console.log("I may transitionTo");
self.transitionToRoute('otherRoute', 'someParam');
}
}
});
App.IndexController = Ember.Controller.extend({
actions:{
remove: function(){
var self = this;
this.get('content').remove().then(function() {
// you could transition from the controller
self.transitionToRoute('otherRoute', 'someParam');
// or if you need some handling in the route
self.send('removeConfirmed');
});
// you could let the action remove action bubble up
return true;
}
}
});
If there's no need to wait for a promise then I don't see why returning true and letting the action bubble wouldn't work, since the action in the route would be called.
The important thing to note is that the controller can send actions to itself. If they are not handled by controller itself the action will bubble up to its route and any subsequent parent routes
I hope this helps you!
Related
I have an action that may be triggered from different routes/templates, but ultimately should be sent to an action in the application controller. How do I do this? I've only seen examples of the needs property being used for sending actions to child controllers.
So how can actions sent from /posts/post and / (application) both be sent to the application controller?
You usually define the action handler in the ApplicationRoute as:
App.ApplicationRoute = Em.Route.extend({
actions: {
print: function() {
console.log('hello');
}
}
});
Then, if your action is not defined either on your controller or specific route, the action will bubble up to any parent routes until the ApplicationRoute.
If you want to handle the action in your route and at the application level, you must return true in your action handler in order the action can bubble up.
App.IndexRoute = Em.Route.extend({
actions: {
print: function() {
console.log('hello');
return true;
}
}
});
Check the guide for a detailed description.
I have a controller (KbRelatedGroupController) that is loaded via a {{render}} helper in a template.
In the controller's action, if I do this.get('target'), it returns a reference to the parent controller for the page (KbShowController).
If I call .target on that, I get a reference to Discourse.Router, which is no good to me.
What I want is a reference to a KbShowRoute, and that is what I expected since .target is supposed to produce the Route when called from a controller is it not?
Really confused here. Why is it so hard to get a reference to the current route from a controller?
The way I see it, you're not supposed to. You can let the action bubble up to the route:
App.KbShowRoute = Ember.Route.extend({
...
actions: {
something: function() {
console.log('called second');
}
}
});
App.KbShowController = Ember.Controller.extend({
...
actions: {
something: function() {
console.log('called first');
}
}
});
see docs
You could:
Handle one part of the action in the controller, and let it bubble to the route by not returning anything in the controller's action handler
Let the route handle the action (by not adding the action to your controller's action hash) and from the route use this.controllerFor(this.routeName).sendAction('..', ...) to call a different action (or part of the action) in the controller.
I hope this helps you!
I have a multi-step flow that the user can go through sequentially or jump straight to a section (if the sections in between are completed). I think this logic should be in the Route object. However, from within the controller, how do I access the route instance. For example, it would be ideal to be able to do something like this in the controller:
App.Flow = Em.ObjectController.extend({
submit: function(){
// Validation and XHR requests
// ...
// Go to the next step
route.goToNextStep();
}
}
From within a controller, you can access the router via this.get('target'). So this.get('target').send('goToNextStep') should work.
Like so:
App.Flow = Em.ObjectController.extend({
submit: function(){
// ...
this.get('target').send('gotoNextStep');
}
}
App.FlowRoute = Ember.Route.extend({
events: {
gotoNextStep: function(){
// ...
this.transitionTo(routeName);
}
}
}
You need to get the route for such conditions,
so from the controller just say,
App.Flow = Em.ObjectController.extend({
submit: function(){
var self =this;
// Validation and XHR requests
// ...
// Go to the next step
self.send('goToNextStep');
}
}
and define your goToNextStep event in your route's event hash
'this' is what points to the router, but you shouldn't add any methods to that prototype. Instead, make some sort of event that triggers the transition to the next step.
In addition to target, another way to do this in Ember now is with getOwner.
For example, to send an action to your application route:
import Component from '#ember/component';
import { action } from '#ember/object'; // https://github.com/pzuraq/ember-decorators-polyfill
import { getOwner } from '#ember/application';
export default class MyTopLevelComponent extends Component {
#action
closeModal() {
getOwner(this).lookup('route:application').send('closeModal');
}
});
I want to save user progress, before user leaves a page. What is the best way to do this in Ember.js (v 1.0.0-pre.4)?
In pure JQuery it will look like:
$(window).unload(function() {
ajaxSaveUserProgress();
return true;
});
In Ember I am trying to do something like this:
Exam.TestView = Ember.View.extend({
unload: function(event){
controller.ajaxSaveUserProgress(); // call controller method
console.log('UNLOADED'+get(this, 'controller.test'));
}
});
Personally I'd put this code in the ApplicationRoute, as I believe the ApplicationRoute's setupController is only executed the once when the application is first initialised. You'll have to double-check this, but that's my understanding of it.
I've commented out the code you'll want because I've also demonstrated how the AJAX request needs to be set to synchronous, otherwise the window will close and your AJAX request won't have finished. We naturally need to wait for it to finish before the window is closed.
App.ApplicationRoute = Ember.Route.extend({
setupController: function() {
// var controller = this.controllerFor('foo');
// controller.ajaxSaveUserProgress();
jQuery(window).on('unload', function() {
jQuery.ajax({ type: 'post', async: false, url: 'foo/bar.json' });
});
}
});
Please ignore my jQuery instead of $ (Personal preference!)
Ember's got a standard way of handling this now. From the docs:
App.FormRoute = Ember.Route.extend({
actions: {
willTransition: function(transition) {
if (this.controller.get('userHasEnteredData') &&
!confirm("Are you sure you want to abandon progress?")) {
transition.abort();
} else {
// Bubble the `willTransition` action so that
// parent routes can decide whether or not to abort.
return true;
}
}
}
});
Sample code for my question is here.
It's a simple Ember app that displays the SearchView containing a TextField by default.
When the user enters some text and hits Enter, I want to transition to another state (displayUserProfile) passing the value entered in the textbox.
At first, in the Textbox's insertNewline callback, I called the transitionTo method of the application's router, passing the value as part of the parameter object:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
App.router.transitionTo('displayUserProfile', {
username: this.get('value')
});
}
});
That works fine, but then I noticed that pangratz's answer on a question about infinite scrolling, uses a different approach. Instead he invokes a method on the view's controller, which in turn calls a method on the controller's target (which is the router).
This changes my code to:
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
Em.tryInvoke(this.get('controller'), 'displayUserProfile', this.get('value').w());
}
});
App.SearchController = Em.Object.extend({
displayUserProfile: function(username) {
this.get('target').transitionTo('displayUserProfile', {
username: username
});
}
});
My question is: which approach is better?
Calling transitionTo directly from the view or delegating it to the view's controller?
I would recommend a different approach. insertNewLine should trigger an action that is handled by the router, which will then transition its state.
App.SearchTextFieldView = Em.TextField.extend({
insertNewline: function() {
this.get('controller.target').send('showUser', {username: this.get('value')});
}
});
App.Router = Ember.Router.extend({
...
foo: Ember.Router.extend({
showUser: function(router, evt) {
router.transitionTo('displayUserProfile', evt);
});
}
});
You should put the showUser handler at the top-most route where it is valid in your app.
This approach follows the general pattern of events in Ember apps that views handle DOM-level events and where appropriate, turn them into semantic actions that are handled by the router.
Personally I think the second approach is better.
The first thing is that it's a bad idea to access the router statically. Then for me, you have to keep the views logic-less, so delegating to controller seems a good choice.
In your case this is only a call to the router, but you can imagine processing some algorithms on the textfield value. If you do this proccessing in you view, this will lead to a view, mixing UI code, and logic code. View should handle only UI code.