AsfunctionProxy - asfunction plus de problème de scope
Par eKameleon le vendredi, septembre 16 2005, 10:58 - AS2 - Lien permanent
Il arrive souvant que l'utilisation d'un asfunction dans un champ de texte au format HTML soit un vrai casse tête.
Pour rappel la commande asfunction permet d'exécuter une fonction actionscript lorsque l'utilisateur clique sur un lien contenu dans un champ de texte dynamique au format HTML.
Le scope par défaut de cette commande se caractérise par l'appel automatique de la fonction déclarée sur le _parent du champ de texte ciblé par l'utilisateur. En général il est plus simple d'utiliser des champs de textes sur la scène principale de l'animation et de déclarer les fonctions qui seront appelés dans la chaine au format HTML directement sur le _root de l'animation.
Il existe d'autres moyens de changer la portée du asfunction en tapant directement dans le lien html la cible de la fonction que l'on cherche atteindre :
var o = {} ; o.toString = function () { return "[my object]" ; } o.myFunction = function () { trace (this + " >> " + arguments) ; } var txt:String = "<p>3 <a href='asfunction:_root.o.myFunction,argument1,argument2'>test a complex scope</a></p>"; createTextField("field", 1, 25, 25, 500, 80) ; field.html = true ; field.htmlText = txt ; field.setTextFormat(new TextFormat("arial", 12)) ;
Mais je ne trouve pas du tout élégant de devoir taper dans la chaine html le ciblage de la fonction comme dans l'exemple au dessus. En effet cela oblige le rédacteur du texte de se concentrer à chaque fois au niveau de ses liens hypertexte sur le ciblage plus ou moins complexe des fonctions qu'il souhaite appeler et cela rajoute pas mal de caractères dans la chaine à récupérer et je préfère trouver une solution qui permet de controler correctement en interne le scope du asfunction.
J'ai donc rapidement mi en place une petite classe AsfunctionProxy qui permet d'appliquer sur un champ de texte un scope un peu à la façon de la classe mx.utils.Delegate de macromedia. Voilà donc la version 1.0.0.0 de cette classe et j'espère qu'elle vous sera utile 
La classe eka.src.util.AsfunctionProxy
/* ---------------- Name : AsfunctionProxy Package : eka.src.util Version : 1.0.0.0 Date : 2005-09-11 Author : ekameleon URL : http://www.ekameleon.net Mail : contact@ekameleon.net COPYRIGHT Cette création est mise à disposition selon le Contrat Paternité - Pas d'Utilisation Commerciale - Partage des Conditions Initiales à l'Identique disponible en ligne http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ ou par courrier postal à Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. ----------------*/ import eka.src.commands.Command ; import eka.src.events.Proxy ; class eka.src.util.AsfunctionProxy implements Command { // ----o Author Properties public static var className:String = "AsfunctionProxy" ; public static var classPackage:String = "eka.src.util" ; public static var version:String = "1.0.0.0" ; public static var author:String = "ekameleon" ; public static var link:String = "http://www.ekameleon.net" ; // ----o Constructor public function AsfunctionProxy( tf:TextField, fname:String, o , f:Function ) { _tf = tf ; _o = o ; _f = f ; name = fname || "callFunction" ; execute() ; } // ----o Public properties public var name:String ; // ----o Public Methods public function execute():Void { if (_tf == undefined) return ; var o = _o || _tf ; var f:Function = _f || o[name] ; _tf._parent[name] = Proxy.create(o, f) ; } // ----o Virtual Properties public function get field():TextField { return getField() } public function set field(tf:TextField):Void { setField(tf) } public function get func():Function { return getFunction() } public function set func(f:Function):Void { setFunction(f) } public function get target():TextField { return getTarget() } public function set target(o):Void { setTarget(o) } // ----o Getter/Setter Methods public function getField():TextField { return _tf ; } public function getFunction():Function { return _f ; } public function getTarget() { return _o ; } public function setField(tf:TextField):Void { _tf = tf ; execute() ; } public function setFunction(f:Function):Void { _f = f ; execute() ; } public function setTarget(o):Void { _o = o ; execute() ; } public function toString():String { return "[AsfunctionProxy]" ; } // ----o Private Properties private var _tf:TextField ; private var _f:Function ; private var _o ; }
Description
Permet de régler le scope de la fonction invoqué par un asfunction sur un champ de texte dynamique. Par défaut il faut utiliser un asfunction ayant pour nom de méthode "callFunction" :
<a href='asfunction:callFunction,argument1,argument2'><u>small example here</a>
Utilisation
var proxy:AsfunctionProxy = new AsfunctionProxy(field, fname, obj, func) ;
Paramètres
- field : champ de texte qui va utiliser un asfunction..
- obj : objet recevant le scope de la commande asfunction .
- func : fonction invoquée lors de la sélection du lien hypertexte paramétré par le asfunction.
Propriétés
- name : Nom de la fonction à utiliser dans le asfunction. Cette chaine de caractère défini également le nom de la fonction par défaut utilisée si la propriété func n'est pas définie. (Par défault cette propriété vaut "callFunction")
- field [Read/Write] : Permet de définir le champ de texte que l'on souhaite configurer pour l'utilisation d'un asfunction.
- target [Read/Write] : Permet de définir l'objet servant de cible pour le scope du asfunction.
- func [Read/Write] : Permet de définir la fonction (instruction) à utiliser lors de l'utilisation du asfunction.
Remarque : Pour tout changement de la propriété name il ne faut pas oublier de relancer le proxy sur le champ de texte avec la méthode execute()
Methodes
- execute() : Initialise le proxy du asfunction sur le champ de texte défini.
- getField() : renvoi la valeur du champ de texte.
- getFunction() : renvoi la valeur de la fonction.
- getTarget() : renvoi la valeur de l'objet servant de scope.
- setField(tf:TextField) : permet de définir le champ de texte.
- setFunction(f:Function) : permet de définir l'objet servant de cible pour le scope du asfunction.
- setTarget(o) : permet de définir la fonction (instruction) à utiliser lors de l'utilisation du asfunction.
Exemple
import eka.src.util.* ; var p = { fontFamily : "verdana" , color : "#FFFFFF" , fontSize : "12px" , maginLeft : "4px" , maginRight : "3px" } var a = { color : "#F3F372", fontWeight : "bold" } var css:TextField.StyleSheet = new TextField.StyleSheet ; css.setStyle("p", p) ; css.setStyle("a", a) ; var o = {} ; o.toString = function () { return "[my object]" } o.myFunction = function () { trace (this + " >> " + arguments) ; } var txt:String = "" ; txt += "<p>1 <a href='asfunction:callFunction,argument1,argument2'>- default example</a></p>"; txt += "<p>2 <a href='asfunction:customCall,argument1,argument2'>- custom call name & proxy</a></p>"; createTextField("field", 1, 25, 25, 500, 80) ; field.multiline = true ; field.wordWrap = true ; field.styleSheet = css ; field.html = true ; field.htmlText = txt ; field.callFunction = function () { trace (this + " >> " + arguments) ; } var proxy1 = new AsfunctionProxy(field) ; // default var proxy2 = new AsfunctionProxy(field, "customCall", o, o.myFunction) ; // custom function's name & scope
A noter que j'utilise un Pattern Command et une classe de proxy dans mon code ci-dessus, je vais vous posez rapidement l'interface et la classe que j'utilise à la fin de cet article (vous pouvez retrouver une partie de mon frameworks sur le wiki de mediabox en attendant que je mette à jour mon site).
Interface eka.src.commands.Command
/* ---------- interface Command Name : Command Package : eka.src.commands.Command Version : 1.0.0.0 Date : 2004-12-21 Author : ekameleon URL : http://www.ekameleon.net Mail : contact@ekameleon.net THKS : Francis Bourre - www.tweenpix.net for Design Pattern Command concept : http://www.tweenpix.net/archives/000423.html COPYRIGHT Cette création est mise à disposition selon le Contrat Paternité - Pas d'Utilisation Commerciale - Partage des Conditions Initiales à l'Identique disponible en ligne http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ ou par courrier postal à Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. ---------- */ interface eka.src.commands.Command { function execute():Void ; }
Classe eka.src.events.Proxy
Classe équivalente à la classe mx.utils.Delegate de macromedia avec possibilité de passer des arguments supplémentaires lors de la création de la fonction.
/* ---------- Proxy Name : Proxy Package : eka.src.events Version : 1.0.0.1 Date : 2005-04-18 Author : ekameleon URL : http://www.ekameleon.net Mail : contact@ekameleon.net COPYRIGHT Cette création est mise à disposition selon le Contrat Paternité - Pas d'Utilisation Commerciale - Partage des Conditions Initiales à l'Identique disponible en ligne http://creativecommons.org/licenses/by-nc-sa/2.0/fr/ ou par courrier postal à Creative Commons, 559 Nathan Abbott Way, Stanford, California 94305, USA. ---------- */ class eka.src.events.Proxy { // ----o Author Properties public static var className:String = "Proxy" ; public static var classPackage:String= "eka.src.events"; public static var version:String= "1.0.0.1"; public static var author:String= "ekameleon"; public static var link:String= "http://www.ekameleon.net" ; // ----o Static Methods public static function create(o, f:Function):Function { var a:Array = arguments.slice(2) ; return function():Void { f.apply(o, arguments.concat(a)); } } }
Commentaires
Pas mal
Par contre pour ton proxy, tu gagnerais peut-être à utiliser simplement Array.slice:
var a:Array = arguments.slice(2);
oui en effet ! lol

En fait c'est juste que j'ai fait la même classe en SSAS pour FCS et le arguments dans FCS ne réagit pas vraiment comme un Array et du coup j'ai pas pensé à optimiser cela en AS2. Je vais le faire dès que possible
Merci
Voilà pour la classe Proxy, c'est mi à jour dans l'article
Hello,
Juste une petite précision sur ta classe Proxy.. :mrgreen:
j'aimerais savoir pourquoi tu fais :
f.apply(o, arguments.concat(a)); :?
au lieu de :
f.apply(o, a);
qui est encors plus directe.
@++
Non c'est pas la même chose
là je récupère les paramètres que l'on peut envoyer à la fonction par défaut et aussi les paramètres que j'ai envi d'envoyer dans la méthode statique create de la classe Proxy.


Exemple :
[code]
import eka.src.events.Proxy ;
var o = {} ;
o.toString = function():String {
return "[my object]" ;
}
o.myFunction = function () {
trace (this + " >> " + arguments) ;
}
var target = {} ;
target.toString = function():String {
return "[my target]" ;
}
var f:Function = Proxy.create( target , o.myFunction, "coucou", "hello") ;
// ----- test de la fonction proxy
f("item1", "item2") ;
[/code]
Tu vois que là j'envois des paramètres dans la fonction f mais aussi dans la méthode Proxy.create(...), donc j'ai besoin d'ajouter à la liste des arguments de f celles de ma méthode Proxy.create avec un concat()
La fonction mx.utils.Delegate de macromedia n'a pas justement cette faculté de pouvoir ajouter des paramètres dynamiquement lors de la création de la fonction de délégation et parfois cela peut servir :D
EKA+
Oki, g capté :ouch: .
C'est une utilisation de la classe Proxy que je
ne connaissait pas.
Thanks pour l'astuce => je modifie ma classe :mrgreen:
Pas mal ton idée, Eka, mais je doute que l'idée soit aboutit : dans ton développement, tu doit appeler la bonne fonction par l'appel du constructeur et donc déclarer autant de AsfunctionProxy que tu as d'appel asfunction pour des fonctions distinctes.
Je pense que tu pourrais gagner en changeant 2, 3 trucs :
1. le constructeur de ta classe AsfunctionProxy en méthode static nommé "initialize". Cela t'éviterait de rajouter des variables un peu partout sans quelles soit vraiment utiles (var proxy1...)
2. paramétrer comme seul argument les namespaces possibles où se trouve tes méthodes du style addNamespaceListener et ainsi ne pas s'occuper à attribuer tel fonction à tel fonction, les deux ayant le même nom. Du coup, au lien d'avoir autant de déclaration de méthode pour le protocole asfunction, n'avoir qu'une seule déclaration (enfin dans le cas où toutes les méthodes sont situées au même endroit)
Enfin, cela me donne des idées, et je vais pouvoir stopper mon rafistolage actuel ( http://www.roikku.com/dotclear/index.php?2005/07/29/2-asfunction-bug-ou-pas-bug )
En effet.. il manque quelques petits truc en y réfléchissant bien avec un enregistrement de toutes les fonctions dans une Map et la possibilité de les désactiver une par une ou toutes d'un coup.


Au passage, je vais virer le pattern Command qui ne sert à rien.
Pour ce qui est de l'initialisation via une méthode statique j'ai pas encore trouvé une vraie utilité là dessus.. à part d'insérer directement dans le champ de texte une nouvelle propriété de type AsfunctionProxy .. mais bon je ne suis pas certain que cela soit vraiment utile du coup.
J'ai déjà commencé à transformer la classe mais je pense que je mettrai à jour celle ci dans la semaine en ayant pri le temps un peu de la tester avant
EKA+
En fait, cela te s'impliferait le code et surtout alègerait le scope :
AsfunctionProxy.initialize( tonTextField )
tonTextField.addNamespaceListener ( unConteneurdeMéthode );
ou
tonTextField.addNamespaceListener ( unConteneurdeMéthode; uneMéthode );
L'intéret, c'est que tu n'as plus besoin de stocker une occurence de la classe AsfunctionProxy par déclaration de fonction, tu pourrais avoir le choix entre recréer une méthode en local par rapport au textField qui lancerait un callback sur la fonction voulu ou tout simplement, paramétrer une ou plusieurs références aux cibles voulues, voir même par rapport à ton dernier commentaire, faire des permutation de ciblage pour lancer des méthodes de même nom.
J'essayerais de coder un truc dans la soirée et le posterais sur mon blog.
En fait c'est surtout un problème de délégation des fonctions invoquées par le asfunction au niveau de la cible parente d'un ou plusieurs champs de textes. Donc il est préférable de simplifier à fond le AsfunctionProxy et donnant juste une cible et en fonction de celle ci d'attribuer les délégations nécessaires pour des namespace précis. Du coup au choix il faut voir si on délègue directement une cible contenant des champs de texte ou un champ de texte et dans ce cas la cible va être défini par le _parent du champ de texte...
je vais reprendre tout cela moi aussi 
Bref