Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

/**
        A wrapper around Flash 8's ExternalInterface; DojoExternalInterface is needed so that we
        can do a Flash 6 implementation of ExternalInterface, and be able
        to support having a single codebase that uses DojoExternalInterface
        across Flash versions rather than having two seperate source bases,
        where one uses ExternalInterface and the other uses DojoExternalInterface.
        
        DojoExternalInterface class does a variety of optimizations to bypass ExternalInterface's
        unbelievably bad performance so that we can have good performance
        on Safari; see the blog post
        http://codinginparadise.org/weblog/2006/02/how-to-speed-up-flash-8s.html
        for details.
        
        @author Brad Neuberg, bkn3@columbia.edu
*/
import flash.external.ExternalInterface;

class DojoExternalInterface{
        public static var available:Boolean;
        public static var dojoPath = "";
        
        private static var flashMethods:Array = new Array();
        private static var numArgs:Number;
        private static var argData:Array;
        private static var resultData = null;
        
        public static function initialize(){
                // extract the dojo base path
                DojoExternalInterface.dojoPath = DojoExternalInterface.getDojoPath();
                
                // see if we need to do an express install
                var install:ExpressInstall = new ExpressInstall();
                if(install.needsUpdate){
                        install.init();
                }
                
                // register our callback functions
                ExternalInterface.addCallback("startExec", DojoExternalInterface, startExec);
                ExternalInterface.addCallback("setNumberArguments", DojoExternalInterface,
                                                                                                                                        setNumberArguments);
                ExternalInterface.addCallback("chunkArgumentData", DojoExternalInterface,
                                                                                                                                        chunkArgumentData);
                ExternalInterface.addCallback("exec", DojoExternalInterface, exec);
                ExternalInterface.addCallback("getReturnLength", DojoExternalInterface,
                                                                                                                                        getReturnLength);
                ExternalInterface.addCallback("chunkReturnData", DojoExternalInterface,
                                                                                                                                        chunkReturnData);
                ExternalInterface.addCallback("endExec", DojoExternalInterface, endExec);
                
                // set whether communication is available
                DojoExternalInterface.available = ExternalInterface.available;
                DojoExternalInterface.call("loaded");
        }
        
        public static function addCallback(methodName:String, instance:Object, 
                                                                                                                                                 method:Function) : Boolean{
                // register DojoExternalInterface methodName with it's instance
                DojoExternalInterface.flashMethods[methodName] = instance;
                
                // tell JavaScript about DojoExternalInterface new method so we can create a proxy
                ExternalInterface.call("dojox.flash.comm._addExternalInterfaceCallback", 
                                                                                                         methodName);
                                                                                                         
                return true;
        }
        
        public static function call(methodName:String,
                                                                resultsCallback:Function) : Void{
                // we might have any number of optional arguments, so we have to 
                // pass them in dynamically; strip out the results callback
                var parameters = new Array();
                for(var i = 0; i < arguments.length; i++){
                        if(i != 1){ // skip the callback
                                parameters.push(arguments[i]);
                        }
                }
                
                var results = ExternalInterface.call.apply(ExternalInterface, parameters);
                
                // immediately give the results back, since ExternalInterface is
                // synchronous
                if(resultsCallback != null && typeof resultsCallback != "undefined"){
                        resultsCallback.call(null, results);
                }
        }
        
        /** 
                        Called by Flash to indicate to JavaScript that we are ready to have
                        our Flash functions called. Calling loaded()
                        will fire the dojox.flash.loaded() event, so that JavaScript can know that
                        Flash has finished loading and adding its callbacks, and can begin to
                        interact with the Flash file.
        */
        public static function loaded(){
                DojoExternalInterface.call("dojox.flash.loaded");
        }
        
        public static function startExec():Void{
                DojoExternalInterface.numArgs = null;
                DojoExternalInterface.argData = null;
                DojoExternalInterface.resultData = null;
        }
        
        public static function setNumberArguments(numArgs):Void{
                DojoExternalInterface.numArgs = numArgs;
                DojoExternalInterface.argData = new Array(DojoExternalInterface.numArgs);
        }
        
