Ejemplo de API externa: Comunicación entre ActionScript y JavaScript en un navegador web

Flash Player 9 y posterior, Adobe AIR 1.0 y posterior

En esta aplicación de ejemplo se muestran técnicas apropiadas para establecer una comunicación entre ActionScript y JavaScript en un navegador web en el contexto de una aplicación de mensajería instantánea que permite a una persona charlar consigo misma (de ahí el nombre de la aplicación: Introvert IM). Los mensajes se envían entre un formulario HTML en la página web y una interfaz SWF mediante la API externa. Este ejemplo ilustra las siguientes técnicas:

  • Iniciación adecuada de la comunicación comprobando que el navegador está listo para ello antes de establecer la comunicación.

  • Comprobación de que el contenedor es compatible con la API externa.

  • Llamada a funciones de JavaScript desde ActionScript, paso de parámetros y recepción de valores como respuesta.

  • Preparación de métodos de ActionScript para que puedan ser llamados y ejecución de dichas llamadas.

Para obtener los archivos de la aplicación de este ejemplo, consulte www.adobe.com/go/learn_programmingAS3samples_flash_es. Los archivos de la aplicación Introvert IM se encuentran en la carpeta Samples/IntrovertIM_HTML. La aplicación consta de los siguientes archivos:

Archivo

Descripción

IntrovertIMApp.fla

o

IntrovertIMApp.mxml

El archivo de aplicación principal para Flash (FLA) o Flex (MXML).

com/example/programmingas3/introvertIM/IMManager.as

La clase que establece y gestiona la comunicación entre ActionScript y el contenedor.

com/example/programmingas3/introvertIM/IMMessageEvent.as

Tipo de evento personalizado distribuido por la clase IMManager cuando se recibe un mensaje del contenedor.

com/example/programmingas3/introvertIM/IMStatus.as

Enumeración cuyos valores representan los distintos estados de "disponibilidad" que se pueden seleccionar en la aplicación.

html-flash/IntrovertIMApp.html

o

html-template/index.template.html

La página HTML de la aplicación para Flash (html-flash/IntrovertIMApp.html) o la plantilla que se utiliza para crear la página HTML contenedora de la aplicación para Adobe Flex (html-template/index.template.html). Este archivo contiene todas las funciones de JavaScript que constituyen la parte contenedora de la aplicación.

Preparación de la comunicación ActionScript-navegador

Uno de los usos más frecuentes de la API externa es el de permitir a las aplicaciones ActionScript comunicarse con un navegador web. Gracias a la API externa, los métodos de ActionScript pueden llamar a código escrito en JavaScript y viceversa. A causa de la complejidad de los navegadores y al modo en el que representan internamente las páginas, no hay manera de garantizar que un documento SWF registrará sus funciones de repetición de llamada antes de que se ejecute el primer fragmento de código JavaScript de la página HTML. Por ese motivo, antes de llamar a las funciones del documento SWF desde JavaScript, es aconsejable que el documento SWF llame siempre a la página HTML para notificarle que está listo para aceptar conexiones.

Por ejemplo, mediante una serie de pasos efectuados por la clase IMManager, Introvert IM determina si el navegador está listo para la comunicación y prepara el archivo SWF para la comunicación. El último paso, que consiste en determinar cuándo está listo el navegador para la comunicación, ocurre en el constructor de IMManager del modo siguiente:

public function IMManager(initialStatus:IMStatus) 
{ 
    _status = initialStatus; 
 
    // Check if the container is able to use the external API. 
    if (ExternalInterface.available) 
    { 
        try 
        { 
            // This calls the isContainerReady() method, which in turn calls 
            // the container to see if Flash Player has loaded and the container 
            // is ready to receive calls from the SWF. 
            var containerReady:Boolean = isContainerReady(); 
            if (containerReady) 
            { 
                // If the container is ready, register the SWF's functions. 
                setupCallbacks(); 
            } 
            else 
            { 
                // If the container is not ready, set up a Timer to call the 
                // container at 100ms intervals. Once the container responds that 
                // it's ready, the timer will be stopped. 
                var readyTimer:Timer = new Timer(100); 
                readyTimer.addEventListener(TimerEvent.TIMER, timerHandler); 
                readyTimer.start(); 
            } 
        } 
        ... 
    } 
    else 
    { 
        trace("External interface is not available for this container."); 
    } 
}

En primer lugar, el código comprueba si la API externa está disponible en el contendor actual utilizando la propiedad ExternalInterface.available. En caso afirmativo, inicia el proceso de establecimiento de la comunicación. Dado que pueden producirse excepciones de seguridad y otros errores al intentar comunicarse con una aplicación externa, el código se escribe dentro de un bloque try (los bloques catch correspondientes se han omitido del listado en aras de la brevedad).

