package com.exanimo.external { import adobe.utils.MMExecute; import flash.utils.describeType; /** * * Calls a jsfl function, passing zero or more arguments. If the function * is not available, the call returns null; otherwise it returns the value * provided by the function. * * @langversion ActionScript 3 * @playerversion Flash 9.0.0 * * @author Matthew Tretter * @since 2008.04.24 * */ public class JSFLInterface { // Create the JavaScript functions needed for serializing values. MMExecute('function __flash__toXML(value) { var type = typeof value; if (type == "string") { return "" + __flash__escapeXML(value) + ""; } else if (type == "undefined") { return ""; } else if (type == "number") { return "" + value + ""; } else if (value == null) { return ""; } else if (type == "boolean") { return value ? "" : ""; } else if (value instanceof Date) { return "" + value.getTime() + ""; } else if (value instanceof Array) { return __flash__arrayToXML(value); } else if (type == "object") { return __flash__objectToXML(value); } else { return ""; }}'); MMExecute('function __flash__escapeXML(s) { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/\'/g, "'");}'); MMExecute('function __flash__objectToXML(obj) { var s = ""; for (var prop in obj) { s += "" + __flash__toXML(obj[prop]) + "";} return s + "";}'); MMExecute('function __flash__arrayToXML(obj) { var s = ""; for (var i = 0; i < obj.length; i++) { s += "" + __flash__toXML(obj[i]) + ""; } return s + "";}'); // // public methods // /** * * Calls a JSFL function, passing zero or more arguments. If the * function is not available, the call returns null; otherwise it * returns the value provided by the function. * * @param functionName * The alphanumeric name of the jsfl function to call. * @param arguments * The arguments to pass to the jsfl function. You can specify zero * or more parameters, separating them with commas. They can be of * any ActionScript data type, but the ActionScript types are * automatically converted into JavaScript types. * @return * The response received from the jsfl function. If the call failed– * for example, if there is no such function or the interface is not * available– null is returned and an error is thrown. * * @see flash.external.ExternalInterface#call() * */ public static function call(functionName:String, ...arguments):* { var args:Array = []; if (arguments.length) { for each (var arg:* in arguments) { args.push(JSFLInterface._serialize(arg)); } } return JSFLInterface._deserialize(MMExecute('__flash__toXML(' + functionName + ' ? ' + functionName + '.apply(null, [' + args.join(',') + ']) : undefined)')); } // // private methods // /** * * Converts a serialized String from a JSFL function into an AS object. * * @param str * The serialized value. * @returns * The object that the provided String represents. * */ private static function _deserialize(str:String):* { return JSFLInterface._xml2Object(new XML(str)); } /** * * Serializes an object so that it can be sent to a jsfl function. * * @param obj * The object to serialize. * @returns * The serialized form of the provided object. * */ private static function _serialize(obj:*):String { var result:String; if (obj is String) { result = '"' + obj.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') + '"'; } else if (obj === null || obj === undefined || obj is int || obj is uint || obj is Number || obj is Boolean) { result = String(obj); } else { // Object and Array are not final classes so an "is" comparison isn't enough. var type:String = describeType(obj).@name; switch (type) { case 'Array': var values:Array = []; var i:int; for (i = 0; i < obj.length; i++) { values.push(JSFLInterface._serialize(obj[i])); } result = '[' + values.join(',') + ']'; break; case 'Object': var props:Array = []; for (var prop:String in obj) { props.push(JSFLInterface._serialize(prop) + ':' + JSFLInterface._serialize(obj[prop])); } result = '{' + props.join(',') + '}'; break; default: throw new Error('Objects of type ' + type + ' cannot be passed to JSFL'); break; } } return result; } /** * * Creates an object based on an XML description. * */ private static function _xml2Object(xml:XML):* { var obj:*; var property:XML; switch (xml.localName()) { case 'string': obj = xml.toString(); break; case 'undefined': obj = undefined; break; case 'number': obj = Number(xml.toString()); break; case 'null': obj = null; break; case 'true': obj = true; break; case 'false': obj = false; break; case 'date': obj = new Date(Number(xml.toString())); break; case 'object': obj = {}; for each (property in xml.property) { obj[property.@id] = JSFLInterface._xml2Object(property.*[0]); } break; case 'array': obj = []; for each (property in xml.property) { obj[property.@id] = JSFLInterface._xml2Object(property.*[0]); } break; } return obj; } } }