Ein Rezept für ein YUI 3 Application

1. April 2011 auf 2.52 von Satyam | In Entwicklung | 18 Kommentare

YUI 3 wurde entwickelt, um Anwendungen rund um Module zu bauen. Ich will nicht diskutieren, was ein Modul, da es wurde auch von Nicholas Zakas in seinem Vortrag beschrieben wird Scalable JavaScript Application Architecture . Ich werde einfach, wie man diese Module zu bauen Stick. Das meiste, was ich sagen kann, in der Online-Dokumentation, zusammen mit einigen anderen Alternativen, nachdem alle, dass der Punkt der gute Dokumentation ist zu finden:, Sie über alle Möglichkeiten, Dinge zu tun erzählen. Deshalb ist dies ein Rezept, nur eine Möglichkeit, es zu tun und vieles mehr. Es nimmt auch ein kleineres Anwendung, mit nicht so vielen Schichten wie Nicholas 'sagt, denn dies ist nur ein Artikel und nicht ein Buch.

Identifizierung der Module

Der erste Schritt ist, um die Module benötigen wir identifizieren. Ein guter Ansatz ist, um zu starten Slicing das Design der Anwendung Bildschirm in einzelne Abschnitte: Titelleiste, Menüleiste, die Inhalte, Seitenwände oder was sonst könnte es da sein. Dann werfen Sie einen Blick an, was die Bibliothek zu bieten hat. Zum Beispiel hat YUI 3 Nr. Menu, aber es ist die Node-MenuNav Plugin , welches eine grundlegende Menüstruktur verschachtelte ungeordnete Liste <UL> Elementen aufnimmt und verwandelt sie in eine aktive Menü. Oder möchten Sie vielleicht den Scheck YUI Galerie für Basiskomponenten. Wie auch immer, du wirst irgendwann den Punkt erreichen, wo man einen Kasten in das Layout, das Sie an sich selbst zu füllen haben, so lassen Sie uns das tun.

Ich empfehle Platzierung jedes Modul in einer eigenen Datei und ein eigenes Verzeichnis mit dem gleichen Namen. So ein weather hätte Modul in sein weather/weather.js . Der Grund dafür ist, dass Ihr Modul wird wahrscheinlich einige Styling, einige CSS-und Bilddateien erfordern, macht es einfach für den eingebauten Loader, wenn man sie, wo sie leicht finden können, in diesem Fall die Haupt-Stylesheet wäre in weather/assets/skins/sam/weather.css , mit den anderen Aktiven, Bilder und so weiter, neben. Dies ist vorausgesetzt, Sie sind nicht mit dem YUI Builder, die bereits tun die Dinge auf diese Weise sowieso, aber das ist eine andere Geschichte. Die Ordnernamen assets und skins sind mehr oder weniger selbsterklärend, sam ist jedoch nicht ganz klar. Es ist der Standardwert für die skin -Eigenschaft des Loader denn das ist der Standard-Skin mit YUI ausgeliefert, nachdem die Designer Sam Lind benannt. Wie dies deutet darauf hin, Ihnen frei, Ihren Namen auf Ihre eigenen Skins abgelegt werden und die skin -Eigenschaft können Sie YUI erzählen, sie zu laden, aber es einfach zu halten, lass uns einfach mit den Standardeinstellungen zu gehen.

Module Dateivorlage

Dies ist die Datei-Struktur I öfter benutzen, die ich in einem Moment beschreiben werde:

  / * JSLint devel: true, undef: true, newcap: true, strict: true, maxerr: 50 * / 
 / * Global YUI * / 
 / ** 
  * Das Modul-name-Modul schafft die blah blah 
  * @ Modul module-name 
  * / 
 YUI.add ('module-name', function (Y) { 
     "Use strict"; 
     / / Handy-Konstanten und Verknüpfungen in das Modul eingesetzt 
     var Lang = Y. Lang, 
         CBX = 'Contentbox " 
         BBX = 'boundingBox "
         NAME = 'xxxx'; 

 
     / ** 
      * Die Xxxx-Klasse hat .... 
      * @ Class Xxxx 
      * @ Erstreckt Widget 
      * @ Nutzt WidgetParent 
      * @ Konstruktor 
      * @ Cfg {object} Konfigurationsattribute 
      * / 
     Y. xxxx = Y.Base.create ( 
         NAME, 
         Y. Widget, 
         [Y. WidgetParent] 
         { 
             / / Instanz Mitglieder hier 
         } 
         { 
             / / Statische Mitglieder hier speziell: 
             ATTRS: { 
             } 
         } 
     ); 

 
 }, '0 ,99 ', { 
     erfordert: ['Widget', 'Widget-parent'], 
     skinnable: true 
 }); 

Die ersten beiden Zeilen der Kommentare sind zum Wohle der JSLint , die JavaScript-Verifikations-Tool, das ich wirklich empfehlen. Wenn Sie den go Web-Version gibt es ein Feld, um Optionen zu setzen. Am unteren Ende, dass die Optionen-Box sehen Sie die Möglichkeit, diese Optionen in der Datei selbst zu codieren. Wenn Sie das YUI Builder verwenden, wird es JSLint für dich laufen und legen Sie diese Optionen für Sie, aber Sie können immer noch überschreiben sie für jede einzelne Datei, wenn Sie es wünschen.

