وصفة لتطبيق 3 YUI

1 أبريل 2011 في 2:52 صباحا بواسطة ساتيام | في تطوير | 18 تعليقات

وقد تم تصميم YUI 3 إلى بناء تطبيقات حول وحدات. ولن أناقش ما هي وحدة منذ أن تم وصفه بشكل جيد من قبل Zakas نيكولاس في العرض الذي قدمه قابلة للبنية التطبيق جافا سكريبت . أنا مجرد عصا لكيفية بناء هذه الوحدات. ويمكن الاطلاع على معظم ما سأقوله في وثائق على الخط، جنبا إلى جنب مع العديد من البدائل الأخرى، بعد كل شيء، وهذا هو نقطة من التوثيق الجيد: ان اقول لكم عن كل السبل الممكنة للقيام بهذه الأمور. هذا هو السبب في هذا هو وصفة، طريقة واحدة فقط من يفعل ذلك من بين أمور أخرى كثيرة. فإنه يفترض أيضا تطبيق الشريك الأصغر، ليس كما مع الكثير من طبقات باسم 'نيكولاس يوحي، لأن هذا هو مجرد مقال وليس كتابا.

التعرف على وحدات

فإن الخطوة الأولى هي للتعرف على وحدات سنحتاج. مقاربة جيدة هي البدء تشريح تصميم شاشة التطبيق إلى أقسام الفردية: شريط العنوان وشريط القوائم، والمحتوى، والألواح الجانبية أو أي شيء آخر يمكن أن تكون هناك هناك. ثم نلقي نظرة على ما في المكتبة لهذا العرض. على سبيل المثال، YUI 3 لا يوجد لديه قائمة، ولكن هناك عقدة MenuNav المساعد ، والتي تأخذ بنية القائمة الأساسية مصنوعة من عناصر متداخلة غير مرتبة <UL> قائمة وتحولهم الى القائمة النشطة. أو قد ترغب في التحقق من معرض YUI للمكونات الأساسية. على أي حال، فسوف يصل في النهاية إلى النقطة التي لديك مربع في ذلك التخطيط الذي لديك لملء في نفسك، لذلك دعونا نفعل ذلك.

أوصي وضع كل وحدة في ملف خاص به، والدليل الخاص به الذي يحمل الاسم نفسه. وهكذا، فإن weather سيكون وحدة تكون في weather/weather.js . والسبب في ذلك لأن وحدة الخاص بك ومن المرجح أن تتطلب بعض التصميم، وبعض الملفات المغلق وصورة، ويجعل من السهل على المدمج في محمل إذا كنت مكان لهم حيث يمكن العثور عليها بسهولة، في هذه الحالة، ورقة الأنماط الرئيسية سيكون weather/assets/skins/sam/weather.css ، مع غيرها من الأصول، والصور، وهلم جرا، جنبا إلى جنب. هذا على افتراض أنك لا تستخدم منشئ YUI التي سوف تفعل بالفعل الأمور على هذا النحو على أية حال، ولكن هذه قصة أخرى. المجلد أسماء assets و skins هي أكثر أو أقل لا تحتاج إلى شرح، sam ولكن ليس واضحا تماما. هذا هو القيمة الافتراضية لل skin ممتلكات محمل لأن ذلك هو الجلد الافتراضية التي يتم شحنها مع YUI، الذي سمي على اسم المصمم، وسام ليند. لأن ذلك يوحي، أنت حر لوضع اسمك على الأسطح الخاصة بك و skin الملكية يسمح لك لنقول YUI لتحميلها، ولكن ليبقيه بسيط، دعونا فقط اذهب مع الافتراضي.

وحدة ملف القالب

هذا هو هيكل الملف الذي استخدم في كثير من الأحيان، والتي سوف وصف في لحظة:

  / * jslint جمعة:، نديف صحيح: صحيح، newcap: صحيح، صارم: صحيح، maxerr: 50 * / 
 / * عالمي YUI * / 
 / ** 
  * وحدة وحدة، اسم يخلق بلاه بلاه 
  * @ وحدة وحدة ذات الأسماء 
  * / 
 YUI.add ('اسم الوحدة النمطية "، وظيفة (ص) { 
     "استخدام الدقيق"؛ 
     / / ثوابت مفيد والاختصارات المستخدمة في وحدة 
     فار لانج = Y.Lang، 
         CBX = 'contentBox'، 
         BBX = 'boundingBox'،
         اسم = 'XXXX'؛ 

 
     / ** 
      * فئة XXXX لا .... 
      * @ فئة XXXX 
      * @ يمتد القطعة 
      * @ يستخدم WidgetParent 
      * @ منشئ 
      سمات التكوين * @ CFG {وجوه} 
      * / 
     Y.Xxxx Y.Base.create = ( 
         الاسم، 
         Y.Widget، 
         [Y.WidgetParent]، 
         { 
             / أعضاء مثيل / هنا 
         
         { 
             / / أعضاء ثابت هنا، وخاصة: 
             ATTRS: { 
             } 
         } 
     

 
 }، '0 0.99 '، { 
     يتطلب: ['القطعة'، 'القطعة الوالد']، 
     سكينبل: صحيح 
 })؛ 

أول سطرين من التعليقات ليست لصالح JSLint ، أداة التحقق من جافا سكريبت التي أوصي حقا. إذا ذهبت إلى إصدار ويب ، هناك مربع لتعيين خيارات. في الجزء السفلي من مربع الخيارات التي يمكنك ان ترى وسيلة لترميز هذه الخيارات في الملف نفسه. إذا كنت تستخدم منشئ YUI، فستعمل JSLint لديك ولدى تعيين هذه الخيارات بالنسبة لك ولكن يمكنك تجاوز لا يزال لهم عن أي ملف الفردية إذا كنت ترغب.

التعليقات الوثيقة هي لبناء YUI مستندات API. وهو أقل من صداع إذا قمت بتضمين قالب الأولية لتلك المستندات API، في نهاية المطاف سوف ملئها. مثل التطبيق ينمو وكنت غير قادر على تذكر كل شيء، وسوف تحتاج إليها.

ويأتي الآن في السطر الأول الفعلي للقانون، و YUI.add() بيان. هذا هو السبيل لمعرفة محمل YUI اسم ومحتويات وحدة وغيرها من القطع العديد من المعلومات. وعادة ما تتم تسمية أسماء وحدة مع جميع الأحرف الصغيرة والواصلات في ما بين الكلمات. تلك هي الأسماء التي تراها في مستندات API في المؤشر من أعلى إلى أكثر على اليسار. يمكنك ان ترى ليست اتفاقية اتباعها بدقة، وبعض الأسماء وحدة لم يكن لديك أي الواصلات. على أي حال، هذا هو ما يصل معظمهم لك ما دمت استخدامها باستمرار.

الحجة الثانية من YUI.add() بيان وظيفة أن يحصل على حجة واحدة، وتسمى تقليديا Y . أن وظيفة تحتوي على مجموعة من وحدة و Y هو إشارة إلى مثيل سندبوإكسد من YUI الذي هو المكان الذي يمكنك العثور على كل YUI غيرها من وحدات معرض كنت قد طلبت. القفز الى الجزء السفلي من هذا المربع رمز، يمكنك ان ترى بقية حجج .add() الأسلوب، وإصدار ( '0.99′ أو لم يصلوا بعد)، والتهيئة لهذه الوحدة، كائن مع سلسلة من AA الخصائص. هنا، أقول للمحمل أن هذه الوحدة يتطلب widget و widget-parent وأنه يحتوي على الجلد. قائمة widget لا لزوم لها، منذ widget-parent يتطلب بالفعل widget ، ولكن لا تزعج نفسك مع ذلك، فإن محمل يتم تحميل وحدة مرتين، وإذا كان في مرحلة لاحقة كنت قطرة واحدة التبعية، لا تحتاج إلى مراجعة لافتراضات أخرى قد قمت بها: دولة كل منهم، والسماح للاتفاق محمل معها. يمكنك العثور على قائمة لجميع الخيارات في مستندات API ل .addModule() طريقة للمحمل.

داخل الجسم من وظيفة، وأول شيء هو “use strict”; إعلان. هذا هو رمز للامتثال لمعيار 5 ECMAScript، في هذه المرحلة التي تضعك على الجانب الآمن لضمان التوافق مع منصات للجميع هل من المحتمل أن تواجه في المستقبل. لكبار السن من المترجمين، وهذا الإعلان ليس أكثر من سلسلة التي لم يتم تعيين إلى أي شيء، وتجاهل و. في “use” إعلان وظيفة لديها نطاق وأنها أكثر أمانا لوضعه داخل الجسم وظيفة مما كانت عليه في الجزء العلوي من الملف، بحيث أنه يؤثر فقط على وحدة كنت تقوم بتعريف. إذا كنت وضعه في الجزء العلوي من الملف، فإنه ينطبق أيضا على أي ملف جافا سكريبت الأخرى التي يتم تحميلها بعد ذلك، وكثير منهم قد لا يتوافق مع ES5.

ثم يأتي الاختصارات والثوابت، والتي هي ثوابت فقط في استخدام منذ جافا سكريبت لا يوجد لديه مفهوم الثوابت. نحن تسميتها في كل كبيرة الحروف ويؤكد، والثوابت وغالبا ما تكون في لغات أخرى. هناك نوعان من أسباب وجيهة لاستخدام الثوابت، والثوابت سلسلة خصيصا. الأول هو أنه عندما تكتب سلسلة نفسه عدة مرات، وكنت قد أخطأت واحد منهم وستلاحظ فقط عندما الشوائب للملوثات العضوية الثابتة. إذا كنت تستخدم والثوابت، وسوف JSLint يحذرك عند كتابة اسم ثابت خاطئ، لأنه سوف يكون غير معروف. والسبب الثاني هو أن ضاغط YUI يمكن القيام بعمل أفضل منذ يمكن ضغط أسماء ثابتة في حين سلسلة حرفية لا تستطيع ذلك. جيدة المرشحين لالثوابت المسماة هي أسماء من سمات التكوين والأحداث.

الاختصارات، مثل Lang ل Y.Lang هي أيضا جيدة لأنها تسمح لك اكتب أقل، ومترجم لديه أقل لتقييم (يعني كل نقطة تفتيش جديدة في أعضاء الجسم)، ويمكن ضغط من قبل ضاغط YUI.

بعد هذه التعليقات مستندات API لفئة، ونصل الى الاعلان الفعلي. علينا أن نعلن طبقتنا كخاصية من Y ، وبالتالي Y.Xxxx . اقتراحي هو استخدام Y.Base.create() لإنشائه، كما هو موضح هنا. يمكن أن تؤدي إلا إلى خلق الفئات المشتقة من BaseWidget التي هي أيضا فئة فرعية من Base ) ولكن من شأنها أن تغطي معظم الوحدات التي سوف تستخدمها لذلك هو غير عادي في حاجة إلى أن تفعل ذلك في طريقة أخرى. الوسيطة الأولى هو اسم وحدة، و NAME العقار ووصف ل Base عنصر. تقليديا، فإن NAME الملكية هو نسخة الإبل حالة من اسم الفئة. ويستخدم هذا الاسم كما البادئة للأحداث (الجزء قبل النقطتين مثل “io:success” )، لأسماء فئة CSS الناتجة عن Widget فئة (أي: “yui3-xxxx-content” )، وبالنسبة للتطبيق الافتراضي toString() ، والتي سوف ترى في كثير من الأحيان على آثار من قبل المصحح. هنا يمكنني استخدام قيمة ثابتة NAME لتحديد فئة NAME الممتلكات.

والحجة الثانية هي الطبقة ذلك بدرجات. سوف تستخدم في كثير من الأحيان إما Y.Base ، لفائدة وحدات والتي سيكون لها أي واجهة المستخدم، Y.Widget لتلك التي سوف يكون لها واجهة المستخدم، Y.Plugin.Base للملحقات أو أي فئة أخرى مشتقة من Y.Base ، مثل أي ربما كنت قد أنشأت بالفعل باستخدام Y.Base.create() .

الوسيطة الثالثة تحمل التمديدات التي سوف تستخدمها. تمديدات هي الطبقات التي الخصائص والأساليب التي تريد أن يخلط في صفك. مرشحة جيدة لتمديد و ArrayList لل Base أو أي من Widget-Xxxx الوحدات الفرعية لل Widget . Attribute ، EventTarget و PluginHost تأتي مختلطة بالفعل في Base بحيث يمكنك دائما الاعتماد على هؤلاء الثلاثة يجري هناك. تمديدات هي قوية جدا، إذا نظرتم الى الكود ل Overlay ، يمكنك ان ترى لا يوجد شيء غير Widget والتمديدات. ويمكن أن تكون مختلطة ملحقات عدة إلى عنصر وبالتالي فإن الحجة الثالثة صفيف.

أخيرا، نصل إلى رمز الفعلي. الحجج والرابع والخامس والحرفية الكائن على حد سواء التي تحتوي على سبيل المثال وأعضاء ثابتة من فئة. أعضاء المثال لا الحصر الخصائص والأساليب التي تدخل في فئة prototype ، وتلك التي ستحصل على سبيل المثال كل نسخة، وعادة ما تحتاج إلى أن تكون المراجع من قبل this . أعضاء ثابتة هي تلك التي من شأنها أن تكون مشتركة من قبل جميع الحالات.

تكوين سمات

وأهم هذه أعضاء ثابت هو ATTRS الممتلكات. يسرد هذا التكوين وتعزو صفك سيكون لها. على سبيل المثال، دعنا نقول أننا نريد أن يكون سمة التكوين تسمى value لعقد القيم الرقمية ووضع في البداية إلى 0. في حجة 5، سوف نعلن ذلك على النحو التالي:

  ATTRS: {
     القيمة: {
         قيمة: 0،
         المدقق: Lang.isNumber
     }
 } 

يمكننا إدراج أي عدد من السمات في ATTRS الممتلكات ويمكن تكوين كل مع خيارات عدة، اثنان منها لقد أظهرت هنا. يمكنك أن تقرأ عن بقية الخيارات في addAttr() طريقة من Attribute . كما يمكن أن يرى، ولقد استخدمت Lang المختصرة التي كنت أعلنت في بداية إعلان الوحدة. و validator يجب أن تكون دالة التي تأخذ قيمة للتحقق من وإرجاع قيمة منطقية. كل من Y.Lang.isXxxx أساليب تفعل بالضبط بحيث يمكن استخدامها مباشرة. لالمصادقون أكثر تفصيلا، واضعة أو حاصل، تحتاج إلى تعريف وظائف. وأوصي بتوفير اسم الدالة كسلسلة، Attribute يعتني حل اسم وظيفة الى وظيفة فعلية. على سبيل المثال، إذا كان لي أن تعريف، ويقول، validCodes السمة التي يمكن أن تتخذ أي رمز واحد صالح أو مجموعة من الرموز صالح ولكن يجب أن تعود دائما صفيف، وأود أن تفعل:

  ATTRS: {
     validCodes: {
         اضع: "_setValidCodes '
     }
 } 

نحن بحاجة إلى أن يعلن _setValidCodes الأسلوب على طول أعضاء المثيل الآخر في الوسيطة الرابعة من Y.Base.create() :

  _setValidCodes: وظيفة (قيمة) {
     إذا (! Lang.isArray (قيمة)) {
         قيمة = [قيمة]؛
     }
     عودة القيمة؛
 } 

فمن الأفضل أن يعلن واضعي، وحاصل ولكن أي أتفه من المصادقون وظائف سبيل المثال مستقل والسماح Attribute حل اسم وظيفة الى وظيفة الدعوة الفعلية.

بشكل عام، واستخدام واضعي لتطبيع قيمة، كما هو مبين أعلاه، وليس لإنتاج الآثار الثانوية. وجميع سمات التكوين النار قبل وبعد أحداث التغيير مع الحدث قبل قادرة على منع سمة من تغيير. استخدام هذه الأحداث لإنتاج أي تغيير التأثيرات الثانوية، وليس واضع. بعد هذا الحدث هو أفضل ومنذ ذلك الحين تعرف ان لا شيء يمنع من السمة التي يجري وضعها منذ شفرة أخرى ربما تكون قد اشتركت في حدث قبل التغيير (في) وشطبه.

يمكنك جعل السمة الخاصة بك أكثر أو أقل صرامة اعتمادا على كيفية تحديد مدقق الخاص بك، واضعة. إذا قمت بإجراء المدقق مقيدة للغاية، السمة الخاصة بك وسوف تكون صارمة جدا، وقبول القيم صالحة فقط، في هذه الحالة، قد تكون غير ضرورية واضع. من ناحية أخرى، قد لا تستطيع استخدام المدقق على الإطلاق، والاعتماد كليا على واضع لتدليك أي قيمة وردت الى شيء مقبول. على سبيل المثال، هل يمكن أن يكون أي من هذين هما:

  المدقق: Y.Lang.isBoolean، / / لجعل السمة يقبل بصرامة منطقية
 اضع: منطقية، / / لجعل السمة قبول أي قيمة ولها منطقية تحويلها الى واحدة 

ويمكن أيضا أن تكون بمثابة واضعي المصادقون. يتعين على واضعي إرجاع القيمة إلى أن تسند إلى سمة لكنها يمكن أن تعود أيضا Y.Attribute.INVALID_VALUE الذي سيغادر السمة دون تغيير، كما لو أن المدقق كانت قد رفضتها.

عندما يمكنني تحديد سمة تكوين يمكنني تحديد ثابت في كثير من الأحيان لذلك، وأنا مكان في الجزء العلوي من وحدة (على طول CBX ، BBX والاختصارات)، على سبيل المثال:

  فار القيمة = 'قيمة'،
     VALID_CODES = 'validCodes'؛ 

وهناك احتمالات بأن سأستخدم تلك سمات تكوين عدة مرات داخل وحدة وهذا سيوفر لي بعض المتاعب. ومع ذلك، كن حذرا، لا تستخدم هذا ثابت عندما يعلن السمة، لا تفعل هذا:

  ATTRS: {
     / / *** لا تفعل ذلك *** / /
     VALUE: {
         قيمة: 0،
         المدقق: Lang.isNumber
     }
 } 

إذا قمت بذلك، سوف تحصل على سمة تسمى VALUE بدلا من value . هذه هي قضية جافا سكريبت، وليس YUI واحد. أيضا، يجب الحرص على عدم تجاوز على أي من التكوين وتعزو أعلنت بالفعل عن أي فئة أساسية أو أي من ملحقات. Widget لديه بالفعل مجموعة من الصفات وأعلن (انظر الجدول )، وعلى الرغم من أنك ستضيف بالكاد boundingBox تنسب نفسك، أنت قد ينسى بسهولة أن visible ، disabled ، height أو width تم تعريفها مسبقا. إذا استخدامك يقصد ما يطابق Widget يستخدمها ل، سيكون كل شيء على ما يرام. قال ذلك، يمكنك تغيير تعريف من أي واحد منهم. Y.Base.create() يدمج تعريف سمات التكوين للتمديد مع تلك الفئة الأساسية لذلك، إذا كنت تريد تغيير، ويقول، القيمة الافتراضية لل سمة القائمة، ويمكنك القيام بذلك من خلال الإعلان عن السمة الخاصة بك مرة أخرى في فئة فرعية.

كن حذرا اذا كنت تقصد لتهيئة سمة مع مجموعة أو كائن. يتم تمرير الكائنات (والمصفوفات هي كائنات) حسب المرجع، وإذا كنت تهيئة سمة مع كائن، فإنها قد ينتهي كل شيء يشير إلى نفس الكائن بحيث عند تغيير جزء منه (إزالة عنصر من مجموعة أو إضافة خاصية إلى الكائن) كنت في نهاية المطاف تغيير كل الحالات في آن واحد. Base ، ومع ذلك، لديه بعض المنطق الداخلي الذي سوف يسمح لك لتهيئة بسلام سمة الكائن والحرفية مع مجموعة. إذا كانت قيمة التهيئة هو كائن أو مجموعة حرفية ثم Base واستنساخ ذلك. استخدام valueFn خيار أو تهيئة ذلك في فئة مهيئ لكائنات أخرى.

ساكنة الأعضاء الأخرى

هناك اثنين من أعضاء ثابتة أخرى قد تحدد في الوسيطة الخامسة من Y.Base.create() . إذا كنت تقوم بإنشاء البرنامج المساعد، ويجب أن تعلن على الاطلاق NS الملكية، وإذا لم تقم بذلك، فإن البرنامج المساعد لن تنجح وتفشل بصمت. في NS يجب تعيين الخاصية إلى سلسلة التي سيتم استخدامها مثل اسم الملكية لتخزين البرنامج المساعد داخل الكائن المضيف، والحفاظ على هذا الأمر في الاعتبار عند اختيار الاسم بحيث لا تجاوز أي من الممتلكات الموجودة.

إذا كنت بناء القطعة وكنت تخطط لدعم التعزيز التدريجي، ثم سوف تستخدم HTML_PARSER ممتلكات ثابتة. يتم تعيين هذا إلى كائن الذي يحتوي على خصائص سميت سمات التكوين لتعيين من تحليل أتش تي أم أل القائمة وإما محددات CSS3 أو الوظائف التي سوف تنتج قيمها. انظر تحسين التقدمي في القطعة دليل المستخدم.

قد تحتاج أيضا إلى توفير قيم ليتم استخدامها من قبل المطورين باستخدام صفك. الثوابت المعلنة في الجزء العلوي من الملف غير مرئية تماما من الخارج وحدة نفسها. إذا كنت ترغب في توفير الثوابت العامة، وهذا هو المكان المناسب للقيام بذلك. ومن أمثلة هذه هي HEADER ، BODY و FOOTER ثوابت WidgetStdMod (لاستخدامها في الواقع لديك لاستخدام اسم مؤهل بشكل كامل: Y.WidgetStdMod.BODY وكذا).

المثال الأعضاء

الوسيطة الرابعة من Y.Base.create() هي الخصائص والأساليب التي من شأنها أن تذهب إلى prototype للفئة إنشاؤه. عادة، فإننا نعلن خصائص الأولى وأساليب في وقت لاحق. ليس لدي أي سبب لهذا، فإن النظام هو في الواقع لا صلة لها بالموضوع، لا جافا سكريبت ولا YUI تتطلب منك أن تفعل ذلك بهذه الطريقة، لكنه يجعل من الاسهل لتحديد موقع الأشياء في الملف المصدر. على الرغم من الخصائص يمكن أن تنشأ مثلا على الطاير في مهيئ، أنا لا أوصي يعلن لهم صراحة وتهيئة لهم. وينبغي أن يسبق كل الممتلكات من قبل تعليق ثيقة API.

وسوف خصائص عادة ما تكون خاصة واسمها مسبوقة من قبل تسطير. فمن الأفضل إذا تعرضت الواجهة العمومية للكائن عن طريق تكوين سمات وخصائص لا. الخصائص هي غبية جدا، ويمكن لها سمات تكوين المصادقون، تحويل نوع (عن طريق واضعة) وتنتج الآثار الثانوية (عن طريق أحداث التغيير)، وغالبا ما يكون من لم يمض وقت طويل حتى تجد أن تريد كل تلك الميزات.

كما هو الحال مع سمات التكوين، لا تهيئة خصائص الكائنات أو المصفوفات، وأنهم جميعا في نهاية المطاف مشيرا إلى نفس الكائن واجهت مشاكل. فمن الأفضل لتعيين الخصائص التي هي لعقد الكائنات إلى null . أيضا، لا تترك خصائص التصفير، إذا كنت لا تعرف قيمتها حتى الآن، مجموعة منهم إلى null بدلا من ذلك. في وقت لاحق، عند التصحيح، وهي خاصية لتعيين undefined يشير إلى وجود خطأ، عادة ما يكون الخطأ المطبعي.

طرق قاعدة مثيل

كنت قد لاحظت أننا لم تعلن أي المنشئ للفئة فرعية لدينا. Base لا تهيئة وحدة ومن ثم يدعو طريقة تسمى initializer ، إذا كان موجودا، مع نفس الحجج التي تلقتها عندما مثيل لذلك، لجميع الأغراض، أنت قد نعتبر أن initializer هو المنشئ الخاص بك. جميع الفئات المشتقة من Base عادة ما تأخذ حجة واحدة عندما يتم إنشاؤه، كائن يحتوي على سمات التكوين. Base (أو Widget ، لأنها هي فئة من Base ) على ما يلي هذه الحجة ويحدد سمات التكوين قبل استدعاء initializer . للحصول على Widget ، إذا كان هناك HTML_PARSER الملكية و، كما أنه قد تم تجهيزها وسيتم تعيين القيم لسمات قراءة من العلامات أيضا.

و initializer الطريقة لها العديد من المهام. أولا، يجب أن تحدد أي الخصائص التي تحتاج إلى تهيئة إلى الأشياء أو صفائف. ثم فإنه سيتم نشر كافة الأحداث التي وقعت هذه الفئة سوف تنتج. EventTarget سوف يسمح لك لاطلاق النار وهو الحدث الذي لم يتم نشر لأول مرة باستخدام الإعدادات الافتراضية للأحداث، ولكن حتى في هذه الحالة، أقترح عليك أن يعلن منهم على أي حال. هذا هو مكان جيد لإضافة مستندات تعليق API لتلك الأحداث حتى لو كانت تبدو غريبة قليلا، ويجري في الجسم من إعلان وظيفة، ولكن لا يوجد مكان أفضل للقيام بذلك.

الحجة التي تلقاها initializer وتمت معالجتها في ذلك الوقت، لكن في بعض الأحيان كنت تريد بعض الخيارات الإضافية لاستخدامها في التهيئة وكنت لا تهتم للحفاظ على سمات الفعلية لهم. على سبيل المثال، Base يقبل سمات on ، after ، bubbleTargets و plugins (انظر Base ) على الرغم من أنه لا يوجد لديه سمات التكوين لهؤلاء. كذلك WidgetParent يأخذ children السمة على التهيئة ولكن لا يوجد لديه سمة تكوين هذا الاسم. و initializer أسلوب واحد هو أن العمليات لهم. وهكذا، على الرغم من الطبقة الخاصة بك وسوف في نهاية المطاف اتخاذ حجة واحدة فقط على مثيل، وهذا يمكن أن حجة واحدة تحمل كل المعلومات التي قد يحتاجونها.

جافا سكريبت لا يوجد لديه فكرة destructors. Base يعوض هذا عن طريق السماح لك لاعلان destructor طريقة حيث يمكنك وضع رمز لتحرير الموارد وجوه الخاص بك قد اتخذت. هذه ليست سوى حل جزئي، ومترجم جافا سكريبت لا يطلق عليه تلقائيا عند إسقاط كائن لذلك كنت لا تزال مسؤولة عن تدمير كائن قبل رميه، ولكن على الأقل كنت تعرف المدمر سيكون هناك.

مستخدمي صفك لن استدعاء initializer و destructor مباشرة. Base وسوف ندعو لهم عند الحاجة. initializer وسوف يطلق عندما يتم إنشاء مثيل الكائن، و destructor وسوف يطلق عندما يقوم المستخدم من صفك يدعو لها destroy طريقة.

واحد من الأمور التي غالبا ما تنتج عمليات تسرب للذاكرة هي المستمعين الحدث تركوها وراءهم. Widget يحاول فصل جميع المستمعين تعلق على عناصر واجهة المستخدم الموجودة داخل عنصر المربع المحيط، ولكن لا يمكن فصل أي أشخاص آخرين. Base لا يمكن فصل أي المستمعين الحدث في جميع . هذا هو رمز استعمل لمساعدتي في ذلك. على طول غيرها من الممتلكات الخاصة سبيل المثال، أعلن في _eventHandles الملكية:

  _eventHandles: باطل، 

ثم، في initializer طريقة، أنا وضعت لمجموعة:

  مهيئ: وظيفة (CFG) {
     this._eventHandles = []؛
     / / ......
 

في نفس initializer (أيضا في bindUI لو كانت Widget ) وأود أن نعلق ثم المستمعين عن طريق العمل:

  this._eventHandles.push (this.after ('someAttributeChange'، this._afterSomeAttributeChange، هذا))؛ 

ثم، في destructor ، ولدي:

  المدمر: وظيفة () {
     Y.each (this._eventHandles، وظيفة (معالجة) {
         handle.detach ()؛
     })؛
 

فمن هنا، في initializer ، الذي وصل المستمعين الحدث السمات التي ينبغي أن يحدث آثارا ثانوية (قد تختلف لأنها bindUI إذا كان هذا التأثير الثانوي للتعامل مع واجهة المستخدم). كما قلت سابقا، يجب أن اضع وظائف سمة التعامل فقط مع تطبيع قيمة السمة. وينبغي أن الإعداد له أي آثار وراء تخزين قيمة، ينبغي معالجة هذه من قبل المستمعين الحدث. في المثال أعلاه، وأنا وضعت طريقة _afterSomeAttributeChange للاستماع لأي تغيير في someAttribute السمة. وسوف يحصل على المستمعين الحدث حجة واحدة، واجهة الحدث الذي أسميه عادة ev ، كائن مع العديد من الخصائص، واحد منهم، newVal التي تحتوي على القيمة التي يجري وضعها.

خصائص القطعة مثيل

خاصيتين المهم أن Widget الاستخدامات هي BOUNDING_TEMPLATE و CONTENT_TEMPLATE . وترد في البداية على حد سواء إلى “<div></div>” والذي ينتج بنية معيار من حاويتين واحدة ضمن أخرى أن معظم الحاجيات استخدام. هذا، ومع ذلك، قد لا تكون مناسبة لجميع الحاجيات، وعلى سبيل المثال، Button يمكن أن يكون أفضل القطعة خدم من قبل <span> عنصر ضمن مرساة ( <a> ) عنصر بدلا من اثنين متداخلة <div> ق. في الواقع، قد لا يهمني أن يكون لها contentBox في كل شيء، Widget لا تتطلب منك. يمكنك تعيين هذه الخصائص سبيل المثال مرتين إلى أي العلامات التي تريدها. على سبيل المثال، ل Button فئة قد لدي:

  BOUNDING_TEMPLATE: "<A> '،
 CONTENT_TEMPLATE: باطل، 

وجود CONTENT_TEMPLATE مجموعة ل null وسوف اقول Widget التي كنت لا ترغب في contentBox على الإطلاق. في هذه الحالة contentBox سيكون السمة التكوين يشير إلى العنصر نفس boundingBox السمة التكوين يفعل.

لا ينبغي عليك أن تضع في هذه القوالب في HTML كامل عن القطعة، جعل هذه عنصرين HTML بسيطة وخلق أي توصيف اضافية عن طريق رمز في renderUI (التي سنرى لاحقا).

Widget سيضيف id السمة والطبقات القياسية التي يستخدمها في أي العلامات التي تريدها، مثل yui3-xxxx ، yui3-xxxx-visible أو yui3-xxxx-disabled ، حيث xxxx هو قيمة NAME ممتلكات تحولت إلى صغيرة.

طرق القطعة مثيل

Widget انشقاقات التهيئة لها في العديد من الخطوات. ما وراء initializer ، ودعا عندما يتم إنشاء مثيل للكائن، و destructor ، ودعا من قبل destroy ، على حد سواء أساليب التعامل معها من قبل Base ، Widget يضيف renderUI ، bindUI و syncUI للمرحلة البناء، والذي سوف يطلق في تسلسل عندما Widget الصورة render الأسلوب هو ودعا.

و renderUI طريقة يعتني إنتاج HTML الأساسية للقطعة. كل من boundingBox و contentBox صيرت عند هذه النقطة. إذا باستخدام التعزيز التدريجي، renderUI الأول يحتوي على التحقق مما اذا كانت العناصر موجودة بالفعل على الصفحة. إذا استخدمنا HTML_PARSER الملكية ثم سيكون قد تم سمات تكوين عقد إشارات إلى تلك العناصر التي وضعتها بعد ذلك، إن لم يكن، ونحن بحاجة لخلق لهم.

للقيام بذلك، وأسهل طريقة (على افتراض عدم التحسين التدريجي) هو استخدام Y.Node.create ، مثل هذا:

  renderUI: وظيفة () {
     فار CBX = this.get (CBX)؛
     cbx.append (Y.Node.create (Y.substitute (Y.Xxxx.TEMPLATE، CLASS_NAMES)))؛
 

هذا يفترض وجود الكثير من الأمور، التي ساوضح على الفور. أولا، أود أن CBX ثابت أعلن كما هو موضح في المربع رمز الأول في هذه المقالة. ثم فإنه يفترض Node يتم تحميل، الذي Widget يستخدم لذلك هو آمن، لكنه يفترض أيضا Y.substitute هناك، والذي هو اختياري. لديك لإضافة 'substitute' لل requires وحدة قائمة لديك. ثم انها تتوقع نموذج للالقطعة أن تكون في وضع ثابت يسمى المتغير TEMPLATE التي متروك لكم لتحديد طول غيرها من أعضاء الفئة ثابتة (الحق ATTRS وكذا). وأخيرا فإنه يفترض وجود ثابت CLASS_NAMES أعلن في مكان ما.

أعلن عادة CLASS_NAMES حتى في تعريف وحدة بلدي، وعلى طول BBX وCBX (انظر المربع رمز الأول في هذه المقالة)، مثل هذه:

  فار BBX = 'boundingBox'،
     CBX = 'contentBox'،
     NAME = 'زر'،
     / / ثوابت أخرى، واختصارات ....
     YCM = Y.ClassNameManager.getClassName،
     getClassName = وظيفة () {
         وسائط فار = Y.Array (حجج)؛
         args.unshift (اسم)؛
         عودة YCM.apply (هذا، وسائط) toLowerCase ()؛
    
     LABEL = 'التسمية'،
     بعد الحاح = 'الضغط'،
     ICON = 'رمز'،
     CLASS_NAMES = {
         ضغطت: getClassName (ضغط)،
         رمز: getClassName (رمز)،
         التسمية: getClassName (التسمية)،
         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 (الأرجنتين):؟ المقترحة)؛
 }، هذا))))؛ 

أضيف الوسيطة الثالثة إلى 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 من شأنها أن تجعل محتوى القطعة ل. تلاعب في سلسلة جافا سكريبت أسرع بكثير من الوصول إلى DOM حتى أكثر كنت قبل استدعاء Y.Node.create مع هذه السلسلة، وأسرع ستحصل عليها القيام به.

الأسلوب المثيل التالي دعا أي القطعة هو bindUI . هذا هو المكان الذي تعلق المستمعين الحدث إلى أي عناصر تم إنشاؤها بواسطة renderUI ، على سبيل المثال، والمستمع عن أي تغييرات في القيمة في <input> مربع من TEMPLATE أعلاه. وينبغي دائما قيمة في مربع النص، وأنه في سمة التكوين أن تبقى على وفاق. و value يمكن تغيير السمة إما عن طريق رمز أو عن طريق الكتابة المستخدم في مربع الإدخال. إذا كان يأتي من رمز خارجي، يجب أن يتم تحديث النص، إذا كان يأتي من النص، فإنه لا ينبغي، وإلا هل خطر الدخول في حلقة لا نهائية: التغيير في النص يحدد value السمة التي تحدد بعد ذلك value في مربع النص الذي ثم التغييرات ويحدد value السمة وهلم جرا. دعونا نرى كيفية التعامل مع هذه القضية. وضعنا المستمع على الاصطناعية valueChange حدث في مربع الإدخال. لفعل ذلك نحن بحاجة إلى إضافة event-valuechange حدة نمطية إلى requires قائمة من هذه الوحدة.

  this._eventHandles.push (this._inputEl.after ('valueChange'، this._afterInputChange، هذا))؛ 