        public static function chunkArgumentData(value, argIndex:Number):Void{
                //getURL("javascript:console.debug('FLASH: chunkArgumentData, value="+value+", argIndex="+argIndex+"')");
                var currentValue = DojoExternalInterface.argData[argIndex];
                if(currentValue == null || typeof currentValue == "undefined"){
                        DojoExternalInterface.argData[argIndex] = value;
                }else{
                        DojoExternalInterface.argData[argIndex] += value;
                }
        }
        
        public static function exec(methodName):Void{
                // decode all of the arguments that were passed in
                for(var i = 0; i < DojoExternalInterface.argData.length; i++){
                        DojoExternalInterface.argData[i] = 
                                DojoExternalInterface.decodeData(DojoExternalInterface.argData[i]);
                }
                
                var instance = DojoExternalInterface.flashMethods[methodName];
                DojoExternalInterface.resultData = instance[methodName].apply(
                                                                                                                                                        instance, DojoExternalInterface.argData);
                // encode the result data
                DojoExternalInterface.resultData = 
                        DojoExternalInterface.encodeData(DojoExternalInterface.resultData);
                        
                //getURL("javascript:console.debug('FLASH: encoded result data="+DojoExternalInterface.resultData+"')");
        }
        
        public static function getReturnLength():Number{
         if(DojoExternalInterface.resultData == null || 
                                                typeof DojoExternalInterface.resultData == "undefined"){
                return 0;
         }
         var segments = Math.ceil(DojoExternalInterface.resultData.length / 1024);
         return segments;
        }
        
        public static function chunkReturnData(segment:Number):String{
                var numSegments = DojoExternalInterface.getReturnLength();
                var startCut = segment * 1024;
                var endCut = segment * 1024 + 1024;
                if(segment == (numSegments - 1)){
                        endCut = segment * 1024 + DojoExternalInterface.resultData.length;
                }
                        
                var piece = DojoExternalInterface.resultData.substring(startCut, endCut);
                
                //getURL("javascript:console.debug('FLASH: chunking return piece="+piece+"')");
                
                return piece;
        }
        
        public static function endExec():Void{
        }
        
        private static function decodeData(data):String{
                // we have to use custom encodings for certain characters when passing
                // them over; for example, passing a backslash over as //// from JavaScript
                // to Flash doesn't work
                data = DojoExternalInterface.replaceStr(data, "&custom_backslash;", "\\");
                
                data = DojoExternalInterface.replaceStr(data, "\\\'", "\'");
                data = DojoExternalInterface.replaceStr(data, "\\\"", "\"");
                
                return data;
        }
        
        private static function encodeData(data){
                //getURL("javascript:console.debug('inside flash, data before="+data+"')");

                // double encode all entity values, or they will be mis-decoded
                // by Flash when returned
                data = DojoExternalInterface.replaceStr(data, "&", "&amp;");
                
                // certain XMLish characters break Flash's wire serialization for
                // ExternalInterface; encode these into a custom encoding, rather than
                // the standard entity encoding, because otherwise we won't be able to
                // differentiate between our own encoding and any entity characters
                // that are being used in the string itself
                data = DojoExternalInterface.replaceStr(data, '<', '&custom_lt;');
                data = DojoExternalInterface.replaceStr(data, '>', '&custom_gt;');
                
                // encode control characters and JavaScript delimiters
                data = DojoExternalInterface.replaceStr(data, "\n", "\\n");
                data = DojoExternalInterface.replaceStr(data, "\r", "\\r");
                data = DojoExternalInterface.replaceStr(data, "\f", "\\f");
                data = DojoExternalInterface.replaceStr(data, "'", "\\'");
                data = DojoExternalInterface.replaceStr(data, '"', '\"');
                
                //getURL("javascript:console.debug('inside flash, data after="+data+"')");
                return data;
        }
        
        /** 
                        Flash ActionScript has no String.replace method or support for
                        Regular Expressions! We roll our own very simple one.
        */
        private static function replaceStr(inputStr:String, replaceThis:String, 
                                                                                                                                                 withThis:String):String {
                var splitStr = inputStr.split(replaceThis)
                inputStr = splitStr.join(withThis)
                return inputStr;
        }
        
        private static function getDojoPath(){
                var url = _root._url;
                var start = url.indexOf("baseRelativePath=") + "baseRelativePath=".length;
                var path = url.substring(start);
                var end = path.indexOf("&");
                if(end != -1){
                        path = path.substring(0, end);
                }
                return path;
        }
}

// vim:ts=4:noet:tw=0: