Comunidad de diseño web y desarrollo en internet online

Tutorial de Flex 2 con WebOrb PHP y acceso a base de datos

En este tutorial aprenderemos desde cero a obtener datos desde una base de datos de MySQL y a representarlos en un DataGrid de Flex, donde se puedan modificar, eliminar e insertar nuevos registros, mediante una clase de PHP. Este tutorial es una adaptación del tutorial de bases de datos con flash y amfphp de Maikel, por ser extremadamente completo y además es de donde yo aprendí.

Primero instalaremos WebOrb, un sistema parecido a AMFPHP, que funciona con Flex, y nos permite acceder, de manera limitada, a los Flex Data Services, en concreto a los remoteObjects, instancias de una clase de un lenguaje de servidor cuyas propiedades y métodos se pueden llamar desde Flex. WebOrb tiene algunos bugs, pero resulta muy rápido trabajar con él (por ejemplo no hacen falta los methodTable de AMFPHP).

Después crearemos una base de datos desde phpmyadmin, agregaremos a WebOrb la clase misquerys de Maikel, y por último haremos una aplicación parecida a la suya en Flex.

Para poder realizar el tutorial es imprescindible conocer, al menos en parte, el funcionamiento de flex, php y las bases de datos. Es recomendable haber hecho algún tuto similar para flash, como el de Maikel, que mencioné arriba.

Para empezar necesitas un servidor que admita PHP 5 (no, con PHP4 no vale) y MySQL. Un programa para crear un servidor local que cumple estas características es wamp (pero no EasyPHP u otros) .

Además usaremos FlexBuilder2 para configurar el proyecto y obtener el swf final. Configurar el proyecto con algún otro framework es bastante complicado, pero siempre pueden probar FlashDevelop.

Instalación de WebOrb

Instalar WebOrb es muy sencillo. Además tiene la ventaja de que podemos tratar en ActionScript instancias de una clase de php con toda naturalidad, y sin tanto código como hace falta para AMFPHP.

Simplemente tenemos que descargar la última versión para php desde aquí: http://www.themidnightcoders.com/weborb/php/weborb-php-latest.zip

Ahora tenemos que descomprimir el archivo .zip que hemos conseguido en la raiz de nuestro sitio (la carpeta www donde tenemos nuestro index, vamos).

Ahora, para comprobar que todo funciona abrimos el archivo http://tusitio/Examples/FlexRemoting/main.html, que si trabajamos en local será http://localhost/Examples/FlexRemoting/main.html. Los 5 primeros test deberían funcionar sin problemas y el sexto una vez hayamos ejecutado el desde phpmyadmin (u otra cosa parecida) el archivo siguiente archivo: DIRECTORIOWEB\Services\weborb\tests\northwind.sql.

Aquí está el tutorial en inglés de la web oficial.

Preparación de la Base de Datos:

Crea una base de datos para el ejemplo (o usa la que tengas si es de alojamiento gratuito), en mi caso llamaré a la base de datos flexphp.

Crea una tabla con 3 campos, en mi caso le di el nombre de prueba, los campos deberán tener esta descripción:

Nombre
Tipo de Dato
Características adicionales
id
bigint
not null, auto_increment, clave primaria
nombre
text
null
comentarios
text
null

Nota: Pueden crear la base de datos ejecutando el archivo flexphp_bd/flexphp.sql de los archivos det tutorial.

Crear y agregar una clase de php a WebOrb

Bien, el primer paso es crear la clase de php. En la carpeta Services de nuestro directorio principal creamos un archivo php llamado "misquerys.php":

<?php

class misquerys{

        
        function insertar($nombre,$comentarios){
                $nombre = utf8_encode($nombre);
                $comentarios = utf8_encode($comentarios);
                $sql= "INSERT INTO prueba (id,nombre,comentarios) VALUES ('','".$nombre."','".$comentarios."')";
                return $this->query($sql,1); //retornamos el id de nuestro registro insertado

        }
        
        function eliminar ($id){
                $sql= "DELETE FROM prueba WHERE id='".$id."' LIMIT 1";
                return $this->query($sql,0);
        }
        
        function modificar ($id,$nombre,$comentarios){
                $nombre = utf8_encode($nombre);
                $comentarios = utf8_encode($comentarios);
                //creamos la cadena de nuestra sentencia(query) SQL

                $sql = "UPDATE `prueba` SET `nombre` ='".$nombre."', `comentarios` ='".$comentarios."' WHERE `id` =".$id." LIMIT 1;";
                return $this->query($sql,0);
        }
        
        function consulta (){
                $sql = "SELECT * FROM prueba ORDER BY id ";
                return $this->query($sql,0);
        }
        
        function query ($sql, $accion){
                $conex= mysql_connect("tuhost","user","password") or die("no se puede conectar porque ".mysql_error());
                mysql_select_db("flexphp");
                $result= mysql_query($sql,$conex);
                if ($accion && result){
                        $result= mysql_insert_id();
                }
                mysql_close($conex);
                return $result;
        }

}

?>

Explicación:

Una descripción detallada de cada método:

Nombre
Parámetros
Explicación
Valor de Retorno
insertar
$nombre, $comentarios Inserta los parámetros a los respectivos campos de la tabla, codificando con utf8_encode los parámetros para entender la codificación que hace flash con el escape para mandarle los parámetros y crea la respectiva sentencia SQL. Retorna el id del registro insertado en tal caso de que se haya hecho exitosamente.
eliminar
$id Crea la sentencia SQL DELETE con la condición de que id sea el que recibio. Retorna 1 si fue exitosa la operación o 0 en caso contrario.
modificar
$id, $nombre, $comentarios Crea la sentencia SQL UPDATE, buscando el registro cuyo campo id corresponda con $id y actualiza los campos nombre y comentarios con los respectivos parámetros. Retorna 1 si fue exitosa la operación o 0 en caso contrario.
consulta
ninguno Crea la sentencia SQL SELECT para seleccionar todos los registros de la tabla. Retorna un RecordSet con los registros.
query
$sql, $accion $sql será la sentencia SQL que ejecutará, $accion es para hacer una excepción de que si la sentencia es un INSERT.
  • Para consulta: retorna un recordSet.
  • Para eliminar y modificar: 1 o 0 en caso de que haya sido exitosa la operación.
  • Para insertar: hay una excepción donde el parámetro $accion valdrá 1 al invocar la función query y es para indicar que retornará el id del último registro insertado.

Noten que en el código, en la función query, donde pone "tuhost, "user" y "password" tendrán que sustituirlos por sus datos.

Ahora tenemos que decirle a WebOrb que este servicio existe:

Vamos a la carpeta principal>Weborb>WEB-INF>flex y abrimos el archivo remoting-config.xml. En él, al final del código, antes del último </service> añadimos lo siguiente

<destination id="misquerys">
       <properties>
       <source>misquerys</source>
       </properties>
       </destination>
       </service>

Configuración del proyecto de Flex

Vamos con Flex Builder:

File> New > Flex Project.

En el primer cuadro de diálogo How will your Flex application access data? seleccionamos Flex Data Services, y después Compile application locally in Flex Builder. Hacemos click en Next.

Después en el siguiente diálogo, en el cuadro "Root folder" tendremos que decir cuál es la ruta de la carpeta weborb, en la que está el servidor. La mía es: C:\wamp\www\Weborb . Y en el cuadro "Root URL" cuál será la URL del servidor, en este caso, http://localhost/Weborb . De nuevo, hacemos click en Next,

Ahora nos pide que demos un nombre al proyecto y la carpeta en la que se guardará. Cuando lo hayamos hecho, volvemos a pinchar en Next.

Allí, dejamos vacío el campo "Main source folder" y donde pone Output folder, ponemos la carpeta en la que deseamos que esté el archivo generado (tendrá que ser dentro del directorio web). Por ejemplo: C:\wamp\www\Examples. También tendremos que darle la ruta que tendrá cuando aparezca en el navegador al darle al botón de compilar, en el caso anterior sería esta: http://localhost/Examples.

De nuevo, esto pueden verlo en inglés en la ayuda de WebOrb.

Y por fin podemos empezar a programar mxml.

Creación de la aplicación de Flex.

Como es un poco difícil de separar por partes, pondré todo el código comentado y luego una explicación más general (y creo que es más rápido que andar explicando con el modo diseñador de Flex). Así que lean atentamente los comentarios (Texto en verde) del siguiente código:

<?xml version="1.0" encoding="utf-8"?>
<!--
Definimos la aplicación añadiendo una función creationComplete que realizará una consulta con el
remote que definiremos.
-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
    creationComplete="remoteObject.consulta()">


<!--



                ****************LAYOUT*****************



-->
<!--El componente Panel es un contenedor "bonito" en el que además podemos poner un título. Lo usaremos para contener toda la 
aplicación, y usando un tamaño relativo del 100% haremos que se adapte al espacio del que dispone, al igual que con el resto de 
los componentes-->
<mx:Panel title="Operaciones de Bases de Datos con Flex2" width="100%" x="0" height="100%" y="0">
    <!--El contenedor VBox fuerza a todos los componentes que tiene dentro a posicionarse uno debajo de otro. 
    Podemos usarlo para posicionar los componentes que iremos añadiendo.
    A este VBox le damos un nombre de instancia (id) ya que lo usaremos después para agregarle componenentes-->
    <mx:VBox x="0" y="0" width="100%" height="100%" horizontalAlign="center" id="vbox1">
        <!--El componente DataGrid será el encargado de mostrar la información de los registros. Haceoms que ocupe todo
        el espacio disponible (que además del tamaño de la ventana estará determinado por los componentes que tengamos en cada estado)
        Al seleccionar otro elemento, hacemos que cambie una variable, que después necesitaremos para seleccionar el datagrid.
        -->
        <mx:DataGrid width="100%" id="datagrid" height="100%" change="{dgIndex=datagrid.selectedIndex}">
            <mx:columns>
                <!--Colocamos 3 columnas, cuyo atributo dataField será el nombre de la propiedad
                del correspondiente item que querramos mostrar, aunque aquí más bien lo usaremos como apuntador, ya que definimos una función
                para mostrar los datos.
                 -->
                <mx:DataGridColumn headerText="ID" dataField="id" sortCompareFunction="numericSort"/>
                <mx:DataGridColumn headerText="Nombre" dataField="nombre" labelFunction="dataGridLabelFunction"/>
                <mx:DataGridColumn headerText="Comentarios" dataField="comentarios" labelFunction="dataGridLabelFunction"/>
            </mx:columns>
        </mx:DataGrid>
        <!--El contenedor Grid es semejante a una tabla de html. Aquí lo he usado para que los botones se distribuyan
        en función del espacio disponible.
         -->
        <mx:Grid width="100%" horizontalAlign="center" id="buttonsGrid">
            <mx:GridRow width="100%" height="100%" horizontalAlign="center" id="gridrow1">
                <mx:GridItem width="100%" height="100%" horizontalAlign="center">
                    <mx:Button label="Consultar" id="btnConsultar" click="onButtonPress(event);"/>
                </mx:GridItem>
                <mx:GridItem width="100%" height="100%" horizontalAlign="center">
                    <mx:Button label="Insertar" id="btnInsertar" click="onButtonPress(event);"/>
                </mx:GridItem>
                <mx:GridItem width="100%" height="100%" horizontalAlign="center">
                    <mx:Button label="Modificar" id="btnModificar" click="onButtonPress(event);"/>
                </mx:GridItem>
                <mx:GridItem width="100%" height="100%" horizontalAlign="center">
                    <mx:Button label="Eliminar" id="btnEliminar" click="onButtonPress(event);"/>
                </mx:GridItem>
            </mx:GridRow>
        </mx:Grid>
        <!--Un componente HBox hace lo mismo que un VBox pero en horizontal (los componentes se 
        alinean uno al lado de otro)-->
        <mx:HBox width="100%">
            <mx:Label text="Estado:"/>
            <mx:Label id="estadotxt" text="cargando..."/>
        </mx:HBox>
    </mx:VBox>
</mx:Panel>
<!--Los estados de Flex permiten alternar rápidamente entre diferentes conjuntos de componentes.
En ActionScript cambiamos de estado con la propiedad currentState="nombreEstado", y al estado 
inicial nos referimos con una cadena vacía ""-->
<mx:states>
    <mx:State name="commands">
        <!--Dentro de la etiqueta AddChild colocamos los componentes que incluiremos en el nuevo estado.
        además usamos el parámetro relativeTo, para referirnos al contenedor en el que colocaremos los componentes
        y position, para indicarle que será al final. De todas formas, si trabajas en modo diseño, esto lo hace el 
        FlexBuilder sólo-->
        <mx:AddChild relativeTo="{vbox1}" position="lastChild">
            <mx:VBox width="100%" horizontalAlign="center">
                <mx:HBox width="100%" horizontalAlign="center">
                    <mx:VBox>
                        <mx:Label text="ID" id="idlbl"/>
                        <mx:TextInput id="idtxt" editable="false"/>
                    </mx:VBox>
                    <mx:VBox>
                        <mx:Label text="Nombre" id="nombrelbl"/>
                        <mx:TextInput id="nombretxt"/>
                    </mx:VBox>
                    <mx:VBox>
                        <mx:Label text="Comentarios" id="comentarioslbl"/>
                        <mx:TextInput id="comentariostxt"/>
                    </mx:VBox>
                </mx:HBox>
                <mx:Label text="¿Desea insertar este registro?" id="preguntatxt"/>
                <mx:HBox width="100%" horizontalAlign="center">
                    <mx:Button label="Sí" id="btnSi" click="onButtonPress(event);"/>
                    <mx:Button label="No" id="btnNo" click="onButtonPress(event);"/>
                </mx:HBox>
            </mx:VBox>
        </mx:AddChild>
        <mx:SetProperty target="{datagrid}" name="enabled" value="false"/>
        <mx:RemoveChild target="{buttonsGrid}"/>
    </mx:State>
</mx:states>


<!--***************CÓDIGO*****************-->


<!--
Usaremos el remoteObject para acceder a nuestro servicio en php. En "destination" ponemos el 
id que hemos definido en remoting-config.xml. Ponemos el parámetro showBusyCursor en true para
que nos salga un relojito en el cursor mientras estamos esperando las operaciones.
-->
<mx:RemoteObject id="remoteObject" destination="misquerys" showBusyCursor="true">
    <!--
    Definimos listeners distintos result y el mismo de fault (error) para cada una de las funciones
    de la clase de php. Los listeners se invocarán cuando se complete la función y devuelva un 
    resultado, event.result.
    
    -->
    <mx:method name="consulta" result="consulta_Result(event)" fault="onFault(event)"/>
    <mx:method name="insertar" result="insertar_Result(event)" fault="onFault(event)"/>   
    <mx:method name="modificar" result="modificar_Result(event)" fault="onFault(event)"/>
    <mx:method name="eliminar" result="eliminar_Result(event)" fault="onFault(event)"/>
</mx:RemoteObject>
<!-- En la etiqueta Script irán todas las funciones que utilizaremos.-->
<mx:Script>
    <![CDATA[ 
    //Importamos la clase ArrayCollection, que usaremos en el DataProvider.
    import mx.collections.ArrayCollection;
    //Importamos la clase para trabajar con las comlumnas del dataGrid, que será necesaria para definir
    //función con la que rellenaremos el dataGrid
    import mx.controls.dataGridClasses.DataGridColumn;
    //Importamos las clases para trabajar con los remoteObjects de WebOrb
    import mx.rpc.remoting.*;
    import mx.controls.*;
    import mx.rpc.events.*
    //Importamos los detectores de eventos
    import flash.events.Event;
        //Una variable que necesitaremos para saber qué operación realizar
        private var action:Number;
        //
        //
        //
        //Esta variable servirá para definir el índuce en el que tendrá que estar el datagrid
        //alrealizar una consulta
        private var dgIndex:int=0;
        //
        //
        //Esta variable contiene el resultado de la petición. Su tipo de datos será
        // ArrayCollection, el recomendado para dataProviders.
        //El metatag Bindable hará que la variable se pase por referencia:
        //si ella cambia, las demás cambian.
        [Bindable]
        private var resultado:ArrayCollection
        //
        //
        //
        //Al ejecutarse una consulta...
        private function consulta_Result(event:ResultEvent):void{
            //rellenamos la variable resultado con los datos obtenidos en la consulta,
            //tratando de convertirlos en un Array usando el operador "as".
            resultado=new ArrayCollection(event.result as Array);
            //Rellenamos el dataGrid con los datos que hemos recibido
            datagrid.dataProvider=resultado;
            //Si hay al menos un registro...
            if (resultado[0]){
                estadotxt.text="Consulta completada";
                //Seleccionamos el primer elemento del datagrid.
                datagrid.selectedIndex=dgIndex;
                //Mostramos los botones de Eliminar y Modificar por si estaban deactivados
                btnModificar.enabled=btnEliminar.enabled=true;
            //Si no...
            }else{
                //Desactivamos los botones de Eliminar y Modificar
                btnModificar.enabled=btnEliminar.enabled=false;
                estadotxt.text="No hay registro que mostrar";
            }
        }
        //
        //
        //
        //El resultado de insertar un registro:
        private function insertar_Result(event:ResultEvent):void{
            //Si todo salió bien...
            if (event.result){
                estadotxt.text = "La inserción se realizó exitosamente"; 
                dgIndex=datagrid.dataProvider.length;
                //Volvemos a cargar los datos actulizados
                remoteObject.consulta();
            } else {
                estadotxt.text = "La inserción no fue completada, intente de nuevo";
            }
        }
        //Lo mismo para las funciones modificar y eliminar
        private function modificar_Result(event:ResultEvent):void{
            if (event.result){
                estadotxt.text = "La modificación se realizó exitosamente"; 
                remoteObject.consulta();
            } else {
                estadotxt.text = "La modificación no fue completada, intente de nuevo";
            }
        }
        private function eliminar_Result(event:ResultEvent):void{
            if (event.result){
                estadotxt.text = "El registro se eliminó correctamente"; 
                remoteObject.consulta();
            } else {
                estadotxt.text = "El registro no se eliminó, la operación falló";
            }
        }
        //
        //
        //
        //Muestra el error al no poder ejecutarse alguna función
        public static function onFault(event:FaultEvent):void 
    {
      Alert.show(event.fault.faultString, 'Error');
    }
    //
    //
    //
    //Esto pasará cada vez que se pusle un botón:
    private function onButtonPress(event:Event):void{
        //Según el botoón que hayamos pulsado...
        switch (event.target){ 
            case btnConsultar:
                remoteObject.consulta();
                estadotxt.text="Consultando..."
                break;
            case btnInsertar:
                //Usamos la variable action para saber a cuál de las acciones posibles nos referimos,
                //para evaluarlas después, al presionar el botón Si
                    action = 1;
                    //Nos ponemos en el estado donde están todos los menús
                currentState="commands";
                    //No necesitamos que se pueda insertar el id
                    idtxt.text="##";
                    //Borramos los otros campos de texto
                    nombretxt.text= "";
                    comentariostxt.text= "";
                    preguntatxt.text="¿Desea insertar este registro?";
                    nombretxt.editable=true;
                    comentariostxt.editable=true;
                    break;
                case btnModificar:
                    //Nos ponemos en el estado donde están todos los menús
                currentState="commands";
                    //Mostramos el elemento seleccionado en el DataGrid, con los carácteres codificados
                    //correctamente
                    idtxt.text= datagrid.selectedItem.id.toString();
                    nombretxt.text= unescape(datagrid.selectedItem.nombre);
                    comentariostxt.text= unescape(datagrid.selectedItem.comentarios);
                    action = 2;
                    preguntatxt.text="¿Desea modificar este registro?";
                    nombretxt.editable=true;
                    comentariostxt.editable=true;
                    break;
                case btnEliminar:
                    //Nos ponemos en el estado donde están todos los menús
                currentState="commands";
                    idtxt.text= datagrid.selectedItem.id.toString();
                    nombretxt.text= unescape(datagrid.selectedItem.nombre);
                    comentariostxt.text= unescape(datagrid.selectedItem.comentarios);
                    action = 3; 
                    preguntatxt.text="¿Desea eliminar este registro?";
                    nombretxt.editable=false;
                    comentariostxt.editable=false;
                    break;
                case btnSi:
                    switch (action){
                        //Si la acción es...
                        case 1:
                            //Realizamos la función php que hemos pedido.
                            remoteObject.insertar(escape(nombretxt.text), escape(comentariostxt.text));
                            break;
                        case 2:
                            remoteObject.modificar(idtxt.text,escape(nombretxt.text), escape(comentariostxt.text));
                            break;
                        case 3:
                            remoteObject.eliminar(idtxt.text);
                            break;
                    }
                    //Volvemos al estado principal
                    currentState="";
                    break;
                case btnNo:
                    currentState="";
                    break;
                    
        }
    }
    //Función para mostrar correctamente el DataGrid
    public function dataGridLabelFunction(item:Object, column:DataGridColumn):String{
        //Devuelve lo mismo, pero aplicando un unescape
        return unescape(item[column.dataField].toString());
    }
    //Función para ordenar los datos de la columna id de manera numérica, no alfabética:
    public function numericSort(a:*,b:*):int{
        var nA:Number=Number(a.id);
        var nB:Number=Number(b.id);
        if (nA<nB){
            return -1;
        }else if (nA>nB){
            return 1;
        }else {
            return 0;
        }
    } 
    ]]>
