Μια συνταγή για ένα YUI 3 Εφαρμογή

Πρώτης Απριλίου 2011 στις 2:52 π.μ. από Satyam | Σε Ανάπτυξη | 18 Σχόλια

YUI 3 έχει σχεδιαστεί για να δημιουργήσουν εφαρμογές γύρω από ενότητες. Δεν θα μιλήσω για το τι είναι μια μονάδα, δεδομένου ότι έχει καλά περιγραφεί από τον Nicholas Ζάκα στην παρουσίασή του επεκτάσιμη αρχιτεκτονική Εφαρμογή JavaScript . Θα κολλήσει ακριβώς στο πώς να χτίσει αυτές τις ενότητες. Τα περισσότερα από αυτά που θα πω μπορεί να βρεθεί στην on-line τεκμηρίωση, μαζί με διάφορες άλλες εναλλακτικές λύσεις, μετά από όλα, αυτό είναι το σημείο της καλής τεκμηρίωσης: να σας πω για όλους τους πιθανούς τρόπους να κάνουμε πράγματα. Γι 'αυτό είναι μια συνταγή, ένας μόνο τρόπος για να γίνει αυτό, μεταξύ πολλών άλλων. Προϋποθέτει επίσης την εφαρμογή μικρούλες, με όχι τόσο πολλά στρώματα ως «Νικόλαος προτείνει, δεδομένου ότι αυτό είναι μόνο ένα άρθρο και όχι ένα βιβλίο.

Ο εντοπισμός των ενοτήτων

Το πρώτο βήμα είναι να προσδιορίσει τις ενότητες που θα χρειαστούμε. Μια καλή προσέγγιση είναι να αρχίσει τον τεμαχισμό του σχεδιασμού της οθόνης αίτησης σε επιμέρους ενότητες: γραμμή τίτλου, γραμμή μενού, το περιεχόμενο, πλαϊνά τοιχώματα ή οτιδήποτε άλλο μπορεί να υπάρχει εκεί. Τότε ρίξτε μια ματιά σε ό, τι η βιβλιοθήκη έχει να προσφέρει. Για παράδειγμα, YUI 3 δεν έχει καμία Μενού, αλλά υπάρχει ο κόμβος-MenuNav plugin , το οποίο λαμβάνει μια βασική δομή μενού που επιτυγχάνεται με την εντολή των ένθετων στοιχείων <UL> λίστα και τους μετατρέπει σε μια ενεργό μενού. Ή μπορεί να θέλετε να ελέγξετε την Πινακοθήκη YUI για τις βασικές συνιστώσες. Τέλος πάντων, θα φτάσει τελικά στο σημείο όπου έχετε ένα κουτί στην εν λόγω διάταξη ότι θα πρέπει να συμπληρώσετε μόνοι σας, οπότε ας το κάνουμε αυτό.

Θα ήθελα να συστήσω τη διάθεση κάθε ενότητα στο δικό της αρχείο και το δικό της κατάλογο με το ίδιο όνομα. Έτσι, ένας weather μονάδα θα είναι weather/weather.js . Ο λόγος για αυτό είναι επειδή το module σας είναι πιθανό να χρειαστούν κάποιες στυλ, ορισμένα αρχεία CSS και εικόνας, καθιστά εύκολο για τον ενσωματωμένο στο δάπεδο, αν τα τοποθετήσετε όπου μπορεί να βρει εύκολα, στην περίπτωση αυτή, το κύριο φύλλο στυλ Θα είναι weather/assets/skins/sam/weather.css , με τα άλλα περιουσιακά στοιχεία, εικόνες και ούτω καθεξής, παράλληλα. Αυτό προϋποθέτει να μην χρησιμοποιείτε το YUI δόμησης που θα έχει ήδη κάνει τα πράγματα με αυτόν τον τρόπο ούτως ή άλλως, αλλά αυτό είναι μια άλλη ιστορία. Τα ονόματα των φακέλων assets και skins είναι περισσότερο ή λιγότερο αυτονόητη, sam όμως δεν είναι αρκετά προφανής. Είναι η προεπιλεγμένη τιμή για το skin ιδιοκτησία του φορτωτή, διότι αυτό είναι το δέρμα προεπιλογή αποστέλλονται με YUI, το όνομά του από τον σχεδιαστή, ο Sam Lind. Όπως αυτό προτείνει, είστε ελεύθεροι να βάλετε το όνομά σας στο δικό σας δέρμα και το skin ιδιότητα σας επιτρέπει να πείτε YUI να τους φορτώσει, αλλά να κρατήσουμε απλή, ας πάμε με την προεπιλογή.

Ενότητα πρότυπο αρχείο

Αυτή είναι η δομή του αρχείου που χρησιμοποιείτε πιο συχνά, η οποία θα περιγράψω σε μια στιγμή:

  / * Jslint ανάπτυξη: αλήθεια, undef: αλήθεια, newcap: αλήθεια, αυστηρή: αλήθεια, maxerr: 50 * / 
 / * Παγκόσμια YUI * / 
 / ** 
  * Η μονάδα-το όνομα module δημιουργεί το μπλα μπλα 
  * @ Ενότητα ενότητα, το όνομα 
  * / 
 YUI.add («ενότητα-όνομα», η λειτουργία (Y) { 
     "Αυστηρή χρήση"? 
     / / Πρακτικό σταθερές και συντομεύσεις που χρησιμοποιούνται για την ενότητα 
     var lang = Y.Lang, 
         CBX = 'contentBox », 
         BBX = 'boundingBox »,
         = ΟΝΟΜΑ «xxxx»? 

 
     / ** 
      * Η κατηγορία Xxxx κάνει .... 
      * @ Τάξη Xxxx 
      * @ Επεκτείνει Widget 
      * @ Χρησιμοποιεί WidgetParent 
      * @ Κατασκευαστή 
      * Cfg @} {αντικείμενο χαρακτηριστικά διαμόρφωσης 
      * / 
     Y.Xxxx = Y.Base.create ( 
         ΟΝΟΜΑ, 
         Y.Widget, 
         [Y.WidgetParent], 
         { 
             / / Μέλη του Πρωτοδικείου εδώ 
         }, 
         { 
             / / Στατική μέλη εδώ, ειδικά: 
             ATTRS: { 
             } 
         } 
     )? 

 
 }, 0 0.99 », { 
     απαιτεί: [«γραφικό», «widget-γονέων], 
     skinnable: αλήθεια 
 })? 

Οι δύο πρώτες γραμμές του σχολιασμού είναι προς όφελος των JSLint , το εργαλείο επαλήθευσης JavaScript που πραγματικά προτείνουμε. Αν πάτε στην ιστοσελίδα έκδοση , υπάρχει ένα κουτί για να ορίσετε τις επιλογές. Στο κάτω μέρος του κουτιού επιλογές που μπορείτε να δείτε τον τρόπο για να κωδικοποιήσει αυτές τις επιλογές στο ίδιο το αρχείο. Εάν χρησιμοποιείτε το YUI Builder, θα τρέξει JSLint για σας και να ορίσετε αυτές τις επιλογές για σας, αλλά μπορείτε να παρακάμψετε τους ακόμα για οποιοδήποτε μεμονωμένο αρχείο, αν θέλετε.