A continuación, el código llama al método isContainerReady(), que se muestra aquí:

private function isContainerReady():Boolean 
{ 
    var result:Boolean = ExternalInterface.call("isReady"); 
    return result; 
}

El método isContainerReady() usa a su vez el método ExternalInterface.call() para llamar a la función de JavaScript isReady() del modo siguiente:

<script language="JavaScript"> 
<!-- 
// ------- Private vars ------- 
var jsReady = false; 
... 
// ------- functions called by ActionScript ------- 
// called to check if the page has initialized and JavaScript is available 
function isReady() 
{ 
    return jsReady; 
} 
... 
// called by the onload event of the <body> tag 
function pageInit() 
{ 
    // Record that JavaScript is ready to go. 
    jsReady = true; 
} 
... 
//--> 
</script>

La función isReady() simplemente devuelve el valor de la variable jsReady . Inicialmente, la variable tiene el valor false; una vez que el evento onload de la página web se ha activado, el valor de la variable cambia a true. Dicho de otro modo, si ActionScript llama a la función isReady() antes de que se cargue la página, JavaScript devuelve false a ExternalInterface.call("isReady") y, por lo tanto, el método isContainerReady() de ActionScript devuelve false. Una vez que la página se ha cargado, la función isReady() de JavaScript devuelve true, de modo que el método isContainerReady() de ActionScript también devuelve true.

De vuelta en el constructor de IMManager ocurre una de las dos cosas siguientes, en función de la disponibilidad del contenedor. Si isContainerReady() devuelve true, el código simplemente llama al método setupCallbacks(), que completa el proceso de configuración de la comunicación con JavaScript. Por otra parte, si isContainerReady() devuelve false, el proceso se pone en espera. Se crea un objeto Timer y se le indica que llame al método timerHandler() cada 100 milisegundos del modo siguiente:

private function timerHandler(event:TimerEvent):void 
{ 
    // Check if the container is now ready. 
    var isReady:Boolean = isContainerReady(); 
    if (isReady) 
    { 
        // If the container has become ready, we don't need to check anymore, 
        // so stop the timer. 
        Timer(event.target).stop(); 
        // Set up the ActionScript methods that will be available to be 
        // called by the container. 
        setupCallbacks(); 
    } 
}

Cada vez que se llama al método timerHandler(), este vuelve a comprobar el resultado del método isContainerReady(). Una vez que se inicializa el contenedor, el método devuelve true. En ese momento, el código detiene el temporizador y llama al método setupCallbacks() para terminar el proceso de configuración de las comunicaciones con el navegador.

Exposición de los métodos de ActionScript a JavaScript

Como se muestra en el ejemplo anterior, una vez que el código determina que el navegador está listo, se llama al método setupCallbacks(). Este método prepara a ActionScript para recibir llamadas desde JavaScript tal y como se muestra a continuación:

private function setupCallbacks():void 
{ 
    // Register the SWF client functions with the container 
    ExternalInterface.addCallback("newMessage", newMessage); 
    ExternalInterface.addCallback("getStatus", getStatus); 
    // Notify the container that the SWF is ready to be called. 
    ExternalInterface.call("setSWFIsReady"); 
}

El método setCallBacks() finaliza la tarea de preparar la comunicación con el contenedor llamando a ExternalInterface.addCallback() para registrar los dos métodos que estarán disponibles para ser llamados desde JavaScript. En este código, el primer parámetro (el nombre por el que el método se conoce en JavaScript, "newMessage" y "getStatus") es el mismo que el nombre del método en ActionScript (en este caso no había ninguna ventaja en usar nombres distintos, así que se reutilizó el mismo nombre por simplificar). Por último, el método ExternalInterface.call() se utiliza para llamar a la función setSWFIsReady() de JavaScript, que notifica al contenedor que las funciones ActionScript se han registrado.

Comunicación desde ActionScript al navegador

La aplicación Introvert IM presenta toda una gama de ejemplos de llamadas a funciones JavaScript en la página contenedora. En el caso más simple (un ejemplo del método setupCallbacks()), se llama a la función setSWFIsReady() de JavaScript sin pasarle ningún parámetro ni recibir ningún valor devuelto:

ExternalInterface.call("setSWFIsReady");

En otro ejemplo del método isContainerReady(), ActionScript llama a la función isReady() y recibe un valor Boolean como respuesta:

var result:Boolean = ExternalInterface.call("isReady");

También se puede pasar parámetros a funciones de JavaScript a través de la API externa. Esto puede observarse, por ejemplo, en el método sendMessage() de la clase IMManager, al que se llama cuando el usuario envía un mensaje nuevo a su "interlocutor":

public function sendMessage(message:String):void 
{ 
    ExternalInterface.call("newMessage", message); 
}

De nuevo se utiliza ExternalInterface.call() para llamar a la función de JavaScript designada, que notifica al navegador que hay un nuevo mensaje. Además, el mensaje en sí se pasa como un parámetro adicional a ExternalInterface.call() y, por lo tanto, se pasa como un parámetro a la función de JavaScript newMessage().

Llamadas a código de ActionScript desde JavaScript

La comunicación suele ser bidireccional y, en este sentido, la aplicación Introvert IM no es ninguna excepción. No solo el cliente de mensajería instantánea de Flash Player llama a JavaScript para enviar mensajes, sino que también el formulario HTML llama al código JavaScript para enviar mensajes y recibir información del archivo SWF. Por ejemplo, cuando el archivo SWF notifica al contenedor que ha terminado de establecer contacto y que está listo para empezar a comunicarse, lo primero que hace el navegador es llamar al método getStatus() de la clase IMManager para recuperar el estado de disponibilidad inicial del usuario desde el cliente de mensajería instantánea de SWF. Esto se lleva a cabo en la página web, en la función updateStatus(), del modo siguiente:

<script language="JavaScript"> 
... 
function updateStatus() 
{ 
    if (swfReady) 
    { 
        var currentStatus = getSWF("IntrovertIMApp").getStatus(); 
        document.forms["imForm"].status.value = currentStatus; 
    } 
} 
... 
</script>

El código comprueba el valor de la variable swfReady, que determina si el archivo SWF ha notificado al navegador que este ha registrado sus métodos en la clase ExternalInterface. Si el archivo SWF está listo para recibir comunicaciones, la siguiente línea (var currentStatus = ...) llama al método getStatus() de la clase IMManager. En esta línea de código ocurren tres cosas:

  • Se llama a la función getSWF() de JavaScript, que devuelve una referencia al objeto JavaScript que representa al archivo SWF. El parámetro pasado a getSWF() determina qué objeto de navegador se devuelve en caso de que haya más un archivo SWF en una página HTML. El valor pasado a ese parámetro debe coincidir con el atributo id de la etiqueta object y el atributo name de la etiqueta embed utilizada para incluir el archivo SWF.

  • Se utiliza la referencia al archivo SWF para llamar al método getStatus() como si fuese un método del objeto SWF. En este caso, se utiliza el nombre de función "getStatus", ya que ese el nombre con el que se ha registrado la función de ActionScript mediante ExternalInterface.addCallback().

  • El método getStatus() de ActionScript devuelve un valor, que se asigna a la variable currentStatus, que se asigna a su vez como contenido (la propiedad value) del campo de texto status.

Nota: si sigue el código, probablemente se habrá dado cuenta de que en el código fuente de la función updateStatus(), la línea de código que llama a la función getSWF() en realidad se escribe del modo siguiente: var currentStatus = getSWF("${application}").getStatus(); El texto ${application} es un marcador de posición de la plantilla de la página HTML; cuando Adobe Flex Builder genera la página HTML real de la aplicación, este texto se sustituye por el texto utilizado como atributo id de la etiqueta object y como atributo name de la etiqueta embed (IntrovertIMApp en el ejemplo). Ese es el valor que espera la función getSWF().

En la función sendMessage() de JavaScript se muestra el modo de pasar un parámetro a una función de ActionScript sendMessage() es la función llamada cuando el usuario pulsa el botón Send de la página HTML).

<script language="JavaScript"> 
... 
function sendMessage(message) 
{ 
    if (swfReady) 
    { 
        ... 
        getSWF("IntrovertIMApp").newMessage(message); 
    } 
} 
... 
</script>

El método newMessage() de ActionScript espera un parámetro, de modo que la variable message de JavaScript se pasa a ActionScript usándola como parámetro en la llamada al método newMessage() en el código JavaScript.

Detección del tipo de navegador

A causa de las diferencias en el modo en el que los navegadores acceden al contenido, es importante usar siempre JavaScript para detectar qué navegador está utilizando el usuario y para acceder a la película de acuerdo con su sintaxis específica, empleando el objeto window o document, según se muestra en la función getSWF() de JavaScript en este ejemplo:

<script language="JavaScript"> 
... 
function getSWF(movieName) 
{ 
    if (navigator.appName.indexOf("Microsoft") != -1) 
    { 
        return window[movieName]; 
    } 
    else 
    { 
        return document[movieName]; 
    } 
} 
... 
</script>

Si el script no detecta el tipo de navegador del usuario, puede producirse un comportamiento inesperado al reproducir archivos SWF en un contenedor HTML.