</mx:Script>
</mx:Application>

En el primer estado coloco un Panel, que ocupará todo el espacio disponible del swf (hago esto con el width=heigth=100%) y dentro un DataGrid que también intentará ocupar todo el espacio.

El DataGrid (datagrid) será quien muestre la información. Sus tres columnas, que muestran cada una la información del objeto del dataProvider que les indica el parámetro dataField, aunque aquí sirve más bien de indicador ya que las columnas tienen una labelFunction para mostrar la información decodificada (ya que si no hay problemas con los carácteres raros y es la solución mas fácil). La columna que muestra la propiedad id tiene una sortCompareFunction, para que al presionar sobre ella los datos se ordenen de forma numérica, no alfabética.

Debajo del DataGrid coloco un Grid de una fila, que permitirá mantener los botones que lleva dentro distribuidos según el espacio disponible.

Debajo hay una caja de texto que nos irá informando sobre el transcurso de las operaciones.

Los tres últimos paneles llevan al segundo estado, que añade tres cajas de texto y los botones para confirmar o cancelar la operación, elimina los botones principales y deshabilita el datagrid.

Según el botón que hayamos pulsado, las cajas de texto serán o no editables y mostrarán unos datos u otros. Al insertar un registro, no podremos usar el campo id, ni modificarlo al pulsar el botón modificar. Con el botón eliminar se verán los tres campos pero ninguno será editable.

Al pulsar el botón No simplemente volveremos al estado principal y al presionar el btnSi además realizaremos la operación solicitada con el Remote Object de php.

Conclusiones

Flex crea un nuevo mundo de posiblidades en las aplicaciones de Internet, permitiendo un manejo de datos potente y fácil y una rapidísima forma de montar proyectos que en Flash costarían mucho más tiempo (eso sí, una vez que te acostumbras a programar con él ;) ).

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Descargar Archivo

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate