/* Copyright (c) 2013 [Web App Solution, Inc.](mailto:admin@webappsolution.com) FlowMVC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. FlowMVC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with FlowMVC. If not, see <http://www.gnu.org/licenses/>. */ /** * Controllers act as the front door to services; they handle application-level events and execute the appropriate * service. When a service succeeds or fails, it is the controller's responsibility to update model and store data * (application state) and dispatch events alerting the rest of the application to the state of a service call. * * The abstract controller class provides base functionality for all application controllers. The main purpose * of this class is to offer helper methods for service execution via the methods {@link FlowMVC.mvc.controller.AbstractController#executeServiceCall}, * executeServiceCallWithAsyncToken(), and executeServiceCallWithPromises(). The pattern to execute a service call was * borrowed from the Swiz [ServiceHelper.executeServiceCall()](http://swizframework.jira.com/wiki/display/SWIZ/Service+Layer) * implementation as it cleanly calls the service and adds custom success and failure handlers in one line: * * ## Example * * @exampleTODO preview * this.executeServiceCall(service, service.authenticate, [username, password], this.loginSuccess, this.loginFailure, this); * * Finally, controllers can be used to handle application-level processes and logic as they are in fact application * aware and often "control" the flow and orchestration of the application. */ Ext.define("FlowMVC.mvc.controller.AbstractController", { extend: "Ext.app.Controller", requires: [ "FlowMVC.mvc.event.EventDispatcher" ], inject: { /** * @property {FlowMVC.mvc.event.EventDispatcher} eventBus Reference to the application-level event bus. */ eventBus: "eventBus" }, statics: { /** * @property {FlowMVC.logger.Logger} logger The logger for the object. * @static */ logger: FlowMVC.logger.Logger.getLogger("FlowMVC.mvc.controller.AbstractController"), /** * @property {Ext.app.Application} ROOT_APPLICATION The Application instance this Controller is attached to. * @static */ ROOT_APPLICATION: null }, config: { /** * @cfg {String} sessionToken The session token for the Application. This should be a single string without * spaces or periods because it is used as the Application's global namespace. * @accessor */ sessionToken: null }, /** * Sets up simple accessor method shortcuts for the global event bus. */ init: function() { FlowMVC.mvc.controller.AbstractController.logger.debug("init"); this.setupGlobalEventListeners(); }, /** * Marker method. Concrete subclasses can implement to setup listeners to the global event bus with * confidence that it exists. This is called during the initialization phase of the controller to ensure * the reference to the application exists when adding event listeners to it. */ setupGlobalEventListeners: function() { FlowMVC.mvc.controller.AbstractController.logger.debug("setupGlobalEventListeners"); }, /** * Simplifies the process of executing a service call that requires custom asynchronous success and failure * handlers. * * This method determines if the service uses AsyncTokens or Promises so the API used to execute service calls is * the same; it's really just a wrapper to the concrete methods that execute service calls with AsyncTokens or * Promises. * * Note that the service call isn't passed in as a function that actually executes the service; it's passed * in via a reference to the service object, the actual service method, and the service method's parameters. * This is done to prevent the service call from being executed before the responder is being set on it. * * @param {Object} service Reference to the actual service. * @param {Function} method Reference to the method on the service object that executes the service call. * @param {Array} args Array of parameters used in the service call's method. * @param {Function} success Callback method for a successful service. * @param {Function} failure Callback method for a failed service. * @param {Object} scope The object that contains the success and failure callback methods. */ executeServiceCall: function(service, method, args, success, failure, scope) { FlowMVC.mvc.controller.AbstractController.logger.group("FlowMVC.mvc.controller.AbstractController.executeServiceCall"); var token; if(service.getUsePromise()) { FlowMVC.mvc.controller.AbstractController.logger.info("executeServiceCall: Using Promises"); token = this.executeServiceCallWithPromises(service, method, args, success, failure, scope); } else { FlowMVC.mvc.controller.AbstractController.logger.info("executeServiceCall: Using AsyncToken"); token = this.executeServiceCallWithAsyncToken(service, method, args, success, failure, scope); } return token; }, /** * Executes a service call that uses AsyncTokens. * * Note that the service call isn't passed in as a function that actually executes the service; it's passed * in via a reference to the service object, the actual service method, and the service method's parameters. * This is done to prevent the service call from being executed before the responder is being set on it. * * @param {Object} service Reference to the actual service. * @param {Function} method Reference to the method on the service object that executes the service call. * @param {Array} args Array of parameters used in the service call's method. * @param {Function} success Callback method for a successful service. * @param {Function} failure Callback method for a failed service. * @param {Object} scope The object that contains the success and failure callback methods. */ executeServiceCallWithAsyncToken: function(service, method, args, success, failure, scope) { FlowMVC.mvc.controller.AbstractController.logger.debug("executeServiceCallWithAsyncToken"); var responder = Ext.create("FlowMVC.mvc.service.rpc.Responder", success, failure, scope); var token = method.apply(service, args); token.addResponder(responder); return token; }, /** * Executes a service call that uses Promises. * * Note that the service call isn't passed in as a function that actually executes the service; it's passed * in via a reference to the service object, the actual service method, and the service method's parameters. * This is done to prevent the service call from being executed before the responder is being set on it. * * @param {Object} service Reference to the actual service. * @param {Function} method Reference to the method on the service object that executes the service call. * @param {Array} args Array of parameters used in the service call's method. * @param {Function} success Callback method for a successful service. * @param {Function} failure Callback method for a failed service. * @param {Object} scope The object that contains the success and failure callback methods. */ executeServiceCallWithPromises: function(service, method, args, success, failure, scope) { FlowMVC.mvc.controller.AbstractController.logger.debug("executeServiceCallWithPromises"); return method.apply(service, args).then({ success: success, failure: failure, scope: scope }).always(function() { // TODO: Consider adding in an additional method callback for this // Do something whether call succeeded or failed FlowMVC.mvc.controller.AbstractController.logger.debug("executeServiceCall: always do after promise"); }); }, /** * Simple helper method that is used to get a reference to a service class from it's fully qualified String * counterpart. * * @param {String/Object} className Either a string or an object with a value property containing the fully * qualified String name of a service class. * @return {Object/null} An instance of the service class or null. */ getService: function(className) { className = (className && className.value) ? className.value : className; FlowMVC.mvc.controller.AbstractController.logger.debug("getService: using: ", className); var clazz = Ext.ClassManager.get(className); return new clazz(); }, /** * @deprecated * * Sencha ExtJS and Touch access the reference to the application in the controller differently; in ExtJS, it's * this.application because it's not setup in the config object where getters/setters are automatically generated * whereas in Touch, it's this.getApplication(). This method aims to abstract that difference. *[ * @return Ext.app.Application} The Application instance this Controller is attached to. This is * automatically provided when using the MVC architecture so should rarely need to be set directly. */ getMVCApplication: function() { if(FlowMVC.mvc.controller.AbstractController.ROOT_APPLICATION == null) { // this is if you're using ExtJS // if (Ext.getVersion("extjs") && Ext.getVersion("core").isLessThan("4.2.0")) { if (Ext.getVersion("extjs")) { FlowMVC.mvc.controller.AbstractController.logger.warn("AbstractController.getMVCApplication: using 'this.application' because ExtJS 4.1 and below doesn't use a getter for the root application."); FlowMVC.mvc.controller.AbstractController.ROOT_APPLICATION = this.application; // this is if you're using Touch // } else if(Ext.getVersion('touch')) { } else { FlowMVC.mvc.controller.AbstractController.logger.info("AbstractController.getMVCApplication: using 'this.getApplication() because we're in Touch 2.x+'"); FlowMVC.mvc.controller.AbstractController.ROOT_APPLICATION = this.getApplication(); } } return FlowMVC.mvc.controller.AbstractController.ROOT_APPLICATION; } });