DevSecOps

7 ago, 2008

Flash Remoting (AMF) no ActionScript 3.0

Publicidade
Pré-requisitos

Conhecer o método de trabalho do Flash, comunicando-se com uma base de dados através do AMF (Action Message Format) no ActionScript 3.0.

Programação Orientada a Objetos no Flash: Artigo de nível básico.

Objetivos

Executar ações como ler, gravar, alterar e excluir dados de um Banco de Dados.

Conteúdo

Primeiramente necessitamos de um banco de dados para teste (ACCESS, MySQL, SQLServer…), uma linguagem de servidor que trabalhe com o sistema AMF (ColdFusion, Java, .Net, PHP…), e claro, do próprio Flash utilizando ActionScript 3.0.

Lembrando que o assunto do artigo está voltado ao ActionScript 3, usando o AMF, assuntos como SQL e ColdFusion – os quais usarei neste artigo – não serão discutidos, mas o código também estará incluso no documento abaixo.

SQL para a criação da Tabela com seus respectivos campos:

CREATE TABLE GUESTBOOK(

ID INT NOT NULL AUTO_INCREMENT,

NOME VARCHAR(200) NOT NULL,

EMAIL VARCHAR(50) NOT NULL,

MENSAGEM TEXT,

PRIMARY KEY(ID)

) ENGINE=MYISAM

ColdFusion para acesso ao Banco de Dados:
<cfcomponent>

	<!-- RETORNA TODAS AS MENSAGENS -->

	<cffunction name="getMensagens" access="remote" returntype="query">
      <cfquery name="results" datasource="amf" dbtype="odbc">
	  	SELECT * FROM GUESTBOOK
	  	ORDER BY ID ASC</cfquery>
      <cfreturn results>
 	</cffunction>

	<!-- ADICIONA UMA MENSAGEM -->

	<cffunction name="setMensagem" access="remote" returntype="string" hint="Incluído com sucesso!">
      <cfargument name="nome" type="string" required="true">
      <cfargument name="email" type="string" required="true">
	  <cfargument name="mensagem" type="string" required="true">
        <cfquery datasource="amf" dbtype="odbc">
    		insert into GUESTBOOK(NOME,EMAIL,MENSAGEM)
	    	values('#nome#','#email#','#mensagem#')
		</cfquery>
		<cfset results="Incluído com sucesso!">
      <cfreturn results>
 	</cffunction>
	
    <!-- ALTERA UMA MENSAGEM -->
	
	<cffunction name="updateMensagem" access="remote" returntype="string">
	<cfargument name="nome" type="string" required="true">
	<cfargument name="email" type="string" required="true">
    <cfargument name="mensagem" type="string" required="true">
	<cfargument name="id" type="numeric" required="true">
		<cfquery datasource="amf" dbtype="odbc">
                UPDATE GUESTBOOK
                SET NOME='#nome#', EMAIL='#email#', MENSAGEM='#mensagem#'
                WHERE ID=#id#
        </cfquery>
		<cfreturn "Alterada com sucesso">
	</cffunction>
    
	<!-- EXCLUI UMA MENSAGEM -->
	
	<cffunction name="deleteMensagem" access="remote" returntype="string">
	<cfargument name="id" type="numeric" required="true">
		<cfquery datasource="amf" dbtype="odbc">
				DELETE
				FROM GUESTBOOK
				WHERE ID=#id#
		</cfquery>
		<cfreturn "Excluída com sucesso">
	</cffunction>
    
</cfcomponent>

Com os dados acima, basicamente manipularemos uma tabela de nome GUESTBOOK, que contém os campos ID, NOME, EMAIL e MENSAGEM.

Um arquivo feito em ASP seguirá em anexo, juntamente ao ColdFusion.

Ambos os arquivos acima estão na pasta wwwroot do servidor ColdFusion, dentro de uma pasta chamada amf, que eu criei.

O arquivo ColdFusion se chama serviços.cfc (componente ColdFusion).

Vamos agora ao Flash, nosso target neste artigo.

Primeiramente, o AMF pode retornar duas coisas quando fazemos um chamado. Um Erro ou um Resultado. Para isso criarei duas classes de eventos como vimos no artigo (https://imasters.com.br/artigo/8397/actionscript/disparando_um_evento_customizado/), uma delas será responsável pela informação do erro, caso ocorra, e outra pelos resultados do ColdFusion, como a query vinda do banco de dados ou uma informação simples, como, por exemplo, se o cadastro de um item foi feito com sucesso.

Classe responsável pelo status (erro, antiga onFault no ActionScript 2):

package {
	import flash.events.Event;
	
	public class StatusEvent extends Event {
		
		public static const STATUS = "status";
		public var status:Object;
		
		public function StatusEvent(type:String, bubbles = false, cancelable = false, status:Object = null):void {
			super(type, bubbles, cancelable);
			this.status = status;
		}
		
		public override function clone():Event {
			return new ResultEvent(type, false, false, status);
		}
		
		public override function toString():String {
			return formatToString("StatusEvent", "type", "bubbles", "cancelable", "eventPhase", "status");
		}
	}
}

Classe responsável pelo resultado (retorno do arquivo ColdFusion neste exemplo, antigo onResult no ActionScript 2):

package {
	import flash.events.Event;
	
	public class ResultEvent extends Event {
		
		public static const RESULT = "result";
		public var result:*;
		
		public function ResultEvent(type:String, bubbles = false, cancelable = false, result:* = null):void {
			super(type, bubbles, cancelable);
			this.result = result;
		}
		
		/**
		 * Creates and returns a copy of the current instance.
		 * @return A copy of the current instance.
		 */
		public override function clone():Event {
			return new ResultEvent(type, false, false, result);
		}
		
		/**
		 * Returns a String containing all the properties of the current
		 * instance.
		 * @return A string representation of the current instance.
		 */
		public override function toString():String {
			return formatToString("ResultEvent", "type", "bubbles", "cancelable", "eventPhase", "result");
		}
	}
}

Classe Responsável pela conexão:

package {
	import flash.net.NetConnection;
	import flash.net.ObjectEncoding;
	import flash.net.Responder;
	import flash.events.EventDispatcher;
	import flash.events.Event;
	
	public class RemoteConnector extends EventDispatcher{
		
		//responsável pela conexão com o banco
		private var conn:NetConnection;
		//responsável pelo resultado ou informação do erro ocorrido
		private var responder:Responder;
		
		//definimos um padrão de leitura para teste local
		public function RemoteConnector(uri:String = "http://localhost:8500/flashservices/gateway"):void {
			conn = new NetConnection();
			conn.objectEncoding = ObjectEncoding.AMF0;
			conn.connect(uri);
		}
		
		public function call(command:String, ... args):void {
			//chama a função onResult caso ocorra tudo bem ou onStatus caso ocorra um erro, o nome das funções é indiferente, desde que as mesmas sejam renomeadas abaixo
			responder = new Responder(onResult, onStatus);
			
			//chama um método co ColdFusion, seguindo pasta, arquivo e nome do método, exemplo: call("amf.servicos.getMensagens");
			conn.call(command, responder, args);
		}
		
		//dispara um ResultEvent para que possa ser reutilizado fora desta classe, já com a propriedade result com as informações do Banco de Dados
		private function onResult(result:*):void {
			dispatchEvent(new ResultEvent(ResultEvent.RESULT, false, false, result));
		}
		
		//dispara um StatucEvent para que possa ser reutilizado fora desta classe, já com a propriedade statuc com as informações do erro
		private function onStatus(status:Object):void {
			dispatchEvent(new StatusEvent(StatusEvent.STATUS, false, false, status));
		}
		
		public function get uri():String {
			return conn.uri;
		}
	}
}

Para testar o arquivo acima, usarei um .fla de teste, que contém o seguinte código no frame 1:

import com.leandroamano.remoting.RemoteConnector;
import com.leandroamano.events.ResultEvent;
import com.leandroamano.events.StatusEvent;

//RemoteConnector

var rc:RemoteConnector = new RemoteConnector();
rc.addEventListener(ResultEvent.RESULT, onResult);
rc.addEventListener(com.leandroamano.events.StatusEvent.STATUS, onStatus);
rc.call("amf.servicos.getMensagens");

trace(rc);

function onResult(e:ResultEvent):void {
	trace(e.result);
	for(var i in e.result.serverInfo){
		trace(i, ":", e.result.serverInfo[i]);
	}
	trace("\t", e);
}

function onStatus(e:com.leandroamano.events.StatusEvent):void {
	trace(e.status.description);
	trace("\t", e);
}

Também criei um .fla com o código no frame 1, sem utilizar as classes citadas acima (somente as do próprio Adobe Flash CS3) com alguns elementos visuais no palco [object MainTimeline], que podem ser observados nos arquivos que acompanham este artigo. O código segue abaixo:

import flash.net.NetConnection;
import flash.net.Responder;
import flash.net.ObjectEncoding;
import flash.display.DisplayObject;
import flash.display.MovieClip;
import flash.text.TextField;
import flash.text.TextFieldType;
import flash.text.TextFieldAutoSize;
import flash.geom.ColorTransform;
import flash.utils.setInterval;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.events.EventPhase;
import fl.data.DataProvider;
import fl.controls.DataGrid;

var selecionado:uint;
var intervalID:uint;
var count:uint;

var gridObj:DataGrid = getChildByName("grid") as DataGrid;

var nome_txt:TextField = getChildByName("nome") as TextField;
var email_txt:TextField = getChildByName("email") as TextField;
var mensagem_txt:TextField = getChildByName("mensagem") as TextField;
var resposta_txt:TextField = getChildByName("resposta") as TextField;

var insert_mc:MovieClip = getChildByName("inserir") as MovieClip;
var update_mc:MovieClip = getChildByName("alterar") as MovieClip;
var delete_mc:MovieClip = getChildByName("excluir") as MovieClip;

var conn:NetConnection = new NetConnection();
var responder:Responder = new Responder(onResult, onFault);
var responderUpdates:Responder = new Responder(onResultUpdates, onFault);

gridObj.addEventListener(Event.CHANGE, onChange);

resposta_txt.mouseEnabled = false;
resposta_txt.wordWrap = true;
resposta_txt.autoSize = TextFieldAutoSize.LEFT;

var initTexto:String = "Para alterar os valores, clique duas vezes sobre o campo de texto desejado";
intervalID = setInterval(displayMessage, 40, initTexto.toUpperCase(), resposta_txt);

for(var i:uint, t:TextField; i<numChildren; i++){
	if(getChildAt(i) is TextField && getChildAt(i) != resposta_txt){
		t = getChildAt(i) as TextField;
		t.doubleClickEnabled = true;
		t.restrict = "A-Z0-9\\@\\ \\-\\?\\.";
		t.maxChars = 50;
		t.autoSize = TextFieldAutoSize.LEFT;
		t.addEventListener(MouseEvent.DOUBLE_CLICK, onDoubleClick);
	}
}

conn.objectEncoding = ObjectEncoding.AMF0;
conn.connect("http://localhost:8500/flashservices/gateway");
conn.call("amf.servicos.getMensagens", responder);

setColor(0x009900, insert_mc);
insert_mc.buttonMode = true;
insert_mc.addEventListener(MouseEvent.CLICK, insertAMF);

setColor(0xFF6600, update_mc);
update_mc.buttonMode = true;
update_mc.addEventListener(MouseEvent.CLICK, updateAMF);

delete_mc.buttonMode = true;
delete_mc.addEventListener(MouseEvent.CLICK, deleteAMF);

stage.addEventListener(MouseEvent.CLICK, deselect);

function onResult(re:Object):void {
	var dados:Array = new Array();
	for(var l:uint; l<re.serverInfo.initialData.length; l++){
		dados.push({id:re.serverInfo.initialData[l][0],
					nome:re.serverInfo.initialData[l][1],
					email:re.serverInfo.initialData[l][2],
					mensagem:re.serverInfo.initialData[l][3]});
	}
	
	gridObj.dataProvider = new DataProvider(dados.sortOn("id", Array.DESCENDING));
	gridObj.columns = ["id", "nome", "email", "mensagem"];
	
	var colunas:Array = new Array("ID", "Nome", "E-mail", "Mensagem");
	for(var i:uint; i<re.serverInfo.columnNames.length; i++){
		gridObj.getColumnAt(i).headerText = colunas[i];
	}
	
	gridObj.selectedIndex = selecionado;
	gridObj.dispatchEvent(new Event(Event.CHANGE));
}

function onFault(fe:Object):void {
	intervalID = setInterval(displayMessage, 50, fe.description.toUpperCase(), resposta_txt);	
}

function onResultUpdates(re:String):void {
	intervalID = setInterval(displayMessage, 50, re.toUpperCase(), resposta_txt);
	conn.call("amf.servicos.getMensagens", responder);
}

function onChange(e:Event):void {
	var grid:DataGrid = e.currentTarget as DataGrid;
	selecionado = grid.selectedIndex;
	nome_txt.text = grid.selectedItem.nome.toUpperCase();
	email_txt.text = grid.selectedItem.email.toUpperCase();
	mensagem_txt.text = grid.selectedItem.mensagem.toUpperCase();
}

function onDoubleClick(e:MouseEvent):void {
	deselect(new MouseEvent(MouseEvent.CLICK));
	
	var texto:TextField = e.currentTarget as TextField;
	texto.type = TextFieldType.INPUT;
	texto.border = true;
	texto.background = true;
	texto.backgroundColor = 0x000000;
	texto.textColor = 0xFFFFFF;
	texto.selectable = true;
}

function deselect(e:MouseEvent):void {
	if(e.eventPhase == EventPhase.AT_TARGET){
		for(var i:uint, t:TextField; i<numChildren; i++){
			if(getChildAt(i) is TextField && getChildAt(i) != resposta_txt){
				t = getChildAt(i) as TextField;
				t.border = false;
				t.selectable = false;
				t.background = false;
				t.backgroundColor = 0xFFFFFF;
				t.textColor = 0x000000;
			}
		}
	}
}

function insertAMF(e:MouseEvent):void {
	deselect(new MouseEvent(MouseEvent.CLICK));
	
	var params:Object = new Object();
	params.nome = nome_txt.text;
	params.email = email_txt.text;
	params.mensagem = mensagem_txt.text;
	
	conn.call("amf.servicos.setMensagem", responderUpdates, params);
}

function updateAMF(e:MouseEvent):void {
	deselect(new MouseEvent(MouseEvent.CLICK));
	
	var params:Object = new Object();
	params.nome = nome_txt.text;
	params.email = email_txt.text;
	params.mensagem = mensagem_txt.text;
	params.id = gridObj.selectedItem.id;
	
	conn.call("amf.servicos.updateMensagem", responderUpdates, params);
}

function deleteAMF(e:MouseEvent):void {
	deselect(new MouseEvent(MouseEvent.CLICK));
	
	var params:Object = new Object();
	params.id = gridObj.selectedItem.id;
	
	conn.call("amf.servicos.deleteMensagem", responderUpdates, params);
	
	selecionado = 0;
}

function setColor(color:uint, object:DisplayObject):void {
	var cor:ColorTransform = new ColorTransform();
	cor.color = color;
	object.transform.colorTransform = cor;
}

function displayMessage(value:String, t:TextField):void {
	count = count == 0 ? count = 0 : count;
	if(count <= value.length){
		t.text = value.substr(0, count);
		count++;
	}else {
		count = 0;
		clearInterval(intervalID);
	}
}

Espero que esse artigo ajude quando o assunto for Flash com Banco de Dados, com grande volume de troca de dados.

Obrigado e até a próxima!

Download dos arquivos.