Une recette pour un YUI 3 Application
1 avril 2011 à 2:52 am par Satyam | En développement | Les 18 commentairesYUI 3 a été conçu pour créer des applications autour de modules. Je ne vais pas discuter de ce qu'est un module car il a été bien décrit par Nicolas Zakas dans sa présentation Application Architecture évolutive JavaScript . Je vais juste coller à la façon de construire ces modules. La plupart de ce que je vais dire peut être trouvée dans la documentation en ligne, ainsi que plusieurs autres solutions de rechange, après tout, qui est le point d'une bonne documentation: pour vous parler de toutes les manières possibles de faire les choses. C'est pourquoi il s'agit d'une recette, une seule façon de le faire parmi beaucoup d'autres. Elle suppose également une application assez petite, avec pas autant de couches que Nicolas suggère, puisque c'est juste un article et non pas un livre.
Identifier les modules
La première étape consiste à identifier les modules dont nous aurons besoin. Une bonne approche est de commencer à découper la conception de l'écran de l'application en sections individuelles: barre de titre, barre de menu, le contenu, les panneaux latéraux ou tout autre chose qu'il pourrait y avoir là-bas. Alors jetez un oeil à ce que la bibliothèque a à offrir. Par exemple, YUI 3 n'a pas de menu, mais il est le plug-in Node-MenuNav , qui prend une structure de menu de base formé de non-ordonnée des éléments imbriqués <UL> liste et les transforme en un menu actif. Ou vous voudrez peut-être vérifier la Galerie YUI pour les composants de base. Quoi qu'il en soit, vous finirez par atteindre le point où vous avez une boîte dans cette mise en page que vous avez à remplir vous-même, nous allons donc le faire.
Je recommande de placer chaque module dans son propre fichier et de son propre répertoire du même nom. Ainsi, une weather module serait en weather/weather.js . La raison pour cela est parce que votre module est susceptible d'exiger un peu de style, certains fichiers CSS et de l'image, il le rend facile pour le haut-chargeur si vous les placez où il peut les trouver facilement, dans ce cas, la feuille de style principale serait weather/assets/skins/sam/weather.css , avec les autres éléments d'actif, des images et ainsi de suite, aux côtés de. Ceci suppose que vous n'utilisez pas le Générateur de YUI qui font déjà des choses de cette façon en tout cas, mais c'est une autre histoire. Les noms de dossier assets et skins sont plus ou moins explicites, sam n'est cependant pas tout à fait évident. Il est la valeur par défaut pour la skin des biens de la chargeuse, parce que c'est la peau par défaut livré avec YUI, nommé d'après le concepteur, Sam Lind. Comme le suggère, vous êtes libre de mettre votre nom sur vos propres skins et la skin propriété vous permet de dire YUI pour les charger, mais pour faire simple, disons tout simplement aller avec le défaut.
Modèle de fichier de module
Il s'agit de la structure de fichiers que j'utilise le plus souvent, que je décrirai dans un instant:
/ * JSLint développement: true, undef: true, Newcap: true, stricte: true, MAXERR: 50 * / / * Mondiale YUI * / / ** * Le module nom-module crée le bla bla * @ Module de nom-module * / YUI.add («module-name ', function (Y) { "Use strict"; / / Constantes pratiques et des raccourcis utilisés dans le module var = Lang Y.Lang, CBX = 'ContentBox », BBX = 'boundingBox », NOM = 'xxxx'; / ** * La classe Xxxx fait .... * @ Classe Xxxx * @ Extends Widget * @ Utilise WidgetParent * @ Constructeur Attributs de configuration * cfg @ {objet} * / Y.Xxxx = Y.Base.create ( NOM, Y.Widget, [Y.WidgetParent], { Ou les membres d'instance / ici }, { / / Les membres statiques ici, spécialement: ATTRS: { } } ); }, '0 0,99 ', { requise: ['widget', 'widget-parent'], skinnable: true });
Les deux premières lignes de commentaires sont à l'avantage de JSLint , l'outil de vérification JavaScript que je recommande vraiment. Si vous allez à la version web , il ya une case pour définir les options. Au bas de cette boîte options que vous pouvez voir la façon de coder ces options dans le fichier lui-même. Si vous utilisez le Générateur de YUI, elle se déroulera JSLint pour vous et définissez les options pour vous, mais vous pouvez toujours les remplacer par n'importe quel fichier individuel si vous le souhaitez.
Les commentaires sont doc pour le constructeur YUI API docs. Il est moins de maux de tête si vous incluez le modèle initial pour ceux documentation de l'API, vous finirez par les remplir. Comme l'application grandit et que vous êtes incapable de se rappeler tout cela, vous en aurez besoin.
Maintenant vient la première ligne réelle de code, le YUI.add() déclaration. C'est le moyen de dire au chargeur de YUI le nom et le contenu du module et plusieurs autres éléments d'information. Les noms de modules sont habituellement nommés à toutes les lettres minuscules et les tirets entre les mots. Ce sont les noms que vous voyez dans la documentation de l'API dans l'index de plus haut niveau sur la gauche. Vous pouvez voir la convention n'est pas strictement suivie, certains noms de module n'ont pas de traits d'union. Quoi qu'il en soit, il s'agit surtout à vous aussi longtemps que vous l'utilisez régulièrement.
Le deuxième argument de la YUI.add() déclaration est une fonction qui reçoit un seul argument, classiquement appelé Y . Cette fonction contient le corps du module et Y est la référence à une instance isolée de YUI qui est où vous pouvez trouver tous les autres modules YUI et Galerie vous avez demandé. Saut vers le bas de cette boîte de code, vous pouvez voir le reste des arguments de l' .add() la méthode, la version ( '0.99′ ou pas encore là) et la configuration de ce module, un objet avec des séries de aa propriétés. Ici, je dis le chargeur que ce module nécessite widget et widget-parent et qu'il a une peau. Liste des widget est redondant, puisque widget-parent exige déjà widget , mais ne vous inquiétez pas avec ça, le chargeur ne sera pas charger un module à deux reprises et, si à un moment plus tard, vous déposez une dépendance, vous n'avez pas besoin de vérifier pour d'autres hypothèses que vous pourriez avoir fait: l'état tous et laisser le chargeur avec elle. Vous pouvez trouver une liste de toutes les options dans le documentation de l'API pour l' .addModule() méthode de la chargeuse.
Dans le corps de la fonction, la première chose est la “use strict”; déclaration. C'est pour votre code pour se conformer à la spécification ECMAScript 5 standard, qui, à ce stade, vous met sur le côté sécuritaire pour assurer la compatibilité avec toutes les plates-formes que vous êtes susceptible de rencontrer dans le futur. Pour les plus grands interprètes, cette déclaration n'est rien de plus qu'une chaîne qui n'est pas affecté à rien et est ignoré. Le “use” déclaration a une portée de fonction et il est préférable de le placer dans le corps de la fonction que dans le haut du fichier, de sorte qu'il ne touche que le module que vous définissez. Si vous le placez au début du fichier, il serait également s'appliquer à tout autre fichier JavaScript que vous chargez ensuite, et beaucoup d'entre eux pourraient ne pas se conformer à ES5.
Puis viennent les raccourcis et les constantes, qui sont des constantes que dans l'utilisation depuis JavaScript n'a pas de concept de constantes. Nous leur nom dans toutes les majuscules-lettres et caractères de soulignement, comme des constantes sont souvent dans d'autres langues. Il ya deux bonnes raisons d'utiliser des constantes, chaîne spécialement. La première est que quand vous écrivez la même chaîne à plusieurs reprises, vous pourriez faute de frappe l'un d'eux et vous ne remarquerez quand un bug apparaît. Si vous utilisez des constantes, JSLint vous avertira lorsque vous tapez le nom de la constante tort, car il ne sera pas défini. La deuxième raison est que le compresseur YUI peut faire un meilleur travail puisque les noms de constantes peuvent être compressés tout en les littéraux de chaîne ne peut pas. Les bons candidats pour des constantes nommées sont les noms des attributs de configuration et d'événements.
Raccourcis, comme Lang pour Y.Lang sont également bonnes, car ils vous permettent de taper moins, l'interprète a moins à évaluer (chaque point implique une nouvelle recherche dans les membres de l'objet) et ils peuvent être comprimé par le compresseur YUI.
Après les commentaires documentation de l'API pour la classe, nous arrivons à la déclaration réelle. Nous devons déclarer notre classe comme une propriété de Y , ce qui Y.Xxxx . Ma suggestion est d'utiliser Y.Base.create() pour le créer, comme indiqué ici. Il ne peut que créer des classes dérivées de la Base (et Widget qui est aussi une sous-classe de Base ), mais qui couvrent la plupart des modules que vous allez utiliser de sorte qu'il est inhabituel d'avoir besoin de le faire d'une autre manière. Le premier argument est le nom du module, le NAME bien décrit pour la Base composant. Classiquement, le NAME propriété est une version de chameau cas du nom de classe. Ce nom est utilisé comme préfixe pour les événements (la partie avant du côlon comme “io:success” ), pour les noms de classes CSS générés par le Widget de classe (ex: “yui3-xxxx-content” ) et pour la mise en œuvre par défaut de toString() , qui vous verrez souvent dans les traces par le débogueur. Ici, j'ai utilisé la valeur de la constante NAME pour définir la classe NAME propriété.
Le deuxième argument est la classe qu'il degrés. Vous utiliserez souvent soit Y.Base , pour les modules de services publics qui n'ont pas d'interface utilisateur, Y.Widget pour ceux qui ont une interface utilisateur, Y.Plugin.Base pour les plugins ou toute autre classe dérivée de Y.Base , comme toute vous pourriez avoir déjà créé à l'aide Y.Base.create() .
Le troisième argument tient les extensions que vous allez utiliser. Les extensions sont des classes dont les propriétés et les méthodes que vous voulez avoir mélangé dans votre classe. Les bons candidats pour les extensions sont ArrayList pour Base ou l'un des Widget-Xxxx sous-modules pour Widget . Attribute , EventTarget et PluginHost déjà venu mélangé dans Base sorte que vous pouvez toujours compter sur ces trois être là. Les extensions sont très puissant, si vous regardez à la code source pour Overlay , vous pouvez le voir il n'y a rien, mais Widget et les extensions. Plusieurs extensions peuvent être mélangés dans un composant de sorte que le troisième argument est un tableau.
Enfin, nous arrivons à le code réel. Les quatrième et cinquième arguments sont des littéraux d'objet contenant les deux membres de l'instance et statiques de la classe. Les membres d'instance sont les propriétés et les méthodes qui entrent dans la classe prototype , ceux qui chaque instance obtiendrez une copie et ont généralement besoin d'être des références par this . Les membres statiques sont ceux qui seront partagés par toutes les instances.
Attributs de configuration
La plus importante de ces membres statiques est le ATTRS propriété. Cette liste énumère la configuration attribue votre classe aura. Par exemple, disons que nous voulons avoir un attribut de configuration appelé value pour stocker des valeurs numériques et initialement fixée à 0. Dans le cinquième argument, nous le déclarer ainsi:
ATTRS: { valeur: { valeur: 0, validateur: Lang.isNumber } }
Nous pouvons énumérer un certain nombre d'attributs dans le ATTRS la propriété, et chacun peut être configuré avec plusieurs options, dont deux que j'ai présentés ici. Vous pouvez lire sur le reste des options dans le addAttr() méthode de Attribute . Comme on peut le constater, j'ai utilisé le Lang raccourci que j'ai déclaré au début de la déclaration du module. Le validator doit être une fonction qui prend la valeur de vérifier et retourne un booléen. Tous les Y.Lang.isXxxx méthodes font exactement ce que si elles peuvent être utilisées directement. Pour validateurs plus élaborées, setters ou getters, vous avez besoin de définir des fonctions. Je recommande en fournissant le nom de la fonction en tant que chaîne, Attribute prend soin de résoudre le nom de la fonction à la fonction réelle. Par exemple, si je devais définir un, disons, validCodes attribut qui peut soit prendre un code unique valable ou un tableau des codes valides, mais doit toujours retourner un tableau, je ferais:
ATTRS: { validCodes: { setter: «_setValidCodes ' } }
Nous avons besoin de déclarer la _setValidCodes méthode sur les autres membres d'instance dans le quatrième argument de Y.Base.create() :
_setValidCodes: function (value) { if (! Lang.isArray (value)) { = valeur [valeur]; } la valeur de retour; }
Il est préférable de déclarer setters, les getters et des mais le plus trivial des validateurs que les fonctions instance séparée et laissez Attribute de résoudre le nom de la fonction dans l'appel de fonction réelle.
En général, utilisez set pour normaliser la valeur, comme indiqué ci-dessus, de ne pas produire des effets secondaires. Tous les attributs de configuration se déclenche avant et après les événements de changement avec l'événement avant capables d'empêcher l'attribut de changer. Utilisez ces événements de changement de produire des effets secondaires, et non le setter. L'événement après est le meilleur car d'ici là vous savez que rien n'empêchait l'attribut d'être fixé depuis un autre code pourrait avoir souscrit à l'avant l'événement de changement (sur) et il a annulé.
Vous pouvez faire votre attribut plus ou moins stricte selon la façon dont vous définissez votre validateur et setter. Si vous faites le validateur très restrictive, votre attribut sera très stricte, en acceptant uniquement des valeurs valides, dans ce cas, le passeur peut-être inutile. D'autre part, vous ne pourriez pas utiliser un validateur du tout et s'appuient entièrement sur le setter pour masser toute la valeur reçue en quelque chose d'acceptable. Par exemple, vous pouvez avoir un de ces deux:
validateur: Y.Lang.isBoolean, / / pour faire l'attribut accepte strictement une valeur booléenne setter: Boolean, / / pour faire l'attribut accepter toute valeur booléenne et ont le transformer en un
Setters peut aussi servir de validateurs. Setters doit retourner la valeur à attribuer à l'attribut, mais ils peuvent aussi retourner Y.Attribute.INVALID_VALUE qui va laisser l'attribut inchangée, comme si un validateur avait rejetée.
Quand je définir un attribut de configuration j'ai souvent définir une constante pour elle, que je place au sommet du module (le long CBX , BBX et les raccourcis), par exemple:
var value = 'valeur', VALID_CODES = 'validCodes';
Les chances sont que je vais utiliser ces attributs de configuration à plusieurs reprises dans le module, ce qui me permettra de gagner quelques ennuis. Cependant, soyez prudent, ne pas utiliser cette constante lors de la déclaration de l'attribut, ne faites pas cela:
ATTRS: { / / *** Ne faites pas cela *** / / VALEUR: { valeur: 0, validateur: Lang.isNumber } }
Si vous faites cela, vous obtiendrez un attribut appelé VALUE lieu de value . C'est une question JavaScript, pas un YUI un. Aussi, soyez prudent de ne pas dépasser sur tout de la configuration attribue déjà déclaré pour la classe de base soit ou l'un des extensions. Widget dispose déjà d'un tas d'attributs déclarés (voir tableau ) et si vous ne sauriez ajouter un boundingBox vous attribuer, vous pourrait facilement oublier que visible , disabled , height ou width sont déjà définis. Si votre utilisation prévue correspond à ce que Widget les utilise pour, tout ira bien. Cela dit, vous pouvez modifier la définition de l'un d'eux. Y.Base.create() fusionne la définition des attributs de configuration de l'extension avec ceux de la classe de base afin, si vous voulez changer, par exemple, la valeur par défaut pour un attribut existant, vous pouvez le faire en déclarant que l'attribut de nouveau dans votre sous-classe.
Soyez prudent si vous voulez dire pour initialiser un attribut avec un tableau ou un objet. Objets (et les tableaux sont des objets) sont passés par référence et si vous initialiser un attribut avec un objet, ils pourraient tous se retrouver pointant vers le même objet de telle sorte que lorsque vous modifier une partie de celui-ci (supprimer un élément d'un tableau ou ajouter une propriété à l'objet) vous vous retrouvez modifier toutes les instances à la fois. Base , cependant, a une certaine logique interne qui vous permettra en toute sécurité d'initialiser un attribut avec des littéraux d'objet et de tableau. Si la valeur d'initialisation est un objet ou un tableau littérale puis Base se cloner. Utilisez le valueFn option ou l'initialiser dans l'initialiseur de classe pour d'autres objets.
Autres membres statiques
Il ya deux autres membres statiques vous pouvez définir dans le cinquième argument de Y.Base.create() . Si vous créez un plugin, vous devez absolument déclarer la NS la propriété, si vous n'avez pas, le plugin ne fonctionnera pas et échouer en mode silencieux. Le NS propriété doit être définie pour une chaîne qui sera utilisée comme nom de propriété pour stocker le plug-in dans l'objet hôte, gardez cela à l'esprit lorsque vous choisissez le nom si vous ne l'emporte pas sur la propriété existante.
Si vous construisez un widget et vous l'intention d'appuyer l'amélioration progressive, puis vous allez utiliser le HTML_PARSER propriété statique. Il est fixé à un objet qui contient des propriétés nommées d'après les attributs de configuration pour définir à partir de l'analyse du code HTML existant et soit sélecteurs CSS3 ou des fonctions qui produisent leurs valeurs. Voir amélioration progressive dans le guide de l'utilisateur Widget.
Vous pouvez également fournir des valeurs pour être utilisé par les développeurs qui utilisent votre classe. Les constantes déclarées dans la partie supérieure du fichier sont totalement invisibles de l'extérieur du module lui-même. Si vous souhaitez fournir des constantes publiques, c'est l'endroit pour le faire. Des exemples de tels sont les HEADER , BODY et FOOTER constantes de WidgetStdMod (pour les utiliser vous avez réellement à utiliser le nom entièrement qualifié: Y.WidgetStdMod.BODY et autres).
Les membres de l'instance
Le quatrième argument de Y.Base.create() sont les propriétés et les méthodes qui vont dans le prototype de la classe créée. Habituellement, nous déclarons premières propriétés et les méthodes plus tard. Je n'ai pas de raison pour cela, l'ordre est en fait hors de propos, ni JavaScript, ni YUI vous obliger à faire de cette façon, mais il est plus facile de situer les choses dans le fichier source. Bien que les propriétés d'instance peuvent être créés à la volée dans l'initialiseur, je ne vous recommandons de les déclarer explicitement et les cours d'initialisation. Chaque propriété doit être précédée d'un commentaire API doc.
Propriétés sera généralement privé et son nom préfixé par un caractère de soulignement. Il est préférable que l'interface publique de l'objet est exposé via des attributs de configuration et non pas les propriétés. Les propriétés sont très stupide, les attributs de configuration peuvent avoir des validateurs, la conversion de type (par l'intermédiaire setter) et produisent des effets secondaires (par l'intermédiaire d'événements de changement) et il n'est souvent pas long jusqu'à ce que vous découvrez que vous voulez toutes ces fonctionnalités.
Comme avec les attributs de configuration, ne pas initialiser les propriétés d'objets ou des tableaux, ils finissent tous par pointant vers le même objet et vous avez des problèmes. Il est préférable de définir des propriétés qui sont à tenir des objets à null . Aussi, ne laissez pas unset propriétés, si vous ne savez pas encore leur valeur, les mettre à null au lieu. Plus tard, lors du débogage, un ensemble de propriétés à undefined points à une erreur, une faute de frappe habituellement.
Méthodes d'instance de base
Vous avez sans doute remarqué que nous n'avons pas déclaré tout constructeur pour notre sous-classe. Base fait l'initialisation du module, puis appelle une méthode appelée initializer , si elle existe, avec les mêmes arguments qu'il a reçus lors instancié oui, à toutes fins, vous peut considérer que initializer est votre constructeur. Toutes les classes dérivées de Base prennent généralement un seul argument lorsqu'il est créé, un objet contenant les attributs de configuration. Base (ou Widget , car il est une classe de Base ) se lit comme suit cet argument et définit les attributs de configuration avant d'appeler initializer . Pour un Widget , s'il ya une HTML_PARSER la propriété, il aurait également été traitées et les valeurs des attributs de lecture à partir du balisage sera mis aussi bien.
Le initializer méthode a plusieurs tâches. Tout d'abord, il convient de définir toutes les propriétés qui ont besoin d'être initialisé à des objets ou des tableaux. Ensuite, il publiera tous les événements de cette classe produisent. EventTarget vous permettra de déclencher un événement qui n'a pas été publié d'abord en utilisant les paramètres par défaut pour les événements, mais même dans ce cas, je vous suggère de les déclarer de toute façon. C'est un bon endroit pour ajouter des commentaires API docs pour ces événements, même si cela semble un peu bizarre, étant dans le corps d'une déclaration de fonction, mais il n'ya pas de meilleur endroit pour le faire.
L'argument reçu par initializer aurait été traitée d'ici là, mais parfois vous voulez quelques options supplémentaires pour être utilisées lors de l'initialisation et vous ne se soucient pas de garder les attributs réels pour eux. Par exemple, Base accepte les attributs on , after , bubbleTargets et plugins (voir Base ) si elle n'a pas les attributs de configuration pour ceux-ci. De même WidgetParent prend quelques children attribuent à l'initialisation, mais n'a pas d'attribut de configuration de ce nom. Le initializer méthode est celle qui les traite. Ainsi, si votre classe finira par prendre un seul argument sur l'instanciation, ce seul argument ne peut contenir toutes les informations que vous pourriez avoir besoin.
JavaScript n'a pas de notion de destructeurs. Base compenser cela en vous permettant de déclarer un destructor méthode où vous pouvez placer le code pour libérer les ressources de votre objet pourrait avoir prises. Ce n'est qu'une solution partielle, l'interpréteur JavaScript ne l'appelle pas automatiquement lors de la suppression d'un objet si vous êtes encore responsable de la destruction d'un objet avant de le jeter, mais au moins vous savez un destructeur sera là.
Les utilisateurs de votre classe ne sera jamais appeler initializer et destructor directement. Base les appellera en cas de besoin. initializer sera appelée lorsque l'objet est instancié, destructor sera appelé lorsque l'utilisateur de votre classe appelle sa destroy méthode.
Une des choses qui produisent souvent des fuites de mémoire sont des écouteurs d'événements laissés derrière. Widget essaie de détacher tous les auditeurs attachés à des éléments de l'interface utilisateur contenus dans l'élément Bounding Box, mais il ne peut pas détacher les autres. Base peut pas se détacher des écouteurs d'événement à tous les . C'est le code que j'utilise pour m'aider à ce sujet. Le long des autres propriétés privées par exemple, je déclare la _eventHandles la propriété:
_eventHandles: null, Puis, dans le initializer méthode, je l'ai mis dans un tableau:
initialiseur: function (cfg) { this._eventHandles = []; / / ...... },
Dans le même initializer (également en bindUI s'il s'agissait d'un Widget ) Je voudrais ensuite joindre des écouteurs en faisant:
this._eventHandles.push (this.after ('someAttributeChange', this._afterSomeAttributeChange, this)); Puis, en destructor , j'ai:
destructeur: function () { Y.each (this._eventHandles, la fonction (poignée) { handle.detach (); }); },
C'est ici, dans le initializer , que vous brancher les écouteurs d'événements pour les attributs qui devraient produire des effets secondaires (vous pouvez le différer pour bindUI si cet effet secondaire doit faire face à l'interface utilisateur). Comme je l'ai dit plus tôt, les fonctions setter attribut ne devrait traiter que la normalisation de la valeur de l'attribut. Si ce paramètre produire des effets au-delà de stockage de la valeur, celles-ci doivent être manipulés par des écouteurs d'événements. Dans l'exemple ci-dessus, j'ai mis la méthode _afterSomeAttributeChange à écouter pour tout changement dans le someAttribute attribut. Les écouteurs d'événement recevront un seul argument, la façade événement qui j'ai l'habitude de téléphoner ev , un objet avec plusieurs propriétés, l'un d'eux, les newVal contenant la valeur à définir.
Propriétés de l'instance Widget
Deux propriétés importantes qui Widget utilisations sont BOUNDING_TEMPLATE et CONTENT_TEMPLATE . Les deux sont initialement configurés pour “<div></div>” qui produit la structure standard de deux conteneurs l'un dans l'autre que la plupart des widgets utiliser. Ceci, cependant, pourrait ne pas convenir à tous les widgets, par exemple, un Button graphique pourrait être mieux servi par un <span> élément au sein d'une ancre ( <a> ) élément au lieu de deux imbriqué <div> s. En fait, vous pourriez ne pas prendre soin d'avoir un contentBox du tout, Widget ne vous obligent pas à. Vous pouvez définir ces propriétés de l'instance de deux à n'importe quelle balise que vous voulez. Par exemple, pour le Button de classe I pourrait avoir:
BOUNDING_TEMPLATE: «<a>», CONTENT_TEMPLATE: null,
Avoir CONTENT_TEMPLATE ensemble à null dira Widget que vous ne voulez pas un contentBox du tout. Dans ce cas, le contentBox attribut de configuration va pointer vers le même élément que la boundingBox attribut de configuration fait.
Vous ne devez pas mettre dans ces modèles le code HTML entier pour le widget, faire de ces deux simples éléments HTML et de créer un balisage supplémentaire via le code dans renderUI (dont nous verrons plus tard).
Widget va ajouter un id attribut et les classes standard qu'elle utilise pour toutes les balises que vous voulez, comme yui3-xxxx , yui3-xxxx-visible ou yui3-xxxx-disabled , où xxxx est la valeur de la NAME propriété transformée en minuscule.
Méthodes d'instance Widget
Widget divise son initialisation en plusieurs étapes. Au-delà de l' initializer , appelée lorsque l'objet est instancié, et le destructor , appelé par destroy , les deux méthodes traités par Base , Widget ajoute renderUI , bindUI et syncUI pour la phase de construction, qui sera appelé dans l'ordre où Widget l ' render méthode est appelé.
Le renderUI méthode prend soin de produire le code HTML de base pour le widget. Tant le boundingBox et contentBox ont été rendus à ce point. Si vous utilisez l'amélioration progressive, renderUI doit d'abord vérifier si les éléments existent déjà sur la page. Si nous avons utilisé le HTML_PARSER propriété, alors les attributs de configuration en maintenant les références à ces éléments auront été mis en ce moment-là, si elle n'est pas, nous avons besoin de les créer.
Pour ce faire, la façon la plus simple (en supposant l'absence d'amélioration progressive) est d'utiliser Y.Node.create , comme ceci:
renderUI: function () { var cbx = this.get (CBX); cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE, CLASS_NAMES))); },
Cela suppose beaucoup de choses, dont je vais vous expliquer tout de suite. Tout d'abord, je dois le CBX constante déclarée comme indiqué dans la boîte de premier code dans cet article. Ensuite, il assume Node est chargé, ce qui Widget utilise donc il est prudent, mais elle suppose aussi Y.substitute est là, qui est facultative. Vous devez ajouter 'substitute' à l' requires la liste de votre module. Ensuite, il s'attend à un modèle correspondant au widget d'être dans un cadre statique appelée variable TEMPLATE qui est à vous de définir le long d'autres membres de classe statique (à droite par ATTRS et autres). Enfin, il suppose qu'il existe une constante CLASS_NAMES déclaré quelque part.
J'ai l'habitude de déclarer CLASS_NAMES dans ma définition du module, le long BBX et CBX (voir l'encadré premier code dans cet article), comme ceci:
var BBX = 'boundingBox », CBX = 'ContentBox », NOM = "bouton", / / Autres constantes et des raccourcis .... YCM = Y.ClassNameManager.getClassName, getClassName = function () { args = var Y.Array (arguments); args.unshift (NOM); retourner YCM.apply (ce, args) toLowerCase ().; }, LABEL = "label", PRESSÉ = 'pressed', ICON = «icône», CLASS_NAMES = { pressé: getClassName (pressé), icône: getClassName (ICON), label: getClassName (LABEL), nolabel: getClassName («non», LABEL) };
CLASS_NAMES sera alors une constante contenant un objet avec des propriétés créées par ClassNameManager (qui vient également inclus avec Widget ). Dans le code ci-dessus, j'ai d'abord créer le raccourci YCM pour faire accède plus tard, plus rapide, puis-je créer la fonction getClassName , une fonction privée qui n'est accessible que dans la définition du module. La fonction fonctionne un peu comme la méthode de la même nom de Widget , mais il est une fonction statique qui je peux utiliser pour définir plus précisément les valeurs statiques. C'est exactement ce que je fais, plus tard, lorsque je crée CLASS_NAMES comme un objet avec les noms de classes générées que leurs propriétés. Cela me permet d'écrire un TEMPLATE chaîne telle que:
MODÈLE: «<label class="{label}"> <input/>», Quel est assez stupide à ce jour. Je voudrais aussi à se fondre dans ces valeurs de modèles provenant d'autres sources, plus précisément, les attributs de configuration. C'est ainsi que j'arrive à le faire:
this.get (CBX). append (Y.Node.create (Y.substitute (TEMPLATE, CLASS_NAMES, Y.bind (fonction (touche, a suggéré, arg) { retour (touche === '_' this.get (arg):? suggéré); }, Ce))));
J'ajoute un troisième argument à Y.substitute , une fonction. Habituellement, les espaces réservés pour Y.substitute sont faites de personnages enfermés dans entre accolades, toutefois, si il ya un espace, il se divisera l'espace réservé en deux, la partie à l'espace est la clé et la seconde un argument optionnel. Cette pratique lorsque vient le troisième argument est une fonction, comme ici. La fonction recevra trois arguments, le premier est la clé, la seconde est la valeur trouvée dans l'objet de remplacement, ici CLASS_NAMES , le cas échéant, et le troisième est l'argument optionnel. Ainsi, dans la déclaration ci-dessus, je peux utiliser un modèle comme celui-ci:
MODÈLE: «<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" /> ', Y.substitute trouverez {label} et chercher dans CLASS_NAMES . Il sera le trouver et d'obtenir des 'yui3-button-label' . Il va alors appeler la fonction de remplacement avec des arguments 'label' , 'yui3-button-label' et undefined . Depuis key n'est pas égal à '_' il sera de retour la valeur dans le deuxième argument, le nom de classe d'origine. Quand il arrive à {_ id} , il n'y a pas de valeur pour une propriété appelée _ dans CLASS_NAMES il va appeler la fonction de remplacement avec des arguments '_' , undefined et 'id' . Avec key égale '_' , la fonction sera d'aller chercher la valeur de la 'id' attribut. Il fera la même chose de nouveau pour la {_ value} espace réservé.
Toutes les constantes déclarées dans la partie supérieure sont cachés de tout code extérieur du module, mais vous pourriez vouloir faire certains d'entre eux visibles, comme CLASS_NAMES . Pour ce faire, dans la section membres statiques, le dernier argument de Y.Base.create , vous pourriez avoir:
CLASS_NAMES: CLASS_NAMES Ensuite, l'objet de tous les noms de classe serait visible que Y.MyWidget.CLASS_NAMES .
Je vous suggère de faire la mise en forme autant que vous pouvez avec la chaîne HTML qui fera le contenu du widget. La manipulation de chaînes en JavaScript est plus rapide que l'accès au DOM donc plus vous ne avant d'appeler Y.Node.create par cette chaîne, plus vite vous aurez fait.
La méthode d'instance a ensuite appelé pour n'importe quel widget est bindUI . C'est là que vous attachez écouteurs d'événement à tous les éléments créés par renderUI , par exemple, l'auditeur pour toutes les modifications de la valeur dans le <input> boîte de l' TEMPLATE ci-dessus. La valeur de la zone de texte et que, dans l'attribut de configuration doit toujours être en synchronisation. La value l'attribut peut être modifié soit via le code ou par le typage utilisateur dans la zone de saisie. Si elle vient à partir du code externe, la zone de texte doit être actualisé, si elle provient de la zone de texte, il ne devrait pas, sinon vous risquez d'entrer dans une boucle infinie: le changement dans le champ définit la value l'attribut qui définit alors la value sur la zone de texte qui alors les changements et les ensembles de la value d'attribut et ainsi de suite. Permet de voir la façon de traiter ce cas. Nous avons mis un écouteur sur le synthétique valueChange événement sur la zone de saisie. Pour ce faire, nous devons ajouter l' event-valuechange module à l' requires la liste de ce module.
this._eventHandles.push (this._inputEl.after ('valueChange', this._afterInputChange, this)); Nous supposons l'objet a une référence à la zone de texte sauvegardés dans _inputEl . L'auditeur fait ceci:
_afterInputChange: function (ev) { this.set (VALEUR, ev.target.get (VALUE), {source: UI}); },
Ici, nous supposons que nous avons les constantes VALUE et UI a déclaré que 'value' et 'ui' , respectivement. Nous avons simplement définir l'attribut value à la valeur lue à partir de la zone de saisie. Cependant, nous ajoutons un troisième argument à la méthode des ensembles: {source:UI} . Le set méthode peut prendre un troisième argument, un objet, dont les propriétés seront mélangés dans la façade événement de l'événement changement d'attribut. C'est la façon dont nous pouvons faire la différence entre les valeur définie à partir de la zone de texte ou de code externe. En bindUI nous aurions dû mettre cet écouteur:
this._eventHandles.push (this.after («valueChange ', this._afterValueChange)); C'est l'auditeur pour un changement dans la value l'attribut de l'objet, l'autre était pour un changement dans la valeur de la <input> boîte, ils sont appelés le même, après tout, ils ont tous deux écoutent à l'évolution de quelque chose qui s'appelle la valeur, mais ne sont pas la même chose. Habituellement, les auditeurs pour des modifications d'attributs sont définis dans le initializer , mais puisque celui-ci affecte un élément d'interface, on le met dans bindUI afin que nous sachions la zone de texte seront là. L'auditeur aura:
_afterValueChange: function (ev) { if (ev.source === UI) { retour; } this._inputEl.set (VALEUR, ev.newVal); },
La première chose à faire est de vérifier la source l'événement. Si elle vient de l' UI , puis nous l'ignorons. Both the property name, source and its value, UI are arbitrary, those are the ones I used when setting the value attribute so those are the ones I check for in the listener, but any name/value would do just as well. Actually, Widget provides a constant for that, Y.Widget.UI_SRC , but it is kind of long so I would probably use a shortcut anyway.
Another tidbit: you can set attributes declared as read-only by using _set instead of set . The _set method is meant to be protected, to be used internally but, as we know, JavaScript knows nothing about security so _set is open to any but, at least, we try by declaring the attribute with readOnly:true and documenting it as such in the API docs.
Finally we declare syncUI . While the first two, renderUI and bindUI are going to be called once and only once, syncUI will be called at least once by Widget itself and you might call it several times afterwards. Its purpose is to refresh the UI to reflect the current state of the object. Since the state might change, the UI might need to be refreshed over time. However, I can't provide a simple recipe for handling this. For a simple UI, syncUI might refresh everything in the screen and be called every time anything changes. For more complex UIs refreshing the whole UI might take time and cause flickering so you might want to refresh only the bits and pieces you need. If so, you will have separate methods to refresh each of these parts and syncUI will call each of them just once. Moreover, as I've shown in the example for renderUI , I set the value of the textbox right there, though that should be done in syncUI .
In the more general case, you will have a function for each UI element that can be set separately. That function will be called once from syncUI , when initializing, and any number of times from the after attribute change event listener. For example, we could have:
_valueUIRefresh: function (value) {
this._inputEl.set(VALUE, value);
} Which could be called from syncUI along other similar setters:
syncUI: function () {
this._valueUIRefresh(this.get(VALUE));
// other such refreshers
}, and by the after listener:
_afterValueChange: function (ev) {
if (ev.source === UI) {
return;
}
this._valueUIRefresh(ev.newVal);
}, Communicating with others
Once you have the logic of one of your modules finished, you want it to interact with other modules on your page. If you've seen Nicholas Zakas video, you already know what tight and loose coupling is. Calling methods and setting attributes from one module to another means having those modules tightly coupled and it is the traditional way, so I won't talk about it since you know how to do it. The other way to do it is to fire custom events. Base already includes everything you need to do that.
First, in initializer , you publish the custom events you want everybody to find out about.
initializer: function (cfg) {
this.publish('eventName', { /*… options … */});
}, Normally, the name of the event will come from a constant, since you will use that same name every time you fire it and you don't want typos there.
Normally, when you have a reference to an object, such as:
var myWidget = new Y.MyWidget({ /* .. attributes … */ }); you can listen to its events by doing:
myWidget.after('eventName', this._eventNameListener, this); However, to do this, you need to have a reference to myWidget , which is not as tightly coupled as calling its methods directly but it is still quite tight: at least one module knows about the other or, perhaps, a supervisor module knows about both and sets the links in between them. Two options are important to get modules to communicate in between themselves, broadcast and emitFacade .
The first, broadcast , lets you set listeners for that event in other modules. When broadcast is left at 0, the default, you have to do as shown above. If you want the event to be listened to elsewhere, you will want broadcast set to 1, so events are broadcast within the same sandbox and sometimes 2, so they can go across sandboxes. In this context, a sandbox is what you get when you call:
YUI().use( 'module1′, …, 'moduleN', function (Y) {
// this is your sandbox
}); You can have several such sandboxes in your page:
YUI().use( 'module1′, …, 'moduleN', function (Y) {
// this is your sandbox
});
YUI().use( 'moduleX-1′, …, 'moduleX-N', function (Z) {
// this is another sandbox
}); If you set broadcast to 2, then an object in the second sandbox can listen to an event when fired in the first. You can see the details in the Event user guide . Lets just stick to the simple sandbox case.
To listen to an event fired from another module within the same sandbox you need to know the value of the NAME static property of that module and the name of the event. Remember, Y.Base.create takes, as its first argument, the value that it will use for its NAME property, thus, if you created a module in this way:
Y.MyWidget = Y.Base.create(
'xxxx',
Y.Widget,
// … and so on and then, in the initializer you published the 'help' event like this:
initializer: function (config) {
this.publish('help', {
broadcast: 1,
emitFacade: true
});
}, To listen to that event in any other module within the same sandbox, you do:
Y.after('xxxx:help', function (ev) { … }, this); Here, I am calling Y.after , not myWidget.after , I don't need to have a reference to the module firing the event. This is the same method used to listen to DOM events or other synthetic events such as 'valueChange' the only difference being the prefix, the part before the colon. Base already takes care to prefix all events with the value of the NAME property so you don't have to take care of that when publishing them. You can do so, you can even use something else as a prefix; if one such prefix is there, Base will respect it, but usually you just want the default, which Base provides.
You also want to set emitFacade because you will want to have a reference to the instance that fired the event, which the event facade provides in ev.target . But wait, if the listener module gets a reference to the firing module, don't they become tightly coupled once again? Not quite, as long as you don't preserve that reference in the listening module, the coupling will be volatile. Still, we can do better.
When firing the event we may add all the information the listener needs in the facade, like this:
this.fire('help', {helpTopic: 'Event Broadcasting'}); Method fire takes the name of the event being fired (which Base will further prefix with the NAME of the class) and an object containing any number of properties which will be merged into the event facade. The listener then doesn't need to query the firing module for any information, all that might be needed is there. This is as loose as it gets. The listener simply knows that some module, and there may be many such modules, is asking for help on 'Event Broadcasting' and that is really all it needs to know. It doesn't even care which module asked for it. New modules may be added later and the help system will also work for them.
Events and Default Behaviors
The usual solution to changing the behavior of a class is to sub-class it so you can override one of its functions and do whatever it is you want to do instead. You can still do that. You can use Y.Base.create to define a module based on, say Y.Widget and then use Y.Base.create again using your new module as the base to change a particular behavior. For example, I might have:
Y.MySimpleWidget = Y.Base.create(
'simpleWidget',
Y.Widget,
[],
{
// instance members here, amongst them:
renderUI: function () {
this.get(CBX).append(Y.Node.create(' … whatever goes into the widget … ' ));
}
},
{
ATTRS: {
// configuration attributes
}
// other static members
}
); and then:
Y.MyFancyWidget = Y.Base.create(
'fancyWidget',
Y.MySimpleWidget,
[],
{
renderUI: function () {
Y.MyFancyWidget.superclass.renderUI.apply(this, arguments);
this.get(CBX).append(Y.Node.create(' … add some bells and whistles … ' ));
}
// Presumably the fancy version does not need any further static members so I skip the last argument
); MyFancyWidget improves over MySimpleWidget by adding some bells and whistles. This might be too much of a trouble in some cases, you might plan for a base class more flexible and easier to change. Custom events can help with that.
Imagine you have a class that has a sort function. The sort function takes a key and direction argument and is declared like this:
sort: function (key, direction) {
// sorting happens here
}, If you know that the behavior of that function might be changed in some circumstances, you might do the following. In the initializer method, you can have:
initializer: function (config) {
// amongst many other things:
this.publish(SORT, {defaultFn: this._defSortFn});
}, Where SORT is a constant containing 'sort' . Then, you declare the sort function like this:
sort: function(key, direction) {
this.fire(SORT, {key:key, direction:direction});
}, The sort function simply transforms the standard function call into a fired event containing the same arguments. Though this is meant to provide alternatives, you still want the class to sort somehow, you do that through the default sort function:
_defSortFn: function (ev) {
var key = ev.key, direction = ev.direction;
// same code as the original sort function
}, The class will do sort as before, the body of _defSortFn might be just the same as the original one, once you have read the key and direction arguments from the event facade, but any other piece of code can set a listener for that same sort event and change it, for example:
myObjectThatSorts.on('sort', function (ev) {
var key = ev.key, direction = ev.direction;
ev.preventDefault();
// now do your own sort
}); By calling preventDefault I tell myObjectThatSorts not to call _defSortFn . I could do this conditionally and decide, based on whatever I want, whether I may leave the original sort go ahead or unconditionally stop it, as I did here. I might not even care to stop it ever, I might listen to the after event and simply flip an arrow somewhere in the UI to signal which way the sort went.
I may also alter the event facade. There is only one copy of the event facade that gets built when the event is fired and it is propagated through all before (on) listeners, to the default function and then to the after listeners until finally it is dropped. You can change the values of its properties at any point. Of course, it hardly matters any changes you might do after the default function is called but any changes done in the before (on) listeners will reach the default function, for example:
myObjectThatSorts.on('sort', function (ev) {
ev.direction = (ev.direction==='desc'?'asc':'desc');
}); This would get the sort done upside down.
YUI_config
The easiest way to get your module on your page is to include it in its own <script> tag or in a script tag pointing to a combo URL (via creating a file on the server that is a manual concatenation of files or a combo service is the server supports one). Integrating custom modules into the Loader is a more advanced option, though it might improve performance. The important point in this case is to make sure the YUI.add() includes the requires: [...] in the last parameter, so use() will apply the module and its dependencies in the proper order.
For small applications, you will probably have everything loaded from the start as outlined above. However, for larger applications, you might not want everything loaded from the start since it can take too long. You can call use() more than once to request extra functionality as needed. However, having the Loader find out about each module's dependencies when it loads each is time consuming since it might take several sequential requests until it finally gets everything it needs. Instead, you can forewarn the Loader of your modules and their dependencies so, when the time comes, it knows how to deal with them and can load them all in parallel.
To do so, you need to add the module description and requirements to the tables that the YUI Loader uses to fetch modules. The easiest way is to build a yui_config.js file (or whatever you want to call it) that contains all those definitions. That file will look like this:
YUI_config = {
filter:'raw',
//combine:false,
gallery: 'gallery-2011.02.18-23-10′,
groups: {
js: {
base: 'build/',
modules: {
'myWidget': {
path: 'myWidget/myWidget.js',
requires: ['widget', 'widget-parent', 'widget-child', 'widget-stdmod', 'transition'],
skinnable: true
},
// other modules here
}
}
// other groups here
}
}; You include this file in a regular <script> tag in your HTML file before you issue the first YUI().use() statement. They replace those options you would otherwise place as the first argument to YUI().use() , as if you did YUI(YUI_config).use() , but YUI does it for you. You can use any of the options listed here .
The filter option can be set to 'min' for production code (the default so you would usually comment out), 'debug' for the fully expanded with log statements (which might overwhelm your console) and 'raw' for fully expanded without log statements, the last two used only in development. Likewise with the combine option, only used when you have really tough bugs and you want to find out what is going on and get lost in those huge combos. Then you put your gallery option, if you use any gallery modules, to freeze your gallery modules to a version you know it works.
The groups option is where you start describing your own modules. The first name, in this case js , can be anything, whatever you want to call your group of files. You could create one such group for each family of files in a common location. The first declaration in each group is the base location of the group of files relative to the home page or an absolute path. That is, basically, the criteria for grouping files, however, there are several more options, listed here .
Finally, in the modules section you start listing your modules. The key for each entry is the module name, the very same name that you have used as the first argument in the YUI.add in your file and the same that you will use in the module list when you issue the YUI().use() call in your application. Then you specify the location of the module file, relative to the previous base or the fullpath if located elsewhere, and the rest of the options that where at the very end of the YUI.add declaration and are listed here . The requires list can list YUI modules, gallery modules or modules of your own either within the same group or from other groups in your config file. Skins will be loaded automatically by setting skinnable:true if you locate them as I recommended at the beginning of this article.
To simplify things for myself, I created a Windows script file that builds the YUI_config options for me. It basically scans the folder with the module files and reads each of them and extracts the information from each YUI.add call by defining a fake YUI.add function that extracts the arguments for me. It makes plenty of quite simplistic assumptions but it works for me as it is, you use it at your own risk.
Conclusion
YUI3 is very flexible and you can build your modules in many ways. This is no more than one way to do that; I don't always do it this way, sometimes, not often, I don't need all of what Base provides so Y.Base.create is of no use, but this works most of the time.
Share and extend: Bookmark with del.icio.us | digg it! | reddit!
18 commentaires
Désolé, les commentaires sont fermées à cette heure.

Copyright © 2006-2012 Yahoo! Inc Tous droits réservés. Politique de confidentialité - Conditions d'utilisation
Propulsé par WordPress sur Yahoo! Hébergement Web .

Thanks a lot for this article. It is difficult to find informations about skins in the official doc. It is now much clearer for me.
Comment by plv — April 1, 2011 #
L'article Awesome!
Thank you, Satyam!
Comment by John — April 1, 2011 #
Magnifique article!
Comment by Daniel Stockman — April 1, 2011 #
“This is a JavaScript issue, not a YUI one.”
That is NOT issue, but pretty normal behavior. :)
Comment by John — April 3, 2011 #
Thanks for the article! Cleared a lot of doubts that I had.
I have one question though :) I don't quite understand the use of syncUI method. You have stated its use as “Its purpose is to refresh the UI to reflect the current state of the object”. Aren't you doing that in bindUI() method when you register afterValueChange events for widget's attributes to refresh UI?
Comment by Vignesh — April 5, 2011 #
i am just start to learn YUI3, i think this article will help me to understand it deeply.
Comment by lily — April 5, 2011 #
[...] mobile support, a “Simple” theme, a new kitchen-sink-like Widget Browser, and more A Recipe for a YUI 3 Application – Satyen Desai of the YUI team goes into detail about how to organize a YUI application WebGL [...]
Pingback by JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: iOS viewport fixes, Mobile Boilerplate, CommunityJS, Ender.js — April 8, 2011 #
I've put it into Chinese.
http://ued.taobao.com/blog/2011/04/14/a-recipe-for-a-yui-3-application/
Comment by jayli — April 14, 2011 #
@Vignesh:
Regarding the syncUI method, the listeners you add in bindUI dont get fired during sync, because the object state at the time you bind the listeners has already been set, so there's no change in the state.
I'll use pseudo code to help explain:
var obj = new Y.Widget({attr1: 55});
//attr1 state is now 55
obj.render()
So during render, since nothing changes, no change event is fired, hence why you may need to call the syncUI method.
Ideally, you should have a _uiSetAttr1 method that is just called by the after change event, and can be called during sync that simply handles updating the ui in response to that attr changing.
There are also properties that help make this glue wiring easier. Off the top of my head I don't remember if they're private still, but if you set them (I think it's either BIND_UI or BIND_UI_ATTRS and same for SYNC) all you have to do is set a method called _uiSetAttrname and list the ATTRS in that object, and it will automatically do all of that wiring for you.
I hope that helps :)
Comment by Nate Cavanaugh — April 16, 2011 #
[...] Recipe for a YUI Application Building Reusable Widgets with YUI3 Alloy UI [...]
Pingback by Build A Chrome App with YUI – (1) « Triptych — April 17, 2011 #
Nate
You are referring to property _UI_ATTRS which is an object with two properties,
BINDandSYNC. As its initial underscore signals and the documentation states, they are private and unless you really know what you are doing, it is better keep your hands off them. (I mean 'you' in generic terms)Each of these has an array of attribute names which are already initialized by Widget so any changes need to be done carefully not to destroy their initial values.
For those listed in the
_UI_ATTRS.BINDarray, an “after change” listener will be attached before ourbindUImethod which will call a method named like_uiSetXxxx, wherexxxxis the name of the attribute.Likewise, configuration attributes named in
_UI_ATTRS.SYNCwill have this same_uiSetXxxxcalled right before our ownsyncUImethod. Each of these_uiSetXxxxmethods will receive the value of the attribute, either the initial value or the changed value.Indeed, this is a clever mechanism and it would be great if it was made public, or an alternative public equivalent were to be provided and thus supported in the long term. A formal statement by the YUI team regarding the long-term support for this mechanism would be welcome.
Nevertheless, I should have mentioned it or suggested a similar custom approach. So, since the Pandora's box is open, let me explain it.
Say you have an attribute “myAttr” which has an effect on the UI. You need to provide a method called _uiSetMyAttr (note the M in myAttr turned uppercase) which receives the new value to set and a second argument which might either be
undefined(not there) or set to Widget.UI_SRC and affects the UI when the second argument is not set. You would define it in the instance member section like this:_uiSetMyAttr: function (value, src) {if (src === Y.Widget.UI_SRC) { return; }// set the UI element}To get the UI set initially, right before your own
syncUIis called, push the name of the attribute into_UI_ATTRS.SYNC, usually in theinitializer, like this:this._UI_ATTRS.SYNC.push("myAttr");To have it further called after any change in the attribute, push it also into
_UI_ATTRS.BIND, like this:this._UI_ATTRS.BIND.push("myAttr");Merci pour le tuyau.
Comment by Satyam — April 17, 2011 #
Here's the enhancement request:
http://yuilibrary.com/projects/yui3/ticket/2529439
Comment by Satyen Desai — April 18, 2011 #
Heya Satyam, thanks for the detailed writeup. We actually support this as a public attribute in our Alloy Component gallery module since it's so useful, but we allow it to be defined on the class level and handle automatically copying of the arrays so that if you pass in a custom set it is merged with a copy of the defaults instead of having to create a new array and concat it (or doing it in the initializer).
The only downside I've found to the API (and it's a small one at that) are the times when I want other data from the event passed in (maybe the second or third arg would be good to have the event object being passed in).
Btw, great article, thanks for consolidating this all here :)
Comment by Nate Cavanaugh — April 21, 2011 #
[...] Recipe for a YUI 3 Application 原文地址:http://www.yuiblog.com/blog/2011/04/01/a-recipe-for-a-yui-3-application/ 译文:使用YUI [...]
Pingback by [译]使用YUI 3开发Web应用的诀窍 « Uedmagazine — April 22, 2011 #
A correction to my previous comment :
The extra attributes to monitor should be added to the
_UI_ATTRSarrays via method concat() not push():this._UI_ATTRS.SYNC.concat("myAttr");this._UI_ATTRS.BIND.concat("myAttr");Using push() will add the new attribute to an array which is shared by Widget and all its subclasses while concat() will create a new copy for this particular subclass
BTW: concat() allows several values to be concatenated at once:
this._UI_ATTRS.BIND.concat("myAttr1","myAttr2","myAttr3");Comment by Satyam — May 14, 2011 #
Sorry I missed once again in my correction . The way to add an attribute to be handled by Widget is this:
this._UI_ATTRS = {BIND: this._UI_ATTRS.BIND.concat("myAttr"),SYNC: this._UI_ATTRS.SYNC.concat("myAttr")};Method concat() does not modify the array it applies to so the code in the previous comment does nothing. Sorry about the confusion.
Comment by Satyam — May 14, 2011 #
Hi Satyam. Grand article! I'm in doubt with something and hope you could clarify this issue for me. You wrote: “Base cannot detach any event listeners at all”. But Base's destroy() method calls detachAll(), inherited from EventTarget, which according to the documentation, it should remove all listeners. Qu'est-ce que je suis absent?
Comment by alejandroci — May 25, 2011 #
alejandroci ,
You are right, the wording of that phrase is not right, it should have been 'Base cannot remove all events'. Indeed, it does remove some via
detachAll(), which it inherits from EventTarget which comes along Attribute. Event Target is capable of removing all events created by itself viathis.onandthis.after.It cannot detach those subscribed to via
Y.on,Y.afteron events broadcast by other objects (subscribed via the" publisher : event "notation) or via a reference to some other object (myPublisher.on()).Widget can only detach events picked by delegation to its bounding box.
So, YUI tries to keep it safe as much as possible with what it knows but it can't do it all, not with any reasonable amount of code. If you forget, YUI will cope as best as it can, but it is better if you do it yourself.
Comment by Satyam — May 26, 2011 #