Die doc Kommentare sind für den YUI API docs Builder. Es ist weniger über Kopfschmerzen, wenn Sie die erste Vorlage für die API-Dokumentation enthalten, irgendwann werden Sie füllen. Da die Anwendung wächst, und Sie sind nicht in der Lage, um alles zu erinnern, werden Sie sie brauchen.

Jetzt kommt der erste eigentliche Codezeile, die YUI.add() -Anweisung. Dies ist der Weg zu den YUI Loader den Namen und die Inhalte des Moduls und einige andere Stücke von Informationen zu erzählen. Module Namen sind in der Regel mit allen Kleinbuchstaben und Bindestriche zwischen Wörtern benannt. Das sind die Namen, die Sie sehen in der API Docs in der obersten Index auf der linken Seite. Sie können die Konvention nicht strikt befolgt, tun sich einige Modulnamen keine Bindestriche. Auf jeden Fall ist dies meist an Ihnen, solange Sie es konsequent um.

Das zweite Argument der YUI.add() -Anweisung ist eine Funktion, die ein einzelnes Argument, üblicherweise als erhält Y . Diese Funktion enthält den Körper des Moduls und Y ist der Verweis auf ein Sandbox-Instanz von YUI, die wo man all die anderen YUI und Gallery Module, die Sie gefragt haben, zu finden ist. Jumping auf den Boden, die Code-Box können Sie sehen, der Rest der Argumente der .add() -Methode, die Version ( '0.99′ oder noch nicht ganz) und die Konfiguration für das Modul, ein Objekt mit aa Reihe von Eigenschaften. Hier, sage ich den Loader, dass dieses Modul erfordert widget und widget-parent und dass sie eine Haut. Listing widget ist überflüssig, weil widget-parent erfordert bereits widget , sondern bemühe dich nicht mit, dass die Loader nicht laden ein Modul zweimal, und wenn zu einem späteren Zeitpunkt you drop eine Abhängigkeit, Sie brauchen nicht zu prüfen, für andere Annahmen, die Sie gemacht haben könnte: Staat sie alle und lassen Sie den Loader mit ihr umgehen. Hier finden Sie eine Liste aller Optionen in der API-Dokumentation für die .addModule() -Methode des Loader.

Im Körper der Funktion ist das erste, was die “use strict”; Erklärung. Dies ist für Ihren Code, mit dem ECMAScript-5-Norm, die an diesem Punkt setzt Sie auf der sicheren Seite, um die Kompatibilität mit allen Plattformen werden Sie wahrscheinlich in der Zukunft begegnen zu gewährleisten entsprechen. Für ältere Dolmetscher, ist diese Erklärung nichts anderes als eine Zeichenkette, die nichts zu vergeben, und wird ignoriert. Die “use” Erklärung hat Funktionsumfang und es ist sicherer, es im Hauptteil der Funktion statt als am Anfang der Datei, so dass es wirkt sich nur auf das Modul, die Sie definieren. Wenn Sie es Platz am Anfang der Datei, wäre es auch für alle anderen JavaScript-Datei danach laden gelten, und viele von ihnen möglicherweise nicht mit ES5 entsprechen.

Dann kommen die Abkürzungen und Konstanten, die Konstanten nur in Verwendung sind, da JavaScript kennt das Konzept der Konstanten. Wir nennen sie in all-in Großbuchstaben und unterstreicht, wie Konstanten oft in anderen Sprachen sind. Es gibt zwei gute Gründe, um Konstanten verwenden, die speziell String-Konstanten. Erste ist, dass, wenn Sie die gleiche Zeichenfolge schreiben mehrmals, Sie könnten einer von ihnen vertippt und du wirst nur bemerken, wenn ein Fehler erscheint. Wenn Sie Konstanten verwenden, wird JSLint warnen, wenn Sie den Namen der Konstanten falsche Art, denn es wird nicht definiert. Der zweite Grund ist, dass die YUI Compressor können einen besseren Job zu tun, da Namen von Konstanten, während String-Literale können nicht komprimiert werden kann. Gute Kandidaten für benannte Konstanten sind die Namen der Konfiguration Attribute und Ereignisse.

Shortcuts, wie Lang für Y.Lang sind auch gut, weil sie Sie weniger Art ermöglichen, hat der Dolmetscher weniger zu bewerten (jeder Punkt bedeutet eine neue Suche in das Objekt Mitglieder) und sie können von den YUI Compressor komprimiert werden.

Nach dem API docs Kommentare für die Klasse, wir die tatsächliche Erklärung zu bekommen. Wir müssen unsere Klasse als Eigenschaft erklären Y , so Y.Xxxx . Mein Vorschlag ist, benutzen Y.Base.create() , um es zu schaffen, wie hier gezeigt. Es können nur Klassen abgeleitet Base (und Widget , das auch eine Unterklasse von Base ), aber das werden die meisten Module, die Sie verwenden Decke, damit es ungewöhnlich, müssen sie in anderer Weise zu tun ist. Das erste Argument ist der Name des Moduls, die NAME Eigenschaft für die beschriebenen Base Komponente. Üblicherweise werden die NAME ist Eigentum ein Kamel-case-Version den Namen der Klasse. Dieser Name wird als Präfix für Ereignisse (der Teil vor dem Doppelpunkt dergleichen verwendet “io:success” ), für die CSS-Klasse Namen, die von den generierten Widget -Klasse (zB: “yui3-xxxx-content” ) und für die Default-Implementierung von toString() , die man oft in Spuren sehen, die vom Debugger. Hier benutze ich den Wert des konstanten NAME , um die Klasse zu definieren NAME Eigentum.

Das zweite Argument ist die Klasse, Extents. Sie werden oft entweder Y.Base für Utility-Module, die keine Benutzeroberfläche haben wird Y.Widget für diejenigen, die eine Benutzeroberfläche haben wird, Y.Plugin.Base für Plugins oder jede andere Klasse abgeleitet Y.Base , ebenso wie alle Sie können bereits erstellte mit Y.Base.create() .

Das dritte Argument hält die Erweiterungen, die Sie verwenden möchten. Erweiterungen sind Klassen, deren Eigenschaften und Methoden, die Sie in Ihrer Klasse gemischt haben. Gute Kandidaten für Erweiterungen sind ArrayList für Base oder einen der Widget-Xxxx -Module für Widget . Attribute , EventTarget und PluginHost kommen bereits in gemischten Base , so dass Sie immer auf diese drei zählen kann da zu sein. Extensions sind sehr mächtig, wenn man sich den Blick Quellcode für Overlay können Sie sehen, es ist nichts anderes als Widget und Erweiterungen. Mehrere Erweiterungen können in eine Komponente gemischt werden, so das dritte Argument ist ein Array.

Schließlich kommen wir zum eigentlichen Code. Die vierte und fünfte Argumente sind sowohl Objekt-Literale mit der Instanz-und statischen Member der Klasse. Instance-Mitglieder sind die Eigenschaften und Methoden, die in die Klasse gehen prototype , diejenigen, die jede Instanz eine Kopie bekommen und müssen in der Regel Hinweise werden von this . Statische Mitglieder sind jene, die von allen Instanzen gemeinsam genutzt werden.

Configuration Attributes

Die wichtigsten dieser statische Member ist das ATTRS Eigentum. Dieser listet die Konfigurations-Attribute Ihrer Klasse haben wird. Zum Beispiel, sagen können wollen wir eine Konfiguration Attribut namens haben value , um numerische Werte zu halten und zunächst auf 0 gesetzt. Im fünften Argument, würden wir erklären es so:

  ATTRS: {
     Wert: {
         Wert: 0,
         validator: Lang.isNumber
     }
 } 

Wir können Sie eine beliebige Anzahl von Attributen in der ATTRS Eigentum und jeder kann mit verschiedenen Optionen, von denen zwei, die ich hier gezeigt konfiguriert werden. Sie können über den Rest der Optionen in der Lese addAttr() -Methode der Attribute . Wie man sehen kann, habe ich die verwendeten Lang Verknüpfung, die ich am Anfang des Moduls deklariert. Der validator ist eine Funktion, die den Wert zu überprüfen und gibt einen booleschen dauert. Alle Y.Lang.isXxxx Methoden genau das tun, so dass sie direkt verwendet werden kann. Für aufwendigere Prüfungen, Setter oder Getter, müssen Sie Funktionen zu definieren. Ich empfehle die Bereitstellung der Name der Funktion als String, Attribute kümmert sich die Lösung der Name der Funktion, um die eigentliche Funktion. Zum Beispiel, wenn ich zu einem, sagen wir, zu definieren waren validCodes Attribut, entweder kann ein einziger gültiger Code oder ein Array von gültigen Codes sollte aber immer ein Array, würde ich tun:

  ATTRS: {
     validCodes: {
         Setter: "_setValidCodes"
     }
 } 

Wir müssen die erklären _setValidCodes Methode entlang der anderen Instanz Mitglieder in das vierte Argument der Y.Base.create() :

  _setValidCodes: function (value) {
     if (! Lang.isArray (value)) {
         Wert = [Wert];
     }
     Rückgabewert;
 } 

Am besten ist es Setter, Getter und alle außer den trivialen von Prüfungen als separate Instanz Funktionen zu deklarieren und lassen Attribute lösen den Namen der Funktion in die eigentliche Funktion aufrufen.

Im Allgemeinen verwenden Setter, um den Wert zu normalisieren, wie oben gezeigt, nicht auf sekundäre Effekte zu produzieren. Alle Konfigurations-Attribute werden vor und nach der Änderung Veranstaltungen mit dem vor Veranstaltungsbeginn in der Lage, das Attribut aus wechselnden Vermeidung von Feuer. Verwenden Sie diese ändern Ereignisse keine Nebenwirkungen, nicht die Setter zu produzieren. Die nach der Veranstaltung ist die beste seit der dann weißt du, dass nichts verhindert das Attribut entfernt, eingestellt werden, da ein anderer Code, um die vor (auf) change-Ereignis abonniert haben könnte und annullierte es.

Sie können Ihren Attribut mehr oder weniger strenge je nachdem, wie definieren Sie Ihre Validator-und Setter. Wenn Sie den Validator sehr restriktiv zu machen, wird Ihr Attribut ist sehr streng, akzeptieren nur gültige Werte, in diesem Fall die Setter könnte unnötig sein. Auf der anderen Seite, können Sie nicht mit einem Validator überhaupt und verlassen sich ganz auf die Setter auf einen beliebigen Wert in etwas akzeptabel erhalten Massage. Zum Beispiel können Sie einen der beiden:

  validator: Y.Lang.isBoolean, / /, um das Attribut akzeptiert ausschließlich ein Boolean
 setter: Boolean, / /, um das Attribut übernimmt keinerlei Wert haben und Boolean schalten Sie ihn in eine 

Setter kann auch als Validatoren dienen. Setters sollte den Wert, der dem Attribut zugewiesen werden, sondern sie können auch wieder Y.Attribute.INVALID_VALUE die das Attribut nicht werden, als ob ein Validator es abgelehnt hatte.

Wenn ich eine Konfiguration Attribut definieren ich oft definieren eine Konstante für das, was ich Platz an der Spitze des Moduls (zusammen CBX , BBX sowie die Verknüpfungen), zum Beispiel:

  var VALUE = "Wert",
     VALID_CODES = 'validCodes'; 

Die Chancen stehen, dass ich verwenden diese Konfiguration Attribute mehrmals innerhalb des Moduls und das spart mir einige Sorgen. Seien Sie jedoch vorsichtig, verwenden Sie nicht, dass konstant bei der Deklaration das Attribut, tun dies nicht:

  ATTRS: {
     / / *** Tu das nicht *** / /
     VALUE: {
         Wert: 0,
         validator: Lang.isNumber
     }
 } 

Wenn Sie dies tun, würden Sie ein Attribut namens VALUE statt value . Dies ist ein JavaScript Problem, nicht ein YUI eins. Auch darauf achten, nicht zu überschreiten auf einem der Konfiguration Attribute bereits entweder für die Basisklasse oder eine der Erweiterungen erklärt. Widget hat bereits eine Reihe von Attributen deklariert (siehe Tabelle ), und obwohl man kaum hätte fügen Sie ein boundingBox Attribut selbst, Sie könnte leicht vergessen, dass visible , disabled , height oder width sind bereits definiert. Wenn Ihr Verwendungszweck übereinstimmt, was Widget nutzt sie für, alles wird in Ordnung sein. Das heißt, Sie können die Definition einer von ihnen zu verändern. Y.Base.create() führt die Definition der Konfiguration Attribute der Erweiterung mit denen der Basisklasse so, wenn Sie zu verändern, zu sagen, der Standardwert für wollen ein vorhandenes Attribut, können Sie dies tun, indem er erklärte, dass Attribut wieder in der Unterklasse.

Seien Sie vorsichtig, wenn Sie ein Attribut mit einem Array oder ein Objekt zu initialisieren bedeuten. Objects (und Arrays sind Objekte) werden per Referenz übergeben und wenn Sie ein Attribut zu initialisieren mit einem Gegenstand, sie könnten am Ende alle auf dasselbe Objekt, so dass, wenn Sie ändern Teil davon (entfernen Sie ein Element aus einem Array oder eine Eigenschaft hinzufügen , um das Objekt) Sie am Ende verändert alle Instanzen auf einmal. Base hat jedoch einige interne Logik, die Ihnen erlauben, sicher zu initialisieren ein Attribut mit Objekt-und Array-Literale werden. Wenn die Initialisierung Wert ist ein Objekt oder Array-Literal dann Base wird klonen. Verwenden Sie die valueFn Option oder initialisieren Sie sie in die Klasse Initialisierung für andere Objekte.

Andere Static Mitglieder

Es gibt zwei weitere statische Mitglieder, die Sie in die fünfte Argument definieren könnte Y.Base.create() . Wenn Sie ein Plugin gibt, müssen Sie unbedingt erklären, die NS -Eigenschaft, wenn Sie nicht tun, das Plugin nicht funktionieren wird und stillschweigend fehl. Die NS -Eigenschaft muss auf einen String, der als der Name der Eigenschaft verwendet werden, um das Plugin in den Host-Objekt zu speichern, beachten Sie dies, wenn Sie den Namen auswählen, so dass Sie nicht überschreiben vorhandene Eigenschaft festgelegt werden.

Wenn Sie ein Widget sind und Sie planen es zu Progressive Enhancement unterstützen, dann werden Sie die HTML_PARSER statische Eigenschaft. Dies ist ein Objekt, das Eigenschaften, nachdem die Konfiguration Attribute mit dem Namen von Parsen des bestehenden HTML und entweder CSS3-Selektoren oder Funktionen, die ihre Werte erzeugen wird Set enthält gesetzt. Siehe Progressive Enhancement in der Widget-Benutzerhandbuch.

Vielleicht wollen Sie auch auf Werte liefern, die von Entwicklern, die Ihre Klasse verwendet werden. Die Konstanten am Anfang der Datei deklariert sind, von außen völlig unsichtbar das Modul selber. Wenn Sie öffentliche Konstanten zur Verfügung stellen möchten, ist dies der richtige Ort dafür. Beispiele hierfür sind die HEADER , BODY und FOOTER Konstanten WidgetStdMod (um sie zu benutzen haben Sie eigentlich auf den voll qualifizierten Namen zu verwenden: Y.WidgetStdMod.BODY und so weiter).

Instanz Mitglieder

Das vierte Argument der Y.Base.create() sind die Eigenschaften und Methoden, die in die gehen werden prototype der erstellten Klasse. Normalerweise erklären wir, Eigenschaften zuerst und Methoden später. Ich habe keine Ursache dafür ist die Reihenfolge eigentlich irrelevant, benötigen weder JavaScript noch YUI Sie tun es auf diese Weise, aber es macht es leichter, die Dinge in der Quelldatei zu finden. Obwohl Instanzeigenschaften on the fly in der Initialisierung erstellt werden kann, kann ich empfehlen, erklärt sie ausdrücklich und initialisieren sie. Jede Eigenschaft sollte durch eine API-Doc-Kommentar vorangestellt werden.

Eigenschaften wird in der Regel private und seinen Namen durch einen Unterstrich vorangestellt. Es ist am besten, wenn die öffentliche Schnittstelle des Objekts über die Konfiguration Attribute und Eigenschaften nicht ausgesetzt ist. Eigenschaften sind sehr dumm, kann die Konfiguration Attribute haben Validatoren, Typkonvertierung (via Setter) und sekundäre Effekte (über Change Events) und es ist oft nicht lange, bis Sie herausfinden, dass sie all diese Features wünschen.

Wie bei Konfigurationsattribute nicht initialisieren Eigenschaften, Objekte oder Arrays, sie alle am Ende auf dasselbe Objekt, und Sie kommen in Schwierigkeiten. Es ist besser, Eigenschaften, Objekte halten, um eingestellt sind null . Auch nicht verlassen Eigenschaften unset, wenn du nicht weißt, deren Wert noch, müssen diese auf null statt. Später, beim Debuggen, eine Eigenschaft auf undefined deutet auf einen Fehler, in der Regel ein Tippfehler.

Basis Instanzmethoden

Sie haben vielleicht bemerkt, dass wir keine Konstruktor für unsere Unterklasse erklärt haben. Base ist die Initialisierung des Moduls und ruft dann eine Methode namens initializer , wenn sie existiert, mit der gleichen Argumente, die es empfangen, wenn instanziiert so hat, für alle Zwecke, Sie kann der Meinung, dass initializer ist Ihr Konstruktor. Alle Klassen abgeleitet Base in der Regel ein einziges Argument, wenn geschaffen, ein Objekt mit der Konfiguration Attribute. Base (oder Widget , da es eine Klasse ist Base ) liest dieses Argument und stellt die Konfiguration Attribute vor dem Aufruf initializer . Für ein Widget , wenn es eine HTML_PARSER Eigentum, wäre es auch verarbeitet worden sind und die Werte für die Attribute aus dem Markup gelesen werden ebenso eingestellt werden.

Die initializer Methode hat mehrere Aufgaben. Erstens sollte es gesetzt alle Eigenschaften, die auf Objekte oder Arrays initialisiert werden müssen. Dann wird es veröffentlichen, alle Ereignisse dieser Klasse produziert. EventTarget ermöglicht es Ihnen, ein Ereignis, das bisher nicht zuerst mit den Standardeinstellungen für Veranstaltungen veröffentlicht, aber auch in diesem Fall empfehle ich Ihnen erklären, sie trotzdem zu schießen. Dies ist ein guter Ort, um die API docs Kommentare für diese Ereignisse hinzufügen, auch wenn es ein wenig seltsam aussieht, wird in den Körper einer Funktion Erklärung, aber es gibt keinen besseren Ort, um dies zu tun.

Das Argument erhielt initializer würde dann verarbeitet worden sind, aber manchmal will man einige zusätzliche Optionen bei der Initialisierung verwendet werden und Sie nicht darauf, aktuelle Attribute für sie zu halten. Zum Beispiel, Base akzeptiert die Attribute on , after , bubbleTargets und plugins (siehe Base ), obwohl es keine Konfiguration Attribute für diese hat. Ebenso WidgetParent nimmt eine children -Attribut bei der Initialisierung, hat aber keine Konfiguration Attribut mit diesem Namen. Die initializer Methode ist diejenige, die sie verarbeitet. So, obwohl die Klasse wird am Ende unter nur ein Argument bei der Instanziierung, kann dies ein Argument tragen alle Informationen die Sie benötigen.

JavaScript hat keine Ahnung von Destruktoren. Base kompensiert dies, indem Sie einen erklären destructor -Methode, wo Sie den Code platzieren können, um die Ressourcen Ihres Objekts genommen haben könnte frei. Dies ist nur eine Teillösung, wird der JavaScript-Interpreter nicht nennen es automatisch beim Löschen eines Objekts, so dass Sie immer noch verantwortlich für die Zerstörung eines Objekts vor dem Wegwerfen es sind, aber zumindest wissen Sie, einen Destruktor wird da sein.

Die Benutzer Ihrer Klasse wird nie nennen initializer und destructor direkt. Base wird sie nennen, wenn erforderlich. initializer aufgerufen wird, wenn das Objekt instanziiert werden, destructor wird aufgerufen, wenn der Benutzer Ihrer Klasse ruft seine sein destroy Methode.

Eines der Dinge, die oft zu Speicherlecks sind Ereignis-Listener hinter sich gelassen. Widget versucht, alle Zuhörer an Elemente der Benutzeroberfläche in der Bounding Box-Element enthalten lösen, aber es kann nicht getrennt werden alle anderen. Base nicht lösen jeden Fall Zuhörer auf allen . Dies ist der Code, den ich verwenden, um mir dabei zu helfen. Entlang der anderen privaten, Instanzeigenschaften Ich erkläre die _eventHandles Eigenschaft:

  _eventHandles: null, 

Dann, in der initializer Methode, habe ich es in ein Array:

  Initialisierung: function (cfg) {
     this._eventHandles = [];
     / / ... ...
 } 

In die gleiche initializer (auch in bindUI wenn es ein Widget ) Ich würde dann legen Zuhörer by doing:

  this._eventHandles.push (this.after ('someAttributeChange', this._afterSomeAttributeChange, this)); 

Dann, im destructor , habe ich:

  destructor: function () {
     Y.each (this._eventHandles, Funktion (Griff) {
         handle.detach ();
     });
 } 

Es ist hier, in der initializer , dass Sie den Anschluss der Ereignis-Listener für die Attribute, die sekundäre Effekte erzeugen soll (Sie können sie für unterschiedliche bindUI wenn diese Nebenwirkung hat mit dem UI deal). Wie ich bereits sagte, sollte Attribut Setter-Funktionen nur mit der Normalisierung der Wert des Attributs umzugehen. Sollte diese Einstellung produzieren keine Auswirkungen über das Speichern der Werte, sollten diese durch Ereignis-Listener verarbeitet werden. Im obigen Beispiel habe ich die Methode festgelegt _afterSomeAttributeChange für jede Änderung der zuhören someAttribute Attribut. Ereignis-Listener erhalten ein einziges Argument, das Ereignis Fassade, die ich in der Regel rufen ev , ein Objekt mit mehreren Eigenschaften, einer von ihnen, newVal mit dem Wert gesetzt.

Widget Instanzeigenschaften

Zwei wichtige Eigenschaften, die Widget verwendet werden BOUNDING_TEMPLATE und CONTENT_TEMPLATE . Beide sind anfänglich auf “<div></div>” , die die Standard-Struktur aus zwei Behältnissen eine in dem anderen, dass die meisten Widgets Gebrauch produziert. Dies ist jedoch möglicherweise nicht für alle Widgets, zum Beispiel ein Button Widget könnte besser durch eine serviert werden <span> Element innerhalb eines Ankers ( <a> ) Element anstelle von zwei ineinander geschachtelten <div> s. In der Tat, Sie könnten nicht darauf, den haben contentBox überhaupt Widget nicht erforderlich, dass Sie. Sie können diese beiden Eigenschaften der Instanz keine Markup Sie wollen. Zum Beispiel für die Button Klasse I haben könnte:

  BOUNDING_TEMPLATE: "<a>"
 CONTENT_TEMPLATE: null, 

Nachdem CONTENT_TEMPLATE eingestellt null wird Ihnen sagen Widget , das Sie wollen keine contentBox überhaupt. In diesem Fall wird die contentBox Konfiguration Attribut wird auf das gleiche Element Punkt als boundingBox Konfiguration Attribut hat.

Sie sollten nicht in diese Vorlagen stellen die gesamte HTML-Code für das Widget machen diese beiden einfachen HTML-Elemente und schaffen zusätzliche Markup via Code in renderUI (die wir später sehen werden).

Widget wird fügen Sie ein id -Attribut und die Standard-Klassen, die es nutzt, um eine Markup Sie wollen, wie yui3-xxxx , yui3-xxxx-visible oder yui3-xxxx-disabled , wo xxxx ist der Wert der NAME Eigentum in Kleinbuchstaben verwandelt.

Widget Instanzmethoden

Widget teilt der Initialisierung in mehreren Schritten. Jenseits der initializer , wird aufgerufen, wenn das Objekt instanziiert, und der destructor von Namen destroy , die beide Methoden behandelt Base , Widget fügt renderUI , bindUI und syncUI für die Bauphase, die in der Reihenfolge aufgerufen werden, wenn Widget 's render -Methode ist aufgerufen.

Die renderUI Methode kümmert sich um die Herstellung der grundlegenden HTML-Code für das Widget. Sowohl die boundingBox und contentBox haben an dieser Stelle gemacht worden. Bei Verwendung von Progressive Enhancement, renderUI muss zunächst prüfen, ob die Elemente, die bereits auf der Seite vorhanden sind. Wenn wir die genutzt haben HTML_PARSER Eigentum dann die Konfiguration Attribute hält die Verweise auf diese Elemente bis dahin wird gesetzt, wenn nicht, müssen wir sie schaffen.

Dazu ist der einfachste Weg (sofern keine Progressive Enhancement) zur Nutzung Y.Node.create , wie folgt aus:

  renderUI: function () {
     var cbx = this.get (CBX);
     cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE, CLASS_NAMES)));
 } 

