Pas grand chose de neuf en ce moment, je passe beaucoup de temps en maintenance de code. Du coup, je vous propose deux petites classes qui me servent énormément au quotidien. Chaque développeur qui passe ses journées dessus doit avoir un équivalent je suppose: une classe de proxy, héritière du fameux Delegate, et une classe Interval, qui m'évite la permanente prise de tête avec les ids des setInterval...
mardi, août 22 2006
Proxy et Interval
Par -Alexandre LEGOUT aka LAlex- le mardi, août 22 2006, 14:02
vendredi, juin 9 2006
Number = parseInt
Par -Alexandre LEGOUT aka LAlex- le vendredi, juin 9 2006, 18:49
Number, en plus d'être une classe, est également une fonction, tout comme le sont Array, Boolean et String. Elle permet de convertir un objet quelconque en une instance de Number.
Seulement, ce qui n'est pas précisé dans la doc, c'est que cette fonction suit les mêmes règles que parseInt, sans pouvoir toutefois lui passer de second paramètre, qui précise dans quelle base doit se faire la conversion. Ce qui signifie que :
- Les chaines de caractères commencant par "0x" seront considérées comme héxadécimales
- Les chaines de caractères commencant par "0" seront considérées comme octales.
Et c'est sur ce dernier point que je me suis fait avoir, en cherchant un long moment pourquoi mon "062" devenait 50... :\ J'ai donc du remplacer toutes mes conversions :var num:Number = Number("062") // num = 50
// par
var num:Number = parseInt("062", 10) // num = 62
Donc voilà, si ca peut profiter à quelqu'un.. ![]()
mercredi, mai 10 2006
Constructeur et arguments
Par -Alexandre LEGOUT aka LAlex- le mercredi, mai 10 2006, 15:06
Lors qu'une classe hérite d'une autre, tout l'interêt est bien évidemment de ne pas réimplémenter les methodes de la classe mère. En effet, en l'absence d'une méthode portant le nom demandé, la méthode de la classe mère est appelée, transmettant ainsi tous les arguments. Eh bien ce qui est dommage, c'est que ce n'est pas le cas du constructeur... :\
En effet, si une classe mère prend un argument en paramètre, et qu'une classe fille hérite de cette dernière sans implémenter de constructeur, les paramètres passés au constructeur de la classe fille ne seront pas transmis.
La preuve par l'exemple: j'utilise actuellement un ensemble de managers gérant chacun des objets (Contacts, Articles, etc...) dont les classes implémentent toutes la même interface. Le but est donc de créer un manager "de base" (non instanciable) qui factorise les comportements communs... Chaque manager est lié à un contexte dans l'application, qui est passé en paramètre au constructeur du manager:
/* == Classes utilisées
Ceci est trés certainement du à la conversion en AS1. En effet, en AS1, déclarer une classe revient à déclarer son constructeur, et donc a relayer "manuellement" les paramètres du constructeur si besoin. Le "compilo" AS2 du coup crée automatiquement un constructeur dont le code est:
interface Item
class Contact implements Item
class Context
*/
// Classe mère
class BaseManager {
private var _context:Context;
private var _selectedItem:Item;
// Le constructeur prend un paramètre
private function BaseManager(ctx : Context) {
_context = ctx;
}
public function getContext() : Context {
return _context;
}
public function getSelectedItem() : Item {
return _selectedItem;
}
private /*protected*/ function setSelectedItem(i : Item) {
if (i != _selectedItem) {
_selectedItem = i;
dispatchEvent({type:"selectionChanged", selected:_selectedItem});
}
}
}
class ContactManager
extends BaseManager {
// Force le typage à 'Contact'
private function setSelectedItem(c : Contact) {
super.setSelectedItem(c);
}
public function getSelectedContact() : Contact {
return Contact(getSelectedItem());
}
}
// Création du contexte
var ctx:Context = new Context("mainApp");
// Création du ContactManager
var contactManager:ContactManager = new ContactManager(ctx);
// Affichage du contexte
trace(contactManager.getContext()); // undefined function ContactManager() {
alors que dans mon cas, il faut que je crée le constructeur moi-même:
super();
}function ContactManager(c : Context) {
super(c);
}
C'est peut-être déjà connu de beaucoup, mais moi j'ai cherché un p'tit moment quand-même... Bref, cela revient encore et toujours au problème d'appeler le constructeur d'une classe mère avec un nombre indéfini d'arguments (l'idéal étant avec apply)... :\
mercredi, février 15 2006
Switch/case original : tester la classe d'une variable
Par -Alexandre LEGOUT aka LAlex- le mercredi, février 15 2006, 14:19
Pour ceux qui ne lisent pas la mailing-list de OSFlash, Ralf Bokelberg y donne une petite astuce pour tester la classe d'une variable, et de manière plus globale comment éviter une succession de if...else if...else.
En fait, le switch...case a pour limitation de ne servir qu'à comparer une valeur à une autre (ou plusieurs autres), alors qu'un if va vérifier si une condition est vraie. Mais, et c'est là qu'est l'astuce, vérifier si une condition est vraie consiste uniquement à comparer son résultat à true... et voila!
Petit exemple:public static function getEnvironement(eng : Engine) : String {
var env:String = "Twilight zone";
switch(true) {
case (eng instanceof Boat):
env = "Water";
break;
case (eng instanceof Car):
case (eng instanceof Motorcycle):
case (eng instanceof Bus):
env = "Road";
break;
case (eng instanceof Plane):
env = "Sky";
break;
case (eng instanceof Spacecraft):
env = "Outerspace";
break;
default:
trace("ET???");
}
return env;
}
C'est peut-être connu, et certainement utilisable dans de nombreux langages, mais moi j'ai découvert aujourd'hui, et je trouve l'astuce bien élégante! ^^
jeudi, janvier 5 2006
Connecteur à la console SOS
Par -Alexandre LEGOUT aka LAlex- le jeudi, janvier 5 2006, 15:58
Le passage à FDT n'est pas toujours sans douleurs... Les habitudes évoluent, et la façon de coder varie inmanquablement. Un des problèmes auxquels je me suis trouvé confronté a été l'utilisation de la commande 'trace', que l'on peut bien évidemment remplacer par un appel de méthode via l'option -trace de MTASC... Comme j'en ai déjà parlé, j'utilise la console SOS, pour le côté pratique de la connexion socket que sont la connexion à une console sur une machine distante, ou la gestion d'une couleur de fond différente par application (TRES utile !!!)
Le moyen que j'utilise aujourd'hui afin d'éviter les problèmes de non-importation de la classe s'occupant de se connecter à la console consiste à créer un petit répertoire utilitaire contenant les classes utiles à FDT, et de l'inclure dans mes "Linked librairies", et d'en appeler une méthode start(). Pour l'instant, seule le connecteur à la console est présent, mais il se peut que d'autres suivent (notamment mon petit framework de profiling, a venir plus tard :P)
Bref, voici ma classe SOSTracer, utilisée par MTASC avec l'option -trace SOSTracer.out :
/**
Le but est ici d'être indépendant de tout autre package, d'où l'utilisation d'une classe XMLSocket "de base" (j'en ai implémenté une plus évoluée), et l'utilisation d'un Delegate "à la main"
* @author LAlex
* @version 0.1
* @since 2006-01-05
*/
class SOSTracer {
// Console infos
public static var SOS_HOST:String = "localhost";
public static var SOS_PORT:Number = 4444;
// Used for static method 'out'
private static var __instance:SOSTracer;
// Used for line number display
private static var __lineNumber:Number = 1;
// Allow to display line numbers
public static var enableLineNumber:Boolean = false;
// Static function, used for MTASC -trace option
// SOSTracer.start() must be called before
public static function out(o) {
__instance.trace(o);
}
// Launch the static SOSTracer with
// given background color
public static function start(col:Number):Boolean {
__instance = new SOSTracer(col);
return true;
}
private var _bufferize:Boolean = true;
private var _socket: XMLSocket;
private var _isConnected:Boolean = false;
private var _color:Number;
// For later ;oP
// private var _itv:Number = -1;
// Buffer
private var _stack: Array;
function SOSTracer(col:Number) {
if (col != undefined) {
_color = col;
}
_stack = [];
_socket = new XMLSocket();
_socket.onConnect = function (suc) {
var tr = arguments.callee.tracer;
tr._connected(suc);
};
_socket.onConnect.tracer = this;
_socket.connect(SOS_HOST, SOS_PORT);
}
// Launched on socket connection
private function _connected(sc:Boolean) {
if (sc) {
if (_color != undefined) {
_socket.send("!SOS<appColor>" + _color + "</appColor>");
}
_flush();
_isConnected = true;
} else {
_bufferize = false;
throw new Error("SOS Socket connection failed");
}
}
// Flush buffer
private function _flush() {
while(_stack.length) _socket.send(_stack.shift());
}
// Display a string (or object representation)
public function trace(o) {
var no:String = (enableLineNumber ? "[" + __lineNumber++ + "] " : "") + o.toString();
if (_isConnected) {
_socket.send(no);
} else if (_bufferize) {
_stack.push(no);
}
}
}
Afin d'éviter de surcharger la connexion, je prévois ensuite d'utiliser un système de bufferisation éventuel, au moyen du buffer utilisé avant la connexion, et d'un setInterval (vous avez surement remarqué que la propriété pour l'intervalle est déjà prévue, mais pas utilisée encore :$).
Pour l'utiliser, rien de plus simple, je lance un start() dans mon main et j'utilise trace() dans mon code :
public static function main() {
SOSTracer.start(0XFFFFCC); // Couleur de fond dans la console
}
Pour le déploiement, il suffit de changer dans MTASC l'option -trace SOSTracer.out par -trace no, et de commenter la ligne SOSTracer.start() (pas de import à commenter, du fait de l'absence de package 8))
jeudi, décembre 22 2005
Simuler une énumération et/ou des contantes
Par -Alexandre LEGOUT aka LAlex- le jeudi, décembre 22 2005, 14:12
Il peut être trés pratique, voire parfois indispensable pour obtenir un code "clean", de ne pouvoir passer une valeur à une méthode, qui doit obligatoirement être une des valeurs "possibles"... C'est ce que l'on appelle des énumérations. Java gère cela nativement grâce à la classe Enum.
De même, avoir la possibilité de créer des constantes est également bien utile. Dans la pratique, il s'agit en fait de créer une propriété statique en lecture seule. Le moyen le plus sûr de faire cela serait de créer une variable statique privée, et un getter statique public.
Voici ce que cela pourrait donner dans le cadre d'une classe d'encryptage:
class Crypter {
Bien que tout cela soit quand-même assez clean, cette implémentation pose quelques problèmes:
// Private static members
private static var _NONE:String = "none";
private static var _MD5:String = "md5";
private static var _SHA1:String = "sha1";
// Public constants
public static function get NONE():String {
return _NONE;
}
public static function get MD5():String {
return _MD5;
}
public static function get SHA1():String {
return _SHA1;
}
private var _cryptMode:String;
function Crypter(mode:String) {
_cryptMode = mode;
}
function encrypt(s:String):String {
// Case use private vars, to avoid decreasing
// performances by using a getter
switch(_cryptMode) {
case _NONE:
return s;
case _MD5:
var md5crypted:String = "";
// Perform MD5 cryptage
return md5crypted;
case _SHA1:
var sha1crypted:String = "";
// Perform SHA1 cryptage
return sha1crypted;
}
}
}
// Usage
var myCrypter:Crypter = new Crypter(Crypter.MD5);
trace(myCrypter.encrypt("String to be encrypted"));
- Tout accés aux constantes (de l'exterieur) fait appel à un getter, et donc les performances peuvent s'en ressentir, selon la fréquence de ces accés
- Il est possible de passer au constructeur une chaine de caractères qui ne fasse pas partie des valeurs attendues en entrée... Cela peut se résoudre avec un default: dans le case, mais ce n'est pas vraiment une solution satisfaisante...
// Enum
class CryptMode
extends String {
public static var NONE:CryptMode = new CryptMode("none");
public static var MD5:CryptMode = new CryptMode("md5");
public static var SHA1:CryptMode = new CryptMode("sha1");
private function CryptMode(content:String) {
super(content);
}
}
class Crypter {
private var _cryptMode:CryptMode;
function Crypter(mode:CryptMode) {
_cryptMode = mode;
}
function encrypt(s:String):String {
// Case use private vars, to avoid decreasing
// performances by using a getter
switch(_cryptMode) {
case CryptMode.NONE:
return s;
case CryptMode.MD5:
var md5crypted:String = "";
// Perform MD5
return md5crypted;
case CryptMode.SHA1:
var sha1crypted:String = "";
// Perform SHA1
return sha1crypted;
}
}
}
// Usage
var myCrypter:Crypter = new Crypter(CryptMode.MD5);
trace(myCrypter.encrypt("String to be encrypted"));
Cette méthode à tous les avantages:- Impossible de rajouter des constantes non-prévues au runtime, du fait du constructeur privé.
- Toujours grâce au constructeur privé, impossible de modifier la valeur d'une constante, bien qu'il s'agisse d'une propriété publique.
- Impossible de passer une valeur non prévue à une méthode, si l'argument est typé avec l'énumération.
- Une compatibilité avec les types de bases. Bien que l'exemple se base sur un héritage de String, il est bien sûr possible de l'utiliser sur n'importe quel type "de base" (Number par exemple) comme sur des types plus complexes. Le '==' continue de fonctionner par exemple :
trace(CryptMode.MD5 == "md5"); // true
- Petit bémol, le switch...case semble utiliser une comparaison stricte (===), donc ne pas oublier de faire un toString() ou valueOf() dans ce cas...

- Remplace presque parfaitement le fait de ne pas pouvoir créer de constantes dans les interfaces, offrant ainsi la possibilité de disposer d'une série de valeurs dans la création d'une API "pure" (sans aucune implémentation).
Bref, je suis assez content d'avoir trouvé ce moyen de rendre mes codes un peu plus clean. J'utilise dorénavant énormément cette astuce, notamment concernant la diffusion d'évenements, en créant une classe collection contenant les évenements diffusables par une classe définie, puis en utilisant le typage dans la signature de mes addEventListener (en parlant de ça, bientôt un tool de diffusion d'évenements maison).
Wala, wala! ^^
vendredi, décembre 9 2005
Bug du compilateur Flash 8 (...et moins?)
Par -Alexandre LEGOUT aka LAlex- le vendredi, décembre 9 2005, 17:02
Le compilateur de Flash 8 semble contenir un bug qui, s'il est mineur, peut provoquer quelques arrachages de cheveux. En fait, lorsque l'on hérite d'une classe en précisant son chemin complet (donc sans import), le compilo va la considérer comme importée, mais ce n'est pas le cas à l'éxecution. La où l'on s'attend donc à obtenir une erreur de compilation, tout se passe bien, et on comprend donc mal où est l'erreur... :\
Petit exemple :class com.lalex.myappli.CustomSocket extends com.lalex.net.ExtendedSocket {
function CustomSocket() {
/**
* ExtendedSocket.CONNECT_FAILED sera 'undefined'
* car on a pas importé com.lalex.net.ExtendedSocket
* Pourtant, le compilo laisse passer
*/
addEventListener(ExtendedSocket.CONNECT_FAILED, this, handleConnectionFail);
}
private function handleConnectionFail(o) {
trace("Connection failed");
}
}
Bon, je vous l'accorde, on pourrait dire "Ben il n'avait qu'à importer la classe", ou "Il n'avait qu'à donner le chemin complet de la classe!"... mais le compilateur n'est-il pas là pour nous signaler ce genre d'oublis? :o
mardi, novembre 15 2005
Typage :Object, le retour
Par -Alexandre LEGOUT aka LAlex- le mardi, novembre 15 2005, 17:03
Eh oui, ce détail anodin du compilateur qui nous empêchait de typer avec Object est enfin résolu dans Flash 8. Je trouve ca bien, ca permet de spécifier explicitement que n'importe quel objet peut être transmis en paramètre ou affecté à une variable, et cela fait aussi bien la différence aussi entre une méthode qui ne retourne rien (Void) et un objet quelconque (Object)... ![]()
PS: Je suis en plein dans les entrailles de la communication temps réel actuellement: Flash Media Server 2 et connexions XMLSocket... Je devrais avoir de la matière sur ce blog prochainement! ![]()
lundi, septembre 26 2005
FileLink : Téléchargement de fichier et menu contextuel
Par -Alexandre LEGOUT aka LAlex- le lundi, septembre 26 2005, 11:51
En tant qu'utilisateur, je trouve parfois assez exaspérant de devoir ouvrir, par exemple, un fichier PDF dans mon navigateur. Je préfère largement le télécharger et l'ouvrir avec Acrobat Reader...
Lorsqu'il s'agit d'un lien dans une page HTML, il est facile d'utiliser un clic droit (ou CTRL pour les maceux) pour pouvoir choisir entre l'ouvrir réellement ou le télécharger. Par contre, dans un site Flash c'est plus délicat...
C'es pourquoi j'ai créé vite-fait une petite classe qui permet d'assigner à un clip un menu contextuel qui va permettre soit d'ouvrir, soit de télécharger un fichier déterminé. Elle est basique pour l'instant, mais l'on peut prévoir de la faire évoluer avec quelques options (localisation, cible du getURL, comportement par défaut du onRelease, etc...).
A noter que le téléchargement ne fonctionne que dans un environnement HTTP (cela est du aux restrictions de la classe FileReference), donc on oublie le test dans l'IDE Flash ou dans une page HTML accédée directement depuis le disque dur... ![]()
import flash.net.FileReference;
Et voici son utilisation (celle du bouton ci-dessous)
import mx.utils.Delegate;
/**
* FileLink
*
* @notice Can only be used in HTTP environment due to FileReference restrictions
* @author <a href="http://www.lalex.com/">LAlex</a>
* @version 1.0
* @since
*/
class com.lalex.utils.FileLink {
// File path
private var _path:String;
function FileLink(f:String) {
_path = f;
}
/**
* Assign a link to a movieclip, setting its context menu
*
* @usage fLink.assignTo(myLinkButton, true, true);
* @param clip MovieClip to click to obtain download menu
* @param setRelease Set the onRelease handler of the clip ?
* @param hide Hide context menu builtin items ?
*/
function assignTo(clip:MovieClip, setRelease:Boolean, hide:Boolean) {
hide = !!hide;
setRelease = !!setRelease;
var dl:Function = Delegate.create(this, this.download);
var op:Function = Delegate.create(this, this.open);
var m:ContextMenu = new ContextMenu();
m.customItems.push(new ContextMenuItem("Ouvrir", op));
m.customItems.push(new ContextMenuItem("Télécharger", dl));
if (hide)
m.hideBuiltInItems();
if (setRelease)
clip.onRelease = op;
clip.menu = m;
}
/**
* Open a file in the browser
* Used by a proxy (Delegate) function
*/
private function open() {
getURL(_path);
}
/**
* Dowload the file to the local computer
* Used by a proxy (Delegate) function
*/
private function download() {
var f:FileReference = new FileReference();
f.download(_path);
}
}import com.lalex.utils.FileLink;
var fl:FileLink = new FileLink("files/FileLink.zip");
fl.assignTo(bt, true, true);
mardi, septembre 20 2005
SuperColor devient XColor pour Flash 8
Par -Alexandre LEGOUT aka LAlex- le mardi, septembre 20 2005, 14:31
Pour le retour de mon blog, quelque peu perturbé par un changement de serveur (mais qui pulse bien, ca valait le coup
), je vous propose une version Flash 8 de ma classe SuperColor, qui devient pour l'occasion XColor (pour eXtended Color).
En effet, comme vous l'avez peut-être déjà vu, la classe Color est déclarée deprecated dans Flash 8. Et comme j'aime pas spécialement trop changer mes habitudes de codage (surtout quand elles sont efficaces), j'ai donc refactorisé la classe pour qu'elle puisse être utilisée strictement de la même manière dans Flash 8, sans utiliser une classe qui est à priori vouée à disparaitre... Il s'agissait donc de recréer le comportement de Color en utilisant sa remplacante : ColorTransform!
Etant donné que ma classe héritait de Color, il m'a alors suffit de réimplémenter les méthodes setTransform, getTransform, setRGB et getRGB pour que tout remarche exactement comme avant. C'est reparti pour du Tween facile sur les couleurs! ![]()
Par contre, cette classe ne marche que sur Flash 8, pour les "MX2004iens", il suffit de continuer à utiliser SuperColor... ![]()
lundi, août 22 2005
Debug 0.2
Par -Alexandre LEGOUT aka LAlex- le lundi, août 22 2005, 10:33
Ca ne fait pas longtemps que je re-code en AS2 au quotidien. Et depuis que j'ai repris, j'utilise ma classe Debug intensivement, et la fait donc évoluer en fonction de mes besoins, et aussi des bugs qu'elle peut comporter... ![]()
Au programme de cette nouvelle version, tout d'abord le fait d'éviter une boucle infinie dans le cas ou un object est composé d'un autre, qui lui-même pointe vers son parent. En gros, c'est comme si on parcourait récursivement un clip, et sa propriété _parent...
Ensuite, une méthode éxécutée au premier appel de dump(), qui va parcourir toutes les classes présentes et leur créer une propriété contenant le nom de la classe, et une propriété conternant le nom du package. Cela permet d'afficher tout ca lors du dump d'un objet... En fait, c'est le seul moyen de retrouver le nom d'une classe sans le préciser dans le code de cette classe... Je crois me souvenir que Liguorien avait déjà évoqué ce sujet, s'inspirant de la partie reflection de la librairie as2lib...
Et troisièmement, la possibilité de spécifier un caractère qui permettra d'ignorer toute propriété dont le nom commence par celui-ci... Par exemple, il m'arrive d'utiliser une propriété contenant un XML dans mes classes. Il me suffit de faire commencer son nom par '$' (caractère par défaut) pour qu'elle ne soit pas incluses dans l'affichage...
/**
* Debug
* @version 0.2
* @package com.lalex.utils
*
* @author LAlex
* @since 2005-08-16
*/
class com.lalex.utils.Debug {
/**
* Property used to store class names
*/
public static var classNameProperty:String = "__className";
public static var packNameProperty:String = "__packName";
private static var ignoreChar:String = "$";
private static var __classInit:Boolean = false;
/**
* Trace an object detailled content
* @param o Object to display
*/
public static function dump(o) {
if (!__classInit) {
initClassNames();
__classInit = true;
}
trace(dumpToString(o));
}
/**
* Browse all classes and create a property for class name
* and a property for package name
* Browse is recursive
* @param o Object containing classes
* @param pa Package name
*/
public static function initClassNames(o, pa:String) {
if (!__classInit) {
o = o ? o : _global;
pa = pa.length ? pa : "";
for (var p in o) {
if (typeof o[p] == "function") {
o[p][classNameProperty] = p;
o[p][packNameProperty] = pa.length ? pa.substr(0, pa.length-1) : "";
_global["ASSetPropFlags"](o[p], classNameProperty + "," + packNameProperty, 7);
} else if (typeof o[p] == "object") {
initClassNames(o[p], pa + p + ".");
}
}
}
}
/**
* Return a string containing the object content. Recursive method
* @param o Target object
* @param d Depth. Used to generate tabs
* @param a Array containing already dumped objects
*/
private static function dumpToString(o, d:Number, id:Number):String {
var ret:String = "[";
d = d ? d : 0;
id = id ? id : _dumped.push(new Array())-1;
var dp:Number = d;
var sp:String = "";
while (d-- > 0) {
sp += " ";
}
if (typeof o == "object") {
if ( o["__constructor__"][classNameProperty] ) {
ret += o["__constructor__"][classNameProperty] + " (" + o["__constructor__"][packNameProperty] + ")";
} else if (o instanceof Array) {
ret += "Array";
} else {
ret += "Object";
}
if (!o.__dumped) {
ret += "
" + sp;
o.__dumped = true;
_global["ASSetPropFlags"](o, "__dumped", 1);
_dumped[id].push(o);
for (var p in o) {
if (typeof p != "function") {
if (p.substr(0,1) != ignoreChar) {
ret += p + ": " + dumpToString(o[p], dp+1, id) + "
" + sp;
}
}
}
}
ret += "]";
} else {
ret = o.toString();
}
var nd:Number = _dumped[id].length;
while (nd-- > 0) {
var df = _dumped[id].pop();
df.__dumped = false;
delete df.__dumped;
}
return ret;
}
private static var _dumped:Array;
}
En prévision, pourquoi pas de pouvoir retrouver l'héritage d'une classe, comme l'a suggéré ekameleon dans le premier post de cette classe ? ![]()
vendredi, août 19 2005
Identifiant unique pour un objet
Par -Alexandre LEGOUT aka LAlex- le vendredi, août 19 2005, 14:55
Pour des raisons de performances, il peut etre trés utile d'attribuer un identifiant unique à un objet. En effet, un identifiant numérique peut devenir index dans un tableau, et ainsi augmenter considérablement les performances pour retrouver celui-ci sans avoir à parcourir la totalité d'un tableau.
C'est pourquoi j'ai développé une classe ObjectCollector, qui permet d'attribuer un identifiant unique à un objet, et éventuellement de retrouver cet objet à partir de ce dernier, mais uniquement s'il a été "collecté" (on a pas toujours besoin de le retrouver par son id)... ![]()
/**
Utilisation :
* Collect objects and give them a unique Id.
* @author <a href="http://www.lalex.com/">LAlex</a>
* @version 1.0
* @since 2005-08-19
*/
class com.lalex.core.ObjectCollector {
// Stores last given id
private static var __id:Number = 0;
// Store collected objects
private static var __instances:Array = [];
/**
* If not already done, return a unique Id for the object
* @param o
* @return Object's unique id
*/
public static function uId(o):Number {
if (!o["__ocId"]) {
o["__ocId"] = ++__id;
_global.ASSetPropFlags(o, "__ocId", 7, 1);
}
return o["__ocId"];
}
/**
* Collect an object. Must be call before using getById method
* @param o Object to be collected
* @return Object's unique id
*/
public static function collect(o):Number {
__instances[uId(o)] = o;
return uId(o);
}
/**
* Release an object. Must be used
* before any delete of a collected object
* @param o Object to be released
*/
public static function release(o) {
delete __instances[uId(o)];
}
/**
* Release all collected objects
*/
public static function clear() {
var n:Number = __instances.length;
while(n-- > 0) {
release(__instances[n]);
}
}
/**
* Retrieve an object from its id
* The object must have been collected first
* @param id Object's id
* @see #collect
*/
public static function getById(id:Number) {
return __instances[id];
}
}import com.lalex.core.ObjectCollector;
// Objects
var o1 = {a:150, b:"LAlex"};
var o2 = {d:false, e:"Blablabla"};
// Collect the 'o1' object
var o1id = ObjectCollector.uId(o1);
trace("o1 id : " + o1id); // o1 id : 1
ObjectCollector.collect(o1);
trace("Retrieved ? " + (o1 == ObjectCollector.getById(o1id))); // Retrieved ? true
// Release 'o1' object
ObjectCollector.release(o1);
trace("Retrieved ? " + (o1 == ObjectCollector.getById(o1id))); // Retrieved ? false
// Get the 'o2' unique id
var o2id:Number = ObjectCollector.uId(o2);
trace("o2 id : " + o2id); // o2 id : 2
trace("Trouvé ? " + (o2 == ObjectCollector.getById(o2id)));// Retrieved ? false
vendredi, août 5 2005
Utilisation avancée de EventDispatcher
Par -Alexandre LEGOUT aka LAlex- le vendredi, août 5 2005, 12:54
Je continue l'extension de classes natives de Flash pour leur attribuer le modêle évenementiel de EventDispatcher. J'en suis venu à me pencher sur la classe EventDispatcher elle-même, et j'ai découvert quelques tips, que tout le monde ne connaît pas forcément...
1. INITIALISATION
Une méthode toute simple consiste à déclarer les trois méthodes principales, et d'appeler ensuite la méthode statique 'initialize' qui va s'occuper de décorer le prototype de notre classe. A la base, c'est même dans le constructeur que l'on initialise l'instance, mais dans la classe ca fait plus mieux. En gros, on retrouve le même code un peu partout, excepté le nom de la classe qui change. import mx.events.EventDispatcher;
Il faut bien avouer que à la pratique, au bout d'un moment, ca devient assez lourd de toujours retaper la même chose. Un #include serait bien pratique, mais étant donné que le nom de la classe change à chaque fois, cela obligerait à laisser le initialize() dans le code de la classe. J'ai donc utilisé une autre méthode :
class EventSource {
public var addEventListener:Function;
public var removeEventListener:Function;
private var dispatchEvent:Function;
private static var __initEvent = EventDispatcher.initialize(EventSource.prototype);
}// fichier "initEvent.as"
La méthode dispatchQueue() n'est jamais utilisée en général, excepté par dispatchEvent(), mais elle a besoin d'être là pour faire fonctionner le tout... Bon, je veux bien concevoir que c'est pas trés académique d'utiliser les prototypes.
private static var __dispatcher:Function = mx.events.EventDispatcher;
public function addEventListener(e:String, o) {
__dispatcher.prototype.addEventListener.apply(this, arguments);
}
public function removeEventListener(e:String, o) {
__dispatcher.prototype.removeEventListener.apply(this, arguments);
}
private function dispatchEvent(o) {
__dispatcher.prototype.dispatchEvent.apply(this, arguments);
}
private function dispatchQueue(q, o) {
__dispatcher.prototype.dispatchQueue.apply(this, arguments);
}
// fin "initEvent.as"
// fichier "EventSource.as"
class EventSource {
#include "initEvent.as"
}
Mais en tout cas, ca fonctionne plutôt bien, et ca me permet de chager de "dispatcher" facilement. Par exemple, si je préfère utiliser GDispatcher, il me suffit de changer la première ligne de mon fichier, et toutes les classes qui l'utilisent seront mises à jour à la compilation!
Et si jamais, j'ai un besoin réellement spécifique, il me suffit de copier/coller le code directement dans la classe. Cela peut même éventuellement me permettre de rajouter un typage quelqconque a mes paramètres, ou d'afficher des infos de debug, etc...
2. HANDLER INTERNE
La classe EventDispatcher permet nativement de créer un handler interne qui va intercepter les évenements diffusés. Il suffit pour cela de créer une méthode qui s'appelle <nomEvenement>Handler(). Ca permet d'éviter d'inscrire une instance comme son propre écouteur :// Sans handler interne
class EventSource {
#include "initEvent.as"
function EventSource() {
this.addEventListener("onEvent", this);
}
private function onEvent(o) {
trace(o.type + " event dispatched");
}
}
// Avec handler interne
class EventSource {
#include "initEvent.as"
function EventSource() { }
private function onEventHandler(o) {
trace(o.type