PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/src/framework/controller/Controller.as

https://github.com/talltyler/ASTRID
ActionScript | 442 lines | 309 code | 53 blank | 80 comment | 83 complexity | bedc30e1cbdd38ee4d936f8885825aef MD5 | raw file
  1. /* ,----,
  2. * ,/ .`|
  3. * ,---, .--.--. ,` .' :,-.----. ,---, ,---,
  4. * ' .' \ / / '. ; ; /\ / \ ,`--.' | .' .' `\
  5. * / ; '. | : /`. /.'___,/ ,' ; : \ | : :,---.' \
  6. * : : \ ; | |--` | : | | | .\ : : | '| | .`\ |
  7. * : | /\ \| : ;_ ; |.'; ; . : |: | | : |: : | ' |
  8. * | : ' ;. :\ \ `.`----' | | | | \ : ' ' ;| ' ' ; :
  9. * | | ;/ \ \`----. \ ' : ; | : . / | | |' | ; . |
  10. * ' : | \ \ ,'__ \ \ | | | ' ; | | \ ' : ;| | : | '
  11. * | | ' '--' / /`--' / ' : | | | ;\ \| | '' : | / ;
  12. * | : : '--'. / ; |.' : ' | \.'' : || | '` ,/
  13. * | | ,' `--'---' '---' : : :-' ; |.' ; : .'
  14. * `--'' | |.' '---' | ,.' Tyler
  15. * ActionScript tested rapid iterative dev `---' Copyright2010'---' Larson
  16. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  17. * This program is free software: you can redistribute it and/or modify
  18. * it under the terms of the GNU General Public License as published by
  19. * the Free Software Foundation, either version 3 of the License, or
  20. * (at your option) any later version.
  21. *
  22. * This program is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  25. * GNU Lesser General Public License for more details.
  26. * http://www.gnu.org/licenses
  27. */
  28. package framework.controller
  29. {
  30. import com.asual.swfaddress.SWFAddress;
  31. import com.asual.swfaddress.SWFAddressEvent;
  32. import flash.display.Sprite;
  33. import flash.events.Event;
  34. import flash.events.IOErrorEvent;
  35. import flash.events.EventDispatcher;
  36. import framework.cache.Cache;
  37. import framework.config.Boot;
  38. import framework.events.ControllerEvent;
  39. import framework.events.ViewEvent;
  40. import framework.net.Assets;
  41. import framework.net.Asset;
  42. import framework.view.RendererBase;
  43. import framework.data.UndoRedo;
  44. [Event(name='contextNotFound', type='flash.events.Event')]
  45. [Event(name='methodNotFound', type='flash.events.Event')]
  46. /**
  47. * Your applications trafic cop, the controller will manage calling
  48. * the applications contexts that are mapped though Routes.
  49. */
  50. public class Controller extends EventDispatcher
  51. {
  52. public static const TEMP_DISABLE:String = "tempDisable";
  53. public static const DEEPLINKING_ON:String = "deeplinkingOn";
  54. public static const DEEPLINKING_OFF:String = "deeplinkingOff";
  55. public static const CONTEXT_NOT_FOUND:String = "contextNotFound";
  56. public static const METHOD_NOT_FOUND:String = "methodNotFound";
  57. public static const DEFAULT_METHOD:String = "index";
  58. public var contexts:Object = {};
  59. public var viewBase:String = "assets/context/";
  60. public var viewExtension:String = ".html";
  61. public var currentRoute:Object;
  62. public var currentContext:String;
  63. public var currentMethod:String;
  64. public var currentParams:Object;
  65. public var currentViewData:Object;
  66. public var currentView:RendererBase;
  67. public var started:Boolean;
  68. // separators
  69. private static const PATH_SEPARATOR:String = "|";
  70. private static const SLASH:String = "/";
  71. private static const DOT:String = ".";
  72. private static const QUESTION:String = "?";
  73. private static const AND:String = "&";
  74. private static const EQUALS:String = "=";
  75. private static const COLON:String = ":";
  76. private var _assets:Assets;
  77. private var _routes:Routes;
  78. private var _container:Boot; // Container must be class that extends Sprite
  79. private var _deeplinking:String = DEEPLINKING_ON;
  80. private var _tracking:Function;
  81. private var _pathParts:Array;
  82. private var _pathPartIndex:int;
  83. private var _currentPathPart:int;
  84. private var _history:UndoRedo;
  85. /**
  86. * @param routes Routes
  87. * @param container Sprite
  88. * @param deeplinking String
  89. * @param tracking Function
  90. */
  91. public function Controller( container:Boot, routes:Routes, assets:Assets, deeplinking:String=DEEPLINKING_ON, tracking:Function=null ):void
  92. {
  93. super();
  94. _container = container;
  95. _routes = routes;
  96. _deeplinking = deeplinking;
  97. _tracking = tracking;
  98. _assets = assets;
  99. _history = new UndoRedo();
  100. addEventListener( ControllerEvent.REDIRECT, redirectEvent );
  101. if( _container.parent == null ) {
  102. _container.addEventListener( Event.ADDED_TO_STAGE, start );
  103. }
  104. }
  105. public function get assets():Assets
  106. {
  107. return _assets;
  108. }
  109. public function get container():Boot
  110. {
  111. return _container;
  112. }
  113. public function set deeplinking(value:String):void
  114. {
  115. _deeplinking = value;
  116. }
  117. public function get deeplinking():String
  118. {
  119. return _deeplinking;
  120. }
  121. public function set tracking(value:Function):void
  122. {
  123. _tracking = value;
  124. }
  125. public function get tracking():Function
  126. {
  127. return _tracking;
  128. }
  129. public function add( ...classes ):void
  130. {
  131. for each( var clazz:Class in classes ) {
  132. var instance:ContextBase = new clazz();
  133. instance.setup( this );
  134. contexts[ instance.name ] = instance;
  135. }
  136. }
  137. /**
  138. * Start your application when everything is loaded
  139. * @param event Event, optional
  140. */
  141. public function start(event:Event=null):void
  142. {
  143. if( _deeplinking == DEEPLINKING_ON ){
  144. SWFAddress.addEventListener(SWFAddressEvent.CHANGE, onAddressChange);
  145. }else{
  146. redirectTo("");
  147. }
  148. }
  149. /**
  150. * @param path String
  151. * @param params Object
  152. */
  153. public function redirectTo(path:String, params:Object=null):void
  154. {
  155. if( path.indexOf(PATH_SEPARATOR) != -1 ){
  156. _pathParts = path.split(PATH_SEPARATOR);
  157. _pathPartIndex = 0;
  158. currentParams = params;
  159. nextPart();
  160. }else{
  161. redirectToPart(path,params);
  162. }
  163. _history.add( { path:path, params:params } );
  164. }
  165. /**
  166. * @private
  167. * @param path String
  168. * @param params Object
  169. */
  170. private function redirectToPart( path:String, params:Object=null ):void
  171. {
  172. currentContext = null;
  173. currentParams = params||{};
  174. if( path.charAt(0) == SLASH ) { // strip out leading slash
  175. path = path.substr(1);
  176. }
  177. var pathParts:Array = breakPathUp( path );
  178. var isMatch:Boolean;
  179. var chosenRoute:Object;
  180. var chosenContext:String;
  181. var chosenMethod:String;
  182. routesLoop : for each( var route:Object in _routes.cache ){
  183. var matchingParts:Array = [];
  184. var routeParts:Array = breakPathUp( route.name, true );
  185. var matchingContexts:Array = [];
  186. if( routeParts.toString() == "" && pathParts.toString() == "" ) {
  187. chosenRoute = route;
  188. chosenContext = route.options.context;
  189. chosenMethod = route.options.method;
  190. isMatch = true;
  191. break routesLoop;
  192. }else if( pathParts.toString() == "" ){
  193. continue;
  194. }
  195. pathPartLoop : for each( var pathPart:String in pathParts ){
  196. var partsCount:int = 0;
  197. routePartLoop: for each( var routePart:String in routeParts ){
  198. if( partsCount > pathParts.length ) {
  199. if( chosenContext != null ) {
  200. isMatch = true;
  201. break pathPartLoop;
  202. }
  203. }
  204. partsCount++
  205. var contextName:String;
  206. if( routePart.charAt(0) == COLON ){
  207. if( (routePart == ":context" || routePart == ":controller" ) && contexts.hasOwnProperty( pathPart ) != -1 ) {
  208. contextName = pathPart;
  209. matchingContexts.push( contextName );
  210. chosenContext = contextName;
  211. chosenRoute = route;
  212. }else if( routePart == ":method" || routePart == ":action" ){
  213. for each( contextName in matchingContexts ){
  214. if( contexts[contextName] && contexts[contextName].hasOwnProperty( pathPart.split(COLON).join("") ) ){
  215. chosenContext = contextName;
  216. chosenMethod = pathPart.split(COLON).join("");
  217. chosenRoute = route;
  218. isMatch = true;
  219. break routePartLoop;
  220. }
  221. }
  222. }else{
  223. currentParams[routePart.split(COLON).join("")] = pathPart;
  224. }
  225. }else if( pathPart == routePart ){
  226. matchingParts.push( pathPart );
  227. if( matchingParts.length == routeParts.length ) {
  228. contextName = route.options.context;
  229. chosenContext = contextName;
  230. chosenMethod = route.options.method;
  231. chosenRoute = route;
  232. isMatch = true;
  233. break routePartLoop;
  234. }
  235. }else{// Should be continue routesLoop; but this doesn't work in some version of the flash player
  236. isMatch = false;
  237. break routePartLoop;
  238. }
  239. }
  240. }
  241. if( isMatch ){
  242. break routesLoop;
  243. }
  244. }
  245. if( chosenRoute ) {
  246. for( var prop:String in chosenRoute.options ) {
  247. if( currentParams[prop] == null ){
  248. currentParams[prop] = chosenRoute.options[prop];
  249. }
  250. }
  251. }
  252. if( chosenContext == null ){
  253. contextName = "missing";
  254. chosenContext = contexts[contextName];
  255. // Debug.log( "MissingContext:", path );
  256. dispatchEvent( new Event( CONTEXT_NOT_FOUND ) );
  257. }
  258. currentContext = chosenContext;
  259. currentParams.context = currentContext;
  260. if( chosenMethod == null ){
  261. chosenMethod = DEFAULT_METHOD;
  262. // Debug.log( "MissingMethod:", path );
  263. dispatchEvent( new Event( METHOD_NOT_FOUND ) );
  264. }
  265. currentMethod = chosenMethod;
  266. currentParams.method = currentMethod;
  267. if( currentParams.content != null && contexts[currentContext] && contexts[currentContext][currentMethod] is Function ) {
  268. currentViewData = {data:currentParams.content};
  269. contexts[currentContext][currentMethod]( currentParams ); // call method on context
  270. started = true;
  271. }else if( currentParams.content == null ) {
  272. currentViewData = _assets.add( viewBase + currentContext + SLASH + currentMethod + viewExtension );
  273. currentViewData.addEventListener( Event.COMPLETE, onViewLoaded );
  274. currentViewData.addEventListener( IOErrorEvent.IO_ERROR, onViewNoFound );
  275. _assets.load();
  276. }
  277. currentRoute = chosenRoute;
  278. // trace( "redirect", path, contextName, currentMethod, currentParams );
  279. }
  280. public function back():void
  281. {
  282. var result:Object = _history.undo();
  283. if( result != null ) {
  284. redirectTo( result.path, result.params );
  285. }
  286. }
  287. public function forward():void
  288. {
  289. var result:Object = _history.redo();
  290. if( result != null ) {
  291. redirectTo( result.path, result.params );
  292. }
  293. }
  294. /**
  295. * @private
  296. * @param event Event
  297. */
  298. private function nextPart(event:Event=null):void
  299. {
  300. if( hasEventListener(ViewEvent.RENDERED) ){
  301. removeEventListener( ViewEvent.RENDERED, nextPart );
  302. _currentPathPart++;
  303. }
  304. if( _pathParts.length > _pathPartIndex && _pathParts[_pathPartIndex] != "" ) {
  305. redirectToPart( _pathParts[_pathPartIndex], currentParams );
  306. addEventListener( ViewEvent.RENDERED, nextPart );
  307. }
  308. }
  309. /**
  310. * @private
  311. * @param event Event
  312. */
  313. private function onAddressChange(event:Event):void
  314. {
  315. if( _deeplinking == DEEPLINKING_ON ) {
  316. redirectTo( SWFAddress.getValue() || "" );
  317. }else if( _deeplinking == TEMP_DISABLE ) {
  318. _deeplinking == DEEPLINKING_ON;
  319. }
  320. }
  321. /**
  322. * @private
  323. * @param event ControllerEvent
  324. */
  325. private function redirectEvent(event:ControllerEvent):void
  326. {
  327. redirectTo( event.path, event.params );
  328. }
  329. /**
  330. * @private
  331. * @param event Event
  332. */
  333. private function onViewLoaded( event:Event ):void
  334. {
  335. event.target.removeEventListener( "removed", onViewNoFound );
  336. event.target.removeEventListener( Event.COMPLETE, onViewLoaded );
  337. currentParams.content = event.target.data;
  338. if( contexts[currentContext] != null && contexts[currentContext][currentMethod] != null ) {
  339. contexts[currentContext][currentMethod]( currentParams ); // call method on context
  340. }else{
  341. trace("missing context", currentContext, currentMethod );
  342. }
  343. started = true;
  344. }
  345. /**
  346. * @private
  347. * @param event Event
  348. */
  349. private function onViewNoFound( event:Event ):void
  350. {
  351. event.target.removeEventListener( IOErrorEvent.IO_ERROR, onViewNoFound );
  352. event.target.removeEventListener( Event.COMPLETE, onViewLoaded );
  353. contexts[currentContext][currentMethod]( currentParams ); // call method on context
  354. }
  355. /**
  356. * @private
  357. * @param path String
  358. * @param temp Boolean
  359. * @return Array
  360. */
  361. private function breakPathUp( path:String, temp:Boolean=false ):Array
  362. {
  363. var parts:Array = path.split("?");
  364. var locationString:String = parts[0];
  365. var paramsString:String = parts[1];
  366. var result:Array = locationString.split( SLASH );
  367. var lastItem:String = result[ result.length - 1 ];
  368. if( lastItem == "" ) {
  369. result.pop();
  370. }
  371. var formatIndex:int = lastItem.indexOf( DOT );
  372. if( formatIndex != -1 ) {
  373. result[ result.length - 1 ] = lastItem.substr(0,formatIndex);
  374. result.push( DOT + lastItem.substr(formatIndex) );
  375. }
  376. if( paramsString != null && !temp ){
  377. var params:Array = paramsString.split(AND);
  378. for each( var param:String in params ) {
  379. var paramsParts:Array = param.split(EQUALS)
  380. currentParams[paramsParts[0]] = paramsParts[1];
  381. }
  382. }
  383. if(result.length == 0) {
  384. result.push("")
  385. }
  386. return result;
  387. }
  388. }
  389. }