Dies setzt voraus, eine Menge Dinge, die ich sofort erklären werde. Zuerst habe ich die CBX Konstante deklariert, wie im ersten Code-Box in diesem Artikel gezeigt. Dann nimmt sie Node geladen wird, das Widget nutzt so ist es sicher, aber es geht auch davon aus Y.substitute ist es, welche optional sind. Sie müssen add 'substitute' für die requires Liste für Ihr Modul. Dann erwartet Sie eine Vorlage für das Widget in einer statischen Variablen aufgerufen werden TEMPLATE , die bis zu Ihnen, an andere statische Klasse Mitgliedern define (rechts von ATTRS und so weiter). Schließlich nimmt sie gibt es eine Konstante CLASS_NAMES erklärt irgendwo.

Ich in der Regel erklären CLASS_NAMES in meinem Modul-Definition sowie BBX und CBX (siehe den ersten Code-Box in diesem Artikel), wie folgt aus:

  var BBX = 'boundingBox "
     CBX = 'Contentbox "
     NAME = 'Taste'
     / / Andere Konstanten und Verknüpfungen ....
     YCM = Y.ClassNameManager.getClassName,
     getClassName = function () {
         var args = Y. Array (Argumente);
         args.unshift (NAME);
         Rückkehr YCM.apply (this, args) toLowerCase ().;
     }
     LABEL = 'label',
     Gedrückt = 'gedrückt',
     ICON = 'Icon',
     CLASS_NAMES = {
         gedrückt: getClassName (gepresst),
         Symbol: getClassName (ICON),
         Label: getClassName (LABEL),
         noLabel: getClassName ('no', LABEL)
     }; 

