Recept za Yui 3 prijave

1. travanj 2011 u 2:52 am Satyam | U razvoju se | 18 Komentari

YUI 3 je dizajniran za izgradnju aplikacija oko modula. Neću raspravljati o tome što je modul, jer je dobro opisao Nikole Zakas u svom izlaganju Scalable JavaScript Application Architecture . Ja ću samo držati kako izgraditi ove module. Većina onoga što ću reći može se naći u on-line dokumentaciju, zajedno s nekoliko drugih alternative, nakon svega, to je točka dobroj dokumentaciju: da vam kažem o svim mogućim načinima obavljanja stvari. Zato je ovo recept, samo jedan način to radiš među mnogim drugima. Također pretpostavlja omanje program, sa ne onoliko slojeva kao Nikole 'sugerira, jer to je samo jedan članak, a ne knjiga.

Identificiranje modula

Prvi korak je utvrditi module ćemo trebati. Dobar pristup je da počnete rezanje dizajn primjene zaslona u pojedinim poglavljima: Naslov bar, traka izbornika, sadržaj, bočne ploče ili bilo što drugo bi moglo biti tamo. Zatim uzeti pogledati što knjižnica ima za ponuditi. Na primjer, YUI 3 Izbornik nema, ali postoji Node-MenuNav plugin , koji ima osnovnu strukturu izbornika napravio ugniježdenih neuređen popis <ul> elemenata i pretvara ih u aktivnom izborniku. Ili ste svibanj želite provjeriti Yui Galerija za osnovne komponente. U svakom slučaju, na kraju ćete doći do točke gdje imate okvir u tom izgledu da morate ispuniti u sebe, tako da ćemo to učiniti.

Preporučujem stavljanje svakog modula u vlastitu datoteku i vlastitim direktorija istog imena. Dakle, weather modul će biti u weather/weather.js . Razlog za to je zato što vaše modul je vjerojatno da će zahtijevati neke styling, neke CSS datoteka i slika, to ga čini lako za izgrađen-in loader ako ih mjesto gdje se lako može pronaći ih, u ovom slučaju, glavni style sheet će biti u weather/assets/skins/sam/weather.css , s druge imovine, slike i tako dalje, zajedno. To je uz pretpostavku da ne koristite Yui Builder koji već će učiniti stvari na taj način nekako, ali to je druga priča. Nazivi mapa assets i skins su više ili manje samorazumljivo, sam međutim nije sasvim jasno. To je zadana vrijednost za skin imovine loader jer to je zadano kože isporučen s Yui, nazvan po dizajneru, Sam Lind. Kao što to sugerira, vi ste slobodni da stavi svoje ime na svoje vlastite kože, a skin vlasništvo omogućava da Yui ih učitavanje, ali da i dalje ostane jednostavan, neka je samo ići s zadani.

Modul datoteka predložak

To je datoteka struktura se koristi češće, što ću opisati u jednom trenutku:

  / * Jslint razvoj: istina, UNDEF: istina, newcap: istina, strogi: istina, maxerr: 50 * / 
 / * Globalna YUI * / 
 / ** 
  * Modul-name modul stvara blah blah 
  * @ Modul modul ime- 
  * / 
 YUI.add ('Modul-name ", funkcija (Y) { 
     "Koristiti strogi"; 
     / / Zgodan konstante i prečacima koji se koriste u modulu 
     var Lang = Y.Lang, 
         CBX = 'contentBox' 
         BBX = 'boundingBox'
         IME = 'xxxx'; 

 
     / ** 
      * Xxxx klasa ne .... 
      * @ Klase xxxx 
      * @ Proteže Widget 
      * @ Koristi WidgetParent 
      * @ Konstruktor 
      * @ Cfg {objekt} konfiguracije atributi 
      * / 
     Y.Xxxx Y.Base.create = ( 
         IME, 
         Y.Widget, 
         [Y.WidgetParent] 
         { 
             / / Instance članove ovdje 
         }, 
         { 
             / / Statički članovi ovdje, posebno: 
             ATTRS: { 
             } 
         } 
     ); 

 
 }, 0,99 '0 ', { 
     Potrebno: ['dodatak', 'widget-roditelja'], 
     skinnable: istina 
 }); 

Prve dvije linije komentari za dobrobit JSLint i alata JavaScript provjera koje sam stvarno preporučujem. Ako idete na web inačice , postoji okvir za podešavanje opcija. Na dnu tog okviru Mogućnosti možete vidjeti način za kodiranje te opcije u datoteci sama. Ako koristite Yui Builder, ona će se izvoditi JSLint za vas i postavite sljedeće mogućnosti za vas, ali još uvijek ih možete zaobići za svaku pojedinačnu datoteku ako želite.

U doc komentari za Yui API docs graditelja. To je manje od glavobolje ako su početni predložak za one API dokumente, na kraju ćete ih ispuniti. Kao aplikacija raste i ne možete se sjetiti svega, vi ćete ih trebati.

Sada dolazi prva stvarna linija koda, YUI.add() iskaz. To je način da se Yui Loader ime i sadržaj modula i nekoliko drugih djelića informacija. Modul imena su obično zove sa svim malim slovima i crtica u između riječi. To su imena koje vidite na API dokumenata na top-većina indeksa na lijevoj strani. Možete vidjeti konvencija nije strogo slijedio, neka imena modula nemaju crtice. U svakom slučaju, to je uglavnom ovisi o vama dok ga koristite dosljedno.

