YUI 3アプリケーションのためのレシピ

中|午前2時52時2011年4月1日は、サティヤムで午前の開発 | 18コメント

YUI 3は、モジュールの周りにアプリケーションを構築するために設計されています。 私はそれがなく、彼のプレゼンテーションでニコラスZakasによって記述されているので、モジュールが何であるかについては説明しませんスケーラブルなJavaScriptアプリケーション·アーキテクチャ 私はちょうどこれらのモジュールを構築する方法に固執するでしょう。 私が言うであろうことのほとんどは他のいくつかの選択肢に加えて、オンラインドキュメントで見つけることができ、結局、それは良いドキュメントのポイントです。物事のすべての可能な方法をご紹介します。 このレシピ、他の多くの間でそれを行う方法の1つである理由です。 ニコラス "が示すように、これは単なる記事ではなく、本ですので、それもなく、多くの層などで、小さめのアプリケーションを想定しています。

モジュールを識別

最初のステップは、我々が必要とされるモジュールを識別することです。 タイトルバー、メニューバー、コンテンツ、サイドパネルまたは任意の他があるかもしれません:良いアプローチは、個々のセクションにアプリケーション画面のデザインをスライスして開始することです。 その後、ライブラリが提供しなければならないものを見てみましょう。 たとえば、YUI 3はメニューがありませんが、そこにあるノードMenuNavプラグインが順序付けられていないネストされたリスト<UL>要素で作られた基本的なメニュー構造をとり、アクティブなメニューにそれらを回す。 か、確認するとよいでしょうYUIギャラリーを基本的なコンポーネントのために。 とにかく、あなたが最終的にあなた自身に記入しなければならないことは、そのレイアウトにボックスを持っている点に到達するだろうので、そんなことみましょう。

私は、同じ名前で独自のファイルと独自のディレクトリに各モジュールを配置することをお勧めします。 したがって、 weatherモジュールは、であろうweather/weather.js この理由は、あなたのモジュールは、いくつかのスタイリング、いくつかのCSSファイルとイメージファイルを必要とする可能性があるので、簡単にこのケースでは、それらを見つけることができ、それらを配置する場合、それがビルトインローダーのためにそれを容易にされると、メインのスタイルシートであろうweather/assets/skins/sam/weather.cssと一緒に、他の資産なので、上の画像とで、。 これは、とにかくすでに物事を行いますYUI·ビルダーは、この方法を使用していないと仮定されていますが、それはまた別の話です。 フォルダ名のassetsskins多かれ少なかれ自明であり、 samしかし、非常に明白ではありません。 それはデフォルトの値であるskinそれはデザイナー、サム·リンドにちなんで名付けられYUI、同梱されているデフォルトのスキンであるため、ローダのプロパティです。 これが示唆するように、あなた自身のスキンで自分の名前を入れて無料で、 skinプロパティでは、それらをロードするためには、それを単純にするために、デフォルトに行くだけにしましょうYUIに指示することができます。

モジュールファイルのテンプレート

これは私がその瞬間に説明します、私はより頻繁に使用するファイル構造は、次のとおりです。

  / * jslint開発:trueの場合、undefを:真、newcap:trueの場合は、厳格な:trueの場合、maxerr:50 * / 
 / *グローバルYUI * / 
 / ** 
  *モジュール名のモジュールは、何とか何とかを作成します。 
  * @モジュールmodule-name 
  * / 
 YUI.add( 'モジュール名'、関数(Y){ 
     "厳格な使用"; 
    モジュールで使用される/ /便利な定数とショートカット 
     VAR LANG = Y.Lang、 
         CBX = 'contentBox'、 
         BBX = 'のBoundingBox'、
         NAME = 'XXXX'; 

 
     / ** 
      * xxxxというクラスがない.... 
      * @クラスXxxxが 
      * @を拡張ウィジェット 
      * @ WidgetParent使用 
      * @コンストラクタ 
      * @ CFG {オブジェクト}の構成属性 
      * / 
     Y.Xxxx = Y.Base.create( 
         NAME、 
         Y.Widget、 
         [Y.WidgetParent] 
         { 
             / /ここにインスタンスメンバー 
         } 
         { 
            特にここで/ /静的メンバ: 
             ATTRS { 
             } 
         } 
     。) 

 
 }、'0 '0.99'、{ 
    必要があります:['ウィジェット'、 'ウィジェットの親']、 
    スキンはtrue 
 }); 