CLASS_NAMES wird dann eine Konstante sein, die ein Objekt mit Eigenschaften erstellt ClassNameManager (das kommt auch im Lieferumfang Widget ). In den obigen Code, I zunächst die Verknüpfung YCM später greift schneller zu machen, dann erstelle ich die Funktion getClassName , eine private Funktion, die nur innerhalb des Moduls Definition ist. Die Funktion arbeitet ganz ähnlich wie die Methode des gleichen Namens von Widget , aber es ist eine statische Funktion, die ich nutzen, um weitere statische Werte definieren können. Das ist genau das, was ich später tun, als ich schaffen CLASS_NAMES als ein Objekt mit der generierten Klasse Namen wie ihre Eigenschaften. Dies ermöglicht es mir, eine Schreib TEMPLATE String wie zB:

  TEMPLATE: "<label class="{label}"> <input/>" 

Welche ist ziemlich dumm so weit. Ich möchte auch in dieser Vorlage Werte aus anderen Quellen, insbesondere Konfigurationsattribute verschmelzen. Dies ist, wie ich es zu tun bekommen:

  this.get (CBX). append (Y.Node.create (Y.substitute (TEMPLATE, CLASS_NAMES, Y.bind (function (key, vorgeschlagen, arg) {
     return (Schlüssel === '_' this.get (arg):? vorgeschlagen);
 }, Diese )))); 