Τα σχόλια είναι doc για το YUI API docs οικοδόμος. Είναι λιγότερο από έναν πονοκέφαλο, αν συμπεριληφθεί η αρχική πρότυπο για όσους API Docs, τελικά θα τους γεμίσει. Καθώς μεγαλώνει η εφαρμογή και δεν μπορείτε να θυμηθείτε όλα, θα τα χρειαστείτε.

Τώρα έρχεται η πρώτη πραγματική γραμμή του κώδικα, η YUI.add() δήλωση. Αυτός είναι ο τρόπος για να πει το YUI Loader το όνομα και τα περιεχόμενα της ενότητας και πολλά άλλα κομμάτια των πληροφοριών. Τα ονόματα που συνήθως ονομάζεται Ενότητα με όλα τα πεζά γράμματα και παύλες ανάμεσα στις λέξεις. Αυτά είναι τα ονόματα που βλέπετε στην API Docs στο top-πλέον δείκτη στα αριστερά. Μπορείτε να δείτε, η σύμβαση δεν ακολουθείται αυστηρά, μερικά ονόματα μονάδα δεν έχει παύλες. Τέλος πάντων, αυτό είναι ως επί το πλείστον σε σας όσο μπορείτε να το χρησιμοποιήσετε με συνέπεια.

