/* * Copyright 2008 (c) Lance Pollard * www.systemsofseven.com/blog * www.4ddesignlab.com * LanceJPollard@gmail.com * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package com.fourD.core { import com.asfusion.mate.core.EventMap; import flash.net.registerClassAlias; import flash.utils.Dictionary; import flash.utils.getQualifiedClassName; import mx.binding.utils.ChangeWatcher; import mx.events.FlexEvent; import mx.events.PropertyChangeEvent; /** * 1) Collect all XML of components using describeType into an array * 2) For each "extendsClass" in the XML for each component, slowly build * a map of the components' tree/hierarchy */ public class TectonMap extends EventMap { /** Tropons keeps a map of parent views to child views */ [Bindable] public var tropons:Array; public var tectonMap:Dictionary; public var tectonStringMap:Dictionary; /** map used in routing... the tropon and action in the url are turned into a tecton */ public var troponAndActionToTecton:Dictionary; /** the tectons that are listening for tectons to start and whatnot */ public var tectonReceptors:Dictionary; /** maps tectons to tropons (actions to objects in a system) */ public var tectonTroponMap:Dictionary; /** if you only have a string of the view, turn it into the actual view */ public var troponNameToTropon:Dictionary; /** creates a map or path of a currently active string of tectons */ public var tectonPathway:Dictionary; /** in case you are dynamically creating a new tecton, figure out which one from the tropon class */ public var troponClassToTecton:Dictionary; /** holds our tecton objects from XML (easily done in RestfulX) */ private var _tectons:Array; [Bindable] public function get tectons():Array { return _tectons; } public function set tectons(value:Array):void { _tectons = value; trace("TECTONS SET"); if (_tectons) { initializeMaps(); } } /** global dispatcher injected by the application */ private var _dispatcher:IEventDispatcher; [Bindable] public function get dispatcher():IEventDispatcher { return _dispatcher; } public function set dispatcher(value:IEventDispatcher):void { _dispatcher = value; if (_dispatcher) { attachHandlers(); } } public function TectonMap() { super(); trace("TECTONMAP CONSTRUCTOR"); troponAndActionToTecton = new Dictionary(true); tectonMap = new Dictionary(true); tectonReceptors = new Dictionary(true); tectonTroponMap = new Dictionary(true); troponNameToTropon = new Dictionary(true); tectonPathway = new Dictionary(true); troponClassToTecton = new Dictionary(true); dispatcher = this.getDispatcher(); } private function initializeMaps():void { var i:int=0, len:int = tectons.length; trace("INITIALIZE MAPS!"); // 1) create map of tectonReceptors to tectons // 2) create map of tropons (from the troponTree) to tectons var tecton:Tecton, tropon:*, tropons:Array, action:String, kanon:*; for (i; i < len; i++) { tecton = tectons[i]; var fqnTecton:String = getQualifiedClassName(tecton); registerClassAlias(fqnTecton.replace("::","."), Tecton); action = tecton.actionName; if (tecton.tropon) { trace("tecton exists"); tropon = tecton.tropon; if (!tropon is Class) { trace("tropon is displayObject"); trace(tropon); if (!tecton.kanon) { trace("kanon wasn't explicitly defined"); kanon = tropon.parent; } } else if (tropon is Class) { trace("tropon is class = " + tropon); if (!tecton.kanon) { trace("kanon doesn't exist for class"); //THROW ERROR } else { kanon = tecton.kanon; } } } else if (tecton.tropons) { tropons = tecton.tropons; } // doesn't matter if it's a Class or DisplayObject :) var fqnKanon:String = getQualifiedClassName(kanon); var fqnTropon:String; var j:int=0, lenj:int; trace(tropon); if (tropon != null) { trace("Tropon is not equal to null"); fqnTropon = getQualifiedClassName(tropon); if (tectonMap[fqnKanon] == undefined) { tectonMap[fqnKanon] = new Dictionary(true); trace("first dictionary"); } if (tectonMap[fqnKanon][fqnTropon] == undefined) { tectonMap[fqnKanon][fqnTropon] = new Dictionary(true); trace("second dictionary"); } if (action) { tectonMap[fqnKanon][fqnTropon][action] = tecton; trace("ALL OF IT: " + "\nKanon = " + fqnKanon + "\nTropon = " + fqnTropon + "\nAction = " + action); } } else if (tropons != null && tropon == null) { lenj = tropons.length; for (j; j < lenj; j++) { tropon = tropons[j]; fqnTropon = getQualifiedClassName(tropon); if (tectonMap[fqnKanon] == undefined) { tectonMap[fqnKanon] = new Dictionary(true); } if (tectonMap[fqnKanon][fqnTropon] == undefined) { tectonMap[fqnKanon][fqnTropon] = new Dictionary(true); } if (action) { tectonMap[fqnKanon][fqnTropon][action] = tecton; } } } if (tecton.receptors != null) { lenj = tecton.receptors.length; for (j; j < lenj; j++) { var receptor:Object = tecton.receptors[j]; if (tectonReceptors[tecton] == undefined) { tectonReceptors[tecton] = new Dictionary(true); } if (tectonReceptors[tecton][receptor.eventType] == undefined) { tectonReceptors[tecton][receptor.eventType] = new Array(); } tectonReceptors[tecton][receptor.eventType].push(receptor.tecton); // PheronEvent has a "type" of the tecton defined //receptor.tecton.addEventListener( } } /* lenj = tecton.tropons.length; for (j; j < lenj; j++) { trace("IN LOOP FOR TROPON AND ACTION"); tropon = tecton.tropons[i]; if (tecton.tropon == tropon) { if (troponAndActionToTecton[tropon] == undefined) { troponAndActionToTecton[tropon] = new Dictionary(true); }; // 2) create map of actions + tropons to tectons troponAndActionToTecton[tropon][action] = tecton; if (tectonTroponMap[tecton] == undefined) { tectonTroponMap[tecton] = new Array(); }; // 3) create a map of tectons to tropons tectonTroponMap[tecton].push(tropon); if (tectonTroponMap[tropon] == undefined) { tectonTroponMap[tropon] = new Array(); }; // 3) create a reverse map of tropons to tectons tectonTroponMap[tropon].push(tecton); } } */ } } private function attachHandlers():void { dispatcher.addEventListener(TectonEvent.INITIALIZE, routeTectonEvent); dispatcher.addEventListener(TectonEvent.START, routeTectonEvent); dispatcher.addEventListener(TectonEvent.COMPLETE, routeTectonEvent); dispatcher.addEventListener(PathwayEvent.TRAVERSE, routePathwayEvent); } private function routeTectonEvent(event:TectonEvent):void { var type:String = event.type; var tecton:Tecton = event.tecton; if (type == TectonEvent.INITIALIZE) { initialize(tecton); } else if (type == TectonEvent.START) { run(tecton); } else if (type == TectonEvent.COMPLETE) { complete(tecton); } } private function routePathwayEvent(event:PathwayEvent):void { trace("pathwayEvent"); var type:String = event.type; var url:Array = event.url; var i:int=0, len:int = url.length; var tropon:*, action:String, tecton:Tecton, kanon:*; var fqnTropon:String, fqnKanon:String; for (i; i < len; i++) { tropon = url[i].view; kanon = tropon.parent; fqnTropon = getQualifiedClassName(tropon); fqnKanon = getQualifiedClassName(kanon); trace("tropon = " + tropon); trace("kanon = " + kanon); action = url[i].action; trace("action = " + action); // somehow wait for the first one to be done, then the second... //tecton = ObjectUtil.copy(tectonMap[fqnKanon][fqnTropon][action]) as Tecton; tecton = TectonUtils.clone(tectonMap[fqnKanon][fqnTropon][action]) as Tecton; tecton.tropon = tropon; //tecton.tropon = tropon; trace("tectonClass = " + tecton); initialize(tecton); } } public function initialize(tecton:Tecton):void { // 1) check conditions to see if the current tecton should even be run, // then 2) dispatch pherons to allow the receptor tectons to execute and repeat the cycle... // if tecton is actually the tecton name... // 1) Find tecton if (!tecton) { return; } // throw error // 2) validate tecton conditions to see if it's viable var conditions:Array = tecton.conditions; if (conditions != null) { var value:Boolean = ConditionsUtil.validateConditions(conditions) if (!value) { return; } } // 3) tecton is now initialized trace("does tecton exist?? -> " + tecton); tecton.isInitialized = true; // 4) Find tectons that are listening for this tecton to initialize var receptorTectons:Array = findReceptorTectons(tecton, TectonEvent.INITIALIZE); trace("now receptors = "+ receptorTectons); if (receptorTectons != null) { // 5) Don't execute this tecton until the receptor tectons have initialized waitForTectonInitialized(receptorTectons, tecton); // 6) execute receptor tectons execute(receptorTectons); } else { execute(tecton); } } public function run(tecton:Tecton):void { trace("RUN TECTON"); trace(tecton.tropon); trace("parent of tecton is " + tecton.tropon.parent); trace("presenter = " + tecton.tropon.parent.presenter); var tropon:* = tecton.tropon; var params:Object = new Object(); var paramsArray:Array; if (tecton.actions) { var i:int=0, len:int = tecton.actions.length; for (i; i < len; i++) { var action:* = tecton.actions[i]; if (action is InstanceMethodInvoker) { trace("action is method invoker!"); action.ins = tropon.parent.presenter; action.run(); } } } /* if (tecton.actions is Array || !tecton.action is Function) { // tropon.tweens = tecton.action; params.when = "onStart"; params.fxn = tectonStarted; params.functionParams = [tecton]; params.when = "onComplete"; params.fxn = tectonCompleted; params.functionParams = [tecton]; if (tecton.actionParams) { if (tecton.actionParams is Array) { tecton.actionParams.push(params); } else { paramsArray = [params, tecton.actionParams]; } } else { paramsArray = [params]; } // tropon.tweenParams = paramsArray; } else if (tecton.action is Function) { // tropon.tweenFunction = tecton.action; // if (tecton.actionParams) { tropon.tweenFunctionParams = tecton.actionParams; } } (tropon as Object).tween(); tecton.isRunning = true; var receptorTectons:Array = findReceptorTectons(tecton, TectonEvent.RUN); if (receptorTectons) { execute(receptorTectons); } */ } protected function execute(tectons:*):void { if (tectons is Array) { var i:int=0, len:int = tectons.length; for (i; i < len; i++) { execute(tectons[i]); } } else { var tecton:Tecton = tectons; if (!tecton.isInitialized) { initialize(tecton); } if (!tecton.isRunning && tecton.isInitialized) { run(tecton); } else { return; } } } private function findReceptorTectons(tecton:*, phase:String):Array { var receptorTectons:Array; if (tectonReceptors[tecton] != undefined && tectonReceptors[tecton][phase] != undefined) { trace("RECEPTORS EXIST"); receptorTectons = tectonReceptors[tecton][phase]; } if (receptorTectons != null) { trace("recpecptors aren't null"); var i:int=0, len:int = receptorTectons.length; for (i; i < len; i++) { // map of child to parent tectonPathway[receptorTectons[i]] = tecton; } } trace("return receptors = " + receptorTectons); return receptorTectons; } public function complete(tecton:*, target:*=null):void { } private function waitForTectonInitialized(tectons:Array, callBackTecton:*):void { var i:int=0, len:int = tectons.length; for (i; i < len; i++) { var tecton:* = tectons[i]; ChangeWatcher.watch(tecton, "isInitialized", tectonInitialized); } } private function tectonInitialized(event:PropertyChangeEvent):void { // var tecton:* = event.source; // var property:* = event.property; var count:int = 0; var numTectons:int = 0; for each (var tecton:* in tectonPathway) { numTectons ++; if (tecton.isInitialized) { count++; } } if (count == numTectons) { var initializedTecton:* = tectonPathway[event.source]; run(initializedTecton); } } /** tectonStarted and tectonCompleted simply restart the initialization process */ private function tectonStarted(tecton:*):void { tecton.isComplete = false; tecton.isRunning = true; // dispatcher.dispatchEvent(new Pheron()); } private function tectonCompleted(tecton:*):void { tecton.isRunning = false; tecton.isComplete = true; // dispatcher.dispatchEvent(new Pheron()); } /* private function createTroponTreeMap():void { for each (var view:Class in views) { var fqn:String = getQualifiedClassName(view); var viewName:String = fqn.split("::")[1] as String; viewClasses[view] = fqn; viewClasses[fqn] = view; } // once we have set up the core data structures we need another pass to compute // the hierarchy of the components/views views.forEach(function(elm:Class, index:int, array:Array):void { extractMetadata(elm); }); } private function extractMetadata(viewClass:Class):void { var fqn:String = viewClasses[viewClass]; var view:UIComponent = viewInstances[viewClass]; var meta:XML = describeType(view); // extract superclasses for each (var node:XML in meta..accessor) { var declaredBy:String = node.@declaredBy; var name:String = node.@name; var child:UIComponent; // if the accessor was declared in one of your classes and it's a UIComponent... // if (viewClasses[declaredBy] && view[name] is UIComponent) { // child = view[name] as UIComponent; // mapChildToParent(troponMap, view, child); // } } } */ private function mapChildToParent(map:*, parent:String, child:*):void { if (map[parent] == undefined) map[parent] = []; map[parent].push(child); } /** Actions are defined by the parent, with a name and a target * The steps to running an action: * 1) Find the current action by name * 2) Find the target object by either the data object or the renderer * 3) If the action is already active, skip the whole process * 4) If the action isn't active, run other actions first to prepare the landscape * 5) After the preparation, run the action and set the action to Active * 6) After the action is run, dispatch an Event? */ /* public function action(action:*, callBackAction:*=null, delay:Number=NaN):void { if (this.actions == null || action == null) { return; } if (action is String) { var i:int=0, len:int = view.actions.length; for (i; i < len; i++) { // 1) Find action by name if (view.actions[i].name == action as String) { action = view.actions[i]; continue; } } } // 2) If action is already active, skip it if (action.isActive) { return; } if (!action.isPrepared) { beforeAction(action, callBackAction, delay); } // 3) If the action isn't yet prepared, skip it, and on the call back run straight through if (!action.isPrepared) { prepareForAction(action.preparationActions, action, delay); action.isPrepared = true; // jump out of the method so you can have time to execute other methods and then come back. return; } // 4) Figure out if the target is an index, renderer, or data item var view:UIComponent; if (target is int) { view = getRendererForItemAt(target as int); } else if (target is UIComponent) { view = target as UIComponent; } else { view = itemToRenderer[target]; } // there may be onCompleteParams and such here. These should all be executed in a single function, // in this class, call it executeTweenParams(params:Object); view.tweens = action.tweens; view.tween(); // blah blah blah afterAction(target, actionName, callBackTarget, callBackAction); } */ public function afterAction(action:*, callBackAction:*=null):void { if (action) { action(action, callBackAction); } } /** Tween functions to handle transitions between NavigationEvents */ protected function onStart(params:Object):void { executeParams(params); } protected function onComplete(params:Object=null):void { executeParams(params); } /** This is where you define what can be found in that params object */ protected function executeParams(p:Object=null):void { } /** * Handle view creation complete initialization * Override in subclasses * @param event creation complete event * */ protected function onCreationComplete(event:FlexEvent):void { } } }