Ich füge ein drittes Argument Y.substitute , eine Funktion. In der Regel Platzhalter für Y.substitute sind von Zeichen in geschweiften Klammern gemacht, aber wenn es einen Raum, es wird der Platzhalter in zwei Teile gespalten, die teilweise bis in den Raum als Schlüssel und die zweite ein optionales Argument. Dies kommt praktisch, wenn das dritte Argument ist eine Funktion, wie hier. Die Funktion wird drei Argumente erhalten, der erste ist der Schlüssel, der zweite den Wert in der Ersatz-Objekt, hier CLASS_NAMES , wenn überhaupt, und das dritte ist das optionale Argument. Also, in der Erklärung erwähnt, kann ich eine Vorlage wie folgt aus:

  TEMPLATE: "<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" /> ', 

Y.substitute finden {label} und suchen Sie im CLASS_NAMES . Es wird ihn finden und zu bekommen 'yui3-button-label' . Es wird dann rufen Sie die Ersatz-Funktion mit den Argumenten 'label' , 'yui3-button-label' und undefined . Seit key ist nicht gleich '_' wird der Wert in das zweite Argument, das ursprüngliche Klassenname zurückzukehren. Wenn es um die bekommt {_ id} , gibt es keinen Wert für eine Eigenschaft namens _ in CLASS_NAMES so dass es der Ersatz-Funktion mit Argumenten nennen '_' , undefined und 'id' . Mit key gleich '_' , wird die Funktion holen den Wert der 'id' Attribut. Es wird das gleiche wieder tun für die {_ value} Platzhalter.

Alle Konstanten am Anfang deklariert sind aus beliebigem Code außerhalb des Moduls verborgen, aber möchten Sie vielleicht machen einige von ihnen zu sehen, wie CLASS_NAMES . Um dies zu erreichen, in dem statische Member Bereich, das letzte Argument zu Y.Base.create , könnten Sie:

  CLASS_NAMES: CLASS_NAMES 

Dann das Objekt mit allen Klassen-Namen sichtbar wäre als Y.MyWidget.CLASS_NAMES .

Ich schlage vor, Sie tun so viel Formatierung, wie Sie können mit dem HTML-String, der die Widget-Inhalte machen wird. String-Manipulation in JavaScript ist viel schneller als der Zugriff auf das DOM Je mehr Sie also tun, bevor Sie Y.Node.create mit dieser Zeichenfolge ist, desto schneller erhalten Sie werden es tun.

Die nächste Instanz-Methode für jedes Widget genannt wird bindUI . Hier finden Sie Event-Listener anhängen, um alle Elemente erstellt renderUI , zum Beispiel, den Hörer auf Änderungen in den Wert in der <input> Box des TEMPLATE oben. Der Wert auf das Textfeld, und dass in der Konfiguration Attribut sollte immer synchron gehalten werden. Der value Attribut kann entweder über Code oder durch den Benutzer die Eingabe in das Eingabefeld geändert werden. Wenn es von externem Code kommt, das Textfeld aktualisiert werden sollte, wenn es aus dem Textfeld kommt, sollte es nicht, sonst laufen Sie Gefahr, in eine Endlosschleife: der Wechsel in das Textfeld setzt den value Attribut, dann wird der value auf das Textfeld, die ändert sich dann und setzt den value Attribut und so weiter. Mal sehen wie in diesem Fall behandeln. Wir setzen einen Listener auf dem synthetischen valueChange Veranstaltung auf das Eingabefeld ein. Dazu müssen wir, um das Add benötigen event-valuechange Modul zur requires Liste dieses Moduls.

  this._eventHandles.push (this._inputEl.after ('ValueChange', this._afterInputChange, this)); 

Wir gehen davon aus das Objekt eine Referenz auf das Textfeld in gespeichert _inputEl . Der Hörer bedeutet dies:

  _afterInputChange: function (ev) {
     this.set (VALUE, ev.target.get (VALUE), {source: UI});
 } 

Hier gehen wir davon haben wir die Konstanten VALUE und UI erklärt, wie 'value' und 'ui' bzw.. Wir haben einfach das Attribut value , um den Wert aus dem Eingabefeld zu lesen. Allerdings sind wir ein drittes Argument an die Set-Methode: {source:UI} . Die set -Methode kann ein drittes Argument, ein Objekt, dessen Eigenschaften werden in der Veranstaltung Fassade des Attributs ändern Ereignis gemischt werden. Dies ist die Art, wie wir den Unterschied zwischen Wert aus dem Textfeld oder von externen Code-Satz sagen kann. In bindUI hätten wir setzen diesen Listener:

  this._eventHandles.push (this.after ('ValueChange', this._afterValueChange)); 

Dies ist die Zuhörer für eine Veränderung in der value -Attribut des Objekts, das andere für eine Änderung des Wertes der war <input> Feld nennt man sie die gleiche, schließlich sind sie sowohl auf Veränderungen in der so genannten Wert zu hören, sind aber nicht dasselbe. In der Regel sind Zuhörer für das Attribut Änderungen im Set initializer , aber da diese Auswirkungen auf ein UI-Element, setzen wir sie in bindUI so, dass wir das Textfeld wird es wissen. Die Zuhörer haben:

  _afterValueChange: function (ev) {
     if (ev.source === UI) {
         return;
     }
     this._inputEl.set (VALUE, ev.newVal);
 } 

Das erste, was wir tun ist, um den Scheck source des Ereignisses. Wenn es aus dem kommt UI dann ignorieren wir es. Sowohl der Name der Eigenschaft, source und seinen Wert, UI sind willkürlich, das sind die, die ich bei der Einstellung der value Attribut so die, die ich prüfen in den Hörer, sondern ein beliebiger Name / Wert würde genauso gut tun. Eigentlich Widget bietet eine Konstante für das, Y.Widget.UI_SRC , aber es ist ziemlich lang, so würde ich wahrscheinlich mit Hilfe einer Tastenkombination sowieso.

Ein weiterer Leckerbissen: Sie können Attribute als read-only mit deklarierten Satz _set statt set . Die _set Methode gemeint ist, geschützt werden intern genutzt werden soll, sondern, wie wir wissen, weiß, JavaScript nichts über die Sicherheit so _set ist offen für alle, aber zumindest versuchen wir, indem er erklärt das Attribut mit readOnly:true und dokumentieren sie als solche in der API-Dokumentation.

Schließlich erklären wir 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.

Abschluss

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 erweitern: Lesezeichen mit del.icio.us | digg it! | reddit!

18 Comments »

RSS-Feed für Kommentare zu diesem Beitrag. TrackBack URI

  1. 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 #

  2. Awesome article!

    Thank you, Satyam!

    Comment by John — April 1, 2011 #

  3. Wonderful article!

    Comment by Daniel Stockman — April 1, 2011 #

  4. “This is a JavaScript issue, not a YUI one.”

    That is NOT issue, but pretty normal behavior. :)

    Comment by John — April 3, 2011 #

  5. 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 #

  6. i am just start to learn YUI3, i think this article will help me to understand it deeply.

    Comment by lily — April 5, 2011 #

  7. [...] 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 #

  8. 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 #

  9. @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 #

  10. [...] 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 #

  11. Nate

    You are referring to property _UI_ATTRS which is an object with two properties, BIND and SYNC . 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.BIND array, an “after change” listener will be attached before our bindUI method which will call a method named like _uiSetXxxx , where xxxx is the name of the attribute.

    Likewise, configuration attributes named in _UI_ATTRS.SYNC will have this same _uiSetXxxx called right before our own syncUI method. Each of these _uiSetXxxx methods 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 syncUI is called, push the name of the attribute into _UI_ATTRS.SYNC , usually in the initializer , 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");

    Thanks for the tip.

    Comment by Satyam — April 17, 2011 #

  12. Here's the enhancement request:

    http://yuilibrary.com/projects/yui3/ticket/2529439

    Comment by Satyen Desai — April 18, 2011 #

  13. 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 #

  14. [...] 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 #

  15. A correction to my previous comment :

    The extra attributes to monitor should be added to the _UI_ATTRS arrays 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 #

  16. 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 #

  17. Hi Satyam. Toller Artikel! 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. Was bin ich?

    Comment by alejandroci — May 25, 2011 #

  18. 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 via this.on and this.after .
    It cannot detach those subscribed to via Y.on , Y.after on 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 #

Hinterlasse einen Kommentar

Note: Comments are moderated for first-timers. Spam gelöscht.

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Hosted by Yahoo!

Copyright © 2006-2011 Yahoo! Inc. Alle Rechte vorbehalten. Datenschutzbestimmungen - Nutzungsbedingungen

Präsentiert von WordPress auf Yahoo! Web Hosting .