Το δεύτερο επιχείρημα της YUI.add() η δήλωση είναι μια λειτουργία που λαμβάνει ένα μοναδικό επιχείρημα, που συμβατικά ονομάζεται Y . Η λειτουργία αυτή περιλαμβάνει το σώμα της ενότητας και Y είναι η αναφορά σε μια sandboxed περίπτωση YUI η οποία είναι όπου μπορείτε να βρείτε όλες τις άλλες YUI και Γκαλερί ενότητες που έχετε ζητήσει. Πηδώντας στο κάτω μέρος του πλαισίου αυτού κώδικα, μπορείτε να δείτε το υπόλοιπο των επιχειρημάτων του .add() μέθοδος, η έκδοση ( '0.99′ ή δεν έχουμε φτάσει εκεί ακόμα) και η διαμόρφωση για αυτήν την ενότητα, ένα αντικείμενο με τη σειρά της α ιδιότητες. Εδώ, λέω του Φορτωτή ότι αυτή η ενότητα απαιτεί widget και widget-parent και ότι έχει ένα δέρμα. Λίστα widget είναι περιττή, δεδομένου ότι widget-parent απαιτεί ήδη widget , αλλά δεν σας απασχολήσω με αυτό, ο φορτωτής δεν θα φορτώσετε ένα module δύο φορές και, αν σε μεταγενέστερο στάδιο, ρίχνετε μία εξάρτηση, δεν πρέπει να ελέγξετε για άλλες υποθέσεις μπορεί να κάνει: κατάσταση όλα αυτά και να αφήσει τη συμφωνία φορτωτή με αυτό. Μπορείτε να βρείτε μια λίστα με όλες τις επιλογές της API για την docs .addModule() μέθοδος του φορτωτή.

Στο σώμα της συνάρτησης, το πρώτο πράγμα είναι η “use strict”; δήλωση. Αυτό είναι για τον κωδικό σας για να συμμορφωθούν με το πρότυπο ECMAScript 5, η οποία στο σημείο αυτό σας βάζει στην ασφαλή πλευρά να εξασφαλίσει τη συμβατότητα με όλες τις πλατφόρμες που είναι πιθανό να αντιμετωπίσουν στο μέλλον. Για τους παλαιότερους ερμηνευτές, η δήλωση αυτή δεν είναι τίποτα περισσότερο από μια σειρά που δεν έχει ανατεθεί σε τίποτα και αγνοείται. Η “use” δήλωση έχει πεδίο εφαρμογής και λειτουργίας, είναι ασφαλέστερο να το τοποθετήσετε μέσα στο σώμα από τη λειτουργία στην κορυφή του αρχείου, έτσι ώστε να επηρεάζει μόνο την ενότητα που καθορίζουν. Αν το τοποθετήσετε στο επάνω μέρος του αρχείου, θα εφαρμόζονται επίσης σε κάθε άλλο αρχείο το JavaScript φορτώσετε αργότερα, και πολλοί από αυτούς δεν μπορεί να συμμορφωθεί με ES5.

Στη συνέχεια έρχονται οι συντομεύσεις και σταθερές, οι οποίες είναι σταθερές μόνο στη χρήση του από το JavaScript δεν έχει καμία έννοια του σταθερές. Τους αναφέρουμε σε όλους-κεφαλαία γράμματα και υπογραμμίζει, ως σταθερές συχνά βρίσκονται σε άλλες γλώσσες. Υπάρχουν δύο καλοί λόγοι για να χρησιμοποιήσει σταθερές, ειδικά σταθερές συμβολοσειράς. Πρώτη είναι ότι όταν γράφετε την ίδια σειρά αρκετές φορές, ίσως να γράφετε τη μία από αυτές και το μόνο που θα παρατηρήσετε όταν ένα bug σκάει επάνω. Εάν χρησιμοποιήσετε σταθερές, JSLint θα σας προειδοποιήσει όταν πληκτρολογείτε το όνομα σταθεράς λάθος, δεδομένου ότι θα είναι απροσδιόριστες. Ο δεύτερος λόγος είναι ότι η YUI συμπιεστή μπορεί να κάνει καλύτερη δουλειά από σταθερά ονόματα μπορούν να συμπιεστούν ενώ κυριολεκτικές συμβολοσειρές δεν μπορούν. Καλή υποψήφιοι για το όνομα σταθερές είναι τα ονόματα των χαρακτηριστικών διαμόρφωσης και εκδηλώσεις.

Συντομεύσεις, όπως Lang για Y.Lang είναι επίσης καλό, επειδή σας επιτρέπουν να πληκτρολογήσετε λιγότερο, ο διερμηνέας πρέπει να αξιολογήσει λιγότερο (κάθε κουκίδα υποδηλώνει μια νέα έρευνα στα μέλη του αντικειμένου) και μπορούν να συμπιέζεται από το YUI συμπιεστή.

Μετά τα σχόλια Docs API για την τάξη, παίρνουμε την πραγματική δήλωση. Πρέπει να δηλώσει την τάξη μας ως ιδιοκτησία της Y , έτσι Y.Xxxx . Η πρότασή μου είναι να χρησιμοποιήσετε Y.Base.create() για τη δημιουργία του, όπως φαίνεται εδώ. Μπορεί να δημιουργήσει μόνο τις κατηγορίες που προέρχονται από Base (και Widget που επίσης είναι μια υποκλάση της Base ), αλλά αυτό θα καλύψει τις περισσότερες από τις ενότητες που θα χρησιμοποιήσει γι 'αυτό είναι ασυνήθιστο να χρειαστεί να το κάνουμε με άλλο τρόπο. Το πρώτο επιχείρημα είναι το όνομα της μονάδας, η NAME ιδιοκτησία που περιγράφεται για τη Base συνιστώσα. Συμβατικά, το NAME ιδιοκτησία είναι μια καμήλα-περίπτωση έκδοσης του το όνομα της κλάσης. Αυτό το όνομα χρησιμοποιείται ως πρόθεμα για τα γεγονότα (πριν από το τμήμα του παχέος εντέρου, όπως “io:success” ), για ονόματα κλάσεων CSS που παράγεται από το Widget τάξη (δηλ.: “yui3-xxxx-content” ) και για την εφαρμογή του ορισμού toString() , η οποία συχνά θα δείτε σε ίχνη από το πρόγραμμα εντοπισμού σφαλμάτων. Εδώ μπορώ να χρησιμοποιήσω την τιμή της σταθεράς NAME να καθορίσει την τάξη NAME ιδιοκτησίας.

Το δεύτερο επιχείρημα είναι ότι η κατηγορία εκτάσεις. Θα χρησιμοποιούν συχνά είτε Y.Base , για τις ενότητες χρησιμότητα που θα έχει καμία διεπαφή χρήστη, Y.Widget για εκείνους που θα έχουν UI, Y.Plugin.Base για plugins ή κάθε άλλης κατηγορίας που προέρχονται από Y.Base , όπως κάθε μπορεί να έχετε ήδη δημιουργήσει χρησιμοποιώντας Y.Base.create() .

Το τρίτο επιχείρημα κρατά τις επεκτάσεις που θα χρησιμοποιήσετε. Οι επεκτάσεις είναι τάξεις του οποίου τις ιδιότητες και τις μεθόδους που θέλετε να έχουν αναμειχθεί στην τάξη σας. Καλές οι υποψήφιοι για τις επεκτάσεις είναι ArrayList για Base ή σε οποιοδήποτε από τα Widget-Xxxx υποενότητες για Widget . Attribute , EventTarget και PluginHost έρθει ήδη αναμειχθεί σε Base , ώστε να μπορείτε πάντα να υπολογίζετε σε αυτά τα τρία να είναι εκεί. Επεκτάσεις είναι πολύ ισχυρό, αν κοιτάξετε το πηγαίο κώδικα για Overlay , μπορείτε να δείτε δεν υπάρχει τίποτα, αλλά Widget και επεκτάσεις. Πολλές επεκτάσεις μπορούν να αναμειγνύονται μεταξύ τους σε μια συνιστώσα έτσι το τρίτο επιχείρημα είναι μια σειρά.

Τέλος, έχουμε την ευκαιρία να τον πραγματικό κώδικα. Η τέταρτη και η πέμπτη επιχειρήματα είναι δύο κατεξοχήν αντικείμενο που περιέχει το παράδειγμα και στατική μέλη της τάξης. Μέλη του Πρωτοδικείου είναι οι ιδιότητες και οι μέθοδοι που πηγαίνουν στην τάξη prototype , αυτά ότι κάθε περίπτωση θα πάρετε ένα αντίγραφο και συνήθως πρέπει να γίνονται από this . Στατική μέλη είναι εκείνα που θα μοιραστεί με όλες τις περιπτώσεις.

Χαρακτηριστικά Διαμόρφωση

Η σημαντικότερη από αυτές είναι στατικές μέλη η ATTRS ιδιοκτησία. Αυτό αναφέρει η διαμόρφωση αποδίδει την τάξη σας θα έχουν. Για παράδειγμα, ας πούμε ότι θέλουμε να έχουμε ένα χαρακτηριστικό που ονομάζεται διαμόρφωση value να κρατήσει αριθμητικές τιμές και αρχικά οριστεί σε 0. Στο πέμπτο επιχείρημα, θα το δηλώσει ως εξής:

  ATTRS: {
     τιμή: {
         τιμή: 0,
         validator: Lang.isNumber
     }
 } 

Μπορούμε να παραθέσουμε οποιοδήποτε αριθμό χαρακτηριστικών στην ATTRS ιδιοκτησίας και κάθε ένα μπορεί να ρυθμιστεί με διάφορες επιλογές, δύο εκ των οποίων έχω δείξει εδώ. Μπορείτε να διαβάσετε για το υπόλοιπο των επιλογών στην addAttr() μέθοδος της Attribute . Όπως μπορεί να δει, έχω χρησιμοποιήσει το Lang συντόμευσης που δήλωσα στην αρχή της δήλωσης ενότητας. Η validator πρέπει να είναι μια συνάρτηση η οποία παίρνει την τιμή να ελέγχει και να επιστρέφει μια Boolean. Όλα τα Y.Lang.isXxxx μέθοδοι κάνουν ακριβώς αυτό, ώστε να μπορεί να χρησιμοποιηθεί άμεσα. Για περισσότερες περίτεχνα επικύρωσης, ρυθμιστές ή κτήτορες, θα πρέπει να ορίσετε λειτουργίες. Θα ήθελα να συστήσω παρέχοντας το όνομα της συνάρτησης ως συμβολοσειρά, Attribute φροντίζει για την επίλυση του ονόματος λειτουργία στην πραγματική λειτουργία. Για παράδειγμα, αν ήταν να καθοριστεί ένας, ας πούμε, validCodes χαρακτηριστικό που μπορεί να πάρει είτε ένα μόνο έγκυρο κωδικό ή μια σειρά από έγκυρους κωδικούς, αλλά θα πρέπει να επιστρέφουν πάντα μια σειρά, θα ήθελα να κάνω:

  ATTRS: {
     validCodes: {
         setter: «_setValidCodes»
     }
 } 

Πρέπει να κηρύξει την _setValidCodes μέθοδο κατά μήκος των άλλων μελών παράδειγμα το τέταρτο επιχείρημα της Y.Base.create() :

  _setValidCodes: λειτουργία (αξία) {
     αν (! Lang.isArray (αξία)) {
         = αξία [αξία]?
     }
     επιστρέψει τιμή?
 } 

Είναι καλύτερο να δηλώσει setters, getters και οποιαδήποτε αλλά το πιο ασήμαντο των συστημάτων αναγνώρισης ως ξεχωριστές λειτουργίες παράδειγμα και ας Attribute επιλύσει το όνομα της συνάρτησης για την πραγματική κλήση της συνάρτησης.

Σε γενικές γραμμές, χρησιμοποιήστε τους ρυθμιστές για να ομαλοποιήσει την αξία, όπως φαίνεται από τα παραπάνω, να μην παράγουν δευτερογενή αποτελέσματα. Όλα τα χαρακτηριστικά διαμόρφωσης θα φωτιά πριν και μετά τα γεγονότα με την αλλαγή πριν από την εκδήλωση μπορείτε να εμποδίσετε την ιδιότητα να αλλάζουν. Χρησιμοποιήστε αυτά τα γεγονότα αλλαγή παράγουν δευτερογενή αποτελέσματα, όχι ο ρυθμιστής. Η μετά την εκδήλωση είναι η καλύτερη αφού από τότε ξέρεις ότι τίποτε δεν εμπόδιζε το χαρακτηριστικό από το να ορίζεται από τον κώδικα κάποιου άλλου μπορεί να εγγραφεί με το πριν (στις) περίπτωση αλλαγής και να ακυρωθεί.

Μπορείτε να κάνετε χαρακτηριστικό σας περισσότερο ή λιγότερο αυστηρές ανάλογα με το πώς ορίζουν επικύρωσης και ρυθμιστής σας. Αν κάνετε το validator πολύ περιοριστική, χαρακτηριστικό σας θα είναι πολύ αυστηρές, για την αποδοχή μόνο έγκυρες τιμές, στην περίπτωση αυτή, ο ρυθμιστής μπορεί να είναι περιττή. Από την άλλη πλευρά, ίσως να μην χρησιμοποιείτε καθόλου επικύρωσης και να στηρίζεται αποκλειστικά στο ρυθμιστή για να κάνετε μασάζ κάθε ποσό που έλαβε σε κάτι αποδεκτό. Για παράδειγμα, μπορείτε να έχετε ένα από αυτά τα δύο:

  validator: Y.Lang.isBoolean, / / ​​να κάνει το χαρακτηριστικό δέχεται αυστηρά μια Boolean
 ρυθμιστής: Boolean, / / ​​να κάνει το χαρακτηριστικό αποδέχεται καμία αξία και έχουν Boolean το μετατρέψει σε ένα 

Ρυθμιστές μπορεί επίσης να χρησιμεύσει ως επικυρωτές. Ρυθμιστές πρέπει να επιστρέψουν την αξία που θα διατεθεί για το χαρακτηριστικό, αλλά μπορεί επίσης να επιστρέψει Y.Attribute.INVALID_VALUE που θα αφήσει αμετάβλητο το χαρακτηριστικό, σαν ένα validator είχε απορριφθεί.

Όταν ορίσετε ένα χαρακτηριστικό διαμόρφωση ορίζω συχνά ένα σταθερό για το οποίο έχω θέση στην κορυφή της μονάδας (μαζί CBX , BBX και τις συντομεύσεις), για παράδειγμα:

  var ΑΞΙΑ = «αξία»,
     VALID_CODES = "validCodes»? 

Οι πιθανότητες είναι ότι θα χρησιμοποιήσει αυτά τα χαρακτηριστικά διαμόρφωσης αρκετές φορές μέσα στην ενότητα και αυτό θα με σώσει μερικά προβλήματα. Ωστόσο, να είστε προσεκτικοί, μην χρησιμοποιήσετε αυτό το σταθερό κατά τη δήλωση της την ιδιότητα, μην το κάνετε αυτό:

  ATTRS: {
     / / *** Μην το κάνετε αυτό *** / /
     ΑΞΙΑ: {
         τιμή: 0,
         validator: Lang.isNumber
     }
 } 

Εάν το κάνετε αυτό, θα λάβετε ένα χαρακτηριστικό που ονομάζεται VALUE αντί της value . Αυτό είναι ένα θέμα το JavaScript, όχι ένα YUI. Επίσης, προσέξτε να μην υπερβούμε σε οποιαδήποτε από τις ρυθμίσεις αποδίδει ήδη δηλωθεί είτε για τη βασική κλάση ή των επεκτάσεων. Widget έχει ήδη μια δέσμη των ιδιοτήτων που δηλώνονται (βλέπε πίνακα ) και αν θα προσθέσετε μια σχεδόν boundingBox αποδίδουν στον εαυτό σας, μπορείτε μπορεί εύκολα να ξεχάσει ότι visible , disabled , height ή width έχουν ήδη καθοριστεί. Αν προβλεπόμενη χρήση σας ταιριάζει με αυτό που Widget για τα χρησιμοποιεί, όλα θα πάνε καλά. Αυτό είπε, μπορείτε να αλλάξετε τον ορισμό του κανένα από αυτά. Y.Base.create() συγχωνεύεται με τον ορισμό των χαρακτηριστικών διαμόρφωσης της επέκτασης με εκείνες της κλάσης βάσης, ώστε, αν θέλετε να αλλάξετε, για παράδειγμα, την προεπιλεγμένη τιμή για ένα υπάρχον χαρακτηριστικό, μπορείτε να το πράξει αυτό δηλώνοντας ότι η ιδιότητα και πάλι στην υποκατηγορία σας.

Να είστε προσεκτικοί αν εννοείτε να προετοιμαστεί ένα χαρακτηριστικό με έναν πίνακα ή ένα αντικείμενο. Τα αντικείμενα (και οι πίνακες είναι αντικείμενα) πέρασε από την αναφορά και αν προετοιμάσει ένα χαρακτηριστικό με ένα αντικείμενο, θα μπορούσαν να καταλήξουν όλα δείχνουν στο ίδιο αντικείμενο, έτσι ώστε όταν αλλάξει μέρος της (καταργήσετε ένα στοιχείο από έναν πίνακα ή να προσθέσετε ένα ακίνητο με το αντικείμενο) που καταλήγουν αλλάζοντας όλες τις περιπτώσεις ταυτόχρονα. Base , όμως, έχει κάποια εσωτερική λογική που θα σας επιτρέψει να προετοιμαστεί με ασφάλεια ένα χαρακτηριστικό με το αντικείμενο και σειρά κυριολεκτικές. Εάν η τιμή αρχικοποίησης είναι ένα αντικείμενο ή έναν πίνακα κυριολεκτική τότε Base θα κλωνοποιήσουμε. Χρησιμοποιήστε την valueFn επιλογή ή την προετοιμασία στην τάξη initializer για άλλα αντικείμενα.

Άλλες Στατική Μέλη

Υπάρχουν δύο άλλες στατικές μέλη που μπορεί να καθορίσει το πέμπτο επιχείρημα της Y.Base.create() . Εάν δημιουργείτε ένα πρόσθετο, πρέπει οπωσδήποτε να δηλώσουν την NS ακίνητο, αν δεν, το plugin δεν θα λειτουργεί αθόρυβα και να αποτύχει. Το NS ιδιοκτησίας πρέπει να οριστεί σε μια σειρά η οποία θα χρησιμοποιείται ως το όνομα ιδιοκτησίας για να αποθηκεύσετε το plugin μέσα στο αντικείμενο υποδοχής, να το θυμάστε αυτό, όταν επιλέγετε το όνομα έτσι ώστε να μην υπερισχύει κάθε υπάρχουσα ιδιοκτησία.

Εάν χτίζετε ένα widget και μπορείτε να σχεδιάζουν να υποστηρίξουν την προοδευτική βελτίωση, τότε θα χρησιμοποιήσετε το HTML_PARSER στατική ιδιοκτησίας. Αυτό έχει οριστεί σε ένα αντικείμενο που περιέχει τις ιδιότητες ονομάζεται μετά από τα χαρακτηριστικά διαμόρφωσης για να ρυθμίσετε από αναλύοντας την υπάρχουσα HTML και είτε CSS3 επιλογείς ή λειτουργίες που θα παράγουν τις αξίες τους. Δείτε Προοδευτική Ενίσχυση στον οδηγό χρήσης Widget.

Μπορεί επίσης να θέλει να δώσει τιμές που πρέπει να χρησιμοποιούνται από προγραμματιστές που χρησιμοποιούν την τάξη σας. Οι σταθερές που δηλώνονται στην κορυφή του αρχείου είναι εντελώς αόρατα έξω από τη λειτουργική μονάδα. Αν θέλετε να παρέχει δημόσια σταθερές, αυτό είναι το μέρος για να το κάνει. Παραδείγματα τέτοιων είναι η HEADER , BODY και FOOTER σταθερές WidgetStdMod (για να τους χρησιμοποιήσουν θα πρέπει πραγματικά να χρησιμοποιήσετε το πλήρως αναγνωρισμένο όνομα: Y.WidgetStdMod.BODY και τέτοια).

Μέλη Πρωτοδικείου

Το τέταρτο επιχείρημα της Y.Base.create() είναι οι ιδιότητες και οι μέθοδοι που θα πάει στο prototype της τάξης δημιουργήθηκε. Συνήθως, δηλώνουμε τις ιδιότητες και τις μεθόδους πρώτο αργότερα. Δεν έχω κανένα λόγο γι 'αυτό, η σειρά είναι πραγματικά άσχετο, ούτε το JavaScript YUI ούτε απαιτούν από εσάς να κάνετε αυτό τον τρόπο, αλλά καθιστά πιο εύκολο να εντοπίσετε τα πράγματα στο αρχείο προέλευσης. Αν και ιδιότητες παράδειγμα μπορεί να δημιουργηθεί σχετικά με την πετάξει στο initializer, εγώ συνιστούμε να τα δηλώσει ρητά και την προετοιμασία τους. Κάθε ιδιοκτησία θα πρέπει να προηγηθεί ένα σχόλιο API doc.

Ακίνητα είναι συνήθως ιδιωτικά και το όνομά του από ένα πρόθεμα υπογράμμισης. Είναι καλύτερα αν η δημόσια διεπαφή του αντικειμένου που εκτίθενται μέσω του ιδιότητες διαμόρφωσης και όχι ιδιότητες. Ακίνητα είναι πολύ χαζή, τη διαμόρφωση των χαρακτηριστικών μπορεί να έχει επικύρωσης, μετατροπή τύπου (μέσω του ρυθμιστή) και παράγουν δευτερογενή αποτελέσματα (μέσω εκδηλώσεων αλλαγή) και δεν είναι συχνά πολύ μέχρι να βρείτε ότι θέλετε όλα αυτά τα χαρακτηριστικά.

Όπως χαρακτηριστικά με τη διαμόρφωση, δεν προετοιμαστεί ιδιότητες σε αντικείμενα ή πίνακες, όλα αυτά μέχρι το τέλος δείχνουν στο ίδιο αντικείμενο και τρέχετε στο πρόβλημα. Είναι καλύτερα να ορίσετε τις ιδιότητες που είναι να κρατήσει αντικείμενα null . Επίσης, μην αφήνετε τις ιδιότητες απενεργοποίηση, αν δεν ξέρεις την αξία τους ακόμα, που να null αντ 'αυτού. Αργότερα, όταν τον εντοπισμό σφαλμάτων, μια ιδιότητα που να undefined σημεία σε ένα λάθος, συνήθως ένα τυπογραφικό λάθος.

Μέθοδοι Πρωτοδικείο Βάση

Ίσως έχετε παρατηρήσει ότι δεν έχουν δηλώσει κανένα κατασκευαστή για την υποκατηγορία μας. Base κάνει την εκκίνηση της μονάδας και στη συνέχεια καλεί μια μέθοδο που ονομάζεται initializer , αν υπάρχει, με τα ίδια επιχειρήματα που έχει λάβει, όταν αρχικοποιείται έτσι, για όλους τους σκοπούς, που μπορεί να θεωρήσει ότι initializer είναι κατασκευαστής σας. Όλες οι κατηγορίες προέρχονται από Base λαμβάνουν συνήθως ένα επιχείρημα, όταν δημιουργείται, ένα αντικείμενο που περιέχει τα χαρακτηριστικά διαμόρφωσης. BaseWidget , δεδομένου ότι είναι μια κλάση Base ) διαβάζει το επιχείρημα αυτό και καθορίζονται τα χαρακτηριστικά διαμόρφωσης πριν από την κλήση initializer . Για Widget , αν υπάρχει ένα HTML_PARSER ιδιοκτησίας, αλλά θα έχουν υποστεί επεξεργασία και οι τιμές για τις ιδιότητες διαβάσει από την σήμανση θα καθοριστούν επίσης.

