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 = "";}');
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;
}
}
}