Una ricetta per una applicazione 3 YUI

1 Aprile 2011 alle 2:52 pm Satyam | In Development | 18 commenti

YUI 3 è stato progettato per creare applicazioni attorno a moduli. Non voglio discutere di ciò che è da un modulo che è stato ben descritto da Nicholas Zakas nella sua presentazione Scalable Architecture Application JavaScript . Mi limiterò a attenersi a come costruire questi moduli. La maggior parte di quello che dirò può essere trovato nella documentazione on-line, insieme a molti altre alternative, dopo tutto, questo è il punto di una buona documentazione: per raccontarvi tutti i modi possibili di fare le cose. Ecco perché questa è una ricetta, solo un modo di farlo, tra molti altri. Si presuppone inoltre una applicazione piuttosto piccolo, con non molti strati di Nicholas 'suggerisce, poiché questo è solo un articolo e non un libro.

Identificare i moduli

Il primo passo è quello di individuare i moduli di cui avremo bisogno. Un buon approccio è quello di iniziare a tagliare il design della schermata dell'applicazione in sezioni individuali: barra del titolo, barra dei menu, i contenuti, i pannelli laterali o qualsiasi altra cosa ci possa essere lì. Allora date un'occhiata a ciò che la biblioteca ha da offrire. Ad esempio, YUI 3 non ha Menu, ma c'è il nodo-MenuNav plug , che prende una struttura di base di menu composto da elementi lista non ordinata annidata <UL> e li trasforma in un menu attivo. Oppure si può decidere di verificare la Galleria YUI per i componenti di base. In ogni caso, si finirà per arrivare al punto in cui si dispone di una scatola in quel layout che devi compilare da soli, quindi cerchiamo di farlo.

I consigliamo di posizionare ogni modulo nel proprio file e la sua directory con lo stesso nome. Così, un weather il modulo potrebbe essere in weather/weather.js . La ragione di questo è perché il vostro modulo è probabile che richiedono un certo stile, alcuni file CSS e di immagine, lo rende facile per il built-in loader se vengono posizionati dove si può facilmente trovare, in questo caso, il foglio di stile principale sarebbe weather/assets/skins/sam/weather.css , con le altre attività, immagini e così via, accanto. Questo assumendo che non si utilizza il Generatore di YUI che già fare le cose in questo modo comunque, ma questa è un'altra storia. I nomi delle cartelle assets e le skins sono più o meno auto-esplicativo, sam , tuttavia, non è del tutto evidente. E 'il valore predefinito per la skin di proprietà del loader, perché questa è la skin di default fornito con YUI, dal nome del progettista, Sam Lind. Da queste considerazioni emerge, si è liberi di mettere il vostro nome sulle vostre proprie pelli e la skin proprietà consente di dire YUI per caricarli, ma di mantenerlo semplice, facciamo basta andare con il default.

Modulo file template

Questa è la struttura del file che uso più spesso, che mi descrivono in un attimo:

  / * JSLint devel: true, undef: true, newcap: true, severo: true, maxerr: 50 * / 
 / * YUI globale * / 
 / ** 
  * Il modulo-nome del modulo crea il blah blah 
  * @ Modulo module-name 
  * / 
 YUI.add ('module-name', function (Y) { 
     "Use strict"; 
     / / Costanti a portata di mano ei collegamenti utilizzati nel modulo 
     var = Y.Lang Lang, 
         CBX = 'contentBox', 
         BBX = 'boundingBox',
         NAME = 'xxxx'; 

 
     / ** 
      * La classe Xxxx fa .... 
      * @ Class Xxxx 
      * @ Extends Widget 
      * @ Usa WidgetParent 
      * @ Costruttore 
      * Cfg @ {object} configurazione degli attributi 
      * / 
     Y.Xxxx Y.Base.create = ( 
         NOME, 
         Y.Widget, 
         [Y.WidgetParent], 
         { 
             / / I membri di istanza qui 
         }, 
         { 
             / / Membri statici qui, in particolare: 
             ATTRIBUTI: { 
             } 
         } 
     ); 

 
 }, 0,99 '0 ', { 
     Richiede: ['widget', 'widget-parent'], 
     skinnable: true 
 }); 

Le prime due righe di commento sono a beneficio della JSLint , lo strumento di verifica JavaScript che vi raccomando. Se andate alla versione web , c'è una casella per impostare le opzioni. Nella parte inferiore della finestra di opzioni che è possibile vedere il modo per codificare queste opzioni nel file stesso. Se si utilizza il Generatore di YUI, verrà eseguito JSLint per voi e impostare le opzioni per voi, ma è ancora possibile eseguire l'override per ogni singolo file se lo si desidera.

I commenti sono doc per l'API YUI docs builder. Si tratta di meno di un mal di testa se si include il modello iniziale per coloro documentazione API, alla fine li potrete riempire. Poiché la domanda cresce e non si riesce a ricordare tutto, ne avrete bisogno.

Ora arriva la prima vera linea di codice, l' YUI.add() dichiarazione. Questo è il modo per dire al Loader YUI il nome e il contenuto del modulo e diverse altre informazioni. I nomi dei moduli di solito sono chiamati con tutte le lettere minuscole e trattini in tra le parole. Quelli sono i nomi che vedi nell'immagine Docs API in più in alto indice sulla sinistra. Potete vedere la convenzione non è strettamente seguito, alcuni nomi di moduli non hanno alcun trattini. Comunque, questo è soprattutto a voi fino a quando lo si utilizza in modo coerente.

Il secondo argomento del YUI.add() istruzione è una funzione che riceve un singolo argomento, convenzionalmente chiamato Y . Tale funzione contiene il corpo del modulo e Y è il riferimento a un'istanza sandbox di YUI che è dove si possono trovare tutti gli altri moduli e YUI Galleria hai chiesto. Saltare in fondo quella scatola codice, è possibile vedere il resto degli argomenti del .add() metodo, la versione ( '0.99′ o non ancora lontani) e la configurazione per questo modulo, un oggetto con aa serie di proprietà. Ecco, dico al Loader che questo modulo richiede widget e widget-parent e che ha una pelle. Listing widget è ridondante, poiché widget-parent richiede già widget , ma non stare a disturbarti con quello, il programma di caricamento non verrà caricato un modulo di due volte e, se in un momento successivo si lascia cadere una dipendenza, non è necessario controllare per le altre ipotesi che potreste aver fatto: stato tutti e lasciare che l'affare Loader con esso. Potete trovare un elenco di tutte le opzioni nella documentazione API per l' .addModule() il metodo del Loader.

All'interno del corpo della funzione, la prima cosa è il “use strict”; dichiarazione. Questo è per il codice per rispettare la ECMAScript 5 standard, che a questo punto si mette al sicuro per assicurare la compatibilità con tutte le piattaforme che si possono incontrare in futuro. Per i più grandi interpreti, questa dichiarazione non è altro che una stringa che non è assegnato a nulla e viene ignorato. L ' “use” dichiarazione funzione e campo di applicazione è più sicuro di inserirlo all'interno del corpo della funzione rispetto alla parte superiore del file, in modo che riguarda solo il modulo che si sta definendo. Se lo pongono al vertice del file, sarebbe anche applicabile a qualsiasi altro file JavaScript che si carica poi, e molti di loro potrebbe non essere conforme ES5.

Poi vengono i collegamenti e le costanti, che sono costanti solo in uso poiché JavaScript non ha il concetto di costanti. Li nome in tutte le maiuscole, lettere e underscore, come costanti sono spesso in altre lingue. Ci sono due buone ragioni per utilizzare costanti, specialmente costanti stringa. In primo luogo è che quando si scrive la stessa stringa più volte, si potrebbe digita in modo errato uno di loro e si nota solo quando un insetto si apre. Se si utilizzano le costanti, JSLint avviserà quando si digita il nome della costante sbagliato, dal momento che sarà definito. La seconda ragione è che il YUI Compressor può fare un lavoro migliore dal momento che i nomi di costanti possono essere compressi, mentre le stringhe letterali non possono. Buoni candidati per le costanti denominate sono i nomi degli attributi di configurazione ed eventi.

Tasti di scelta rapida, come Lang per Y.Lang sono buone anche perché consentono di digitare meno, l'interprete ha meno di valutare (ogni punto implica una nuova ricerca nelle membra degli oggetti) e possono essere compressi dal YUI Compressor.

Dopo i commenti documentazione API per la classe, si arriva alla dichiarazione vera e propria. Dobbiamo dichiarare la nostra classe come una proprietà di Y , così Y.Xxxx . Il mio suggerimento è quello di utilizzare Y.Base.create() per creare, come mostrato qui. Si può solo creare classi derivate di Base (e Widget che è anche una sottoclasse di Base ), ma che coprirà la maggior parte dei moduli che verranno utilizzati in modo che è insolito bisogno di farlo in altro modo. Il primo argomento è il nome del modulo, il NAME proprietà descritta per la Base componente. Convenzionalmente, la NAME proprietà è un cammello per caso la versione del nome della classe. Questo nome viene utilizzato come prefisso per gli eventi (la parte prima dei due punti come “io:success” ), per nomi di classe CSS generati dal Widget classe (ad esempio: “yui3-xxxx-content” ) e per l'implementazione di default di toString() , che vedrete spesso in tracce dal debugger. Qui uso il valore della costante NAME per definire la classe NAME proprietà.

Il secondo argomento è la classe che le estensioni. Si usano spesso sia Y.Base , per i moduli di utilità che non hanno interfaccia utente, Y.Widget per coloro che avranno un'interfaccia utente, Y.Plugin.Base per i plugin o di qualsiasi altra classe derivata da Y.Base , come ogni si potrebbe avere già creato utilizzando Y.Base.create() .

Il terzo argomento contiene le estensioni si intende utilizzare. Le estensioni sono classi le cui proprietà e metodi che si desidera aver mescolato nella tua classe. Buoni candidati per le estensioni sono ArrayList di Base o ad uno degli Widget-Xxxx sottomoduli di Widget . Attribute , EventTarget e PluginHost già venuto mescolati in Base in modo da poter sempre contare su quei tre essere lì. Le estensioni sono molto potenti, se si guarda il codice sorgente per Overlay , si può vedere non c'è niente, ma Widget ed estensioni. Diverse estensioni possono essere mescolati in una componente in modo che il terzo argomento è un array.

Infine, arriviamo al codice vero e proprio. Gli argomenti quarto e il quinto sono entrambi valori letterali oggetto contenente l'istanza e dei membri statici della classe. I membri di istanza sono le proprietà ei metodi che vanno nella classe prototype , quelli che ogni istanza otterrà una copia e di solito devono essere i riferimenti di this . I membri statici sono quelli che saranno condivise da tutte le istanze.

Attributi di configurazione

La più importante di questi membri statici è il ATTRS proprietà. Questo elenca la configurazione attribuisce la classe avrà. Ad esempio, diciamo che vogliamo avere un attributo di configurazione chiamato value per contenere i valori numerici e inizialmente impostato su 0. All'interno del quinto argomento, ci si dichiara così:

  ATTRIBUTI: {
     valore: {
         Valore: 0,
         validator: Lang.isNumber
     }
 } 

Siamo in grado di elencare un numero qualsiasi di attributi nel ATTRS proprietà e ognuno di essi può essere configurato con diverse opzioni, due dei quali ho mostrato qui. Potete leggere il resto delle opzioni nel addAttr() metodo di Attribute . Come si può vedere, ho usato il Lang collegamento che ho dichiarato all'inizio della dichiarazione di modulo. Il validator deve essere una funzione che assume il valore di verificare e restituisce un valore booleano. Tutti i Y.Lang.isXxxx metodi che fanno esattamente in modo che possano essere usati direttamente. Per validatori più elaborate, setter o getter, è necessario definire funzioni. Mi consiglia di fornire il nome della funzione come una stringa, Attribute si prende cura di risolvere il nome della funzione per la funzione effettiva. Per esempio, se dovessi definire una, diciamo, validCodes attributo che può prendere un codice unico, valido o un array di codici validi, ma devono sempre restituire un array, vorrei fare:

  ATTRIBUTI: {
     validCodes: {
         setter: '_setValidCodes'
     }
 } 

Abbiamo bisogno di dichiarare il _setValidCodes metodo lungo gli altri membri di istanza nel quarto argomento di Y.Base.create() :

  _setValidCodes: funzione (valore) {
     if (! Lang.isArray (valore)) {
         = valore [valore];
     }
     valore di ritorno;
 } 

E 'meglio dichiarare, getter e setter qualsiasi ma il più banale di validatori come funzioni istanza separati e lasciare che Attribute risolvere il nome della funzione nella chiamata di funzione reale.

In generale, utilizzare setter per normalizzare il valore, come mostrato sopra, non per produrre effetti secondari. Tutti gli attributi di configurazione scatta prima e dopo gli eventi di modifica con l'evento, prima in grado di prevenire l'attributo di cambiare. Utilizzare questi eventi di modifica di produrre effetti secondari, non il setter. L'evento, dopo è la migliore dal momento che da allora si sa che nulla impediva l'attributo di essere impostato dal codice altro potrebbe essere sottoscritto alla prima (a), evento di modifica e la cancellazione esso.

È possibile effettuare l'attributo più o meno rigorose a seconda di come si definisce il validatore e setter. Se si effettua il validatore molto restrittiva, l'attributo sarà molto rigorosa, accettando solo i valori validi, in questo caso, il setter potrebbe non essere necessario. D'altra parte, non è possibile utilizzare un validatore a tutti e contare totalmente sulla setter massaggiare qualsiasi valore ricevuto in un valore accettabile. Ad esempio, si può avere uno di questi due:

  validator: Y.Lang.isBoolean, / / ​​per rendere l'attributo accetta strettamente un valore booleano
 setter: Boolean, / / ​​per rendere l'attributo accettare qualsiasi valore e hanno booleano trasformarlo in uno 

Setters può servire anche come validatori. Setter dovrebbe restituire il valore da assegnare all'attributo ma possono anche restituire Y.Attribute.INVALID_VALUE che lascerà l'attributo immutato, come se un validatore aveva respinto.

Quando ho definire un attributo di configurazione che ho spesso definiscono una costante per questo, che ho posto alla parte superiore del modulo (lungo CBX , BBX e le scorciatoie), ad esempio:

  var VALUE = 'valore',
     VALID_CODES = 'validCodes'; 

Le probabilità sono che userò gli attributi di configurazione più volte all'interno del modulo, e questo mi salverà alcuni problemi. Tuttavia, attenzione, non usare quella costante quando si dichiara l'attributo, non fare questo:

  ATTRIBUTI: {
     / / *** Non farlo *** / /
     VALUE: {
         Valore: 0,
         validator: Lang.isNumber
     }
 } 

Se si esegue questa operazione, si otterrebbe un attributo chiamato VALUE al posto di value . Questo è un problema JavaScript, non un YUI uno. Inoltre, fare attenzione a non oltrepassare qualsiasi della configurazione attribuisce già dichiarato sia per la classe di base o delle estensioni. Widget ha già un mucchio di attributi dichiarati (vedi tabella ) e anche se difficilmente aggiungere un boundingBox stesso attributo, è potrebbe facilmente dimenticare che visible , disabled , height o width sono già definiti. Se la vostra destinazione d'uso corrisponde a ciò che Widget li usa per, tutto andrà bene. Detto questo, è possibile modificare la definizione di uno di essi. Y.Base.create() fonde la definizione degli attributi di configurazione l'estensione con quelle della classe base in modo, se si desidera modificare, ad esempio, il valore predefinito per un attributo esistente, è possibile farlo dichiarando che l'attributo di nuovo nella sottoclasse.

Fate attenzione se avete intenzione di inizializzare un attributo con un array o un oggetto. Oggetti (e array sono oggetti) vengono passati per riferimento, e se si inizializza un attributo con un oggetto, potrebbero finire per tutti che punta allo stesso oggetto in modo che quando si altera parte di esso (rimuovere un elemento da una matrice o aggiungere una proprietà all'oggetto) si finisce per modificare tutte le istanze in una sola volta. Base , tuttavia, ha una logica interna che permette di inizializzare in modo sicuro un attributo con valori letterali oggetto e array. Se il valore di inizializzazione è un oggetto o un array letterale quindi Base lo clonare. Utilizzare l' valueFn opzione o l'inizializzazione della classe inizializzatore per altri oggetti.

Altri membri statici

Ci sono altri due membri statici si potrebbe definire il quinto argomento del Y.Base.create() . Se si sta creando un plugin, è assolutamente necessario dichiarare la NS proprietà, se non, il plugin non funziona e fallire in modo silenzioso. La NS proprietà deve essere impostata su una stringa che verrà utilizzato come il nome della proprietà per memorizzare il plugin all'interno dell'oggetto host, tenere questo in mente quando si sceglie il nome in modo da non sovrascrivere qualsiasi proprietà esistente.

Se si crea un widget e si prevede di supportare il progressive enhancement, quindi si utilizza la HTML_PARSER proprietà statica. Questo è impostata su un oggetto che contiene le proprietà denominate dopo gli attributi di configurazione per impostare dal parsing del codice HTML esistente e sia selettori CSS3 o funzioni che consentono di ottenere i loro valori. Vedi progressive enhancement nel manuale d'uso Widget.

Si potrebbe anche voler fornire i valori per essere utilizzato da sviluppatori che utilizzano la classe. Le costanti dichiarate all'inizio del file sono completamente invisibili dall'esterno del modulo stesso. Se si desidera fornire costanti pubbliche, questo è il posto per farlo. Esempi di tali sono la HEADER , BODY e FOOTER costanti di WidgetStdMod (ad usarli effettivamente necessario utilizzare il nome completo: Y.WidgetStdMod.BODY e simili).

I membri di istanza

Il quarto argomento di Y.Base.create() sono le proprietà ei metodi che andranno nel prototype della classe creata. Di solito, si dichiara prime proprietà e metodi più tardi. Non ho alcuna ragione di ciò, l'ordine è in realtà irrilevante, né YUI JavaScript non richiedono di fare in questo modo, ma rende più facile individuare le cose nel file di origine. Anche se le proprietà di istanza può essere creato al volo nell'inizializzatore, io raccomando che li dichiari in modo esplicito e l'inizializzazione di loro. Ogni proprietà deve essere preceduta da un commento API doc.

Proprietà di solito è privata e il suo nome preceduto da un carattere di sottolineatura. E 'meglio se l'interfaccia pubblica dell'oggetto è esposto tramite attributi di configurazione e non di proprietà. Le proprietà sono molto stupidi, gli attributi di configurazione possono avere validatori, la conversione di tipo (tramite setter) e produrre effetti secondari (via gli eventi di modifica) e non è spesso lunga fino a scoprire che si desidera che tutte queste caratteristiche.

Come con attributi di configurazione, non inizializzare le proprietà di oggetti o array, tutti finiscono con il puntare allo stesso oggetto e si esegue nei guai. E 'meglio impostare le proprietà che devono contenere oggetti di null . Inoltre, non lasciare unset proprietà, se non si conosce ancora il loro valore, impostarli null , invece. Più tardi, durante il debug, una proprietà impostata su undefined punti per un errore, di solito un errore di battitura.

Base di istanza Metodi

Potreste aver notato che non hanno dichiarato alcun costruttore per la nostra sottoclasse. Base fa l'inizializzazione del modulo e poi chiama un metodo chiamato initializer , se esiste, con gli stessi argomenti che ha ricevuto quando vengono esemplificate in modo, a tutti gli effetti, è può ritenere che initializer è il vostro costruttore. Tutte le classi derivate da Base di solito prendono un solo argomento quando viene creato un oggetto contenente gli attributi di configurazione. Base (o Widget , dal momento che è una classe di Base ) legge questo argomento e imposta gli attributi di configurazione prima di chiamare initializer . Per un Widget , se vi è un HTML_PARSER proprietà, sarebbe anche sono stati elaborati ed i valori per gli attributi letti dal markup verrà impostata come bene.

L' initializer metodo ha diversi compiti. In primo luogo, deve impostare tutte le proprietà che devono essere inizializzato a oggetti o array. Poi pubblicherà tutti gli eventi di questa classe produrrà. EventTarget vi permetterà di generare un evento che non è stato pubblicato prima utilizzando le impostazioni predefinite per gli eventi, ma anche in questo caso, ti suggerisco di loro dichiara comunque. Questo è un buon posto per aggiungere i commenti documentazione API per gli eventi anche se sembra un po 'strano, essendo nel corpo di una dichiarazione di funzione, ma non c'è posto migliore per farlo.

L'argomento ricevuto da initializer sarebbero stati trattati da allora, ma a volte si desidera alcune opzioni extra per essere utilizzato su di inizializzazione e non si cura di tenere gli attributi reali per loro. Ad esempio, Base accetta gli attributi on , after , bubbleTargets e plugins (vedi Base ), anche se non ha gli attributi di configurazione per quelli. Allo stesso modo WidgetParent richiede alcuni children attributo di inizializzazione, ma non ha l'attributo di configurazione di questo nome. L' initializer metodo è quello che li elabora. Così, anche se la classe finirà per prendere un solo argomento su istanza, questo singolo argomento può portare tutte le informazioni di cui avrete bisogno.

JavaScript non ha nozione dei distruttori. Base compensa questo consente di dichiarare un destructor metodo in cui è possibile inserire il codice per liberare le risorse tuo oggetto avrebbe potuto prendere. Questa è solo una soluzione parziale, l'interprete JavaScript non lo chiama automaticamente quando far cadere un oggetto in modo non si è ancora responsabile della distruzione di un oggetto prima di disfarsene, ma almeno si conosce un distruttore ci sarà.

Gli utenti della classe non chiamerà initializer e destructor direttamente. Base li chiamerà quando necessario. initializer verrà chiamato quando l'oggetto viene istanziato, destructor sarà chiamato quando l'utente della classe chiama il suo destroy metodo.

Una delle cose che spesso producono perdite di memoria sono i listener di eventi lasciati alle spalle. Widget prova a staccare tutti gli ascoltatori collegati a elementi dell'interfaccia utente contenuto all'interno dell'elemento rettangolo di selezione, ma non riesce a staccare tutti gli altri. Base non può staccare eventuali listener di eventi a tutti . Questo è il codice che utilizzo per aiutarmi in questo. Lungo le altre private, proprietà di istanza Dichiaro la _eventHandles proprietà:

  _eventHandles: null, 

Quindi, nel initializer metodo, ho impostato a un array:

  initializer: function (CFG) {
     this._eventHandles = [];
     / / ......
 }, 

Nello stesso initializer (anche in bindUI se fosse un Widget ) Vorrei quindi collegare ascoltatori facendo:

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

Poi, nel destructor , ho:

  distruttore: function () {
     Y.each (this._eventHandles, function (maniglia) {
         handle.detach ();
     });
 }, 

E 'qui, nel initializer , che si collega i listener di eventi per gli attributi che dovrebbero produrre effetti secondari (lo si può differire per bindUI , se questo effetto secondario deve fare i conti con l'interfaccia utente). Come ho già detto, le funzioni setter degli attributi deve trattare solo con la normalizzazione del valore dell'attributo. Se tale impostazione produce alcun effetto oltre la memorizzazione del valore, queste dovrebbero essere gestiti da listener di eventi. Nell'esempio qui sopra, ho impostato il metodo _afterSomeAttributeChange per ascoltare qualsiasi cambiamento nella someAttribute attributo. I listener di eventi riceverà un singolo argomento, la facciata evento che di solito chiamo ev , un oggetto con caratteristiche diverse, uno di loro, newVal contenenti il valore da impostare.

Widget Proprietà istanza

Due importanti proprietà che Widget usi sono BOUNDING_TEMPLATE e CONTENT_TEMPLATE . Entrambi sono inizialmente impostato su “<div></div>” che produce la struttura standard di due contenitori uno dentro l'altro che la maggior parte utilizza i widget. Questo, tuttavia, potrebbe non essere adatto per tutti i widget, per esempio, un Button offerto potrebbe essere meglio servita da un <span> elemento all'interno di un'ancora ( <a> ) elemento invece di due nidificata <div> s. In realtà, si potrebbe non importa di avere un contentBox a tutti, Widget non richiede a voi. È possibile impostare tali proprietà dell'istanza due ad ogni markup desiderato. Ad esempio, per l' Button classe I potrebbe avere:

  BOUNDING_TEMPLATE: '<a>',
 CONTENT_TEMPLATE: null, 

Avere CONTENT_TEMPLATE impostato null dirà Widget che non si desidera un contentBox a tutti. In questo caso il contentBox attributo di configurazione punterà all'elemento stesso boundingBox attributo di configurazione fa.

Si consiglia di non mettere in questi modelli il codice HTML per il widget intera, rendono questi due semplici elementi HTML e creare qualsiasi markup extra tramite codice in renderUI (che vedremo in seguito).

Widget aggiungerà un id attributo e le classi standard che utilizza a qualsiasi markup che si desidera, ad esempio yui3-xxxx , yui3-xxxx-visible o yui3-xxxx-disabled , dove xxxx è il valore dei NAME proprietà trasformato in minuscolo.

Widget Metodi di istanza

Widget divide la sua inizializzazione in più fasi. Al di là del initializer , chiamato quando l'oggetto viene istanziato, e il destructor , chiamato da destroy , entrambi i metodi gestiti da Base , Widget aggiunge renderUI , bindUI e syncUI per la fase di costruzione, che saranno chiamati in sequenza quando Widget 's render metodo è chiamato.

Il renderUI metodo si occupa di produrre il linguaggio HTML di base per il widget. Sia il boundingBox e contentBox sono stati resi a questo punto. Se si utilizza il progressive enhancement, renderUI prima deve verificare se gli elementi già presenti sulla pagina. Se abbiamo utilizzato la HTML_PARSER proprietà allora gli attributi di configurazione che detengono i riferimenti a quegli elementi che sono stati fissati per allora, se non, abbiamo bisogno di crearli.

Per farlo, il modo più semplice (supponendo che non progressive enhancement) è quello di utilizzare Y.Node.create , in questo modo:

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

Questo presuppone un sacco di cose, che spiegherò subito. In primo luogo, ho la CBX costante dichiarata come mostrato nella finestra di primo codice in questo articolo. Poi assume Node è caricato, che Widget usa così è sicuro, ma assume anche Y.substitute è lì, che è opzionale. È necessario aggiungere 'substitute' alla requires lista per il modulo. Poi si aspetta un modello per il widget di essere in una variabile statica chiamata TEMPLATE che è a voi per definire insieme ad altri membri della classe statici (destra ATTRS e simili). Infine, presuppone che esista una costante CLASS_NAMES dichiarato da qualche parte.

Io di solito dichiarare CLASS_NAMES nella mia definizione di modulo, lungo BBX e CBX (si veda la prima casella codice in questo articolo), in questo modo:

  BBX var = 'boundingBox',
     CBX = 'contentBox',
     NAME = 'pulsante',
     / / Altre costanti e scorciatoie ....
     YCM = Y.ClassNameManager.getClassName,
     GetClassName = function () {
         var args = Y.Array (argomenti);
         args.unshift (nome);
         restituire YCM.apply (questo, args) toLowerCase ().;
     },
     LABEL = 'label',
     Premuto = 'premuto',
     ICON = 'icona',
     CLASS_NAMES = {
         premuto: GetClassName (premuto),
         icona: GetClassName (ICON),
         label: GetClassName (LABEL),
         nolabel: GetClassName ('no', LABEL)
     }; 

CLASS_NAMES sarà poi una costante contenente un oggetto con proprietà create da ClassNameManager (che viene incluso anche Widget ). Nel codice di cui sopra, ho creato il collegamento YCM per rendere accede in seguito più veloce, poi ho creato la funzione getClassName , una funzione privata che è accessibile solo nella definizione del modulo. La funzione è molto simile il metodo della stesso nome del Widget , ma è una funzione statica che posso usare per definire ulteriormente i valori statici. Questo è esattamente quello che faccio tardi, quando creo CLASS_NAMES come un oggetto con i nomi delle classi generati come loro proprietà. Questo mi permette di scrivere una TEMPLATE stringa come:

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

Che è abbastanza stupido finora. Vorrei anche di immergersi in un modello di valori provenienti da altre fonti, in particolare, gli attributi di configurazione. Ecco come riesco a farlo:

  this.get (CBX). append (Y.Node.create (Y.substitute (TEMPLATE, CLASS_NAMES, Y.bind (function (key, suggerito, arg) {
     ritorno (tasto === '_' this.get (arg): consigliato);
 }, Questo)))); 

Aggiungo un terzo argomento di Y.substitute , una funzione. Di solito, segnaposto per Y.substitute sono fatti di caratteri racchiusi tra parentesi graffe, tuttavia, se c'è uno spazio, che si divideranno il segnaposto in due, la parte fino allo spazio è la chiave e il secondo argomento opzionale. Questo è particolarmente utile quando il terzo argomento è una funzione, come ad esempio qui. La funzione riceve tre argomenti, il primo è la chiave, il secondo è il valore trovato nell'oggetto sostituzione, qui CLASS_NAMES , se del caso, e il terzo è l'argomento facoltativo. Così, nella dichiarazione di cui sopra, posso utilizzare un modello come questo:

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

Y.substitute troveranno {label} e cercare in CLASS_NAMES . E lo troverà e ottenere 'yui3-button-label' . Sarà quindi chiamare la funzione di sostituzione con gli argomenti 'label' , 'yui3-button-label' e undefined . Dal momento che key non è uguale a '_' si ritorna il valore del secondo argomento, il nome della classe originale. Quando si arriva a {_ id} , non vi è alcun valore per una proprietà chiamata _ in CLASS_NAMES in modo che chiamerà la funzione di sostituzione con gli argomenti '_' , undefined e 'id' . Con key uguale '_' , la funzione andrà a prendere il valore di 'id' attributo. Farà lo stesso per il {_ value} segnaposto.

Tutte le costanti dichiarate nella parte superiore sono nascoste da qualsiasi codice al di fuori del modulo, ma si potrebbe desiderare di fare un po 'di loro visibili, come CLASS_NAMES . Per fare questo, nella sezione statica membri, l'ultimo argomento di Y.Base.create , si potrebbe avere:

  CLASS_NAMES: CLASS_NAMES 

Poi l'oggetto con tutti i nomi di classe sarebbe visibile come Y.MyWidget.CLASS_NAMES .

Vi suggerisco di fare la formattazione quanto più possibile con la stringa HTML che rendere i contenuti del widget. Manipolazione delle stringhe in JavaScript è molto più veloce l'accesso al DOM quindi più si fa prima di chiamare Y.Node.create con quella stringa, più velocemente avrai fatto.

Il metodo di istanza successiva chiamata per ogni widget è bindUI . Questo è dove si collegano i listener di eventi ad eventuali elementi creati da renderUI , ad esempio, l'ascoltatore per eventuali variazioni del valore nella <input> casella del TEMPLATE sopra. Il valore sulla casella di testo e che l'attributo di configurazione dovrebbe essere sempre tenuti in sincronia. Il value attributo può essere modificato tramite codice o la tipizzazione dall'utente nella casella di input. Se si proviene da codice esterno, la casella di testo deve essere aggiornata, se proviene dalla casella di testo, non dovrebbe, altrimenti si rischia di entrare in un ciclo infinito: il cambiamento nella casella di testo imposta il value attributo che quindi viene impostato il value sulla casella di testo che quindi modifiche e imposta il value attributi e via dicendo. Vediamo come gestire questo caso. Abbiamo impostato un listener sul sintetico valueChange evento sulla casella di input. Per fare questo abbiamo bisogno di aggiungere l' event-valuechange modulo alla requires lista di questo modulo.

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

Non ci assumiamo l'oggetto ha un riferimento alla casella di testo salvato nella _inputEl . L'ascoltatore fa questo:

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

Qui supponiamo di avere la costanti VALUE e UI dichiarata come 'value' e 'ui' rispettivamente. Abbiamo semplicemente impostare l'attributo value per il valore letto dalla casella di input. Tuttavia, stiamo aggiungendo un terzo argomento al metodo set: {source:UI} . Il set metodo può prendere un terzo argomento, un oggetto, le cui proprietà verrà mixato nella facciata caso di evento di modifica degli attributi. Questo è il modo in cui possiamo dire la differenza tra il valore che viene impostato dalla casella di testo o da codice esterno. In bindUI avremmo dovuto impostare questo listener:

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

Questo è l'ascoltatore per una variazione del value di attributo dell'oggetto, l'altra era per una variazione del valore della <input> scatola, vengono chiamati lo stesso, dopo tutto, entrambi ascoltano variazioni qualcosa chiamato valore, ma non sono la stessa cosa. Di solito, gli ascoltatori per le modifiche degli attributi vengono impostati nella initializer , ma poiché questa riguarda un elemento dell'interfaccia utente, abbiamo messo in bindUI modo che sappiamo che il testo ci sarà. L'ascoltatore avrà:

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

La prima cosa da fare è verificare la source della manifestazione. Se si proviene dalla UI allora lo ignoriamo. Sia il nome della proprietà, source e il suo valore, UI sono arbitrari, questi sono quelli che ho usato quando si imposta il value attributo in modo questi sono quelli che ho per verificare l'ascoltatore, ma qualsiasi nome / valore farebbe altrettanto bene. In realtà, Widget fornisce una costante per questo, Y.Widget.UI_SRC , ma è una specie di lungo così avrei probabilmente utilizzare un collegamento comunque.

Un altro tidbit: è possibile impostare gli attributi dichiarati come di sola lettura utilizzando _set invece di set . Il _set metodo è pensato per essere protetto, per uso interno, ma, come sappiamo, JavaScript non sa nulla sulla sicurezza in modo _set è aperta a tutti ma, almeno, cerchiamo dichiarando l'attributo con readOnly:true e documentare come tale nella documentazione API.

Infine si dichiara syncUI . Mentre i primi due, renderUI e bindUI stanno per essere chiamato una sola volta, syncUI sarà convocata almeno una volta da Widget stessa e si potrebbe chiamare più volte in seguito. Il suo scopo è di aggiornare l'interfaccia utente per riflettere lo stato attuale dell'oggetto. Poiché lo stato potrebbe cambiare, l'interfaccia utente potrebbe aver bisogno di essere aggiornati nel tempo. Tuttavia, non in grado di fornire una ricetta semplice per la gestione di questo. Per una semplice interfaccia utente, syncUI potrebbe aggiornare tutto lo schermo ed essere chiamato ogni volta che qualcosa cambia. Per interfacce utente più complesse aggiornare l'intera interfaccia utente potrebbe richiedere tempo e causare lo sfarfallio per cui si potrebbe desidera aggiornare solo i bit e pezzi necessari. Se è così, si hanno metodi diversi per aggiornare ciascuna di queste parti e syncUI chiamerà ognuno di loro solo una volta. Inoltre, come ho mostrato nell'esempio per renderUI , ho impostato il valore della casella di testo proprio lì, anche se questo dovrebbe essere fatto in syncUI .

Nel caso più generale, si avrà una funzione per ogni elemento dell'interfaccia utente che può essere impostato separatamente. Tale funzione verrà chiamata una volta da syncUI , durante l'inizializzazione, e qualsiasi numero di volte dal listener di eventi dopo la modifica degli attributi. Per esempio, potremmo avere:

  _valueUIRefresh: function (value) {
     this._inputEl.set (valore, valore);
 } 

Che potrebbe essere chiamato da syncUI insieme ad altri setter simili:

  syncUI: function () {
     this._valueUIRefresh (this.get (valore));
     / / Rinfrescare altri tali 
 }, 

e l'ascoltatore dopo:

  _afterValueChange: function (ev) {
     if (ev.source === UI) {
         ritorno;
     }
     this._valueUIRefresh (ev.newVal);
 }, 

Comunicare con gli altri

Una volta che avete la logica di uno dei moduli finiti, si desidera interagire con altri moduli della pagina. Se hai visto il video Zakas Nicholas, sai già cosa accoppiamento stretto e libero, è. Chiamare i metodi e l'impostazione degli attributi da un modulo su un altro mezzo con i moduli strettamente accoppiati ed è il modo tradizionale, quindi non mi parlare dal momento che si sa come farlo. L'altro modo per farlo è quello di generare eventi personalizzati. Base include già tutto il necessario per farlo.

In primo luogo, in initializer , si publish gli eventi personalizzati che si desidera a tutti di scoprire.

  initializer: function (CFG) {
     this.publish ('eventName', {/ * ... opzioni ... * /});
 }, 

Normalmente, il nome della manifestazione verrà da una costante, dato che verrà utilizzato lo stesso nome ogni volta che viene fuoco e non vuoi errori di battitura lì.

Normalmente, quando si ha un riferimento a un oggetto, come:

  var = new myWidget Y.MyWidget ({/ * .. attributi ... * /}); 

è possibile ascoltare i suoi eventi facendo:

  myWidget.after ('eventName', this._eventNameListener, this); 

Tuttavia, per fare questo, è necessario disporre di un riferimento a myWidget , che non è così strettamente legato a chiamare i suoi metodi direttamente, ma è ancora abbastanza stretto: almeno un modulo conosce l'altro o, forse, un modulo di supervisore conosce entrambi e imposta i collegamenti tra loro. Due opzioni sono importante per ottenere i moduli per comunicare tra loro, broadcast e emitFacade .

Il primo, broadcast , consente di impostare i listener per l'evento in altri moduli. Quando broadcast viene lasciato a 0, il valore predefinito, che dovete fare come indicato sopra. Se si desidera che l'evento di essere ascoltati altrove, si vuole broadcast impostato a 1, per cui gli eventi vengono broadcast all'interno della stessa sandbox e, a volte due, in modo che possano andare in sandbox. In questo contesto, un sandbox è quello che si ottiene quando si chiama:

  YUI (). Utilizzare ('Module1', ..., 'Modulen', function (Y) {
     / / Questa è la tua sandbox
 }); 

Si possono avere diverse sandbox di questo tipo nella pagina:

  YUI (). Utilizzare ('Module1', ..., 'Modulen', function (Y) {
     / / Questa è la tua sandbox
 });
 YUI (). Uso ('Modulex-1', ..., 'Modulex-N', function (Z) {
     / / Questo è un altro sandbox
 }); 

Se si imposta broadcast a 2, quindi un oggetto nella sandbox secondo può ascoltare un evento quando sparato in prima. Potete vedere i dettagli nel manuale d'uso Event . Consente solo bastone al caso sandbox semplice.

Per ascoltare un evento sparato da un altro modulo all'interno della stessa sandbox è necessario conoscere il valore dei NAME proprietà statica di quel modulo e il nome della manifestazione. Ricordate, Y.Base.create prende come primo argomento, il valore che verrà utilizzato per il suo NAME proprietà, quindi, se avete creato un modulo in questo modo:

  Y.MyWidget Y.Base.create = (
     'Xxxx',
     Y.Widget,
     / / ... E così via 

e poi, l'inizializzatore è stato pubblicato il 'help' evento come questo:

  initializer: function (config) {
     this.publish ('help', {
         broadcast: 1,
         emitFacade: true
     });
 }, 

Per ascoltare l'evento in qualsiasi altro modulo all'interno della stessa sandbox, non è vero:

  Y.after ('xxxx: help', function (ev) {...}, this); 

Ecco, io chiamo Y.after non myWidget.after , non ho bisogno di avere un riferimento al modulo di attivazione dell'evento. Questo è lo stesso metodo utilizzato per ascoltare gli eventi DOM o altri eventi sintetiche come 'valueChange' l'unica differenza è il prefisso, la parte prima dei due punti. Base prende già cura di prefisso di tutti gli eventi con il valore di NAME proprietà in modo da non devono prendersi cura di questo caso di pubblicazione. È possibile farlo, si può anche usare qualcos'altro come un prefisso, se uno tale prefisso è lì, Base lo rispetta, ma di solito si desidera solo il default, che Base offre.

Anche voi volete impostare emitFacade perché si vuole avere un riferimento all'istanza che ha generato l'evento, che la facciata evento fornisce in ev.target . Ma aspettate, se il modulo ascoltatore ottiene un riferimento al modulo di cottura, non diventano strettamente accoppiati ancora una volta? Non proprio, finché non si conservano che fanno riferimento nel modulo di ascolto, l'accoppiamento sarà volatile. Tuttavia, possiamo fare di meglio.

Quando si spara l'evento possiamo aggiungere tutte le informazioni che l'ascoltatore ha bisogno della facciata, in questo modo:

  this.fire ('help', {HelpTopic: 'Event Broadcasting'}); 

Metodo di fire prende il nome dell'evento essere licenziato (che Base favorirà prefisso con il NAME della classe) e un oggetto che contiene un qualsiasi numero di proprietà che verranno uniti nella facciata evento. L'ascoltatore non necessita quindi di interrogare il modulo di cottura per qualsiasi informazione, tutto ciò che potrebbe essere necessario è lì. Questo è come sciolto, come si arriva. L'ascoltatore sa semplicemente che qualche modulo, e ci possono essere molti di tali moduli, chiede aiuto su 'Event Broadcasting' e che in realtà è tutto ciò che ha bisogno di sapere. Non importa neanche quale modulo chiesto. Nuovi moduli potranno essere aggiunti successivamente e il sistema di aiuto anche lavorare per loro.

Eventi e comportamenti predefiniti

La soluzione più comune a cambiare il comportamento di una classe è quello di sub-class in modo è possibile ignorare una delle sue funzioni e di fare quello che vuoi fare, invece. È ancora possibile farlo. È possibile utilizzare Y.Base.create per definire un modulo base, diciamo Y.Widget e quindi utilizzare Y.Base.create nuovamente utilizzando il nuovo modulo come base per modificare un particolare comportamento. Per esempio, potrei avere:

  Y.MySimpleWidget Y.Base.create = (
     'SimpleWidget',
     Y.Widget,
     [],
     {
         / / Membri di istanza qui, tra i quali:
         renderUI: function () {
             . this.get (CBX) append (Y.Node.create ('... tutto ciò che entra il widget di ...'));
         }
     },
     {
         ATTRIBUTI: {
             / Attributi / configurazione
         }
         / / Altri membri statici
     }
 ); 

e successivamente:

  Y.MyFancyWidget Y.Base.create = (
     'FancyWidget',
     Y.MySimpleWidget,
     [],
     {
         renderUI: function () {
             Y.MyFancyWidget.superclass.renderUI.apply (this, arguments);
             . this.get (CBX) append (Y.Node.create ('... aggiungere delle campane e fischietti ...'));
     }
     / / Presumibilmente la versione fantasia non ha bisogno di altri membri statici così saltare l'ultimo argomento
 ); 

MyFancyWidget migliora nel MySimpleWidget con l'aggiunta di alcune campane e fischietti. Questo potrebbe essere troppo di un problema in alcuni casi, si potrebbe pianificare una classe base più flessibile e più facile da cambiare. Eventi personalizzati possono aiutare in questo.

Immaginate di avere una classe che ha una sort di funzione. La funzione di ordinamento prende una key e direction argomento e viene dichiarato in questo modo:

  sort: function (chiave, direzione) {
      / / Ordinamento succede qui
 }, 

Se si sa che il comportamento di tale funzione potrebbe essere modificato in alcune circostanze, è possibile effettuare le seguenti operazioni. Nel initializer metodo, è possibile avere:

  initializer: function (config) {
     / / Fra molte altre cose:
     this.publish (SORT, {defaultFn: this._defSortFn});
 }, 

Dove SORT è una costante contenente 'sort' . Poi, si dichiara l' sort funzione come questa:

  sort: function (chiave, direzione) {
     this.fire (SORT, {key: chiave, direzione: direzione});
 }, 

L' sort funzione trasforma semplicemente la chiamata di funzione standard in un evento cotto contenente gli stessi argomenti. Anche se questo è destinato a fornire alternative, si vuole ancora la classe per ordinare in qualche modo, hai fatto attraverso la funzione di ordinamento di default:

  _defSortFn: function (ev) {
     var key = ev.key, direzione = ev.direction;
     / / Codice stesso la funzione di ordinamento originale
 }, 

La classe farà sorta come prima, il corpo di _defSortFn potrebbe essere proprio come quella originale, dopo aver letto la key e la direction argomenti dalla facciata evento, ma qualsiasi altro pezzo di codice è possibile impostare un listener per questo stesso tipo evento e cambiare, per esempio:

  myObjectThatSorts.on ('sort', function (ev) {
     var key = ev.key, direzione = ev.direction;
     ev.preventDefault ();
     / / Ora fare il vostro proprio ordinamento
 }); 

Chiamando preventDefault dico myObjectThatSorts di non chiamare _defSortFn . Potrei farlo in modo condizionale e decidere, sulla base di ciò che voglio, se mi può lasciare il tipo originale di andare avanti o fermarsi incondizionatamente, come ho fatto qui. Forse non importa neanche fermarsi mai, potrei ascoltare il after evento e semplicemente girare una freccia da qualche parte nel UI per segnalare che modo il tipo è andato.

Mi può anche alterare la facciata evento. C'è solo una copia della facciata evento che viene costruito quando l'evento viene generato e si propaga attraverso tutti davanti (on) ascoltatori, alla funzione di default e poi gli ascoltatori dopo finché è caduto. È possibile modificare i valori delle sue proprietà in qualsiasi momento. Certo, poco importa le modifiche si potrebbe fare dopo la funzione predefinita viene chiamata, ma le modifiche fatte in prima persona (e) ascoltatori raggiungerà la funzione predefinita, ad esempio:

  myObjectThatSorts.on ('sort', function (ev) {
     ev.direction = (ev.direction === 'desc' 'asc':? 'desc');
 }); 

Questo otterrebbe il tipo fatto a testa in giù.

YUI_config

Il modo più semplice per ottenere il modulo sulla pagina è quello di includere nella propria <script> tag o in un tag script che punta a un URL combo (tramite la creazione di un file sul server che è una concatenazione manuale di file o di un servizio combo è il server supporta uno). L'integrazione di moduli personalizzati nella Loader è una opzione più avanzata, anche se potrebbe migliorare le prestazioni. Il punto importante in questo caso è quello di assicurarsi che il YUI.add() include il requires: [...] nel l'ultimo parametro, in modo da use() si applica il modulo e le sue dipendenze nel giusto ordine.

Per le applicazioni di piccole dimensioni, probabilmente avrete tutto caricato fin dall'inizio, come sopra descritto. Tuttavia, per applicazioni di grandi dimensioni, non si potrebbe desiderare tutto caricato fin dall'inizio: si può prendere troppo a lungo. È possibile chiamare use() più di una volta a chiedere le funzionalità aggiuntive necessarie. Tuttavia, avendo il Loader scoprire le dipendenze di ogni modulo quando viene caricato ogni molto tempo dal momento che potrebbe richiedere diverse richieste in sequenza fino a quando non ottiene finalmente tutto ciò di cui ha bisogno. Invece, è possibile avvertire il Loader dei vostri moduli e le loro dipendenze così, quando arriva il momento, sa come trattare con loro e in grado di caricare tutti in parallelo.

Per farlo, è necessario aggiungere la descrizione del modulo e le prescrizioni per le tabelle che il Loader YUI utilizza per prendere i moduli. Il modo più semplice è quello di costruire uno yui_config.js file (o come volete chiamarlo) che contiene tutte queste definizioni. Tale file sarà simile a questa:

  YUI_config = {
     filtrare: 'prime',
     / / Combina: false,
     Galleria: 'gallery-2011/2/18-23-10',
     gruppi: {
         js: {
             base: 'costruire /',
             {moduli:
                 'MyWidget': {
                     Percorso: 'myWidget / myWidget.js',
                     Richiede: ['widget', 'widget-parent', 'widget-child', 'widget-stdmod', 'transizione'],
                     skinnable: true
                 },
                 / / Altri moduli qui
             }
         }
         / / Altri gruppi qui
     }
 }; 

Si includere questo file in un normale <script> tag nel file HTML prima di eseguire il primo YUI().use() dichiarazione. Essi sostituiscono quelle opzioni che altrimenti posto come primo argomento di YUI().use() , come se hai fatto YUI(YUI_config).use() , ma YUI fa per voi. È possibile utilizzare una qualsiasi delle opzioni elencate qui .

Il filter opzione può essere impostata su 'min' per il codice di produzione (il default in modo che di solito commentare), 'debug' per il trattamento completamente ampliata con le dichiarazioni di registro (che potrebbe sopraffare la console) e le 'raw' per il completamente sviluppati, senza log dichiarazioni, gli ultimi due utilizzati solo in fase di sviluppo. Allo stesso modo con la combine opzione, utilizzato solo quando si hanno dei bug veramente difficili e volete scoprire cosa sta succedendo e perdersi in quei combo enormi. Poi si mette la gallery opzione, se si utilizzano moduli di galleria, di congelare i moduli galleria a una versione si sa che funziona.

Il groups opzione è dove si inizia descrivendo i propri moduli. Il primo nome, in questo caso js , può essere qualsiasi cosa, quello che vuoi chiamare il tuo gruppo di file. È possibile creare un gruppo come per ogni famiglia di file in un percorso comune. La prima dichiarazione di ciascun gruppo è la base posizione del gruppo di file relativi alla home page o un percorso assoluto. Cioè, sostanzialmente, i criteri per i file di raggruppamento, tuttavia, ci sono diverse opzioni in più, elencati qui .

Infine, nei modules sezione si inizia elencando i moduli. La chiave per ogni voce è il nome del modulo, il nome stesso che avete utilizzato come primo argomento in YUI.add nel file e lo stesso che verrà utilizzato nella lista modulo quando si immette il YUI().use() chiamare la vostra applicazione. Poi si specifica la posizione del file di modulo, rispetto alla precedente, base o il fullpath se ubicati altrove, e il resto delle opzioni che, quando alla fine del YUI.add dichiarazione e sono elencati qui . La requires lista può elencare i moduli YUI, moduli galleria o moduli di tua scelta sia all'interno dello stesso gruppo o da altri gruppi nel file di configurazione. Skin verrà caricato automaticamente impostando skinnable:true se li individuare come ho consigliato all'inizio di questo articolo.

Per semplificare le cose per me stesso, ho creato un file di script di Windows che costruisce le YUI_config opzioni per me. Si esegue la scansione sostanzialmente la cartella con i file del modulo e legge ciascuna di esse ed estrae le informazioni da ogni YUI.add chiamata mediante la definizione di un falso YUI.add funzione che estrae gli argomenti per me. Fa un sacco di ipotesi piuttosto semplicistiche, ma per me funziona così com'è, lo usate a vostro rischio e pericolo.

Conclusione

YUI3 è molto flessibile e si può costruire i moduli in molti modi. 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 commenti

  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. Bellissimo articolo!

    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. Grande articolo! 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. What am I missing?

    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 #

Siamo spiacenti, il commento forma è chiusa in questo momento.

Ospitato da Yahoo!

Copyright © 2006-2012 Yahoo! Inc. Tutti i diritti riservati. Privacy Policy - Termini di servizio

Powered by WordPress su Yahoo! Web Hosting .