Η initializer μέθοδος έχει πολλά καθήκοντα. Πρώτον, θα πρέπει να θέσει οποιεσδήποτε ιδιότητες που πρέπει να προετοιμαστεί για τα αντικείμενα ή πίνακες. Στη συνέχεια, θα δημοσιεύσει όλα τα γεγονότα για αυτή την κατηγορία θα παράγει. EventTarget θα σας επιτρέψει να φωτιά ένα γεγονός που δεν έχει δημοσιευθεί πρώτα χρησιμοποιώντας τις προεπιλεγμένες ρυθμίσεις για εκδηλώσεις, αλλά ακόμη και στην περίπτωση αυτή, σας προτείνω να τους δηλώνουν ούτως ή άλλως. Αυτό είναι ένα καλό μέρος για να προσθέσετε τα σχόλια API docs για τα γεγονότα αυτά ακόμα κι αν φαίνεται λίγο περίεργο, είναι στο σώμα της δήλωσης λειτουργία, αλλά δεν υπάρχει καλύτερο μέρος για να το πράξει.

Το επιχείρημα που λαμβάνονται από initializer θα αποτελούν αντικείμενο επεξεργασίας από τότε, αλλά μερικές φορές θέλετε μερικές επιπλέον επιλογές που θα χρησιμοποιηθούν για την προετοιμασία και δεν σας ενδιαφέρει να κρατήσει πραγματικά χαρακτηριστικά τους. Για παράδειγμα, Base δέχεται τα χαρακτηριστικά on , after , bubbleTargets και plugins (βλέπε Base ) αν δεν έχει τα χαρακτηριστικά διαμόρφωσης για εκείνους. Ομοίως WidgetParent διαρκεί children αποδίδουν στην εκκίνηση, αλλά δεν έχει καμία διαμόρφωση χαρακτηριστικό της εν λόγω ονομασίας. Η initializer μέθοδος είναι εκείνη που τα επεξεργάζεται. Έτσι, αν και την τάξη σας, θα καταλήξετε λαμβάνοντας μόνο ένα επιχείρημα για την συγκεκριμενοποίηση, αυτό και μόνο το επιχείρημα μπορεί να μεταφέρει όλες τις πληροφορίες που μπορεί να χρειαστείτε.