Drugi argument je YUI.add() izjave je funkcija koja prima jedan argument konvencionalno pod nazivom Y . To je funkcija sadrži tijelo modula i Y je referenca na sandboxed primjer od Yui koja je gdje možete pronaći sve druge Yui i galerija module koje ste tražili. Skakanje na dnu tog koda okvir, možete vidjeti ostatak argumenata u .add() metode, verziju ( '0.99′ ili ne postoji još dosta) i konfiguraciju za ovaj modul, objekt s aa nizu svojstva. Evo, ja reći Loader da ovaj modul zahtijeva widget i widget-parent i da ima kožu. Broj widget je suvišan, budući da widget-parent već zahtijeva widget , ali ne muči s tim, utovarivač neće učitati modul dva puta, a ako u kasnijem trenutku ispustiti jednu ovisnost, ne trebate provjeriti za druge pretpostavke koje ste napravili: stanje ih sve i neka Loader nositi s njim. Možete naći popis svih opcija u API dokumente za .addModule() metode za učitavanje.

Unutar tijela funkcije, prva stvar je “use strict”; izjava. To je za tvoje koda skladu s ECMAScript 5 standarda, što u ovom trenutku vam stavlja na sigurnoj strani kako bi se osigurala kompatibilnost sa svim platformama ste vjerojatno naići u budućnosti. Za starije tumača, ova izjava je ništa više od niza koji nije dodijeljen i ništa se ignorira. “use” izjava ima funkciju opseg i to je sigurnije da ga stavite u funkciju tijela nego na vrhu datoteke, tako da utječe samo na modul vas definira. Ako stavite ga na vrhu datoteke, ona također će se primijeniti na bilo koju drugu JavaScript datoteke učitati nakon toga, i mnogi od njih ne može u skladu s ES5.

Slijede kratice i konstante konstante, koje su samo u uporabi od JavaScripta nema pojma konstanti. Mi ih u ime svih-velikih slova i podvlake, kao konstante su često na drugim jezicima. Postoje dva dobra razloga za korištenje konstante, posebno string konstante. Prvi je da kada pišete te isto nekoliko puta, možda pogrešno jedan od njih, a vi ćete primijetiti samo kad bug iskoči. Ako koristite konstante, JSLint će vas upozoriti kad upišete konstanta ime krivo, jer će biti nedefinirana. Drugi razlog je da je YUI kompresor mogu napraviti bolji posao od stalne imena može se sažeti dok string literali ne mogu. Dobri kandidati za imenovanih konstanti su imena konfiguracijske atributima i događajima.

Prečaci, kao što je Lang za Y.Lang su također dobri, jer su vam omogućiti da upišete manje, prevoditelj ima manje za procjenu (svaka točka podrazumijeva novu potragu u objekt članova) i mogu se sažeti je Yui kompresorom.

Nakon API Dokumenata komentara za klasu smo doći do stvarnog deklaracije. Moramo iskazujemo svoju klasu kao posjed Y , čime Y.Xxxx . Moj prijedlog je da koristite Y.Base.create() da ga stvoriti, kao što je prikazano ovdje. To samo može stvoriti klase izvedene iz Base (i Widget koji je također podrazred Base ), ali da će pokriti većinu modula ćete koristiti tako da je neobično da je potrebno to učiniti na drugi način. Prvi argument je naziv modula, NAME nekretnina opisana Base komponente. Konvencionalno, NAME vlasništvo je deva slučaja verzija imena klase. Ovaj naziv se koristi kao prefiks za događaje (dio prije debelog crijeva kao što su “io:success” ), za CSS klase imena generirani od strane Widget klase (npr.: “yui3-xxxx-content” ), a za zadani provedbu toString() , koji često ćete vidjeti u tragovima za ispravljanje pogrešaka. Ovdje sam koristiti vrijednost stalne NAME definirati klase NAME imovine.

Drugi argument je klasa ona opsezima. Često ćete koristiti bilo Y.Base , komunalnih modula koji će nemaju korisničko sučelje, Y.Widget za one koji će imati UI, Y.Plugin.Base za dodataka ili bilo koju drugu klasu izvedenu iz Y.Base , kao što je bilo možda već stvorili pomoću Y.Base.create() .

Treći argument drži proširenje koje će se koristiti. Proširenja su klase čija svojstva i metode želite pomiješali u svoj razred. Dobri kandidati za ekstenzije su ArrayList za Base ili bilo koji Widget-Xxxx podmoduli za Widget . Attribute , EventTarget i PluginHost već došao miješati u Base , tako da uvijek mogu računati na one tri biti tamo. Proširenja su vrlo snažni, ako pogledate u source kodu za Overlay , možete vidjeti nema ništa, ali Widget i ekstenzije. Nekoliko proširenja mogu biti pomiješana u komponente pa treći argument je polje.

Konačno, dobili smo na stvarni broj. Četvrti i peti su argumenti obje objekta literali sadrže instancu i statički članovi klase. Stupnju su članovi svojstva i metode koje idu u klasi prototype , ta da je svaki primjerak će dobiti kopiju i obično trebaju biti reference po this . Statički članovi su oni koji će biti zajednički svim slučajevima.

Konfiguracija Značajke

Najvažniji od tih statičkih članova ATTRS imovine. To navodi konfiguraciju pripisuje vaš razred će imati. Na primjer, recimo da želimo imati konfiguraciju atribut zove value održati brojčane vrijednosti i inicijalno postavljen na 0. U petom argumentu, mi bismo ga proglasiti ovako:

  ATTRS: {
     Vrijednost: {
         vrijednost: 0,
         validator: Lang.isNumber
     }
 } 

Možemo navesti bilo koji broj atributa u ATTRS imovine i svaki od njih može biti podešeni da imaju nekoliko opcija, od kojih sam ovdje prikazano. Možete pročitati o ostalim opcijama u addAttr() metoda Attribute . Kao što se vidi, ja sam koristio Lang prečac da sam deklarirani na početku modula deklaracije. validator mora biti funkcija koja ima vrijednost za provjeru i vraća Boolean. Sve Y.Lang.isXxxx metoda učiniti upravo to, tako da se može koristiti izravno. Za složenije validators i setters ili geteri, morate definirati funkcije. Preporučujem pod uvjetom da je ime funkcije kao string, Attribute brine o rješavanju funkcije ime stvarne funkcije. Na primjer, ako mi je definirati, recimo, validCodes atribut koji može poduzeti je jedan valjani kôd ili bogat izbor valjanih kodova, ali uvijek treba vratiti niz, ja bih učiniti:

  ATTRS: {
     validCodes: {
         seter: '_setValidCodes'
     }
 } 

Moramo proglasiti _setValidCodes način uz drugi primjer članova u četvrtom argument Y.Base.create() :

  _setValidCodes: Funkcija (vrijednost) {
     if (! Lang.isArray (vrijednost)) {
         vrijednost = [vrijednost];
     }
     vratiti vrijednost;
 } 

To je najbolje da se proglasi setters i geteri i sve, ali najviše od validators trivijalno kao zasebna instanca funkcije i neka Attribute riješili funkcijsku ime u stvarne funkcije poziva.

U principu, koristite setters za normalizaciju vrijednosti, kao što je prikazano gore, ne izazvati sekundarne efekte. Svi atributi konfiguracije će pucati prije i nakon promjene događaja s prije događaja u mogućnosti da spriječi atribut iz mijenja. Koristite ove događaje promjena za proizvodnju bilo koje sekundarne učinke, a ne tvrtkeGUESS. Nakon događaj je najbolji od tada znate da ništa spriječiti atribut od toga da bude postavljen od druge kod možda ste se pretplatili na prije (na) promjena događaja i otkazan.

Možete napraviti svoj atribut više ili manje stroge ovisno o tome kako definirati validator i postavljač. Ako napravite validator vrlo restriktivan, vaš atribut će biti vrlo strogi, prihvaćajući samo valjane vrijednosti, u ovom slučaju, dizač može biti nepotrebna. S druge strane, ne mogu koristiti validator na sve i oslanjaju se potpuno na setera za masažu bilo koju vrijednost primljene u nešto prihvatljivo. Na primjer, možete imati jednu od ove dvije:

  validator: Y.Lang.isBoolean, / / ​​kako bi atribut prihvaća strogo logičko
 seter: Booleova, / / ​​kako bi atribut prihvatiti nikakvu vrijednost i imaju Boolean se to pretvorilo u jedan 

Setters može poslužiti i kao validators. Setters treba vratiti vrijednost biti dodijeljen atribut, ali oni također mogu vratiti Y.Attribute.INVALID_VALUE koji će ostaviti atribut nepromijenjen, kao da je validator ga je odbio.

Kad sam definirati konfiguraciju atribut često definiraju konstante za to, što sam mjesto na vrhu modula (uz CBX i BBX i kratice), na primjer:

  var VRIJEDNOST = 'vrijednost'
     VALID_CODES = 'validCodes'; 

Šanse su da će koristiti te postavke atribute nekoliko puta unutar modula i to će uštedjeti mi neke nevolje. Međutim, budite oprezni, nemojte koristiti da je konstanta kada izjavljuje atribut, nemojte to učiniti:

  ATTRS: {
     / / *** Ne činite ovaj *** / /
     VRIJEDNOST: {
         vrijednost: 0,
         validator: Lang.isNumber
     }
 } 

Ako ste to učinili, da bi dobili atribut zove VALUE umjesto value . To je JavaScript problem, a ne YUI jedan. Također, budite oprezni da ne prekoračivati ​​na bilo koji od konfiguracija pripisuje već proglasila za oba osnovnog razreda ili bilo koji od korisnika. Widget već ima hrpa deklariranih svojstava (vidi tablicu ) i iako teško da bi dodali boundingBox se pripisuju, što može lako zaboraviti da je visible , disabled , height i width su već definirani. Ako namjeravate koristiti odgovara onome što Widget ih koristi za, sve će biti u redu. To je rekao, možete promijeniti definiciju bilo koju od njih. Y.Base.create() spaja definiciju konfiguracije atribute proširenje s onima osnovne klase i tako, ako želite promijeniti, recimo, zadanu vrijednost za Postojeći atribut, to možete učiniti izjavljujući da atribut ponovno u podrazredu.

Budite oprezni ako ste mislili da inicijalizirati atribut s nizom ili objekta. Objekata (i polja su objekti) donosi referenca i ako inicijalizirati atribut s objektom, svi oni mogu završiti ukazuje na isti objekt tako da kada se mijenja dio nje (uklanjanje stavke iz niza ili dodati nekretninu na objekt) završiti mijenjaju sve instance odjednom. Base , međutim, ima neku unutarnju logiku koja će vam omogućiti sigurno inicijalizirati atribut s objekta i niz literali. Ako inicijalizacije vrijednost objekt ili niz doslovno onda Base će ga klonirati. Koristite valueFn opciju ili ga inicijalizirati u klasi inicijalizatora za drugim objektima.

Ostali statički članovi

Postoje dva statički članovi mogli definirati u petom argument Y.Base.create() . Ako ste stvaranje čep, vi apsolutno morate proglasiti NS imovine, ako ne, čep neće raditi i tiho uspjeti. NS imovine mora biti postavljen na nizu koji će se koristiti kao imovine ime pohraniti plugin u host objekta, imajte to na umu kada odabrati ime tako da ne nadilazi bilo koje postojeće imovine.

Ako ste izgradnji widget, a vi ga namjeravate podršku progresivno poboljšanje, onda ćete koristiti HTML_PARSER statički imovine. Ovaj je postavljen na objekt koji sadrži svojstva imenovane nakon konfiguracije atributa postaviti parsiranje od postojeće HTML i bilo CSS3 selektori ili funkcije koji će proizvoditi svoje vrijednosti. Vidi Progressive Enhancement u korisničkom priručniku Widget.

Možda ćete također htjeti pružiti vrijednosti koji će se koristiti programeri koji koriste svoj razred. Ono što je konstanta proglašene na vrhu datoteke su potpuno nevidljive izvan samog modula. Ako želite dati javnih konstanti, ovo je mjesto za to. Primjeri kao što su HEADER , BODY i FOOTER konstante WidgetStdMod (da ih koriste što zapravo morati koristiti potpuno kvalificirani naziv: Y.WidgetStdMod.BODY i takve).

Stupnju Članovi

Četvrti argument Y.Base.create() su svojstva i metode koje će ići u prototype stvorenog klase. Obično, mi izjavljujemo svojstva prve i metode kasnije. Nemam razloga za to, kako je zapravo nevažno, niti JavaScript niti YUI morat ćete to učiniti na ovaj način, ali to olakšava pronalaženje stvari u izvorišnoj datoteci. Iako instance svojstva mogu se stvoriti u letu u inicijalizatora, ja ne preporučujem ih izričito izjavljuje i da ih pokreće. Svaki objekt treba prethoditi komentar API doc.

Nekretnine će obično biti privatni, a ime prefiksom po naglašavaju. To je najbolje ako javni sučelje objekta je izložena preko konfiguracijske atributa i svojstava ne. Nekretnine su vrlo glupi, konfiguracije atributi mogu imati validators, tip pretvorbe (preko setera) i izazvati sekundarne efekte (preko promjena događaja), a to često nije dugo dok ne sazna da želite sve one značajke.

Kao i kod konfiguracije atributima, ne inicijalizirati svojstva predmeta ili nizova, svi oni kraj gore što ukazuje na isti objekt i naiđete na poteškoće. Bolje je da postavite svojstva koja su za održavanje objekata null . Također, ne ostavljajte svojstva postavljene vrijednosti i sačuvali, ako ne znate njihovu vrijednost još ih postaviti na null umjesto. Kasnije, kad debugging, svojstvo postavljeno na undefined ukazuje na pogreške, obično pri upisu.

Osnovne metode instance

Možda ste primijetili da nismo proglasili bilo konstruktora za naše podrazreda. Base ne inicijalizacije modula, a zatim poziva metodu zvanu initializer , ako postoji, s istim argumentima koje je primilo instanciraju kada je tako, za sve namjene, te može smatrati da initializer je vaš konstruktor. Sve klase izvedene iz Base obično se jedan argument kada se objekt stvoren, koji sadrži konfiguracijske osobine. Base (ili Widget , budući da je klasa Base ) čita ovaj argument i postavlja konfiguracije atributa prije nego nazovete initializer . Za Widget , ako postoji HTML_PARSER imovine, to će također obrađeni su i vrijednosti atributa čitaju od oznake će biti postavljena kao dobro.

initializer metoda ima nekoliko zadataka. Prvo, to bi trebalo postaviti sva svojstva koja trebaju biti postavljene na objekte ili polja. Tada će objaviti sve događaje ova klasa će se proizvoditi. EventTarget će vam omogućiti da požar događaj koji još nije objavljen prvi koristeći zadane postavke za događaje, ali čak iu tom slučaju, ja predlažemo da ih proglasi anyway. Ovo je dobro mjesto za dodavanje API komentare u dokumentima za tih događaja, čak i ako ona izgleda malo čudno, budući da u tijelu funkcije deklaracije, ali ne postoji bolje mjesto za to.

Argument prima initializer bi obrađeni do tada, ali ponekad želite neke dodatne mogućnosti koje će se koristiti na inicijalizaciju i ne zanima da bi stvarne atribute za njih. Na primjer, Base prihvaća atribute on , after , bubbleTargets i plugins (vidi Base ) iako nema konfiguracije za one atribute. Isto tako WidgetParent traje children pripisuju na inicijalizaciju, ali nema konfiguracije atribut tog imena. initializer metoda je onaj koji ih obrađuje. Dakle, iako vaš razred će završiti uzimanje samo jedan argument na primjeru, to jedan argument može nositi sve informacije koje su vam možda trebati.

JavaScript nema pojma destructors. Base kompenzira tako što vam omogućuje da proglasi destructor način, gdje možete postavljati kod osloboditi sredstva vaš objekt mogao poduzeti. To je samo djelomično rješenje, JavaScript tumač ne zovu automatski kada pada objekt, tako da su i dalje odgovorni za uništavanje objekt prije nego što ga bacite, ali barem znate destructor će biti tamo.

Korisnici razredu nikada neće nazvati initializer i destructor izravno. Base će ih nazvati kada je potrebno. initializer će biti pozvani kada je objekt instanciraju, destructor će se zvati kada korisnik u razredu naziva njegova destroy metodu.

Jedna od stvari koje često proizvode memory leaks su događaj slušatelji ostavili. Widget pokušava odvojiti sve slušatelje prilogu elemenata korisničkog sučelja sadržane unutar okvirom elementa, ali ne može odvojiti niti na druge. Base ne može odvojiti bilo koji događaj slušatelje na sve . To je broj mogu koristiti da mi pomogne u tome. Uz drugih privatnih, primjerice svojstva sam proglasiti _eventHandles nekretnine:

  _eventHandles: null, 

Zatim, u initializer metode, ja sam ga postaviti na polju:

  inicijalizatoru: funkcija (cfg) {
     this._eventHandles = [];
     / / ......
 }, 

U istom initializer (također u bindUI da je Widget ) i onda bi se priključiti slušateljima time:

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

Zatim, u destructor , imam:

  destructor: Funkcija () {
     Y.each (this._eventHandles, funkcija (nositi) {
         handle.detach ();
     });
 }, 

Ona je ovdje, u initializer , da spojiti slušatelje događaja za atribute koji bi trebao izazvati sekundarne efekte (možete ga razlikuju za bindUI ako je to sekundarni učinak mora se nositi s UI). Kao što sam rekao ranije, atribut funkcije setera samo treba baviti normalizaciju vrijednosti atributa. Ako je ta postavka proizvode nikakve učinke izvan spremanje vrijednost, to bi trebao biti obrađene od strane slušatelja događaja. U gornjem primjeru, ja sam postaviti metodu _afterSomeAttributeChange slušati za bilo kakvu promjenu u someAttribute atribut. Event slušatelji će dobiti jedan argument događaj fasadu koju sam obično nazivamo ev , objekt s nekoliko svojstava, jedan od njih, newVal sadrže vrijednost se postavili.

Dodatak instance Nekretnine

Dva važna svojstva koja Widget koristi su BOUNDING_TEMPLATE i CONTENT_TEMPLATE . Oba su u početku postavljen na “<div></div>” koja proizvodi standardnu ​​strukturu dva kontejnera jednog u drugo da većina dodataka koristiti. To, međutim, ne može biti pogodan za sve widgete, na primjer, Button dodatak može biti bolje služio je <span> elementa unutar sidra ( <a> ) element umjesto dva ugniježđena <div> a. U stvari, možda nećete imati briga contentBox na sve, Widget ne zahtijevaju od vas. Možete postaviti ta dva svojstva instance bilo oznake želite. Na primjer, za Button razredu možda sam:

  BOUNDING_TEMPLATE: '<a>'
 CONTENT_TEMPLATE: null, 

Imajući CONTENT_TEMPLATE skup na null će reći Widget da ne želite contentBox uopće. U tom slučaju contentBox atribut konfiguracija će ukazati na istom elementu kao boundingBox atribut konfiguracija radi.

Vi ne bi trebali staviti u tih predložaka cijeli HTML za widget, čine ove dvije jednostavne HTML elemente i stvoriti bilo kakve dodatne oznake preko koda u renderUI (što ćemo vidjeti kasnije).

Widget će dodati id atribut i standardne klase se koristi za označavanje bilo koju želite, kao što su yui3-xxxx , yui3-xxxx-visible ili yui3-xxxx-disabled , gdje xxxx je vrijednost NAME imovinu pretvorio u malim slovima.

Dodatak instance metode

Widget dijeli svoj ​​inicijalizacija u nekoliko koraka. Osim initializer , pod nazivom, kada je objekt instanciraju, i destructor , pozvao je destroy , obje metode kojima upravljaju Base , Widget dodaje renderUI i bindUI i syncUI za građevinske faze, koja će se zvati u nizu kada Widget je render metoda je zove.

renderUI način brine o proizvodnji za osnovni HTML elementa. I boundingBox i contentBox su donesena u ovom trenutku. Ako koristite progresivno poboljšanje, renderUI prvo mora provjeriti jesu li elementi već postoji na stranici. Ako smo koristili HTML_PARSER imovine zatim konfiguracijske osobine posjeduju reference na one elemente će biti postavljena do tada, ako ne, treba ih stvoriti.

Da biste to učinili, najlakše (pod pretpostavkom da nema progresivno poboljšanje) je koristiti Y.Node.create , ovako:

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

To podrazumijeva puno stvari, koje ću objasniti odmah. Prvo, moram CBX konstanta proglašen kao što je prikazano u prvom koda okvir u ovom članku. Zatim pretpostavlja Node se učitava, koji Widget koristi tako da je sigurno, ali pretpostavlja Y.substitute je tamo, što je obavezno. Morat ćete dodavanje 'substitute' na requires popis za svoj ​​modul. Tada se očekuje da će predložak za widget biti u statičkom varijable zvane TEMPLATE koji je do vas da definirati uz ostale klase statičkih članova (desno od ATTRS i takvih). Konačno se pretpostavlja da je stalna CLASS_NAMES proglašen negdje.

Ja obično proglasi CLASS_NAMES u mojoj definiciji modula, uz BBX i CBX (vidi prvi koda okvir u ovom članku), ovako:

  var BBX = 'boundingBox'
     CBX = 'contentBox'
     IME = 'gumb'
     / / Ostale konstante i kratice ....
     YCM = Y.ClassNameManager.getClassName,
     getClassName = funkcija () {
         var args = Y.Array (argumenti);
         args.unshift (naziv);
         povratak YCM.apply (to, args) toLowerCase ().;
     },
     LABEL = 'Oznaka'
     Prešani = 'pritisnuli'
     ICON = 'ikona'
     CLASS_NAMES = {
         pritisnuta: getClassName (pritisnutih),
         Ikona: getClassName (ICON),
         Oznaka: getClassName (LABEL),
         noLabel: getClassName ('ne', LABEL)
     }; 

CLASS_NAMES će tada biti konstanta koja sadrži objekt s osobinama koje stvaraju ClassNameManager (koja također dolazi uključene s Widget ). U kodu gore, sam prvi put stvoriti prečac YCM bi kasnije pristupa brže, onda sam stvoriti funkciju getClassName , privatni funkciju koja je dostupna samo unutar modula definiciji. Funkcija radi prilično slično načinu na istim imenom od Widget , ali je statički funkcija koja mogu koristiti za definiranje daljnje statičke vrijednosti. To je upravo ono što mi je činiti kasnije, kada sam stvoriti CLASS_NAMES kao objekt sa nastalih klasnih imena kao njihova svojstva. To mi omogućuje da napisati TEMPLATE string kao što su:

  Predložak: '<label class="{label}"> <input/>' 

Koji je prilično glupi do sada. Ja bi također željeli spojiti u ovaj predložak vrijednosti iz drugih izvora, konkretno, konfiguracije osobina. Ovo je način kako sam se kako to učiniti:

  this.get (CBX). append (Y.Node.create (Y.substitute (predložak, CLASS_NAMES, Y.bind (funkcija (ključ, predložen, arg) {
     povratak (ključ === '_' this.get (arg): predložen);
 }, To)))); 

Mogu dodati i treći argument Y.substitute i funkcije. Obično, rezervirana mjesta za Y.substitute izrađeni su od znakova zatvorenih između vitičastih zagrada, međutim, ako je prostor, bit će podijeljen rezervirano mjesto na dva dijela, dio do prostora od kojih je ključ i drugi dodatni argument. To dolazi ruci kada treći argument funkcije, kao što su ovdje. Funkcija će primiti tri argumente, prvi je ključ, a drugi je vrijednost pronađena u zamjenski objekt, ovdje CLASS_NAMES , ako postoji, i treća je opcija argument. Dakle, u izvještaju gore, mogu koristiti predložak ovako:

  Predložak: '<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" />' 

Y.substitute naći {label} i traži za njega u CLASS_NAMES . To će ga pronaći i dobiti 'yui3-button-label' . Nakon toga će pozvati zamjenski funkciju s argumentima 'label' i 'yui3-button-label' i undefined . Budući da key nije jednak '_' će vratiti vrijednost u drugom argumentu, izvorni naziv klase. Kada dođe do {_ id} , ne postoji vrijednost za svojstvo zove _ u CLASS_NAMES tako da će pozvati zamjenu funkciju s argumentima '_' , undefined i 'id' . S key jednako '_' , funkcija će ići i dohvatiti vrijednost je 'id' atribut. To će učiniti isto opet za {_ value} rezervirano mjesto.

Sve konstante proglašene na vrhu su skriveni od bilo koda izvan modula, ali možda želite da se neki od njih vide, kao što su CLASS_NAMES . Da biste to učinili, u statički članovi sekcije, zadnji argument za Y.Base.create , mogli biste imati:

  CLASS_NAMES: CLASS_NAMES 

Zatim je objekt sa svim klase imena će biti vidljiv kao Y.MyWidget.CLASS_NAMES .

Ja vam predlažem da učinite mnogo oblikovanje kao možete sa HTML string koji će napraviti widget za sadržaj. String manipulaciju u JavaScriptu je mnogo brže od pristupa DOM tako više ne zove prije Y.Node.create s tom nizu, brže ćete dobiti Internet ispunjavanja.

Sljedeći primjer metoda zove za bilo koji widget je bindUI . Ovo je mjesto gdje možete priključiti na bilo koji događaj slušatelja elemenata stvorenih renderUI , na primjer, slušatelja za bilo kakve promjene u vrijednosti u <input> kućicu TEMPLATE gore. Vrijednost na polje i da je u konfiguraciji atribut uvijek treba imati na sync. value atributa može se mijenjati ili putem koda ili korisničkom tipkati u okvir za unos. Ako to dolazi od koda za vanjsku liniju, tekstni okvir bi trebao biti osvježena, ako dolazi iz textbox, to ne bi trebalo, inače riskirate ulaska beskonačnu petlju: promjena u polje postavlja value atribut koji se zatim postavlja value na polje koje tada se mijenja i postavlja value atributa i tako dalje. Omogućuje vidjeti kako se nositi ovaj slučaj. Postavili smo i slušatelja na sintetičke valueChange slučaju na okvir za unos. Da biste to postigli moramo dodati event-valuechange modul na requires popisu ovog modula.

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

Pretpostavljamo objekt ima referencu na polje spremljene u _inputEl . Slušatelj ovo:

  _afterInputChange: funkcija (EV) {
     this.set (VALUE, ev.target.get (vrijednost), {izvor: UI});
 }, 

Ovdje pretpostavljamo imamo konstanti VALUE i UI proglašen 'value' i 'ui' respektivno. Mi jednostavno postaviti atribut value na vrijednosti čitati iz ulazne kutije. Međutim, mi smo dodajući treći argument skup metoda: {source:UI} . set metoda može uzeti treći argument objekt, čija svojstva će se miješati u slučaju da fasadi slučaju promjene atributa. To je način na koji možemo reći da je razlika u vrijednosti između se postaviti od textbox ili iz koda za vanjsku liniju. U bindUI smo morali postaviti ovu slušatelja:

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

Ovo je slušatelj za promjene u value atributa vašeg objekta, a druga je za promjene vrijednosti <input> okvira, oni su pozvani isti, nakon svega, oboje slušati promjene u nešto što se zove vrijednost, ali nisu ista stvar. Obično, slušateljima za atributa promjene su postavljene u initializer , ali budući da ovo utječe na UI element, mi stavite ga u bindUI tako da znamo tekstni okvir će biti tamo. Slušatelj će imati:

  _afterValueChange: funkcija (EV) {
     if (ev.source === UI) {
         povratak;
     }
     this._inputEl.set (VALUE, ev.newVal);
 }, 

Prva stvar koju ćemo učiniti je da provjerite source događaja. Ako se dolazi iz UI onda smo ga ignorirati. Both the property name, source and its value, UI are arbitrary, those are the ones I used when setting the value attribute so those are the ones I check for in the listener, but any name/value would do just as well. Actually, Widget provides a constant for that, Y.Widget.UI_SRC , but it is kind of long so I would probably use a shortcut anyway.

Another tidbit: you can set attributes declared as read-only by using _set instead of set . The _set method is meant to be protected, to be used internally but, as we know, JavaScript knows nothing about security so _set is open to any but, at least, we try by declaring the attribute with readOnly:true and documenting it as such in the API docs.

Finally we declare syncUI . While the first two, renderUI and bindUI are going to be called once and only once, syncUI will be called at least once by Widget itself and you might call it several times afterwards. Its purpose is to refresh the UI to reflect the current state of the object. Since the state might change, the UI might need to be refreshed over time. However, I can't provide a simple recipe for handling this. For a simple UI, syncUI might refresh everything in the screen and be called every time anything changes. For more complex UIs refreshing the whole UI might take time and cause flickering so you might want to refresh only the bits and pieces you need. If so, you will have separate methods to refresh each of these parts and syncUI will call each of them just once. Moreover, as I've shown in the example for renderUI , I set the value of the textbox right there, though that should be done in syncUI .

In the more general case, you will have a function for each UI element that can be set separately. That function will be called once from syncUI , when initializing, and any number of times from the after attribute change event listener. For example, we could have:

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

Which could be called from syncUI along other similar setters:

 syncUI: function () {
    this._valueUIRefresh(this.get(VALUE));
    // other such refreshers 
}, 

and by the after listener:

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

Communicating with others

Once you have the logic of one of your modules finished, you want it to interact with other modules on your page. If you've seen Nicholas Zakas video, you already know what tight and loose coupling is. Calling methods and setting attributes from one module to another means having those modules tightly coupled and it is the traditional way, so I won't talk about it since you know how to do it. The other way to do it is to fire custom events. Base already includes everything you need to do that.

First, in initializer , you publish the custom events you want everybody to find out about.

 initializer: function (cfg) {
    this.publish('eventName', { /*… options … */});
}, 

Normally, the name of the event will come from a constant, since you will use that same name every time you fire it and you don't want typos there.

Normally, when you have a reference to an object, such as:

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

you can listen to its events by doing:

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

However, to do this, you need to have a reference to myWidget , which is not as tightly coupled as calling its methods directly but it is still quite tight: at least one module knows about the other or, perhaps, a supervisor module knows about both and sets the links in between them. Two options are important to get modules to communicate in between themselves, broadcast and emitFacade .

The first, broadcast , lets you set listeners for that event in other modules. When broadcast is left at 0, the default, you have to do as shown above. If you want the event to be listened to elsewhere, you will want broadcast set to 1, so events are broadcast within the same sandbox and sometimes 2, so they can go across sandboxes. In this context, a sandbox is what you get when you call:

 YUI().use( 'module1′, …, 'moduleN', function (Y) {
    // this is your sandbox
}); 

You can have several such sandboxes in your page:

 YUI().use( 'module1′, …, 'moduleN', function (Y) {
    // this is your sandbox
});
YUI().use( 'moduleX-1′, …, 'moduleX-N', function (Z) {
    // this is another sandbox
}); 

If you set broadcast to 2, then an object in the second sandbox can listen to an event when fired in the first. You can see the details in the Event user guide . Lets just stick to the simple sandbox case.

To listen to an event fired from another module within the same sandbox you need to know the value of the NAME static property of that module and the name of the event. Remember, Y.Base.create takes, as its first argument, the value that it will use for its NAME property, thus, if you created a module in this way:

 Y.MyWidget = Y.Base.create(
    'xxxx',
    Y.Widget,
    // … and so on 

and then, in the initializer you published the 'help' event like this:

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

To listen to that event in any other module within the same sandbox, you do:

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

Here, I am calling Y.after , not myWidget.after , I don't need to have a reference to the module firing the event. This is the same method used to listen to DOM events or other synthetic events such as 'valueChange' the only difference being the prefix, the part before the colon. Base already takes care to prefix all events with the value of the NAME property so you don't have to take care of that when publishing them. You can do so, you can even use something else as a prefix; if one such prefix is there, Base will respect it, but usually you just want the default, which Base provides.

You also want to set emitFacade because you will want to have a reference to the instance that fired the event, which the event facade provides in ev.target . But wait, if the listener module gets a reference to the firing module, don't they become tightly coupled once again? Not quite, as long as you don't preserve that reference in the listening module, the coupling will be volatile. Still, we can do better.

When firing the event we may add all the information the listener needs in the facade, like this:

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

Method fire takes the name of the event being fired (which Base will further prefix with the NAME of the class) and an object containing any number of properties which will be merged into the event facade. The listener then doesn't need to query the firing module for any information, all that might be needed is there. This is as loose as it gets. The listener simply knows that some module, and there may be many such modules, is asking for help on 'Event Broadcasting' and that is really all it needs to know. It doesn't even care which module asked for it. New modules may be added later and the help system will also work for them.

Events and Default Behaviors

The usual solution to changing the behavior of a class is to sub-class it so you can override one of its functions and do whatever it is you want to do instead. You can still do that. You can use Y.Base.create to define a module based on, say Y.Widget and then use Y.Base.create again using your new module as the base to change a particular behavior. For example, I might have:

 Y.MySimpleWidget = Y.Base.create(
    'simpleWidget',
    Y.Widget,
    [],
     {
        // instance members here, amongst them:
        renderUI: function () {
            this.get(CBX).append(Y.Node.create(' … whatever goes into the widget … ' ));
         }
    },
     {
        ATTRS: {
            // configuration attributes
         }
        // other static members
     }
); 

and then:

 Y.MyFancyWidget = Y.Base.create(
    'fancyWidget',
    Y.MySimpleWidget,
    [],
     {
        renderUI: function () {
            Y.MyFancyWidget.superclass.renderUI.apply(this, arguments);
            this.get(CBX).append(Y.Node.create(' … add some bells and whistles … ' ));
     }
    // Presumably the fancy version does not need any further static members so I skip the last argument
); 

MyFancyWidget improves over MySimpleWidget by adding some bells and whistles. This might be too much of a trouble in some cases, you might plan for a base class more flexible and easier to change. Custom events can help with that.

Imagine you have a class that has a sort function. The sort function takes a key and direction argument and is declared like this:

 sort: function (key, direction) {
     // sorting happens here
}, 

If you know that the behavior of that function might be changed in some circumstances, you might do the following. In the initializer method, you can have:

 initializer: function (config) {
    // amongst many other things:
    this.publish(SORT, {defaultFn: this._defSortFn});
}, 

Where SORT is a constant containing 'sort' . Then, you declare the sort function like this:

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

The sort function simply transforms the standard function call into a fired event containing the same arguments. Though this is meant to provide alternatives, you still want the class to sort somehow, you do that through the default sort function:

 _defSortFn: function (ev) {
    var key = ev.key, direction = ev.direction;
    // same code as the original sort function
}, 

The class will do sort as before, the body of _defSortFn might be just the same as the original one, once you have read the key and direction arguments from the event facade, but any other piece of code can set a listener for that same sort event and change it, for example:

 myObjectThatSorts.on('sort', function (ev) {
    var key = ev.key, direction = ev.direction;
    ev.preventDefault();
    // now do your own sort
}); 

By calling preventDefault I tell myObjectThatSorts not to call _defSortFn . I could do this conditionally and decide, based on whatever I want, whether I may leave the original sort go ahead or unconditionally stop it, as I did here. I might not even care to stop it ever, I might listen to the after event and simply flip an arrow somewhere in the UI to signal which way the sort went.

I may also alter the event facade. There is only one copy of the event facade that gets built when the event is fired and it is propagated through all before (on) listeners, to the default function and then to the after listeners until finally it is dropped. You can change the values of its properties at any point. Of course, it hardly matters any changes you might do after the default function is called but any changes done in the before (on) listeners will reach the default function, for example:

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

This would get the sort done upside down.

YUI_config

The easiest way to get your module on your page is to include it in its own <script> tag or in a script tag pointing to a combo URL (via creating a file on the server that is a manual concatenation of files or a combo service is the server supports one). Integrating custom modules into the Loader is a more advanced option, though it might improve performance. The important point in this case is to make sure the YUI.add() includes the requires: [...] in the last parameter, so use() will apply the module and its dependencies in the proper order.

For small applications, you will probably have everything loaded from the start as outlined above. However, for larger applications, you might not want everything loaded from the start since it can take too long. You can call use() more than once to request extra functionality as needed. However, having the Loader find out about each module's dependencies when it loads each is time consuming since it might take several sequential requests until it finally gets everything it needs. Instead, you can forewarn the Loader of your modules and their dependencies so, when the time comes, it knows how to deal with them and can load them all in parallel.

To do so, you need to add the module description and requirements to the tables that the YUI Loader uses to fetch modules. The easiest way is to build a yui_config.js file (or whatever you want to call it) that contains all those definitions. That file will look like this:

 YUI_config = {
    filter:'raw',
    //combine:false,
    gallery: 'gallery-2011.02.18-23-10′,
    groups: {
        js: {
            base: 'build/',
            modules: {
                'myWidget': {
                    path: 'myWidget/myWidget.js',
                    requires: ['widget', 'widget-parent', 'widget-child', 'widget-stdmod', 'transition'],
                    skinnable: true
                },
                // other modules here
             }
         }
        // other groups here
     }
}; 

You include this file in a regular <script> tag in your HTML file before you issue the first YUI().use() statement. They replace those options you would otherwise place as the first argument to YUI().use() , as if you did YUI(YUI_config).use() , but YUI does it for you. You can use any of the options listed here .

The filter option can be set to 'min' for production code (the default so you would usually comment out), 'debug' for the fully expanded with log statements (which might overwhelm your console) and 'raw' for fully expanded without log statements, the last two used only in development. Likewise with the combine option, only used when you have really tough bugs and you want to find out what is going on and get lost in those huge combos. Then you put your gallery option, if you use any gallery modules, to freeze your gallery modules to a version you know it works.

The groups option is where you start describing your own modules. The first name, in this case js , can be anything, whatever you want to call your group of files. You could create one such group for each family of files in a common location. The first declaration in each group is the base location of the group of files relative to the home page or an absolute path. That is, basically, the criteria for grouping files, however, there are several more options, listed here .

Finally, in the modules section you start listing your modules. The key for each entry is the module name, the very same name that you have used as the first argument in the YUI.add in your file and the same that you will use in the module list when you issue the YUI().use() call in your application. Then you specify the location of the module file, relative to the previous base or the fullpath if located elsewhere, and the rest of the options that where at the very end of the YUI.add declaration and are listed here . The requires list can list YUI modules, gallery modules or modules of your own either within the same group or from other groups in your config file. Skins will be loaded automatically by setting skinnable:true if you locate them as I recommended at the beginning of this article.

To simplify things for myself, I created a Windows script file that builds the YUI_config options for me. It basically scans the folder with the module files and reads each of them and extracts the information from each YUI.add call by defining a fake YUI.add function that extracts the arguments for me. It makes plenty of quite simplistic assumptions but it works for me as it is, you use it at your own risk.

Zaključak

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.

Podijelite i proširiti: Označi s del.icio.us | digg it! | Reddit!

18 Comments

  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. Izvrstan članak! 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 #

Sorry, the comment form is closed at this time.

Hosted by Yahoo!

Copyright © 2006-2012 Yahoo! Inc Sva prava pridržana. Pravila o privatnosti - Uvjeti pružanja usluge

Powered by WordPress na Yahoo! Web Hosting .