نحن نفترض الكائن لديها في اشارة الى النص المحفوظة في _inputEl . المستمع يفعل ذلك:

  _afterInputChange: وظيفة (EV) {
     this.set (القيمة وev.target.get (القيمة)، {المصدر: واجهة المستخدم})؛
 

هنا نحن نفترض لدينا ثوابت VALUE و UI كما أعلنت 'value' و 'ui' على التوالي. نحن ببساطة تحديد السمة value إلى قيمة القراءة من مربع الإدخال. ومع ذلك، فإننا نقوم بإضافة الوسيطة الثالثة إلى أسلوب مجموعة: {source:UI} . في set يمكن أن أسلوب اتخاذ الحجة الثالثة، كائن، خصائصه سوف تكون مختلطة في واجهة الحدث السمة حدث التغيير. هذه هي الطريقة التي يمكننا ان نقول للفرق بين قيمة في التي يجري وضعها من النص أو من رمز خارجي. في bindUI لكنا قد وضعت هذا المستمع:

  this._eventHandles.push (this.after ('valueChange'، this._afterValueChange))؛ 

هذا هو المستمع للتغيير في value السمة من وجوه الخاص بك، والآخر كان للتغيير في قيمة <input> مربع، ما يطلق عليه هو نفسه، بعد كل شيء، فهي على حد سواء الاستماع إلى تغييرات في ما يسمى قيمة، ولكنها ليست نفس الشيء. عادة، يتم تعيين المستمعين للتغييرات السمة في initializer ، ولكن منذ هذا واحد يؤثر على عنصر واجهة المستخدم، نضعه في bindUI حتى يتسنى لنا معرفة النص سيكون هناك. والمستمع لديك:

  _afterValueChange: وظيفة (EV) {
     إذا كان (ev.source === UI) {
         العودة؛
     }
     this._inputEl.set (القيمة وev.newVal)؛
 

أول شيء نقوم به هو للتحقق من source الحدث. إذا كان يأتي من UI ثم نتجاهل ذلك. Both the property name, source and its value, UI are arbitrary, those are the ones I used when setting the value attribute so those are the ones I check for in the listener, but any name/value would do just as well. Actually, Widget provides a constant for that, Y.Widget.UI_SRC , but it is kind of long so I would probably use a shortcut anyway.

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

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

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

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

Which could be called from syncUI along other similar setters:

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

and by the after listener:

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

Communicating with others

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

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

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

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

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

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

you can listen to its events by doing:

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

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

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

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

You can have several such sandboxes in your page:

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

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

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

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

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

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

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

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

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

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

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

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

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

Events and Default Behaviors

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

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

and then:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This would get the sort done upside down.

YUI_config

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

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

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

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

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

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

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

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

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

Conclusion

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.

تقاسم وتوسيع نطاق: مع الإشارة المرجعية متواجد حاليا | يسو أنها! | صديق!

18 تعليقات

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

    Comment by plv — April 1, 2011 #

  2. Awesome article!

    Thank you, Satyam!

    Comment by John — April 1, 2011 #

  3. Wonderful article!

    Comment by Daniel Stockman — April 1, 2011 #

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

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

    Comment by John — April 3, 2011 #

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

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

    Comment by Vignesh — April 5, 2011 #

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

    Comment by lily — April 5, 2011 #

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

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

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

    Comment by jayli — April 14, 2011 #

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

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

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

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

    I hope that helps :)

    Comment by Nate Cavanaugh — April 16, 2011 #

  10. [...] Recipe for a YUI Application Building Reusable Widgets with YUI3 Alloy UI [...]

    Pingback by Build A Chrome App with YUI – (1) « Triptych — April 17, 2011 #

  11. Nate

    You are referring to property _UI_ATTRS which is an object with two properties, BIND and SYNC . As its initial underscore signals and the documentation states, they are private and unless you really know what you are doing, it is better keep your hands off them. (I mean 'you' in generic terms)

    Each of these has an array of attribute names which are already initialized by Widget so any changes need to be done carefully not to destroy their initial values.

    For those listed in the _UI_ATTRS.BIND array, an “after change” listener will be attached before our bindUI method which will call a method named like _uiSetXxxx , where xxxx is the name of the attribute.

    Likewise, configuration attributes named in _UI_ATTRS.SYNC will have this same _uiSetXxxx called right before our own syncUI method. Each of these _uiSetXxxx methods will receive the value of the attribute, either the initial value or the changed value.

    Indeed, this is a clever mechanism and it would be great if it was made public, or an alternative public equivalent were to be provided and thus supported in the long term. A formal statement by the YUI team regarding the long-term support for this mechanism would be welcome.

    Nevertheless, I should have mentioned it or suggested a similar custom approach. So, since the Pandora's box is open, let me explain it.

    Say you have an attribute “myAttr” which has an effect on the UI. You need to provide a method called _uiSetMyAttr (note the M in myAttr turned uppercase) which receives the new value to set and a second argument which might either be undefined (not there) or set to Widget.UI_SRC and affects the UI when the second argument is not set. You would define it in the instance member section like this:

    _uiSetMyAttr: function (value, src) {
    if (src === Y.Widget.UI_SRC) { return; }
    // set the UI element
    }

    To get the UI set initially, right before your own syncUI is called, push the name of the attribute into _UI_ATTRS.SYNC , usually in the initializer , like this:

    this._UI_ATTRS.SYNC.push("myAttr");

    To have it further called after any change in the attribute, push it also into _UI_ATTRS.BIND , like this:

    this._UI_ATTRS.BIND.push("myAttr");

    شكرا لأجل الطرف.

    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. Great article! I'm in doubt with something and hope you could clarify this issue for me. You wrote: “Base cannot detach any event listeners at all”. But Base's destroy() method calls detachAll(), inherited from EventTarget, which according to the documentation, it should remove all listeners. What am I missing?

    Comment by alejandroci — May 25, 2011 #

  18. alejandroci ,
    You are right, the wording of that phrase is not right, it should have been 'Base cannot remove all events'. Indeed, it does remove some via detachAll() , which it inherits from EventTarget which comes along Attribute. Event Target is capable of removing all events created by itself via this.on and this.after .
    It cannot detach those subscribed to via Y.on , Y.after on events broadcast by other objects (subscribed via the " publisher : event " notation) or via a reference to some other object ( myPublisher.on() ).
    Widget can only detach events picked by delegation to its bounding box.
    So, YUI tries to keep it safe as much as possible with what it knows but it can't do it all, not with any reasonable amount of code. If you forget, YUI will cope as best as it can, but it is better if you do it yourself.

    Comment by Satyam — May 26, 2011 #

آسف، يتم إغلاق شكل تعليق في هذا الوقت.

استضاف برنامج رائع

Copyright © 2006-2012 Yahoo! Inc. All rights reserved. Privacy Policy - Terms of Service

مدعوم من وورد في ياهو! Web Hosting .