コメントの最初の2行の利益のためにあるJSLint 、私は本当にお勧めのJavaScriptの検証ツール。 あなたに行く場合、 Web版 、オプションを設定するボックスがあります。 そのオプションボックスの下部には、ファイル自体にこれらのオプションをエンコードする方法を見ることができます。 あなたはYUI Builderを使用する場合は、それはあなたのためにJSLintを実行すると、これらのオプションを設定しますが、必要に応じてあなたはまだ、任意の個々のファイルのためにそれらを無効にすることができます。

docコメントは、YUIのAPIドキュメントビルダーのためのものです。 あなたは、これらのAPIドキュメントの最初のテンプレートが含まれている場合は、それが頭痛の少ない、最終的にそれらを記入します。 アプリケーションが成長し、あなたがそれをすべて覚えておくことはできませんので、それらが必要となります。

今のコードの最初の実際の行、来るYUI.add()ステートメントは、。 これは、YUI Loaderのモジュールといくつかの情報の他の部分の名前と内容を伝えるための方法です。 モジュール名は通常、単語の間のすべての小文字とハイフンで名前が付けられています。 それらはあなたがに表示名です。 APIドキュメントの左の一番上のインデックスに。 あなたは規則が厳密に従わない場合見ることができ、いくつかのモジュール名は任意のハイフンを持っていません。 とにかく、これはあなたまで、ほとんどあなたが一貫してそれを使用する限りです。

の第二引数YUI.add()ステートメントは、従来と呼ばれる単一の引数を受け取り、関数であるY その関数は、モジュールの本体が含まれており、 Yあなたが求めているすべての他のYUIとギャラリーモジュールを見つけることができる場所ですYUIのサンドボックス化されたインスタンスへの参照です。 そのコードボックスの下部にジャンプし、あなたの残りの引数は見ることができます.add()メソッドのバージョン( '0.99′まだかなり存在しないか)と、このモジュールの設定、AAシリーズの持つオブジェクトをプロパティ。 ここでは、このモジュールが必要であることをローダー言うwidgetwidget-parent 、それが皮膚を持っていることを。 リストwidgetあるため、冗長であるwidget-parent既に必要とするwidget 、それを自分でトラブルはありません、ローダーは二回モジュールをロードしませんと、1つの依存関係をドロップ後の時点であれば、チェックする必要はありません他の仮定のためにあなたは行ったことがあります。状態それらのすべてと、それにローダーに対処してみましょう。 あなたのためのAPIドキュメント内のすべてのオプションのリストは見つけることができます.addModule()ローダーの方法。

関数の本体内で、最初は“use strict”;宣言。 これは、この時点では、将来的に発生する可能性があり、すべてのプラットフォームとの互換性を確保するため、安全側にあなたを置くECMAScript 5の標準に準拠するようにコードのためのものです。 古いインタープリターでは、この宣言は、何にも割り当てられていないため、無視される文字列以外の何ものでもありません。 “use”宣言は、関数のスコープを持ち、それはあなたが定義しているモジュールに影響を与えるように、ファイルの先頭ではなく、関数の本体内に配置する方が安全です。 あなたはファイルの先頭にそれを配置した場合、それはまた、その後ロード、他のJavaScriptファイルに適用されますと、それらの多くは、ES5に準拠していない可能性があります。

その後、JavaScriptは定数の概念がありませんので、のみの使用では定数であり、ショートカットや定数を、来る。 定数はしばしば他の言語であるので我々は、すべて大文字とアンダースコアで名前を付けます。 定数を使用するには、2つの理由、特別な文字列定数があ​​ります。 まず、同じ文字列を複数回書き込むときには、それらのいずれかを間違えた可能性があり、バグがポップアップ表示されたときにだけ注意することです。 あなたは定数を使用している場合、間違った定数名を入力すると、JSLintは、それが不定となりますので、警告が表示されます。 第二の理由は、文字列リテラルはできませんが、定数名は圧縮することができますので、YUI Compressorは良い仕事をすることができるということです。 名前付き定数のためのよい候補者は、コンフィギュレーションの属性およびイベントの名前です。

などのショートカットが、 LangのためにY.Lang彼らはあなたが以下に入力できるためにも良いですが、インタプリタは(それぞれのドットはオブジェクトのメンバに新たな検索を意味する)を評価するために以下を持っており、彼らはYUI Compressorで圧縮することができます。

クラスの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プラグインやから派生した他のクラスのY.Baseなどのいずれかとし、あなたは既に使用して作成している可能性がありますY.Base.create()

三番目の引数は、使用する拡張子を保持しています。 拡張子は、そのプロパティとメソッドを、あなたのクラスに混合しておきたいクラスです。 拡張のための良い候補であるArrayListためのBaseまたは任意のWidget-Xxxx用のサブWidgetAttributeEventTargetPluginHostに既に混在してくるBaseは常にそこにいるこれらの3つに数えることができるようにします。 拡張機能は、あなたが見れば、非常に強力であるためのソースコードOverlay 、あなたは何もありません見ることができるWidgetと拡張子が。 三番目の引数が配列であるので、いくつかの拡張機能は、コンポーネントに混在させることができます。

最後に、私たちは、実際のコードを取得する。 4番目および5番目の引数は、インスタンスとクラスの静的メンバを含むオブジェクトリテラルの両方があります。 インスタンスメンバは、クラスに入るのプロパティとメソッドがありprototypeの各インスタンスは、コピーを取得し、通常で参照する必要がありますもの、 this 静的メンバは、すべてのインスタンスで共有されるものである。

コンフィグレーション属性

これらの静的メンバの中で最も重要であるATTRSプロパティです。 これは、構成があなたのクラスがあります属性が表示されます。 たとえば、我々が呼ばれるコンフィギュレーション属性にしたいと言うことができますvalueの数値を保持するために、最初は0に設定します。 第五引数内で、我々は、このようにそれを宣言します。

  ATTRS {
    値:{
        値:0、
        バリ:Lang.isNumber
     }
 } 

我々は、で、任意の数の属性を一覧表示することができますATTRSプロパティと、それぞれが私がここに示された2つのうちのいくつかのオプションを設定することができます。 あなたのオプションの残りの部分を読むことができますaddAttr()する方法Attribute 見られるように、私が使用しているLang私はモジュールの宣言の先頭で宣言されたショートカットを。 validatorブール値を検証し、返すように値をとる関数でなければなりません。 すべてのY.Lang.isXxxx方法は、正確にできるようにそれらが直接使用することができますか。 より精巧なバリ、セッターやゲッターのために、関数を定義する必要があります。 私は、文字列として関数の名前を提供するお勧めします、 Attribute実際の関数に関数名を解決するの面倒を見る。 例えば、私が定義した場合、例えばvalidCodes 、単一の有効なコード、または有効なコードの配列を取ることができる属性は、常に配列を返す必要があり、私がするだろう:

  ATTRS {
     validCodes:{
        セッター: '_setValidCodes'
     }
 } 

我々は宣言する必要があります_setValidCodesの4番目の引数内の他のインスタンスメンバに沿って方法をY.Base.create()

  _setValidCodes:関数(値){
     {(!Lang.isArray(値))の場合
        値= [値];
     }
    値を返します。
 } 

それはセッター、ゲッター、任意しかし、バリの中で最も簡単な別のインスタンス関数を宣言してみましょうするのが最善ですAttribute実際の関数呼び出しに、関数名を解決します。

一般的に、二次効果を生み出すためではなく、上記のように、値を正規化するセッターを使用しています。 すべてのコンフィギュレーション属性は、変更から属性を防止することができる前にイベントとchangeイベントの前後に発生します。 任意の二次的影響ではなく、セッターを生成するように、これらの変更イベントを使用しています。 それまでは、他のコード(上)変更イベントの前にサブスクライブし、それをキャンセルしたかもしれないので、何も設定されているから属性を妨げません知っているので、後のイベントは最高です。

あなたは、あなたのバリデータメソッドとsetterを定義する方法に応じて属性が多かれ少なかれ厳密にすることができます。 バリデータは非常に制限する場合は、あなたの属性が有効な値のみを受け入れて、非常に厳格になり、この場合には、セッターは不要かもしれません。 一方、あなたはすべてのバリデータを使用しない場合があり、許容可能なものに受信された任意の値をマッサージしてセッターに完全に依存しています。 たとえば、次のいずれかのこれら二つのことができます。

 バリ:Y.Lang.isBoolean、/ /属性が厳密にブール値を受け入れるようにするには
セッター:ブール値、/ /属性は任意の値を受け入れているように、ブール1にそれを回す 

セッターはまた、バリデータとして使用できます。 セッターは、属性に割り当てられるが、彼 ​​らはまた、返すことができる値を返す必要がありY.Attribute.INVALID_VALUEバリデータがそれを拒否したかのように、属性はそのままにします。

私は、コンフィギュレーションを定義するときに私は頻繁に(一緒に私はモジュールの上部に配置する、そのための定数を定義する属性CBXBBX例えば、ショートカット)

  var値= '値'、
     VALID_CODES = 'validCodes'; 

チャンスは私がモジュール内でこれらの構成属性を複数回使用され、これは私にいくつかのトラブルが保存されていることがあります。 ただし、属性を宣言するときに注意して、その定数を使用しないで、これを実行しないでください。

  ATTRS {
     / / ***この*** / /を実行しないでください。
    値:{
        値:0、
        バリ:Lang.isNumber
     }
 } 

これを行う場合には、と呼ばれる属性になるだろうVALUEの代わりにvalue これは、JavaScriptの問題ではなく、YUIです。 また、ない構成のいずれかに踏み越えに注意して既に基本クラスまたは拡張のいずれかのいずれかに宣言された属性。 Widgetすでに宣言された属性の束を持っています(参照表を )とはほとんど追加しないだろうがboundingBoxする、自分自身を属性簡単には忘れてしまうかもしれませんvisibledisabledheightまたはwidthすでに定義されています。 あなたの使用目的は何かと一致する場合はWidgetのためにそれらを使用して、すべてが正常になります。 それは、あなたがそれらのいずれかの定義を変更することができると述べた。 Y.Base.create()あなたは、言う、のデフォルト値を変更したい場合は、その基底クラスのものと拡張子のコンフィグレーション属性の定義をマージします既存の属性は、サブクラスで再びその属性を宣言することによって行うことができます。

あなたは、配列またはオブジェクトで属性を初期化することを意味する場合は注意が必要です。 オブジェクト(および配列はオブジェクトである)は、参照によって渡され、オブジェクトの属性を初期化する場合、それらはすべて同じオブジェクトを指してしまうかもしれないので、(配列から項目を削除したり、プロパティを追加し、その一部を変更するときオブジェクトへの)あなたは一度にすべてのインスタンスを変更することになります。 Base 、ただし、安全に、オブジェクトと配列リテラルを使用して属性を初期化することができますいくつかの内部ロジックを持っています。 初期値は、リテラルオブジェクトまたは配列である場合、 Baseそれを複製します。 使用valueFnオプションやその他のオブジェクトのクラス初期化子で初期化します。

他の静的メンバー

あなたは5番目の引数で定義したかもしれない2つの他の静的なメンバがありますY.Base.create() あなたは、プラグインを作成している場合、あなたは絶対に宣言する必要がありますNSあなたは、プラグインが動作しませんしない場合は、プロパティを、黙って失敗します。 NSは、任意の既存のプロパティをオーバーライドしないように、名前を選ぶときに、プロパティは、ホストオブジェクト内でプラグインを格納するプロパティ名として使用される文字列に設定する必要があり、これを覚えておく。

ウィジェットを構築していて、プログレッシブ·エンハンスメントをサポートするように計画している場合、あなたは使いますHTML_PARSER静的プロパティ。 これは、既存のHTMLとCSS3セレクターのいずれかまたはそれらの値を生成する関数を解析してから設定するには、コンフィギュレーション属性の後に名前付きプロパティを含むオブジェクトに設定されています。 参照してください。 プログレッシブ·エンハンスメントウィジェットユーザーガイドにします。

また、あなたのクラスを使用する開発者によって使用される値を提供することをお勧めします。 ファイルの先頭で宣言された定数は、モジュール自体が外部から完全に見えません。 あなたがパブリック定数を提供したい場合は、これはそれを行うための場所です。 そのようなの例としては、 HEADERBODYFOOTERの定数WidgetStdMod (:それらを使用するには、実際に完全修飾名を使用する必要がありY.WidgetStdMod.BODYなどを参照)。

インスタンスメンバ

4番目の引数Y.Base.create()入りますのプロパティとメソッドがありprototype作成したクラスの。 通常、我々は後に最初のプロパティとメソッドを宣言します。 私はこのため、何らかの理由を持っていないため、実際には無関係であるには、JavaScriptもYUIどち​​らもあなたがこの方法でそれを実行する必要がありますが、それは簡単にソースファイルで物事を見つけることができます。 インスタンスのプロパティが初期化子でその場で作成することができますが、私はそれらを明示的に宣言し、それらを初期化をお勧めしません。 各プロパティは、APIドキュメントのコメントを付けてください。

プロパティは、通常、プライベートとなり、その名前がアンダースコアで始まる。 オブジェクトのパブリックインターフェイスは、コンフィギュレーション属性とされていないプロパティを介して公開されている場合には最適です。 プロパティは非常に愚かであり、コンフィギュレーション属性は、バリデータ、型変換(セッターを介して)持っていると二次効果(changeイベントを介して)生成し、あなたがすべてのこれらの機能が必要であることを見つけるまで、それはしばしば長くないことができます。

コンフィグレーション属性を持つように、オブジェクトや配列にプロパティを初期化し、それらが同じオブジェクトを指すまでのすべてのエンド、あなたはトラブルに実行されません。 それがためにオブジェクトを保持するためにあるプロパティを設定することをお勧めしますnull あなたはまだその価値を知らない場合にも、それらを設定し、プロパティを未設定のままにしないでnullではなく。 その後、デバッグ時に、プロパティがに設定されundefined通常タイプミス、エラーを指しています。

ベースのインスタンス·メソッド

あなたは我々のサブクラスのいずれかのコンストラクタを宣言していないことに気付いたかもしれません。 Base呼び出されたメソッドを呼び出し、モジュールの初期化を行い、 initializer 、それが存在する場合、それはあなた、すべての目的のためなので、インスタンス化するとき、受信した同じ引数でと考えることができるinitializerあなたのコンストラクタです。 から派生したすべてのクラスBase通常作成される単一の引数は、設定属性を含むオブジェクトを受け取ります。 Base (またはWidget 、それはクラスであるため、 Base )は、この引数を読み込み、呼び出す前に、コンフィギュレーション属性を設定するinitializer のためにWidgetがある場合は、 HTML_PARSERプロパティが、それはまた、処理されたであろうし、マークアップからの読み取り属性の値は、同様に設定されます。

initializerメソッドでは、いくつかのタスクを持っています。 まず、オブジェクトや配列に初期化する必要がある任意のプロパティを設定する必要があります。 それはこのクラスが生成されるすべてのイベントを発行します。 EventTarget 、イベントのデフォルト設定を使用した最初の公開が、この場合でも、私はとにかくそれらを宣言するが示唆されていないイベントを発生させることができます。 これは、関数宣言の本体にいる、それは少し奇妙に見える場合でも、それらのイベントのためのAPIドキュメントコメントを追加するには良い場所ですが、それを行う良い場所はありません。

によって受信された引数はinitializerその後で処理され、時にはあなたは、いくつかの余分なオプションは初期化時に使用したいとあなたが彼らのために実際の属性を維持するために気にしないされていました。 たとえば、 Baseの属性を受け入れonafterbubbleTargetsplugins (参照Baseそれはそれらのための構成属性を持っていませんが)。 同様にWidgetParentかかるchildren初期化時に属性が、その名前のコンフィグレーション属性を持っていません。 initializerメソッドでは、それらを処理するものです。 したがって、クラスがインスタンス化では一つの引数のみを取ることになりますけれども、この単一の引数はあなたが必要とする可能性のあるすべての情報を運ぶことができます。

JavaScriptは、デストラクタの概念はありません。 Baseあなたが宣言することによってこれを補完しdestructorは、オブジェクトが撮影したかもしれないリソースを解放するコードを置くことができる方法を。 オブジェクトをドロップするときにはまだ廃棄する前にオブジェクトを破棄する責任がありますが、少なくともあなたはデストラクタがあります知っているので、これは部分的にしか解決策は、JavaScriptインタプリタはそれを自動的に呼び出すことはありません。

あなたのクラスのユーザーが呼び出すことはありませんinitializerdestructor直接Base必要なときにそれらを呼び出すことになります。 initializer 、オブジェクトがインスタンス化されるときに呼び出され、 destructor 、クラスのユーザーは、その呼び出したときに呼び出されるdestroy方法。

頻繁にメモリリークを生成することの一つは、残されたイベントリスナーです。 Widgetバウンディングボックスの要素内に含まれるユーザー·インタフェースの要素に接続されているすべてのリスナーをデタッチしようとするが、それは他のものを切り離すことはできません。 Baseまったくのイベントリスナーをデタッチできません。 これは私がそれを手伝ってもらうために使用するコードです。 他のプライベートインスタンスのプロパティに沿って、私は宣言する_eventHandlesプロパティ:

  _eventHandles:nullの場合、 

その後にinitializer方法、私は配列に設定します。

 初期化子:関数(CFG){
     this._eventHandles = [];
     / / ......
 } 

同じ方法でinitializer (これもでbindUIそれがあった場合Widget )私は、その後実行してリスナーをアタッチします:

  this._eventHandles.push(this.after( 'someAttributeChange'、this._afterSomeAttributeChange、この)); 

その後でdestructor 、私が持っている:

 デストラクタ:関数(){
     Y.each(this._eventHandles、機能(ハンドル){
         handle.detach();
     });
 } 

それはでは、ここにあるinitializerは、二次的な影響を(あなたがそれを異なる場合があります生成する必要があります属性のイベントリスナーをフックアップすること、 bindUIこの二次的効果は、UIに対処する必要がある場合)。 私は先に述べたように、属性のsetter関数は、属性の値を正規化に対処する必要があります。 その設定値を保存を越えた効果を生成する必要があり、これらはイベントリスナーで処理する必要があります。 上記の例では、私は、メソッドの設定されている_afterSomeAttributeChange任意の変更をリッスンするsomeAttribute属性。 イベントリスナーは、単一の引数、私は通常呼び出すイベントファサード受け取るevは、いくつかのプロパティを持つオブジェクト、それらの1つは、 newValされている値のセットを含有する。

ウィジェットインスタンスのプロパティ

という2つの重要なプロパティWidget用途がありBOUNDING_TEMPLATECONTENT_TEMPLATE 両方は、最初に設定されている“<div></div>”ほとんどのウィジェットが使用する他の内の2つのコンテナ一つの標準的な構造を生成する。 これは、しかし、すべてのウィジェットには適さない場合があり、例えば、 Buttonウィジェットは、優れたによって提供される可能性があり<span> (アンカー内の要素<a> )要素の代わりに2つのネストされた<div> sは。 実際には、あなたが持っている気にしないかもしれませんcontentBoxすべてで、 Widgetあなたがする必要はありません。 あなたは任意のマークアップにこれら二つのインスタンスのプロパティを設定することができます。 たとえば、つぎのようなButtonクラスIは持っている可能性があります。

  BOUNDING_TEMPLATE: '<A>'、
 CONTENT_TEMPLATE:nullの場合、 

持ってCONTENT_TEMPLATEためにセットをnull教えてくれるWidgetはしたくないcontentBoxまったくします。 このケースではcontentBoxコンフィギュレーション属性は、同じ要素を指すようになりboundingBox設定属性がありません。

あなたはこれらのテンプレートにウィジェットの全体のHTMLを置 ​​くべきではありません、これら2つのシンプルなHTML要素を作成して、内のコードを経由して、余分なマークアップを作成renderUI (我々は後述します)。

Widget追加されid属性とのようなそれはあなたが任意のマークアップに使用した標準的なクラス、 yui3-xxxxyui3-xxxx-visibleまたはyui3-xxxx-disabledxxxxの値であるNAMEは小文字になってプロパティを。

ウィジェットのインスタンス·メソッド

Widgetいくつかのステップで、その初期化を分割します。 を超えてinitializerオブジェクトがインスタンス化されるときに呼び出され、およびdestructorから呼び出され、 destroy 、によって処理される両方の方法をBaseWidgetが追加されrenderUIbindUIsyncUI時の順序で呼び出される建物フェーズのために、 Widgetrender方法があるのと呼ばれる。

renderUIメソッドは、ウィジェットの基本的なHTMLを生成するの面倒を見る。 両方boundingBoxcontentBoxこの時点でレンダリングされています。 プログレッシブ·エンハンスメントを使用する場合は、 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 、モジュールのリスト。 それはウィジェットのテンプレートと呼ばれる静的変数であることを期待TEMPLATE他の静的クラスメンバ(右に沿って定義することはあなた次第ですATTRSなど)。 最後に、定数があることを前提とCLASS_NAMESどこかに宣言した。

私は通常、宣言CLASS_NAMESこのように、BBXとCBX(この記事の最初のコードボックスを参照)に沿って、私のモジュールの定義にバックアップします。

  VAR BBX = 'のBoundingBox'、
     CBX = 'contentBox'、
     NAME = 'ボタン'、
     / /他の定数およびショートカット....
     YCM = Y.ClassNameManager.getClassName、
     GetClassNameは、=関数(){
         VAR argsは= Y.Array(引数);
         args.unshift(NAME);
         (これは、args)をYCM.apply返すtoLowerCase();
     }
     LABELは= 'ラベル'、
    プレス= '押さ'、
     ICON = 'アイコン'、
     CLASS_NAMES = {
        押された:getClassNameメソッド(押す)、
        アイコン:getClassNameメソッド(ICON)
        ラベル:getClassNameメソッド(LABEL)、
         NOLABEL:getClassNameメソッド( 'no'に、LABEL)
     }; 

CLASS_NAMESその後で作成されたプロパティを持つオブジェクトを含む一定になりますClassNameManager (これも付属してくるWidget )。 上記のコードでは、私が最初にショートカットを作成YCM後でより速くアクセスするために、私は関数を作成getClassName 、モジュール定義内でのみアクセス可能なプライベート関数を。 関数は、メソッドと同じようにかなりの動作と同じ名前Widgetが、それは、私はさらに、静的な値を定義するために使用できる静的な関数です。 それは私が作成したときに、後で正確に私は何をすべきかであるCLASS_NAMESそれらのプロパティとして生成されたクラス名を持つオブジェクトとして。 これは私が書くことができますTEMPLATEような文字列を:

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

これは、これまでのところかなり間抜けです。 私も他のソースは、具体的には、構成属性からこのテンプレートの値にマージしたいと思います。 これは私がそれを行うに得る方法は次のとおりです。

  this.get(CBX)のappend(Y.Node.create(Y.substitute(テンプレート、CLASS_NAMES、Y.bind(関数(キー、提案は、arg){
    リターン(キー=== '_' this.get(ARG):?推奨)。
 }、この)))); 

私は3番目の引数を追加Y.substitute 、機能します。 通常のプレースホルダY.substituteスペースがある場合は、中括弧の間に囲まれた文字で作られています、しかし、それはキーと2番目のオプション引数である空間に、二つに参加してプレースホルダを分割します。 三番目の引数はそのようなここでは、関数であるときに、これは便利だ。 関数は3つの引数を受け取り、最初のキーで、2番目は、ここで、置換オブジェクトで見つかった値であるCLASS_NAMES 、もしあれば、第三は、オプションの引数です。 したがって、上記の文では、私はこのようなテンプレートを使用することができます。

  TEMPLATE: '<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文字列を使用してできるだけ多くの書式設定を行うことをお勧め。 JavaScriptで文字列操作は、より多くのあなたが前に呼び出すんので、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(VALUE、ev.target.get(VALUE){ソース:UI});
 } 

ここで我々は定数ていることを前提とVALUEUIとして宣言された'value''ui'であった。 我々は、単に属性の設定value入力ボックスから読み取った値です。 しかし、我々は、setメソッドに3番目の引数を追加されています: {source:UI} set方法は、プロパティ属性の変更イベントのイベントファサードに混合される三番目の引数は、オブジェクトを、取ることができます。 これはテキストボックスから、または外部のコードから設定されている値との間の違いを伝えることができる方法です。 bindUI我々は、このリスナーを設定しなければならなかったでしょう。

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

これは変化のリスナーであるvalue 、オブジェクトの属性、他の値の変化のためだった<input>ボックス、彼らは同じと呼ばれ、すべての後、彼らは両方のと呼ばれるものの変化に耳を傾け、しかし、同じものではありません。 通常、属性の変更に対するリスナーがに設定されているinitializerが、この1つのUI要素に影響を与えるので、我々にそれを入れbindUI我々はテキストボックスがあるでしょう知っているようにします。 リスナーがあります:

  _afterValueChange:関数(EV){
     {(ev.source === UI)の場合
        戻り値;
     }
     this._inputEl.set(VALUE、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.

結論

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それ! | reddit!

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");

    Thanks for the tip.

    Comment by Satyam — April 17, 2011 #

  12. Here's the enhancement request:

    http://yuilibrary.com/projects/yui3/ticket/2529439

    Comment by Satyen Desai — April 18, 2011 #

  13. Heya Satyam, thanks for the detailed writeup. We actually support this as a public attribute in our Alloy Component gallery module since it's so useful, but we allow it to be defined on the class level and handle automatically copying of the arrays so that if you pass in a custom set it is merged with a copy of the defaults instead of having to create a new array and concat it (or doing it in the initializer).

    The only downside I've found to the API (and it's a small one at that) are the times when I want other data from the event passed in (maybe the second or third arg would be good to have the event object being passed in).

    Btw, great article, thanks for consolidating this all here :)

    Comment by Nate Cavanaugh — April 21, 2011 #

  14. [...] Recipe for a YUI 3 Application 原文地址:http://www.yuiblog.com/blog/2011/04/01/a-recipe-for-a-yui-3-application/ 译文:使用YUI [...]

    Pingback by [译]使用YUI 3开发Web应用的诀窍 « Uedmagazine — April 22, 2011 #

  15. A correction to my previous comment :

    The extra attributes to monitor should be added to the _UI_ATTRS arrays via method concat() not push():

    this._UI_ATTRS.SYNC.concat("myAttr");
    this._UI_ATTRS.BIND.concat("myAttr");

    Using push() will add the new attribute to an array which is shared by Widget and all its subclasses while concat() will create a new copy for this particular subclass

    BTW: concat() allows several values to be concatenated at once:

    this._UI_ATTRS.BIND.concat("myAttr1","myAttr2","myAttr3");

    Comment by Satyam — May 14, 2011 #

  16. Sorry I missed once again in my correction . The way to add an attribute to be handled by Widget is this:

    this._UI_ATTRS = {
    BIND: this._UI_ATTRS.BIND.concat("myAttr"),
    SYNC: this._UI_ATTRS.SYNC.concat("myAttr")
    };

    Method concat() does not modify the array it applies to so the code in the previous comment does nothing. Sorry about the confusion.

    Comment by Satyam — May 14, 2011 #

  17. Hi Satyam. 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 #

申し訳ありませんが、コメントフォームは、この時点でクローズされます。

ヤフーが主催する

著作権©2006-2012ヤフー株式会社すべての権利を保有。 プライバシーポリシー - サービス利用規約

Powered by WordPress on Yahoo! ウェブホスティング