Το JavaScript δεν έχει την έννοια της καταστροφείς. Base αντισταθμίζει αυτό, επιτρέποντάς σας να κηρύξει destructor μέθοδο, όπου μπορείτε να τοποθετήσετε τον κώδικα για να απελευθερώσει τους πόρους που το αντικείμενό σας θα μπορούσε να λάβει. Αυτή είναι μόνο μια μερική λύση, ο διερμηνέας JavaScript δεν θα καλέσει αυτόματα όταν πέφτει ένα αντικείμενο, έτσι ώστε να εξακολουθούν να είναι υπεύθυνα για την καταστροφή ενός αντικειμένου πριν από την απόρριψη, αλλά τουλάχιστον ξέρετε έναν καταστροφέα θα είναι εκεί.

Οι χρήστες της τάξης σας ποτέ δεν θα καλέσει initializer και destructor άμεσα. Base θα τους καλέσει, όταν απαιτείται. initializer θα κληθεί όταν το αντικείμενο αρχικοποιείται, destructor θα καλείται όταν ο χρήστης της τάξης σας καλεί τους destroy τη μέθοδο.

Ένα από τα πράγματα που παράγουν συχνά διαρροές μνήμης είναι ακροατές εκδήλωση άφησαν πίσω τους. Widget προσπαθεί να αποσπάσει όλες τις ακροατές που συνδέονται με τα στοιχεία της διεπαφής χρήστη που περιέχεται στο εσωτερικό της οριοθέτησης στοιχείο Πλαίσιο, αλλά δεν μπορούν να αποκολληθούν με οποιαδήποτε άλλα. Base δεν μπορούν να αποκολληθούν με οποιαδήποτε ακροατές εκδήλωση σε όλα . Αυτός είναι ο κώδικας που χρησιμοποιώ για να με βοηθήσει με αυτό. Μαζί τους άλλους ιδιώτες, ιδιότητες παράδειγμα Κηρύσσω την έναρξη της _eventHandles ακινήτου:

  _eventHandles: null, 

