ExtJS-コンポーネントを正しく書くことを学ぶ

ExtJSでカスタムコンポーネントを作成する問題に関する短い記事シリーズを開きたいと思います。 それらの中で、この分野での経験をHabr読者と共有し、このプロセスのすべての微妙な点、常に注意すべきこと、初心者プログラマーを待っている間違い、およびそれらを回避する方法を詳しく説明します。



遅かれ早かれ、標準ExtJSコンポーネントが開発者のニーズを満たすことができなくなるときが来ます。 または、アプリケーションのリファクタリングのプロセスで、インターフェイスの一部(いくつかのコンポーネント、フォーム、テーブル、タブ)を別のコンポーネントに移動する必要が生じます。 どちらの場合も、カスタムコンポーネントの作成に頼らなければなりません。



このプロセスの基本は何度も議論され、説明されてきました。私はそれらを描くことはしませんが、それらを模式的に描くことにします。



- –-> Ext.extend –-> xtype Ext.reg







しかし、見かけのシンプルさの背後には多くのニュアンスが隠されています。 まず、適切な祖先を選択する方法は? 初心者の開発者は次のアプローチを使用します-継承されたコンポーネントを選択するので、結果として可能な限り少ないコードを作成し、知っている構造のフレームワーク内でのみ記述します。 それらはonRenderによっておびえ、要素を作成し、イベントハンドラをハングさせます。 特定のケースでは、このアプローチは確かに正しいものであり、そのシンプルさを正当化することは認めません。 隣にボタンのあるフィールドが必要です-Ext.form.TriggerFieldを継承し、ドロップダウンリストのフィールドが必要です-Ext.from.Comboboxを継承し、ビジュアルエディタでビルドする必要があります-Ext.form.TextAreaを継承します。 しかし、継承されたコンポーネントの選択を慎重かつ慎重に実行する必要がある、非常に「偶発的な」状況もあります。



次の実用的な例を考えてみましょう。 ビデオギャラリーがある1つのサイトの管理パネルの場合、クリップの長さを入力するコントロールを作成する必要がありました。 1行に3つの入力フィールド(時間、分、秒)が含まれ、単一の入力/出力(setValue / getValueメソッド)が含まれている必要があります。



1年半前、私がまだExtJS開発の初心者だったとき、この問題を次のように解決しました。



はい、コンポーネントは動作し、値を指定/設定します。 確かに、彼のコードはひどくく、getValue()メソッドは常にフィールドにアクセスし、合計時間を再カウントします(フィールドの値が変更されていなくても)。 しかし、これはそれほど悪くはありません。 将来、フォームを検証するか、フォームをシリアル化/ロードするメソッド(getValues / setValues、loadRecord / updateRecord)を使用する必要がある場合、必然的に問題が発生します。 フォームは、コンポーネント自体の存在を単に「忘れる」だけであり、コンポーネントをフィールドとして永続的に認識しません。 その結果、コンポーネントをフォームフィールドとして機能させるために、Ext.form.Fieldからコードをコピーアンドペーストして、たくさんの「クランチ」を作成する必要があります。



したがって、現時点では、次の原則に従います。フォームフィールドとして機能し、シリアル化および検証プロセスに参加する必要があるコンポーネントはExt.form.Fieldまたはその子孫から排他的に継承される必要があります。



まず、Ext.form.Fieldを継承して新しいコンポーネントを作成します。







  1. Ext.ux.TimeField = Ext.extend(Ext.form.Field、{
  2. });
  3. Ext.reg( 'admintimefield' 、Ext.Admin.TimeField);
*このソースコードは、 ソースコードハイライターで強調表示されました。




各フォームフィールドは、デフォルトで独自の要素をレンダリングします。 標準フォームコンポーネントフィールドでは、これは入力フィールドまたはチェックボックスのいずれかです。 入力フィールド要素は、レンダリング後にelプロパティに保存されます。 また、コンポーネントコンテナのサイズが変更されると、自動的にサイズ変更されます。



コンポーネントには3つのフィールドが含まれているため、3つのフィールドとそのラベルが「ラップ」されるdivをデフォルト要素として作成します。 要素のタグとプロパティをデフォルトで変更するには、defaultAutoCreateプロパティを事前定義します。







  1. Ext.ux.TimeField = Ext.extend(Ext.form.Field、{
  2. defaultAutoCreate:{tag: 'div''class''time-field-wrap' }、
  3. .................................................. ...............
*このソースコードは、 ソースコードハイライターで強調表示されました。




これで、入力フィールドの内部構造(「フレーム」)を作成できます。 6つのdivを連続して配置します。 それらのうち3つは、スピナーコントロール(時間と分と秒の入力用)のコンテナーになり、他の3つには対応するラベルが含まれます。 明確にするために、Ext.DomHelperではなく、Ext.XTemplateテンプレートエンジンを使用して作成します。 すべてのユーザーレンダラーは、親メソッドを呼び出した後、継承されたonRenderメソッドに配置されます。







  1. Ext.Admin.TimeField = Ext.extend(Ext.form.Field、{
  2. timeFieldTpl: 新しい Ext.XTemplate(
  3. '<div class = "hours-ct"> </ div> <div class = "timeunittext-ct"> h </ div>'
  4. '<div class = "minutes-ct"> </ div> <div class = "timeunittext-ct"> m </ div>'
  5. '<div class = "seconds-ct"> </ div> <div class = "timeunittext-ct"> with </ div>'
  6. )、
  7. .................................................. ...............
  8. onRender: 関数 (ct、位置){
  9. Ext.Admin.TimeField.superclass.onRender.call( this 、ct、position);
  10. this .el.update( this .timeFieldTpl.apply( this ));
  11. .................................................. ...............
*このソースコードは、 ソースコードハイライターで強調表示されました。




コンポーネントの「フレーム」が必要な場所に配置されるように、次のcssテーブルを1行で記述して接続します。







  1. div.hours-ct、
  2. div.minutes-ct、
  3. div.seconds-ct、
  4. div.timeunittext-ct {
  5. 表示:インラインブロック。
  6. 幅:10px;
  7. }
  8. div.hours-ct、
  9. div.minutes-ct、
  10. div.seconds-ct {
  11. 幅:50px;
  12. }
*このソースコードは、 ソースコードハイライターで強調表示されました。




実装を簡単にするために、固定フィールドサイズ(50ピクセル)を使用しました。



コンポーネントのワイヤフレームの準備ができました。 レンダリング手順を完了するには、フィールドのコンポーネントを作成して表示するだけです。 まず、Ext.queryを使用して、コンテナのDOM要素を見つけ、次にコンポーネントのインスタンスを作成して、適切なコンテナにレンダリングするように指示します。







  1. onRender: 関数 (ct、位置){
  2. Ext.Admin.TimeField.superclass.onRender.call( this 、ct、position);
  3. this .el.update( this .timeFieldTpl.apply( this ));
  4. Ext.each([ 'hours''minutes''seconds' ]、 function (i){
  5. this [i + 'Ct' ] = Ext.query( '。' + i + '-ct'this .el.dom)[0];
  6. this [i + 'Field' ] = Ext.create({
  7. xtype: 'spinnerfield'
  8. minValue:0、
  9. maxValue:i == 'hours' ? 23:05、
  10. renderTo: この [i + 'Ct' ]、
  11. 幅:45、
  12. 値:0
  13. });
  14. }、 this );
  15. .................................................. ...............
*このソースコードは、 ソースコードハイライターで強調表示されました。




レンダリング後のコンポーネント自体はthis.xxxFieldのプロパティに格納されているため、(上記のいくつかの段落で説明したような激しい構成の代わりに)簡単かつ便利にアクセスできます。



コンポーネントの視覚部分は準備ができています。機能を完了するために残っています-getValue / setValueメソッドと検証/シリアル化のサポート。



getValueメソッドが毎秒の秒数を再カウントしないように、次の手順を実行します。







コンポーネントにメソッドを追加する







  1. .................................................. ...............
  2. getValue: function (){
  3. この .valueを返します。
  4. }、
  5. getRawValue: function (){
  6. この .valueを返します。
  7. }、
  8. onTimeFieldsChanged: function (){
  9. this .value = this .hoursField.getValue()* 3600 + this .minutesField.getValue()* 60 + this .secondsField.getValue();
  10. this .fireEvent( 'change'thisthis .value);
  11. }、
  12. .................................................. ...............
*このソースコードは、 ソースコードハイライターで強調表示されました。




そして、入力フィールドを作成するとき、可能性のあるすべての変更イベントのハンドラーによってonTimeFieldsChangedを設定します。





  1. .................................................. ...............
  2. this [i + 'Field' ] = Ext.create({
  3. xtype: 'spinnerfield'
  4. minValue:0、
  5. maxValue:i == 'hours' ? 23:05、
  6. renderTo: この [i + 'Ct' ]、
  7. 幅:45、
  8. 値:0、
  9. enableKeyEvents: true
  10. リスナー:{
  11. キーアップ: this .onTimeFieldsChanged、
  12. スピンアップ: this .onTimeFieldsChanged、
  13. spindown: this .onTimeFieldsChanged、
  14. スコープ: これ
  15. }
  16. .................................................. ...............
*このソースコードは、 ソースコードハイライターで強調表示されました。




ご覧のとおり、値を更新するときに、入力フィールドから受け取った変更イベントも中継します。 これは、検証をサポートするためにまだ役立ちます。



値を設定するには、setValueメソッドを記述します。 以前はサードパーティの開発者からの多くのカスタムコンポーネントを使用していましたが、そのほとんどの実装で同じ不具合を修正する必要がありました。 開発者はこれをチェックするのを忘れ、すぐにthis.elプロパティ(まだ作成されていない)を使用しました。 コンポーネントでは、これを考慮し、作成中に指定されていない場合は、さらに値をゼロに初期化します。





  1. .................................................. ...............
  2. initComponent: function (){
  3. if (!Ext.isDefined( this .value)) this .value = 0;
  4. Ext.Admin.TimeField.superclass.initComponent.call( this );
  5. }、
  6. setValue: 関数 (v){
  7. var setFn = function (v){
  8. var h = Math.floor(v / 3600)、
  9. m = Math.floor((v%3600)/ 60)、
  10. s = v%60;
  11. this .hoursField.setValue(h);
  12. this .minutesField.setValue(m);
  13. this .secondsField.setValue(s);
  14. };
  15. this .value = v;
  16. ifthis .rendered){
  17. setFn.call( this 、v);
  18. } else {
  19. this .on( 'afterrender' 、setFn.createDelegate( this 、[v])、{single: true });
  20. }
  21. }、
  22. .................................................. ............... <
*このソースコードは、 ソースコードハイライターで強調表示されました。




ご覧のとおり、レンダリング前にコンポーネントの値を設定しようとすると、this.valueプロパティにのみ保存され、コンポーネントが最終的にレンダリングされるまで、入力フィールドで必要な値の実際の置換が遅延します(ワンタイムアフターレンダーイベントハンドラーを設定することにより)



そして、コンポーネントに「プレゼンテーション」を提供するために、検証とシリアル化の面倒を見るだけです。

検証を実装するために、Ext.from.Fieldの標準的な方法、つまり:











  1. ................................................
  2. validationEvent: 'change'
  3. ................................................
  4. initEvents: function (){
  5. Ext.ux.TimeField.superclass.initEvents.call( this );
  6. ifthis .validationEvent!== false && this .validationEvent!= 'blur' ){
  7. this .mon( thisthis .validationEvent、 this .validate、 this 、{buffer: this .validationDelay});
  8. }
  9. }、
  10. ................................................
  11. validateValue: 関数 (値){
  12. ifthis .allowBlank!== false ){
  13. trueを 返し ます
  14. } else {
  15. if (Ext.isDefined(value)&& value!= '' && value!= '0' && value> 0){
  16. this .clearInvalid();
  17. trueを 返し ます
  18. } else {
  19. this .markInvalid( this .blankText);
  20. falseを 返し ます
  21. }
  22. }
  23. }、
*このソースコードは、 ソースコードハイライターで強調表示されました。








  1. .time-field-wrap.x-form-invalid {
  2. 背景:なし;
  3. border:0px none;
  4. }
  5. .time-field-wrap.x-form-invalid .x-form-text {
  6. 背景色:#FFFFFF;
  7. background-image:url(../../ resources / images / default /grid/invalid_line.gif);
  8. バックグラウンド位置:左下。
  9. ボーダーカラー:#CC3300;
  10. }
*このソースコードは、 ソースコードハイライターで強調表示されました。




イベントを監視する場合、バッファリングが適用されます。 this.validationDelay(デフォルトは250)ミリ秒よりも速く値を変更すると、1回のハンドラー呼び出しのみが発生します(シリーズの最後のイベントで)。 これは、検証イベントを監視するための標準的なアプローチであり、すべてのコンポーネントで使用されます。



通常、コンポーネントをシリアル化するには、トリックを行う必要があります。 現時点では、値の読み込みは正常であり、get / setValueメソッドは問題なく機能します。 ただし、シリアル化中に、単一の値の代わりに、秒数で一度に3つの値が与えられます。 これは、標準送信との互換性に基づいて、getValueメソッドにアクセスすることによってフォームがシリアル化されず、レンダリングされたHTMLコードからフォーム要素(<input、<textareaなど)を選択し、値のプロパティ。 したがって、コンポーネントの値を変更した場合は、非表示フィールドを作成および更新する必要があります。 ところで、同じアプローチがExt.form.Comboboxの実装で使用されます







  1. var setFn = function (v){
  2. .................................................. ..............
  3. this .hiddenField.value = v;
  4. };
  5. .................................................. ..
  6. onTimeFieldsChanged: function (){
  7. .................................................. ............................
  8. this .hiddenField.value = this .value;
  9. this .fireEvent( 'change'thisthis .value);
  10. }、
  11. onRender: 関数 (ct、位置){
  12. Ext.ux.TimeField.superclass.onRender.call( this 、ct、position);
  13. .................................................. .................................................. ........
  14. this .hiddenField = this .el.insertSibling({
  15. タグ: 'input'
  16. タイプ: 'hidden'
  17. 名前: この .name || この .id、
  18. id:( this .id + 'hidden'
  19. }、 'before'true );
  20. ifthis .value) this .setValue( this .value);
  21. }
*このソースコードは、 ソースコードハイライターで強調表示されました。




以上です。 ご覧のとおり、Ext.form.Fieldを継承して非標準コンポーネントを作成することは、一見すると思えるほど難しい作業ではありません。 作成したコンポーネントは、わずか99行のコードに収まります。



リンク (ExtJS配布キットなしの代替リンク)使用してサンプルアーカイブダウンロードできます。デモはこちらをご覧ください。



All Articles