200915
Mai

Aucun com

Cairngorm est un très simple framework MVC créé par Adobe Consulting pour le développement Flex. Il permet très rapidement de monter une structure MVC très claire mais s’arrête au Flex! Nous avons donc étendu ce concept MVC au serveur (l’exemple est l’implémentation en Php mais une portabilité dans d’autres langages est très facile).

De cette façon, notre application Flex peut executer des commands sur le client (le Flex) et également sur le serveur! Tout ça sans aucun code supplémentaire pour le développeur (merci eBuildy!).

Comment ça marche?

extendedcairngormschema

En fait chaque commande serveur doit avoir son homologue sur le client Flex, notre nouveau contrôleur qui étend le FrontController de Cairngorm va d’abord exécuter la commande cliente comme une commande normale puis si l’évènement n’a pas été annulé (appel de preventDefault()), le contrôleur utilise un service delegate pour envoyer l’évènement sur le serveur.

Le serveur alors récupère une requête HTTP, extrait les informations (type d’évènement et données) puis appel notre BackController qui va dispatcher l’évènement sur une commande serveur cette fois (implémentant notre interface serveur ICommand), la fonction execute de la commande doit renvoyer un résultat qui sera transmis au client.

Le client reçoit la réponse du serveur grâce au service delegate qui transmet la réponse à un responder qui à son tour va décoder la réponse selon le format souhaité (JSON,XML,AMF…) et rappeler notre commande Flex (fonction onResult).

Voila, un long voyage pour l’évènement! Mais rassurez vous, aucun code à écrire! Juste implémenter notre nouvelle interface IServerCommand au lieu de la classique ICommand de Cairngorm et étendez votre contrôler par notre ExtendedFrontController au lieu du classique FrontController de Cairngorm.

Pour notre démonstration, nous encodons le résultat avec JSON qui est compatible avec Flex (utilisation de la librairie as3core), libre à vous par la suite de choisir votre encodage (JSON,XML,AMF…).

L’interface IServerCommand

Les commandes Cairngorm doivent implémenter l’interface ICommand pour être exécutées par le contrôleur. Elles doivent également implémenter IResponder pour être appelées par un service delegate. L’interface suivante permet à notre nouveau contrôlleur de savoir si il doit dispatcher l’évènement au serveur ou non:

package com.ebuildy.extendedcairngorm.controller
{
	import com.adobe.cairngorm.commands.ICommand;
	import mx.rpc.IResponder;

	public interface IServerCommand extends ICommand,IResponder
	{
	}
}

Exemple d’implémentation sur une commande qui doit récupérer sur le serveur une liste de contacts:

package com.ebuildy.scd.controller
{
	import com.adobe.cairngorm.control.CairngormEvent;
	import com.ebuildy.extendedcairngorm.controller.IServerCommand;
	import com.ebuildy.scd.model.ContactVO;
	import com.ebuildy.scd.model.Model;

	import mx.controls.Alert;

	public class GetListContactsCommand implements IServerCommand
	{
		public function execute(event:CairngormEvent):void
		{
			if (Model.getInstance().listContacts.length > 0)
			{
				event.preventDefault();
			}
		}

		public function result(data:Object):void
		{
			for each (var item:Object in result)
			{
				var newContact:ContactVO = new ContactVO();

				newContact.name = item.name;

				Model.getInstance().listContacts.addItem(newContact);
			}
		}	

		public function fault(info:Object):void
		{
			Alert.show(info);
		}
	}
}

Cette commande Flex démontre toute la simplicité et la puissance de ExtendedCairngorm!

L’interface IServerControllerDelegate

Cette interface permet de spécifier le service delegate à utiliser pour dispatcher l’évènement sur le serveur:

package com.ebuildy.extendedcairngorm.service
{
 import mx.rpc.IResponder;

 public interface IServerControllerDelegate
 {
 function call(responder:IResponder, data:Object = null):void;
 }
}

Exemple d’implémentation: Nous fournissons cette librairie avec un service delegate par défault qui permet de décoder une réponse serveur faite en JSON:

package com.ebuildy.extendedcairngorm.service
{
	import com.adobe.serialization.json.JSONDecoder;

	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.URLLoader;
	import flash.net.URLRequest;
	import flash.net.URLRequestMethod;
	import flash.net.URLVariables;

	import mx.managers.CursorManager;
	import mx.rpc.IResponder;

	public class UrlLoaderDelegate implements IServerControllerDelegate
	{
		private var responder:IResponder;
		private var loader:URLLoader;
		public var url:String;

		public function UrlLoaderDelegate()
		{
			this.loader = new URLLoader();

			this.loader.addEventListener(IOErrorEvent.IO_ERROR, onIoError);
			this.loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecureError);
			this.loader.addEventListener(Event.COMPLETE, onComplete);
		}

		public function call(responder:IResponder,data:Object = null):void
		{
			this.responder = responder;

			var q:URLRequest = new URLRequest(this.url);

			q.method = URLRequestMethod.GET;
			q.data = new URLVariables();

			for (var p:String in data)
			{
				q.data[p] = data[p];
			}

			this.loader.load(q);

			CursorManager.setBusyCursor();
		}

		private function onComplete(e:Event):void
		{
			var dataObject:Object;

			try
			{
				dataObject = (new JSONDecoder(this.loader.data as String)).getValue();
			}
			catch (ex:*)
			{
				this.responder.fault("Impossible to parse data to JSON format!\n" + this.loader.data);
				this.clear();
				return ;
			}

			this.responder.result(dataObject);
			this.clear();
		}

		private function onIoError(e:IOErrorEvent):void
		{
			this.responder.fault(this.url + " not found!");
			this.clear();
		}

		private function onSecureError(e:SecurityError):void
		{
			this.responder.fault(this.url + " caused a security error!");
			this.clear();
		}

		private function clear():void
		{
			this.loader.removeEventListener(IOErrorEvent.IO_ERROR, onIoError);
			this.loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,onSecureError);
			this.loader.removeEventListener(Event.COMPLETE, onComplete);

			CursorManager.removeBusyCursor();

			this.loader.close();
			this.loader = null;
			this.url = null;
			this.responder = null;
		}

	}
}

Le nouveau contrôleur: ExtendedFrontController

Pour executer convenablement les commandes IServerCommand, nous avons du créer un nouveau Contrôleur en étendant celui de Cairngorm. Nous avons simplement overrider la fonction executeCommand:

package com.ebuildy.extendedcairngorm.controller
{
   import com.adobe.cairngorm.commands.ICommand;
   import com.adobe.cairngorm.control.CairngormEvent;
   import com.adobe.cairngorm.control.FrontController;
   import com.ebuildy.extendedcairngorm.service.IServerControllerDelegate;
   import com.ebuildy.extendedcairngorm.service.UrlLoaderDelegate;

   import mx.core.ClassFactory;
   import mx.rpc.IResponder;
   import mx.utils.ObjectUtil;

	public class ExtendedFrontController extends FrontController
	{
		/**
		 * Delegate used for the server communication
		 */
		[Inspectable(category="General")]
		public var serviceDelegate:ClassFactory;

      	/**
      	* URL of the Server Controller (if you select the default UrlLoaderDelegate)
      	*/
      	[Inspectable(category="General")]
     	public function set urlServerController(value:String):void
     	{
     		if (!serviceDelegate)
     		{
     			serviceDelegate = new ClassFactory(UrlLoaderDelegate);
     			serviceDelegate.properties = {'url':value};
     		}
     	}

     /**
      * Executes the command
      */
      override protected function executeCommand( event : CairngormEvent ) : void
      {
         var commandToInitialise : Class = getCommand( event.type );
         var commandToExecute:* = new commandToInitialise();

         ICommand(commandToExecute).execute(event);

         if (commandToExecute is IServerCommand && !event.isDefaultPrevented())
         {
         	var data:Object;

         	if (!event.data)
         	{
         		data = new Object();
         	}
         	else if (ObjectUtil.isSimple(event.data))
         	{
         		data["data"] = event.data;
         	}
         	else
         	{
         		data = ObjectUtil.copy(event.data);
         	}

         	data["__eventtype"] = event.type;

         	var commandDelegate:IServerControllerDelegate = this.serviceDelegate.newInstance();

         	commandDelegate.call(IResponder(commandToExecute),data);
         }
      }
   }
}

 

Le BackController du serveur

Nous devons à présent ajouter quelques codes sur notre serveur pour qu’il puisse comprendre que la requête HTTP reçue est un évènement avec un type et des données et qu’il doit en fonction du type inclure, instancier et exécuter la bonne commande Php.

Nous avons choisi d’implémenter seulement le pattern commande qui suffit dans notre cas.

Télécharger les sources Php

Tags: , , , ,