Στη συνέχεια, στο initializer μέθοδο, εγώ που σε μια σειρά:

  initializer: λειτουργία (cfg) {
     this._eventHandles = []?
     / / ......
 }, 

Στην ίδια initializer (επίσης σε bindUI αν ήταν Widget ) Θα ήθελα να αποδίδουν στη συνέχεια κάνοντας τους ακροατές:

  this._eventHandles.push (this.after («someAttributeChange», this._afterSomeAttributeChange, αυτή η))? 

Στη συνέχεια, σε destructor , έχω:

  destructor: λειτουργία () {
     Y.each (this._eventHandles, λειτουργία (λαβή) {
         handle.detach ()?
     })?
 }, 

Είναι εδώ, στην initializer , ότι μπορείτε να συνδέσετε τους ακροατές εκδήλωση για τα χαρακτηριστικά που θα πρέπει να παράγουν οι δευτερογενείς επιδράσεις (μπορείτε να διαφέρουν για bindUI αν αυτή η δευτερεύουσα πρέπει να ασχοληθεί με το UI). Όπως είπα νωρίτερα, λειτουργίες ρυθμιστής χαρακτηριστικό πρέπει να ασχολείται μόνο με την ομαλοποίηση της τιμής του χαρακτηριστικού. Σε περίπτωση που η ρύθμιση παράγουν αποτελέσματα πέρα ​​από την αποθήκευση του αξία, αυτά πρέπει να αντιμετωπίζονται από τους ακροατές εκδήλωση. Στο παραπάνω παράδειγμα, έχω θέσει τη μέθοδο _afterSomeAttributeChange να ακούσουν για οποιαδήποτε αλλαγή στο someAttribute χαρακτηριστικό. Εκδήλωση ακροατές θα λάβετε ένα μοναδικό επιχείρημα, η πρόσοψη γεγονός που εγώ συνήθως αποκαλούν ev , ένα αντικείμενο με διάφορες ιδιότητες, μία από αυτές, newVal περιέχει την τιμή που καθορίζεται.

Ακίνητα Πρωτοδικείο Widget

Δύο σημαντικές ιδιότητες που Widget χρήσεις είναι BOUNDING_TEMPLATE και CONTENT_TEMPLATE . Και οι δύο είναι αρχικά να “<div></div>” , που παράγει το πρότυπο δομής των δύο δοχεία το ένα μέσα στο άλλο που χρησιμοποιούν οι περισσότεροι widgets. Αυτό, ωστόσο, ενδέχεται να μην είναι κατάλληλο για όλα τα widgets, για παράδειγμα, ένα Button widget θα μπορούσε καλύτερα να εξυπηρετηθεί από ένα <span> στοιχείο μέσα σε μια άγκυρα ( <a> ) στοιχείο, αντί των δύο ένθετα <div> του. Στην πραγματικότητα, μπορεί να μην φροντίζεις να έχεις μια contentBox καθόλου, Widget δεν απαιτούν από εσάς να. Μπορείτε να ορίσετε αυτές τις δύο ιδιότητες σε κάθε περίπτωση σήμανσης που θέλετε. Για παράδειγμα, για τον Button τάξη μπορεί να έχω:

  BOUNDING_TEMPLATE: «<a>»,
 CONTENT_TEMPLATE: null, 

Έχοντας CONTENT_TEMPLATE σετ με null θα πει Widget ότι δεν θέλετε μια contentBox καθόλου. Στην περίπτωση αυτή η contentBox χαρακτηριστικό ρύθμισης θα δείχνουν το ίδιο στοιχείο, όπως η boundingBox χαρακτηριστικό κάνει τη διαμόρφωση.

Δεν θα πρέπει να τεθεί σε αυτά τα πρότυπα το σύνολο HTML για το widget, κάνουν αυτά τα δύο απλά στοιχεία HTML και δημιουργεί επιπλέον σήμανση με τον κωδικό στο renderUI (που θα δούμε αργότερα).

Widget θα προσθέσει ένα id χαρακτηριστικό και των κατ 'αποκοπή τάξεις που χρησιμοποιεί σε κάθε σήμανση που θέλετε, όπως yui3-xxxx , yui3-xxxx-visible ή yui3-xxxx-disabled , όπου xxxx είναι η αξία της NAME ιδιοκτησίας μετατραπεί σε πεζά.

Μέθοδοι Πρωτοδικείο Widget

Widget χωρίζει προετοιμασίας της σε διάφορα στάδια. Πέρα από την initializer , λέγεται, όταν το αντικείμενο αρχικοποιείται, και της destructor , που ονομάζεται από την destroy , και οι δύο μέθοδοι χειρίζεται Base , Widget προσθέτει renderUI , bindUI και syncUI για την οικοδομική φάση, που θα κληθεί με τη σειρά όταν Widget τα render μέθοδος είναι ονομάζεται.

Η renderUI μέθοδος φροντίζει για την παραγωγή του βασικού HTML για το widget. Τόσο η boundingBox και contentBox έχουν γίνει σε αυτό το σημείο. Εάν χρησιμοποιείτε την προοδευτική βελτίωση, renderUI πρώτα πρέπει να ελέγξει αν τα στοιχεία που ήδη υπάρχουν στη σελίδα. Αν έχουμε χρησιμοποιήσει την HTML_PARSER ιδιοκτησίας, τότε οι ιδιότητες διαμόρφωσης κρατώντας τις αναφορές σ 'αυτά τα στοιχεία θα έχουν τεθεί μέχρι τότε, αν όχι, εμείς πρέπει να τους δημιουργήσουν.

Για να γίνει αυτό, ο ευκολότερος τρόπος (αν δεν προοδευτική ενίσχυση) είναι η χρήση Y.Node.create , όπως αυτό:

  renderUI: λειτουργία () {
     var CBX = this.get (CBX)?
     cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE, CLASS_NAMES)))?
 }, 

Αυτό προϋποθέτει πολλά πράγματα, τα οποία θα εξηγήσω αμέσως. Πρώτον, έχω την CBX σταθερά δηλώνεται όπως φαίνεται στην πρώτη θέση κώδικα σε αυτό το άρθρο. Στη συνέχεια, αναλαμβάνει Node είναι φορτωμένο, το οποίο Widget χρησιμοποιεί γι 'αυτό είναι ασφαλές, αλλά αυτό προϋποθέτει επίσης Y.substitute υπάρχει, η οποία είναι προαιρετική. Μπορείτε να προσθέσετε 'substitute' του requires κατάλογο του συστήματός σας. Στη συνέχεια, αναμένει ένα πρότυπο για το widget να είναι σε μια στατική μεταβλητή που ονομάζεται TEMPLATE που είναι στο χέρι σας να καθορίσει μαζί με άλλα μέλη της τάξης στατική (δεξιά από ATTRS και τέτοια). Τέλος, θεωρεί ότι υπάρχει μια σταθερή CLASS_NAMES δηλώνονται κάπου.

Δηλώνω συνήθως CLASS_NAMES σε μονάδα ορισμού μου, μαζί και BBX CBX (δείτε το πρώτο παράθυρο κώδικα σε αυτό το άρθρο), όπως αυτό:

  var BBX = 'boundingBox »,
     CBX = 'contentBox »,
     = ΟΝΟΜΑ «κουμπί»,
     / / Άλλες σταθερές και συντομεύσεις ....
     YCM = Y.ClassNameManager.getClassName,
     getClassName = λειτουργία () {
         var args = Y.Array (επιχειρήματα)?
         args.unshift (ΟΝΟΜΑ)?
         επιστρέψει YCM.apply (αυτό, args) toLowerCase ().?
     },
     LABEL = «ετικέτα»,
     ΠΙΕΣΗ = "πατηθεί",
     ICON = 'εικόνα »,
     CLASS_NAMES = {
         πιεσμένων: getClassName (πατημένος),
         εικονίδιο: getClassName (ICON),
         ετικέτα: getClassName (LABEL),
         noLabel: getClassName («όχι», την ετικέτα)
     }? 

CLASS_NAMES τότε θα είναι μια σταθερή περιέχει ένα αντικείμενο με ιδιότητες που δημιουργούνται από ClassNameManager (το οποίο έρχεται επίσης περιλαμβάνονται Widget ). Στον κώδικα παραπάνω, μπορώ να δημιουργήσω για πρώτη φορά την συντόμευση YCM να κάνει αργότερα πρόσβαση γρηγορότερα, τότε μπορώ να δημιουργήσω τη λειτουργία getClassName , μια ιδιωτική λειτουργία που είναι προσβάσιμη μόνο από τον ορισμό μονάδα. Η συνάρτηση λειτουργεί λίγο πολύ όπως η μέθοδος του ίδιου ονόματος του Widget , αλλά είναι μια στατική λειτουργία που μπορώ να χρησιμοποιήσω για να καθορίσει περαιτέρω στατικές τιμές. Αυτό είναι ακριβώς αυτό που κάνω αργότερα, όταν δημιουργώ CLASS_NAMES ως ένα αντικείμενο με τα ονόματα που δημιουργείται τάξη και οι ιδιότητές τους. Αυτό μου επιτρέπει να γράψω ένα TEMPLATE συμβολοσειρά, όπως:

  ΠΡΟΤΥΠΟ: «<label class="{label}"> <input/>», 

Ποια είναι λίγο χαζό μέχρι τώρα. Θα ήθελα επίσης να συγχωνευθούν σε αυτό το πρότυπο αξιών από άλλες πηγές, συγκεκριμένα, τα χαρακτηριστικά διαμόρφωσης. Αυτό είναι το πώς θα μπορούν να το κάνουν:

  this.get (CBX). προσάρτησης (Y.Node.create (Y.substitute (ΥΠΟΔΕΙΓΜΑ, CLASS_NAMES, Y.bind (λειτουργία (πλήκτρο, πρότεινε, ARG) {
     επιστροφή (πλήκτρο === "_" this.get (ARG):? πρότεινε)?
 }, Αυτή η))))? 

Μπορώ να προσθέσω ένα τρίτο επιχείρημα για να Y.substitute , μια λειτουργία. Συνήθως, κράτησης θέσης για Y.substitute γίνονται χαρακτήρων που περικλείεται ανάμεσα σε αγκύλες, όμως, αν υπάρχει χώρος, θα χωρίσει το σύμβολο κράτησης θέσης σε δύο, το τμήμα μέχρι το χώρο που είναι το κλειδί και το δεύτερο μια προαιρετική επιχείρημα. Αυτό έρχεται βολικό όταν το τρίτο επιχείρημα είναι μια συνάρτηση, όπως εδώ. Η λειτουργία θα λάβει τρία επιχειρήματα, το πρώτο είναι το κλειδί, το δεύτερο είναι η τιμή που βρίσκεται στο αντικείμενο αντικατάστασης, εδώ CLASS_NAMES , αν υπάρχουν, και η τρίτη είναι η προαιρετική επιχείρημα. Έτσι, στην παραπάνω δήλωση, μπορώ να χρησιμοποιήσω ένα πρότυπο σαν αυτό:

  ΠΡΟΤΥΠΟ: «<label class="{label} for="{_ id}"/> <input id="{_ id}" value="{_ value}" />», 

Y.substitute θα βρείτε {label} και το αναζητήσετε σε CLASS_NAMES . Θα το βρείτε και να 'yui3-button-label' . Θα καλέσετε τη λειτουργία αντικατάστασης με επιχειρήματα 'label' , 'yui3-button-label' και undefined . Δεδομένου ότι key δεν είναι ίσο με '_' θα επιστρέψει την τιμή στο δεύτερο επιχείρημα, το αρχικό όνομα της κλάσης. Όταν φτάνει σε {_ id} , δεν υπάρχει καμία τιμή για ένα ακίνητο που ονομάζεται _ σε CLASS_NAMES έτσι θα καλέσει τη λειτουργία αντικατάστασης με επιχειρήματα '_' , undefined και 'id' . Με key ίση '_' , η λειτουργία θα πάει και θα πιάσει την αξία της 'id' χαρακτηριστικό. Θα κάνει το ίδιο και πάλι για την {_ value} κράτησης θέσης.

Όλες οι σταθερές που δηλώνονται στην αρχή κρυφά από οποιοδήποτε κώδικα έξω από τη μονάδα, αλλά ίσως να θέλετε να κάνετε μερικά από τα ορατά, όπως CLASS_NAMES . Για να γίνει αυτό, το στατικό τμήμα των μελών, το τελευταίο επιχείρημα για να Y.Base.create , θα μπορούσατε να έχετε:

  CLASS_NAMES: CLASS_NAMES 

Στη συνέχεια, το αντικείμενο με τα ονόματα όλων των τάξεων θα είναι ορατή ως Y.MyWidget.CLASS_NAMES .

Σας προτείνω να κάνουμε ό, μορφοποίηση, όπως μπορείτε με την σειρά HTML που θα καταστήσει το περιεχόμενο του widget. String χειραγώγηση σε JavaScript είναι πολύ ταχύτερη από ό, τι αφορά την πρόσβαση στην DOM έτσι το πιο κάνετε πριν από την κλήση Y.Node.create με αυτή τη χορδή, τόσο πιο γρήγορα θα έχετε κάνει.

