En opskrift på en Yui 3 Anvendelse
April 1, 2011 kl 02:52 af Satyam | I Udvikling | 18 KommentarerYui 3 er designet til at bygge applikationer omkring moduler. Jeg vil ikke diskutere, hvad et modul er, da det er blevet godt beskrevet af Nicholas Zakas i sin præsentation Skalerbar JavaScript Application Architecture . Jeg vil bare holde sig til, hvordan man opbygger disse moduler. Det meste af hvad jeg vil sige, kan findes i on-line dokumentation, sammen med flere andre alternativer, trods alt, der er pointen med god dokumentation: at fortælle dig om alle de mulige måder at gøre tingene på. Det er grunden til dette er en opskrift, bare én måde at gøre det på blandt mange andre. Det forudsætter endvidere et mindre program, med ikke så mange lag som Nicholas 'antyder, da dette er bare en artikel og ikke en bog.
Identifikation af modulerne
Det første skridt er at identificere de moduler vi får brug for. En god fremgangsmåde er at starte udskæring udformningen af ansøgningen skærmen i de enkelte sektioner: titellinje, menulinje, indhold, sidepaneler eller hvad der ellers måtte være der. Derefter tage et kig på, hvad biblioteket har at tilbyde. For eksempel har Yui 3 ingen menu, men der er Node-MenuNav plugin , som tager en grundlæggende menu struktur lavet af indlejrede uordnet liste <UL> elementer og gør dem til en aktiv menu. Eller du måske ønsker at kontrollere Yui Galleri til grundlæggende komponenter. Anyway, vil du til sidst nå det punkt, hvor du har en boks i dette layout, som du skal udfylde i dig selv, så lad os gøre det.
Jeg anbefaler at placere hvert modul i sin egen fil og sin egen mappe med samme navn. Således er en weather ville modul være i weather/weather.js . Grunden til dette er, fordi din modul er sandsynligvis kræve styling, nogle CSS og billedfiler, det gør det nemt for den indbyggede loader, hvis du placerer dem, hvor det nemt kan finde dem, i dette tilfælde, den vigtigste typografiark ville være i weather/assets/skins/sam/weather.css , med andre aktiver, billeder og så videre, ved siden af. Dette er forudsat at du ikke bruger den Yui Builder som allerede vil gøre tingene på denne måde alligevel, men det er en anden historie. De mappenavne assets og skins er mere eller mindre selvforklarende, sam dog ikke helt indlysende. Det er den standard værdi for skin ejendom på læsseren, fordi det er standard huden leveres med Yui, opkaldt efter designeren, Sam Lind. Som dette antyder, er du velkommen til at sætte dit navn på dit eget skind og skin egenskab giver dig mulighed for at fortælle Yui at indlæse dem, men at holde det simpelt, lad os bare gå med den standard.
Modul-fil skabelon
Dette er filstrukturen jeg bruger oftere, som jeg vil beskrive i et øjeblik:
/ * Jslint devel: sandt, undef: sandt, newcap: sandt, strenge: sandt, maxerr: 50 * / / * Global Yui * / / ** * Modulet-navn modul skaber blah blah * @ Modul-modul-navn * / YUI.add ('modul-navn', funktion (Y) { "Brug streng"; / / Handy konstanter og genveje, der bruges i modulet var Lang = Y. Lang, CBX = 'contentBox', BBX = 'boundingBox', NAME = 'xxxx'; / ** * Den Xxxx klasse gør .... * @ Klasse Xxxx * @ Udvider widget * @ Bruger WidgetParent * @ Konstruktør * @ CFG {objekt} konfiguration attributter * / Y. xxxx = Y.Base.create ( NAVN, Y. widget, [Y. WidgetParent], { / / Instans medlemmer her }, { / / Statisk medlemmer her, specielt: ATTRS: { } } ); }, '0 0,99 ', { kræver: ['widget', 'widget-forælder'], skinnable: sandt });
De første to linjer af kommentarer er til gavn for JSLint , JavaScript verifikation redskab, som jeg virkelig anbefale. Hvis du går til web-versionen , der er en boks til at angive indstillinger. Nederst i, at valgmuligheder kassen kan du se den måde, at kode disse muligheder i selve filen. Hvis du bruger Yui Builder, vil det køre JSLint for dig og sæt disse indstillinger for dig, men du kan stadig tilsidesætte dem for en individuel fil, hvis du ønsker det.
Doc kommentarer til Yui API docs Builder. Det er mindre af en hovedpine, hvis du inkluderer den oprindelige skabelon for de API docs, til sidst vil du fylde dem. Da anvendelsen vokser, og du ikke kan huske det hele, du har brug for dem.
Nu kommer den første egentlige linje kode, de YUI.add() redegørelse. Det er den måde at fortælle Yui Loader navnet og indholdet af modulet og flere andre oplysninger. Modul navne er normalt navngivet med alle små bogstaver og bindestreger i mellem ord. Det er de navne, du ser i API Docs i top-mest-indekset til venstre. Du kan se Denne konvention er ikke strengt følges, nogle modulnavne ikke have nogen bindestreger. Anyway, det er for det meste op til dig, så længe du bruger det konsekvent.
Det andet argument for YUI.add() udsagn er en funktion, der modtager et enkelt argument, konventionelt kaldet Y . Denne funktion indeholder liget af modulet og Y er henvisning til en sandboxed forekomst af Yui som er der, hvor du kan finde alle de andre Yui og Galleri moduler, som du har bedt om. Jumping til bunden af denne kode boksen, kan du se resten af argumenter .add() -metoden, er den version ( '0.99′ eller ikke helt der endnu) og konfigurationen for dette modul, et objekt med AA række egenskaber. Her vil jeg fortælle Loader, at dette modul kræver widget og widget-parent , og at det har en hud. Liste widget er overflødigt, da widget-parent kræver allerede widget , men ikke problemer selv med det, vil Loader ikke indlæse et modul to gange, og, hvis det på et senere tidspunkt du slippe en afhængighed, behøver du ikke at tjekke for andre antagelser, du kunne have gjort: state dem alle og lad Loader beskæftige sig med det. Du kan finde en liste over alle mulighederne i API docs for .addModule() metoden i Loader.
I kroppen af funktionen, er det første, “use strict”; erklæring. Dette er for din kode til at efterkomme ECMAScript 5 standard, som på dette tidspunkt sætter dig på den sikre side for at sikre kompatibilitet med alle de platforme, er du sandsynligvis støde på i fremtiden. For ældre tolke, er denne erklæring ikke andet end en snor, som ikke er tildelt til noget, og ignoreres. Den “use” erklæring funktion rækkevidde, og det er sikrere at placere den i funktion kroppen end i toppen af filen, så den kun påvirker det modul du definerer. Hvis du placerer den i toppen af filen, vil det også gælde for enhver anden JavaScript-fil, du indlæse bagefter, og mange af dem måske ikke overholder ES5.
Derefter kommer de genveje og konstanter, som er konstanter kun i brug, da JavaScript har intet begreb om konstanter. Vi nævne dem i alle store bogstaver og underscores, som konstanter ofte på andre sprog. Der er to gode grunde til at bruge konstanter, specielt streng konstanter. Første er, at når du skriver den samme streng flere gange, kan du skriver forkert én af dem, og du vil kun bemærke, når en fejl popper op. Hvis du bruger konstanter, vil JSLint advare dig, når du skriver den konstante navn forkert, da det vil være udefineret. Den anden grund er, at Yui Kompressoren kan gøre et bedre stykke arbejde, da konstant navne kan komprimeres, mens streng litteraler ikke kan. Gode kandidater til navngivne konstanter er navnene på konfiguration attributter og begivenheder.
Genveje, såsom Lang for Y.Lang er også godt, fordi de giver dig mulighed for at skrive mindre, tolken har mindre at vurdere (hver prik indebærer en ny søgning ind i objektet medlemmer), og de kan komprimeres ved Yui kompressoren.
Efter API docs kommentarer til den klasse, får vi til selve erklæringen. Vi er nødt til at erklære vores klasse som en egenskab ved Y , og dermed Y.Xxxx . Mit forslag er at bruge Y.Base.create() til at skabe det, som vist her. Det kan kun skabe klasser afledt af Base (og Widget som også er en underklasse af Base ), men der vil dække de fleste af de moduler, du vil bruge, så det er usædvanligt, at behovet for at gøre det på anden måde. Det første argument er navnet på det modul, de NAME ejendom, der er beskrevet for Base komponent. Konventionelt, at NAME ejendom er en kamel-sag udgave af klassen navn. Dette navn bruges som præfiks for arrangementer (den del, før kolon som “io:success” ), for CSS-klasse navne genereret af Widget klasse (dvs.: “yui3-xxxx-content” ) og til den standard gennemførelsen af toString() , som du ofte vil se i spor af debugger. Her vil jeg bruge værdien af den konstante NAME at definere klassen NAME ejendom.
Det andet argument er klassen den grad. Du vil ofte bruge enten Y.Base , for utility moduler, som vil have nogen brugergrænseflade, Y.Widget for dem, der vil have et UI, Y.Plugin.Base for plugins eller nogen anden klasse, der stammer fra Y.Base , som enhver anden du måske allerede har oprettet ved hjælp af Y.Base.create() .
Det tredje argument holder udvidelser, du vil bruge. Udvidelser er klasser, hvis egenskaber og metoder, du ønsker at have blandet ind i din klasse. Gode kandidater til udvidelser er ArrayList til Base eller nogen af de Widget-Xxxx undermoduler til Widget . Attribute , EventTarget og PluginHost kommer allerede er blandet ind i Base , så du altid kan regne med disse tre at være der. Extensions er meget magtfulde, hvis man ser på kildekoden for Overlay , kan du se, at der er intet, men Widget og udvidelser. Flere udvidelser kan blandes ind i en komponent, så det tredje argument er en matrix.
Endelig, vi kommer til den konkrete kode. Den fjerde og femte argumenter er begge genstand litteraler indeholder instans og statiske medlemmer af klassen. Retten medlemmer er de egenskaber og metoder, der går ind i klassen prototype , dem, der hver instans vil få en kopi, og normalt skal referencer af this . Statisk medlemmer er dem, der vil blive delt af alle tilfælde.
Konfiguration attributter
Den vigtigste af disse statiske medlemmer er ATTRS ejendom. Dette viser konfigurationen attributter din klasse vil have. For eksempel sige, lader vi ønsker at have en konfiguration attribut kaldet value at holde numeriske værdier og i første omgang sat til 0. Inden den femte argument, ville vi erklærer det således:
ATTRS: { værdi: { værdi: 0, validator: Lang.isNumber } }
Vi kan liste et vilkårligt antal attributter i ATTRS ejendommen og hver enkelt kan konfigureres med flere muligheder, hvoraf jeg har vist her. Du kan læse om resten af mulighederne i addAttr() metode til Attribute . Som det kan ses, har jeg brugt Lang genvej, jeg erklærede i starten af modulet erklæring. Den validator skal være en funktion, der tager værdien for at kontrollere og returnerer en boolean. Alle de Y.Lang.isXxxx metoder gøre lige netop det, så de kan bruges direkte. For mere omfattende validatorer, settere eller getters, skal du definere funktioner. Jeg anbefaler at give navnet på den funktion som en streng, Attribute tager sig af at løse den funktion, navnet til selve funktionen. For eksempel, hvis jeg skulle definere en, siger, validCodes , attribut, der enten kan tage en enkelt valid kode eller en vifte af gyldige koder, men bør altid returnerer en matrix ville jeg gøre:
ATTRS: { validCodes: { setter: '_setValidCodes' } }
Vi er nødt til at erklære _setValidCodes metoden langs den anden instans medlemmer i det fjerde argument Y.Base.create() :
_setValidCodes: funktion (værdi) { if (! Lang.isArray (værdi)) { værdi = [værdi]; } returværdi; }
Det er bedst at erklære settere, getters og enhver, men de mest trivielle af validatorer som separate fx funktioner og lad Attribute løse den funktion navn ind i selve funktionen opkald.
Generelt skal du bruge setters at normalisere den værdi, som vist ovenfor, ikke at producere sekundære effekter. Alt konfiguration attributter vil brand før og efter ændringen arrangementer med før begivenheden stand til at forhindre attribut fra at skifte. Brug disse ændre begivenheder til at fremlægge nogen sekundære virkninger, ikke setter. Resultatet efter begivenhed er det bedste siden af så ved du, at intet forhindrer den egenskab fra at være indstillet siden anden kode kunne have abonneret på den før (om) ændring begivenhed, og aflyst det.
Du kan gøre din attribut mere eller mindre strenge, afhængigt af hvordan du definerer din validatoren og setter. Hvis du gør validator meget restriktiv, vil din attribut være meget streng, accepterer kun gyldige værdier, i dette tilfælde, kan setter være unødvendigt. På den anden side, kan du ikke bruge en validator på alle, og stole helt på setter til at massere alle modtagne værdi til noget acceptabelt. For eksempel kan du have en af disse to:
validator: Y.Lang.isBoolean, / / at gøre attributten accepterer strengt en boolesk setter: Boolean, / / at gøre attributten acceptere nogen værdi og har booleske gøre det til en
Setters kan også bruges som validatorer. Setters skal returnere den værdi, der skal tildeles attributten, men de kan også vende tilbage Y.Attribute.INVALID_VALUE , der vil forlade attribut uændret, som om en validator havde afvist det.
Når jeg definerer en konfiguration attribut jeg ofte definere en konstant for det, som jeg placere på toppen af modulet (langs CBX , BBX og genveje), for eksempel:
var VALUE = "værdi", VALID_CODES = 'validCodes';
Chancerne er, at jeg vil bruge dem konfiguration attributter flere gange inden for modulet, og det vil spare mig nogle problemer. Men vær forsigtig, skal du ikke bruge det konstant, når han angiver attributten, skal du ikke gøre dette:
ATTRS: { / / *** Gør ikke dette *** / / VÆRDI: { værdi: 0, validator: Lang.isNumber } }
Hvis du gør dette, vil du få en attribut kaldet VALUE stedet for value . Dette er en JavaScript-problem, ikke en Yui én. Desuden skal du være forsigtig med ikke at overskride på nogen af de konfiguration attributter, der allerede er anmeldt til enten basen klasse eller nogen af de udvidelser. Widget har allerede en masse attributter erklæret (se tabel ), og selvom man næppe ville tilføje en boundingBox attribut dig selv, du kan let glemme, at visible , disabled , height eller width er allerede defineret. Hvis din påtænkte brug matcher hvad Widget bruger dem til, vil alt være fint. Når det er sagt, kan du ændre definitionen af nogen af dem. Y.Base.create() fusionerer definition af konfigurationen attributter for udvidelse med de af basen klassen, så hvis du ønsker at ændre, siger, standardværdien for en eksisterende attribut, kan du gøre det ved at erklære, at attribut igen i din underklasse.
Vær forsigtig, hvis du mener at initialisere en attribut med en matrix eller et objekt. Objekter (og arrays er objekter) sendes som reference, og hvis du initialisere en attribut til et objekt, kan de alle ender med at pege på det samme objekt, så når du ændrer en del af det (fjerne et element fra et array eller tilføje en ejendom til det objekt), du ender med at ændre alle forekomster på én gang. Base , dog har nogle interne logik, der vil give dig mulighed for sikkert at initialisere en attribut med objekt og array litteraler. Hvis initialiseringen værdi er et objekt eller array bogstavelig derefter Base vil klone det. Brug valueFn option eller initialisere det i klassen initializer for andre objekter.
Andre Statisk Medlemmer
Der er to andre statiske medlemmer du kan definere i femte argument Y.Base.create() . Hvis du opretter et plugin, du absolut skal erklære NS ejendom, hvis du ikke gør det, vil plugin ikke virker og lydløst mislykkes. Den NS ejendom skal indstilles til en streng, som vil blive brugt som ejendommen navnet for at gemme plugin i værtssamfundet objektet, holde dette i tankerne, når du vælger det navn, så du ikke tilsidesætte alle eksisterende ejendom.
Hvis du er ved at opbygge en widget, og du planlægger den til at støtte progressiv forbedring, så vil du bruge den HTML_PARSER statiske ejendom. Dette er indstillet til et objekt, som indeholder egenskaber er opkaldt efter den konfiguration, tilskriver indstilles fra parsing den eksisterende HTML og enten CSS3 selectors eller funktioner, der vil producere deres værdier. Se Progressive Enhancement i Widget brugervejledningen.
Du kan også prøve at give værdier, der skal bruges af udviklere, der bruger din klasse. Konstanterne erklærede i toppen af filen er helt usynlige udefra modulet selv. Hvis du ønsker at give offentlig konstanter, så er dette stedet at gøre det. Eksempler på sådanne er HEADER , BODY og FOOTER konstanter WidgetStdMod (for at bruge dem, du faktisk nødt til at bruge det fuldt kvalificerede navn: Y.WidgetStdMod.BODY og sådan).
Instans Medlemmer
Den fjerde argument Y.Base.create() er de egenskaber og metoder, som vil gå ind i prototype af den oprettede klasse. Normalt, erklærer vi ejendomme først og metoder senere. Jeg har ikke nogen grund til dette, ordren er faktisk irrelevant, hverken JavaScript eller Yui kræver, at du gøre det på denne måde, men det gør det nemmere at finde ting i kildefilen. Selvom eksempelvis egenskaber kan oprettes på flue i initializer, jeg anbefaler at erklære dem eksplicit og initialisering dem. Hver enkelt ejendom bør indledes med en API doc kommentar.
Egenskaber vil normalt være private, og dens navn præfiks med en understregning. Det er bedst, hvis den offentlige grænseflade på objektet er eksponeret via konfiguration attributter og ikke egenskaber. Egenskaber er meget dumme, kan konfigurationen attributter have validatorer, typekonvertering (via setter) og producere sekundære effekter (via ændre hændelser) og det er ofte ikke længe, indtil du finder ud af, at du ønsker, at alle disse funktioner.
Som med konfiguration egenskaber, ikke initialisere egenskaber til objekter eller arrays, de alle ender med at pege på det samme objekt, og du løber ind i problemer. Det er bedre at indstille egenskaber, som er til at holde objekter til null . Du må heller ikke forlade ejendomme frakoblet, hvis du ikke kender deres værdi endnu, sæt dem til null i stedet. Senere, når debugging, en ejendom sat til undefined peger på en fejl, som regel en slåfejl.
Base Instans Metoder
Du har måske bemærket, at vi ikke har erklæret en konstruktør til vores underklasse. Base gør initialisering af modulet, og derefter kalder en metode kaldet initializer , hvis det findes, med de samme argumenter, det har modtaget, når instantierede så, til alle formål, du kan overveje at initializer er din konstruktør. Alle klasser stammer fra Base normalt tager et enkelt argument, når der oprettes en genstand, der indeholder konfigurationen attributter. Base (eller Widget , da det er en klasse af Base ) læser dette argument og sætter konfiguration attributter, før du ringer initializer . For en Widget , hvis der er en HTML_PARSER , ejendom, ville det også have været behandlet, og værdierne for attributterne læses fra opmærkningen vil blive sat så godt.
Den initializer Metoden har flere opgaver. For det første skal det satte alle egenskaber, der skal initialiseres til objekter eller arrays. Så den vil offentliggøre alle de begivenheder denne klasse vil producere. EventTarget vil give dig mulighed for at fyre en begivenhed, der ikke har været offentliggjort først at bruge standardindstillingerne til arrangementer, men selv i dette tilfælde, foreslår jeg du erklære dem alligevel. Dette er et godt sted at tilføje API docs kommentarer til de begivenheder, selv om det ser lidt mærkeligt, at være i kroppen af en funktion erklæring, men der er ikke noget bedre sted at gøre det.
Det argument, der modtages af initializer ville have været behandlet inden da, men nogle gange du ønsker nogle ekstra muligheder skal bruges på initialisering og du behøver ikke sørge for at holde faktiske egenskaber for dem. For eksempel Base accepterer attributterne on , after , bubbleTargets og plugins (se Base ), selvom det har intet konfigurations-attributter til dem. Ligeledes WidgetParent tager children attribut på initialisering, men har intet konfigurations-attribut af samme navn. Den initializer metode er den, der behandler dem. Derfor, selvom din klasse vil ende med at tage kun ét argument om instantiering, kan dette enkelt argument bære alle de oplysninger, du får brug for.
JavaScript har ingen forestilling om destructor. Base kompenserer for dette ved at tillade dig at erklære en destructor metode, hvor du kan placere koden til at frigøre ressourcer, dit objekt kunne have taget. Dette er kun en delvis løsning, gør JavaScript-tolk ikke kalde det automatisk, når slippe et objekt, så er du stadig ansvarlig for at ødelægge en genstand, før de kasserer det, men i det mindste du kender en destructor vil være der.
Brugere af din klasse vil aldrig kalde initializer og destructor direkte. Base vil kalde dem når det er påkrævet. initializer vil blive kaldt når objektet er instantieret, destructor vil blive kaldt, når brugeren af din klasse kalder sin destroy metode.
En af de ting, som ofte producerer hukommelseslækager er begivenheden lyttere efterladt. Widget forsøger at løsrive alle lyttere knyttet til elementer af brugergrænsefladen, der er indeholdt i den Afgrænsningsområde element, men det kan ikke løsrive nogen andre. Base kan ikke frigøre alle omstændigheder lyttere på alle . Dette er den kode jeg bruger til at hjælpe mig med det. Langs andre private, eksempelvis ejendomme erklærer jeg den _eventHandles ejendom:
_eventHandles: null, Så i den initializer metoden, sæt jeg det til et array:
initializer: funktion (CFG) { this._eventHandles = []; / / ... ... },
I samme initializer (også i bindUI om det var en Widget ) Jeg vil derefter vedhæfte lytterne ved at gøre:
this._eventHandles.push (this.after ('someAttributeChange', this._afterSomeAttributeChange dette)); Så, i destructor , har jeg:
destructor: function () { Y.each (this._eventHandles, funktion (håndtag) { handle.detach (); }); },
Det er her, i initializer , at du tilslutter tilfælde lyttere for de attributter, som skal producere sekundære effekter (du kan variere det for bindUI hvis denne sekundær effekt har at gøre med UI). Som jeg sagde tidligere, bør tillægge setter funktioner kun beskæftige sig med normalisering af værdien af attributtet. Skulle denne indstilling producerer eventuelle virkninger ud over opbevaring af værdi, skal disse behandles efter begivenhed lyttere. I ovenstående eksempel har jeg sat den metode _afterSomeAttributeChange til at lytte til nogen ændring i someAttribute attribut. Event-lyttere vil få en enkelt argument, hvis facaden, som jeg plejer at kalde ev , et objekt med flere ejendomme, en af dem, newVal indeholder værdien blive sat.
Widget Instans Egenskaber
To vigtige egenskaber, som Widget anvendelser BOUNDING_TEMPLATE og CONTENT_TEMPLATE . Begge er oprindeligt indstillet til “<div></div>” , som producerer den almindelige opbygning af to containere en inden for de andre, at de fleste widgets bruger. Dette er dog muligvis ikke er egnet for alle widgets, for eksempel en Button kan widget bedre være tjent med en <span> element i et anker ( <a> ) element i stedet for to indlejrede <div> S. Faktisk kan du ikke sørge for at have en contentBox på alle, Widget ikke kræver, at du. Du kan indstille disse to tilfælde ejendomme til nogen markup, du ønsker. For eksempel, for Button jeg klassen kunne have:
BOUNDING_TEMPLATE: '<a>', CONTENT_TEMPLATE: null,
Have CONTENT_TEMPLATE sat til null vil fortælle Widget , som du ikke ønsker en contentBox overhovedet. I dette tilfælde contentBox konfiguration attribut vil pege på samme element som de boundingBox konfiguration attribut gør.
Du bør ikke sættes i disse skabeloner hele HTML for widget, gør disse to simple HTML-elementer og skabe ekstra markup via kode i renderUI (som vi skal se senere).
Widget vil føje et id attribut, og standarden klasser den bruger til noget markup, du ønsker, såsom yui3-xxxx , yui3-xxxx-visible eller yui3-xxxx-disabled , hvor xxxx er værdien af de NAME ejendom forvandlet til små bogstaver.
Widget Instans Metoder
Widget opdeler sin initialisering i flere trin. Ud over den initializer , kaldes, når objektet er instantieret, og destructor , kaldet ved destroy , begge metoder håndteres af Base , Widget tilføjer renderUI , bindUI og syncUI for byggefasen, som vil blive kaldt i rækkefølge, når Widget 's render metoden er kaldes.
Den renderUI Metoden tager sig af at fremstille grundlæggende HTML for widget. Både boundingBox og contentBox er gjort på dette punkt. Hvis du bruger progressive ekstraudstyr, renderUI først at kontrollere, om de elementer, der allerede findes på siden. Hvis vi har brugt HTML_PARSER ejendom, så konfigurationen attributter holde henvisningerne til disse elementer vil være indstillet på det tidspunkt, hvis ikke, er vi nødt til at oprette dem.
For at gøre dette, er den letteste måde (forudsat ingen progressiv ekstraudstyr) er at bruge Y.Node.create , som denne:
renderUI: function () { var CBX = this.get (CBX); cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE, CLASS_NAMES))); },
Dette forudsætter en masse ting, som jeg vil forklare det med det samme. Først, jeg har den CBX konstante erklæret som vist i den første kode boksen i denne artikel. Så er det antager Node er indlæst, hvilket Widget bruger så det er sikkert, men det forudsætter også, Y.substitute er der, som er frivillig. Du er nødt til at tilføje 'substitute' til requires listen for dit modul. Så det forventer en skabelon for den pågældende widget for at være i en statisk variabel kaldet TEMPLATE , som er op til dig at definere langs andre statiske klassen medlemmer (lige ved ATTRS og sådan). Endelig er det forudsætter, at der er en konstant CLASS_NAMES erklæret et eller andet sted.
Jeg plejer erklærer CLASS_NAMES op i min modul definition, langs BBX og CBX (se den første kode boksen i denne artikel), som dette:
var BBX = 'boundingBox', CBX = 'contentBox', NAME = 'knappen', / / Andre konstanter og genveje .... YCM = Y.ClassNameManager.getClassName, getClassName = function () { var args = Y. Array (argumenter); args.unshift (NAVN); tilbage YCM.apply (dette, args) toLowerCase (). }, LABEL = 'label', UDPRESSET = 'presset', ICON = 'ikon', CLASS_NAMES = { trykket: getClassName (trykkes), ikon: getClassName (ICON), etiket: getClassName (LABEL), noLabel: getClassName ('nej', LABEL) };
CLASS_NAMES vil derefter være en konstant, som indeholder et objekt med egenskaber, skabt af ClassNameManager (som også kommer der følger med Widget ). I koden ovenfor, først vil jeg oprette en genvej YCM at gøre senere adgang hurtigere, så opretter jeg den funktion getClassName , en privat funktion, der kun er tilgængelig i modulet definition. Funktionen virker stort set ligesom metoden af samme navn af Widget , men det er en statisk funktion, som jeg kan bruge til at definere yderligere statiske værdier. Det er præcis, hvad jeg gøre senere, når jeg opretter CLASS_NAMES som et objekt med den genererede klasse navne som deres egenskaber. Dette tillader mig at skrive en TEMPLATE streng såsom:
Skabelon: '<label class="{label}"> <input/>', Hvilket er temmelig dumt hidtil. Jeg vil også gerne flette ind i denne skabelon værdier fra andre kilder, specielt, konfiguration attributter. Dette er, hvordan jeg kommer til at gøre det:
this.get (CBX). append (Y.Node.create (Y.substitute (skabelon, CLASS_NAMES, Y.bind (funktion (nøgle, foreslog, arg) { tilbage (nøgle === '_' this.get (arg):? foreslået); }, Dette ))));
Jeg tilføje et tredje argument til Y.substitute , en funktion. Normalt pladsholdere for Y.substitute er lavet af tegn lukket i mellem krøllet parentes, men hvis der er en plads, vil det opdele pladsholder i to, den del, op til den plads er nøglen, og den anden et valgfrit argument. Dette kommer praktisk, når det tredje argument er en funktion, som her. Funktionen vil modtage tre argumenter, den første er nøglen, den anden er den værdi, der findes i udskiftning objekt, her CLASS_NAMES , hvis nogen, og den tredje er det valgfrie argument. Så i oversigten ovenfor, kan jeg bruge en skabelon som denne:
Skabelon: '<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" />', Y.substitute vil finde {label} og søge efter det i CLASS_NAMES . Det vil finde det og få 'yui3-button-label' . Det vil derefter kalde udskiftning funktionen med argumenter 'label' , 'yui3-button-label' og undefined . Da key ikke er lig med '_' vil returnere værdien i det andet argument, den oprindelige klasse navn. Når det kommer til {_ id} , er der ingen værdi for en ejendom kaldet _ i CLASS_NAMES så det vil kalde udskiftningen funktionen med argumenter '_' , undefined og 'id' . Med key lige '_' , vil funktionen gå hen og hente værdien af 'id' attribut. Det vil gøre det samme igen for {_ value} pladsholder.
Alle de konstanter erklæret i toppen er skjult fra enhver kode uden for modulet, men du måske ønsker at gøre nogle af dem synlige, såsom CLASS_NAMES . For at gøre dette, i den statiske medlemmer afsnittet, det sidste argument til Y.Base.create , kunne du have:
CLASS_NAMES: CLASS_NAMES Så objekt med alle de klassenavne ville være synlig som Y.MyWidget.CLASS_NAMES .
Jeg foreslår du gør så meget formatering som du kan med HTML-streng, der vil gøre widget indhold. String manipulation i JavaScript er meget hurtigere end at få adgang til DOM, så jo mere du gør, før du ringer Y.Node.create med denne streng, jo hurtigere du får det gjort.
Den næste forekomst metode, der kaldes for enhver widget er bindUI . Det er her du vedhæfter begivenhed lyttere til nogen elementer skabt af renderUI , for eksempel, lytteren for eventuelle ændringer i værdien i <input> kasse med de TEMPLATE ovenfor. Værdien på tekstfeltet, og at der i konfigurationen attribut skal altid holdes i sync. Den value attribut kan ændres enten via kode eller af brugeren skrive ind i indtastningsfeltet. Hvis det kommer fra eksterne kode, skal tekstfeltet være opdateret, hvis det kommer fra tekstfeltet, bør det ikke, ellers risikerer du at indtaste en uendelig løkke: ændringen i tekstfeltet sætter value attribut, som derefter indstiller value på tekstfeltet som Derefter ændringer og sætter value attribut og så videre. Lad os se, hvordan man håndterer denne sag. Vi sætter en lytter på syntetisk valueChange arrangement på indtastningsfeltet. At gøre det vi er nødt til at tilføje event-valuechange modulet til requires listen over dette modul.
this._eventHandles.push (this._inputEl.after ('valueChange', this._afterInputChange dette)); Vi antager, at objektet har en henvisning til tekstfeltet gemt i _inputEl . Lytteren gør dette:
_afterInputChange: funktion (EV) { this.set (værdi, ev.target.get (værdi), {kilde: UI}); },
Her antager vi, har vi konstanter VALUE og UI anmeldt som 'value' og 'ui' hhv. Vi har simpelthen indstille attribut value til den værdi læses fra input boksen. Men vi tilføje et tredje argument til de opstillede metode: {source:UI} . Den set -metoden kan tage et tredje argument, et objekt, hvis egenskaber vil blive blandet ind i tilfælde facade attributtet ændringen begivenhed. Det er den måde, vi kan fortælle forskellen i mellem værdi, der indstilles fra tekstfeltet eller fra eksterne kode. I bindUI ville vi have haft indstillet denne lytter:
this._eventHandles.push (this.after ('valueChange', this._afterValueChange)); Dette er lytteren for en ændring i value attribut af dit objekt, den anden var for en ændring i værdien af de <input> kassen, kaldes de samme, trods alt, lytter de begge over for ændringer i noget der hedder værdi, men er ikke det samme. Normalt er lyttere til attribut ændringer, der i initializer , men da denne ene påvirker et UI element, vi sætter det i bindUI , så vi kender tekstfeltet vil være der. Lytteren vil have:
_afterValueChange: funktion (EV) { if (ev.source === UI) { vende tilbage; } this._inputEl.set (værdi, ev.newVal); },
Det første vi gør er at tjekke source for begivenheden. Hvis det kommer fra UI så vi ignorere det. Både ejendommen navn, source og dens værdi, UI er vilkårlige, det er dem, jeg brugte, når de fastsætter value attribut, så det er dem jeg checker i lytteren, men ethvert navn / værdi ville gøre lige så godt. Faktisk, Widget giver en konstant for det, Y.Widget.UI_SRC , men det er lidt lang, så jeg nok ville bruge en genvej alligevel.
En anden tidbit: du kan indstille attributter angives som read-only ved hjælp _set i stedet for set . Den _set metode er beregnet til at blive beskyttet, der skal bruges internt, men som vi ved, JavaScript kender intet om sikkerhed, så _set er åben for alle, men i det mindste forsøger vi ved at erklære den egenskab med readOnly:true og dokumentere det som sådan i API docs.
Endelig vi erklærer 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) {
vende tilbage;
}
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/', moduler: { '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.
Konklusion
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.
Del og udvide: Bookmark med del.icio.us | digg det! | reddit!
18 Comments »
RSS feed for kommentarer til dette indlæg. TrackBack URI
Efterlad en kommentar

Copyright © 2006-2011 Yahoo! Inc. Alle rettigheder forbeholdes. Fortrolighedspolitik - Servicevilkår
Powered by WordPress på Yahoo! Web Hosting .


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