/home/by-natures/dev*

データ界隈で働くエンジニアとしての技術的なメモと、たまに普通の日記。

ExtJS: Border レイアウトの代わりに hbox / vbox を使う方法

ExtJS シリーズ第2段、今回はレイアウトについてご紹介します。前回と同様、ExtJS 4.2.0 を想定しています。

色々ある ExtJS のレイアウト方法

Ext.panel.Panel クラスの layout コンフィグなどを見てみると、多くのレイアウトが選択できることが分かります。今回携わったプロジェクトでよく使ったのは次のものでした:

  • border
  • hbox / vbox
  • form
  • table
  • ...

accordion なんてレイアウトもあって、動きは面白いのですが使い勝手が悪いです(^_^; どれか1つのメニューが必ず open な状態を維持するため、「開いて閉じる」という動作ができません。もちろんイベントキャッチすればある程度対応できるかもしれませんが、「開いて閉じる」通常のメニューが実現したい場合は自作した方が早そうです。

border レイアウトも便利なのですが、エリア(north/south/east/west/center, 特に center)で微妙に挙動が違っていてハマることが多かったので、基本的には hbox と vbox の組み合わせでレイアウトを組みました。hbox と vbox は自由度が高く便利なため、このエントリーでは hbox と vbox の使い方をご紹介します。

[toc]

hbox / vbox の使い方

hbox の h は horizontal の頭文字であり、横にコンポーネントを並べるためのレイアウトです。vbox の v は vertical の頭文字であり、縦に並べます。panel などの layout コンフィグに hbox を指定すると、items コンフィグに指定したコンポーネント群が横にレイアウトされます。vbox であれば縦に並びます。

次のサンプルは、シンプルな hbox です:

extjs_hbox_sample

Ext.application({
  name: 'Hbox layout sample',
  requires: [
    'Ext.Viewport'
  ],

  launch: function() {
    Ext.create('Ext.Viewport', {
      layout: 'hbox',
      defaults: {
        xtype   : 'component',
        width   : 200,
        padding : 10,
        border  : 1,
        style: {
          borderColor: 'black',
          borderStyle: 'solid'
        }
      },
      items: [{
        html : 'first component'
      },{
        html : 'second component'
      },{
        html : 'third component'
      }]
    });
  }
});

このサンプルを動作させる HTML も記載します:



    <a class="keyword" href="http://d.hatena.ne.jp/keyword/ExtJS">ExtJS</a> sample

    

    

    



defaults が便利

hbox / vbox に限った話ではありませんが、defaults コンフィグは非常に便利です。ソース1では width や padding など、それぞれのコンポーネントに共通する設定を defaults コンフィグにまとめています。クラスの指定である xtype さえも集約可能です。特に hbox / vbox を使う場合は同じ要素を並べたい場合が多いので、同じコンフィグを見つけたら defaults に入れてしまいましょう。

pack と align が少し複雑

hbox / vbox には、align というコンフィグが指定可能です。コンポーネントをどのように並べるか、という指定なのですが、hbox / vbox を併用していると混乱が起きます。

hbox の align の説明を引用します:

align : String Controls how the child items of the container are aligned. Acceptable configuration values for this property are: top : Default child items are aligned vertically at the top of the container. middle : child items are aligned vertically in the middle of the container. bottom : child items are aligned vertically at the bottom of the container. stretch : child items are stretched vertically to fill the height of the container. stretchmax : child items are stretched vertically to the height of the largest item. Defaults to: 'top'

次に vbox の align の説明を引用します:

align : String Controls how the child items of the container are aligned. Acceptable configuration values for this property are: left : Default child items are aligned horizontally at the left side of the container. center : child items are aligned horizontally at the mid-width of the container. right : child items are aligned horizontally at the right of the container. stretch : child items are stretched horizontally to fill the width of the container. stretchmax : child items are stretched horizontally to the size of the largest item. Defaults to: 'left'

そう、設定できる enum が違うんです! hbox と vbox を間違えて指定していて直した場合、center と middle も入れ替える必要があります。left や top は気づくのですが、center / middle は意味からは気づきにくいため厄介です。

これに加えて、コンポーネントをどうまとめるかという pack コンフィグがあり、これは start / center / end の3つの enum から選択できます(hbox / vbox 共通)。enum が共通なので混乱しないかと思いきや、hbox, vbox で align, pack が動作する方向が入れ換わるため、レイアウトを試行錯誤している段階だとパニックになります。

面倒ではありますが、align / pack の設定をする場合はドキュメントを必ず参照し、正しい enum から選択するようにしましょう。

Border レイアウトを hbox / vbox で実現

hbox / vbox は入れ子にもできます。試しに Border レイアウトを実現してみましょう。

border_of_hvbox

注:以下のソースでは、defaults コンフィグのスタイル設定を省いています。

Ext.application({
  name: 'Border layout by hbox / vbox',
  requires: [
    'Ext.Viewport'
  ],

  launch: function() {
    Ext.create('Ext.Viewport', {
      layout: {
        type  : 'vbox',
        align : 'stretch'
      },
      defaults: {
        xtype  : 'component'
      },
      items: [{
        html   : 'north component',
        height : 100
      },{
        // contains west, center, east
        xtype : 'container',
        flex  : 1,
        layout: {
          type  : 'hbox',
          align : 'stretch',
          pack  : 'center'
        },
        defaults: {
          xtype  : 'component'
        },
        items: [{
          html  : 'west component',
          width : 100
        },{
          html : 'center component',
          flex : 1
        },{
          html  : 'east component',
          width : 100
        }]
      },{
        html   : 'south component',
        height : 100
      }]
    });
  }
});

vbox レイアウトの2つ目のコンポーネントに container を指定し、layout を入れ子にしていきます。

通常の Border レイアウトよりもコード量も増えて可読性は下がっていますが、Border レイアウトではなく hbox / vbox レイアウトを使うことにメリットも多いです。

Border レイアウトを hbox / vbox で実現するメリット

Border レイアウトの場合は、エリアによって使えるコンフィグに差異があります。これが解消されることが1つメリットです。特に Border レイアウトでは center に width や height が直指定できないため、コンポーネントのレイアウト的に Border でも、hbox / vbox を使わざるを得ない場面もあります。

2点目は、ソースコードが読みやすくなること。Border layout だとエリアの指定を region コンフィグで行うため、常にどのエリアを読んでいるのかを意識する必要があります。しかし hbox / vbox でレイアウトが組んであれば、基本的に左上から右下に流れていくような順番になるので、ソースコードを眺めやすくなります。

3点目は、拡張が容易であること。エリアを細かく分割したい場合、hbox / vbox は同じ方向であればコンポーネントを1つ増やすだけで対応可能です(hbox レイアウト中に vbox 方向にレイアウト分割したい場合は入れ子になってしまいますが、border だと必ず入れ子が発生します)。複雑なレイアウトになる場合は、Border レイアウトではなく hbox / vbox でレイアウトを組むのがおススメです。私が携わったプロジェクトでは、Border レイアウトでレイアウトが上手くいかずに詰まっていたのに、hbox / vbox にしたらあっさり解決したことも多かったです。

デメリット

ソース2で紹介しましたが、Border レイアウトだけで事済んでいる場合は、hbox / vbox にするとコード量が増え、コンポーネントの入れ子も発生するために可読性が下がります。エリアごとにクラスを作ってしまう場合は Border レイアウトにすればコード量が減り、読みやすくなります。

また、入れ子が多いのでインデントがズレていたりすると訳が分からなくなります。最初は綺麗でも、コンポーネントの位置をズラすにはソースの記述位置を動かす必要があるため、コピペの過程でインデントがズレてしまいます。開発環境に助けてもらい、綺麗な状態を保ちましょう。インデントが綺麗でも入れ子が多いと複雑になるため、機能的にまとめられるのであればクラス化してしまうのがおススメです。