Η επόμενη μέθοδος που ονομάζεται παράδειγμα για κάθε widget είναι bindUI . Αυτό είναι όπου μπορείτε να επισυνάψετε ακροατές εκδήλωση για όλα τα στοιχεία που δημιουργούνται από renderUI , για παράδειγμα, τον ακροατή για τυχόν αλλαγές στην αξία του <input> κουτί της TEMPLATE παραπάνω. Η τιμή για το κουτάκι και αυτό στη διαμόρφωση χαρακτηριστικό θα πρέπει πάντα να διατηρείται σε συγχρονισμό. Η value χαρακτηριστικό μπορεί να αλλάξει, είτε μέσω κωδικού είτε με την πληκτρολόγηση των χρηστών στο πλαίσιο εισαγωγής. Αν προέρχεται από εξωτερικές κώδικα, το textbox θα πρέπει να ανανεωθεί, αν προέρχεται από το πλαίσιο κειμένου, δεν θα πρέπει, αλλιώς κινδυνεύουμε να εισέρχεται ένα άπειρο βρόχο: η αλλαγή στο πλαίσιο κειμένου καθορίζει την value χαρακτηριστικό που καθορίζει στη συνέχεια την value στο πλαίσιο κειμένου που τότε οι αλλαγές και τα σύνολα η value χαρακτηριστικό και ούτω καθεξής. Ας δούμε πώς θα χειριστεί την υπόθεση αυτή. Θέσαμε έναν ακροατή στον τομέα των συνθετικών valueChange εκδήλωση στο πλαίσιο εισαγωγής. Για να το κάνουμε αυτό πρέπει να προσθέσετε το event-valuechange μονάδα στο requires λίστα αυτής της ενότητας.

  this._eventHandles.push (this._inputEl.after («valueChange», this._afterInputChange, αυτή η))? 

Υποθέτουμε ότι το αντικείμενο έχει μια αναφορά στο πλαίσιο κειμένου αποθηκεύονται στο _inputEl . Ο ακροατής κάνει αυτό:

  _afterInputChange: λειτουργία (EV) {
     this.set (ΑΞΙΑ, ev.target.get (ΑΞΙΑ), {πηγή: UI})?
 }, 

Εδώ υποθέτουμε ότι έχουμε την σταθερές VALUE και UI δηλώνονται ως 'value' και 'ui' αντίστοιχα. Θέτουμε απλά το χαρακτηριστικό value με την τιμή διαβάζεται από το πλαίσιο εισαγωγής. Ωστόσο, είμαστε προσθέτοντας ένα τρίτο όρισμα στη μέθοδο σύνολο: {source:UI} . Το set μέθοδος μπορεί να πάρει ένα τρίτο επιχείρημα, ένα αντικείμενο, του οποίου οι ιδιότητες θα πρέπει να αναμιγνύεται στην πρόσοψη περίπτωση περίπτωση αλλαγής χαρακτηριστικό. Αυτός είναι ο τρόπος με τον οποίο μπορεί να πει τη διαφορά μεταξύ της αξίας που καθορίζεται από το πλαίσιο κειμένου ή από το εξωτερικό κωδικό. Σε bindUI θα είχαμε αυτό που ακροατή:

  this._eventHandles.push (this.after («valueChange», this._afterValueChange))? 

Αυτός είναι ο ακροατής για την αλλαγή της value χαρακτηριστικό του αντικειμένου σας, το άλλο ήταν για την αλλαγή στην αξία του <input> πλαίσιο, καλούνται οι ίδιοι, μετά από όλα, και οι δύο να ακούσετε αλλαγές σε κάτι που ονομάζεται αξία, αλλά δεν είναι το ίδιο πράγμα. Συνήθως, οι ακροατές για αλλαγές χαρακτηριστικό που στην initializer , αλλά δεδομένου ότι αυτό επηρεάζει ένα στοιχείο UI, το βάζουμε σε bindUI έτσι ώστε να γνωρίζουμε το textbox θα είναι εκεί. Ο ακροατής θα πρέπει:

  _afterValueChange: λειτουργία (EV) {
     αν (ev.source === UI) {
         επιστροφή?
     }
     this._inputEl.set (ΑΞΙΑ, ev.newVal)?
 }, 

Το πρώτο πράγμα που κάνουμε είναι να ελεγχθεί η source της εκδήλωσης. Αν προέρχεται από το UI , τότε το αγνοούμε. Τόσο το όνομα του ακινήτου, source και την αξία του, UI είναι αυθαίρετη, εκείνοι είναι αυτοί που χρησιμοποιούνται κατά τον καθορισμό της value χαρακτηριστικό, ώστε αυτοί είναι εκείνοι που μπορώ να ελέγξω για το ακροατή, αλλά και οποιοδήποτε όνομα / τιμή θα κάνουν εξ ίσου καλά. 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) {
         επιστροφή?
     }
    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: λειτουργία () {
            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: λειτουργία () {
            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′,
     ομάδες: {
        js: {
            base: 'build/',
             ενότητες: {
                '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.

Συμπέρασμα

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.icio.us Σελιδοδείκτης με | Digg it! | Reddit!

18 Comments »

RSS feed για σχόλια σχετικά με αυτό το post. TrackBack URI

  1. Thanks a lot for this article. It is difficult to find informations about skins in the official doc. It is now much clearer for me.

    Comment by plv — April 1, 2011 #

  2. Awesome article!

    Thank you, Satyam!

    Comment by John — April 1, 2011 #

  3. Wonderful article!

    Comment by Daniel Stockman — April 1, 2011 #

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

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

    Comment by John — April 3, 2011 #

  5. Thanks for the article! Cleared a lot of doubts that I had.

    I have one question though :) I don't quite understand the use of syncUI method. You have stated its use as “Its purpose is to refresh the UI to reflect the current state of the object”. Aren't you doing that in bindUI() method when you register afterValueChange events for widget's attributes to refresh UI?

    Comment by Vignesh — April 5, 2011 #

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

    Comment by lily — April 5, 2011 #

  7. [...] mobile support, a “Simple” theme, a new kitchen-sink-like Widget Browser, and more A Recipe for a YUI 3 Application – Satyen Desai of the YUI team goes into detail about how to organize a YUI application WebGL [...]

    Pingback by JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: iOS viewport fixes, Mobile Boilerplate, CommunityJS, Ender.js — April 8, 2011 #

  8. I've put it into Chinese.
    http://ued.taobao.com/blog/2011/04/14/a-recipe-for-a-yui-3-application/

    Comment by jayli — April 14, 2011 #

  9. @Vignesh:
    Regarding the syncUI method, the listeners you add in bindUI dont get fired during sync, because the object state at the time you bind the listeners has already been set, so there's no change in the state.

    I'll use pseudo code to help explain:
    var obj = new Y.Widget({attr1: 55});
    //attr1 state is now 55
    obj.render()
    So during render, since nothing changes, no change event is fired, hence why you may need to call the syncUI method.

    Ideally, you should have a _uiSetAttr1 method that is just called by the after change event, and can be called during sync that simply handles updating the ui in response to that attr changing.

    There are also properties that help make this glue wiring easier. Off the top of my head I don't remember if they're private still, but if you set them (I think it's either BIND_UI or BIND_UI_ATTRS and same for SYNC) all you have to do is set a method called _uiSetAttrname and list the ATTRS in that object, and it will automatically do all of that wiring for you.

    I hope that helps :)

    Σχόλιο από Nate Cavanaugh - 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");

    Ευχαριστώ για τη συμβουλή.

    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. Μεγάλη άρθρο! 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. Τι έχασα;

    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 #

Αφήστε ένα σχόλιο

Σημείωση: Σχόλια εποπτεύονται για την πρώτη-χρονόμετρα. Spam διαγράφεται.

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

Φιλοξενείται από το Yahoo!

Πνευματικά δικαιώματα © 2006-2012 Yahoo! Με επιφύλαξη παντός δικαιώματος. Πολιτική απορρήτου - Όροι Υπηρεσίας

Κινούμενο από WordPress για το Yahoo! Web Hosting .