こんにちは。
今日も GrapeCity 謹製の万能 UI コンポーネントライブラリ・Wijmo に関するお話です。
中でも僕は FlexGrid というデータグリッドコンポーネントをいたく気に入っています。
さっそく本題に入りましょう。
本日の検証環境
- Angular 12.2.5
- Wijmo 2021J v2 (5.20212.812)
課題: ブラウザのサイズに応じて FlexGrid を伸縮させたい
今日は、ブラウザのウィンドウサイズに応じて Wijmo の FlexGrid を伸縮させる方法についてご紹介していきます。
下図は、本日の完成品をブラウザの開発者ツールで表示させた結果で、FlexGrid の占有領域が水色の矩形として表示されています。
ブラウザのサイズを変えると、FlexGrid のサイズが連動して伸縮していることが分かりますね。
動機付け: これができないと何が困るか
ここからしばらく、モチベーションを上げるための前口上が続きます。 手っ取り早くやり方だけ知りたい方は、この記事の真ん中あたりまで早送りしましょう。
まずは、一般的な組み込み方に従って FlexGrid を UI に組み込んだ場合に起こりうる問題点について説明します。
下図を見ると分かるとおり、データ量が多い場合、FlexGrid の高さがブラウザのサイズを超えてしまい、ページ全体にスクロールバーが表示されてしまいます。
左の図は初期状態、右の図はスクロールバーを下に動かしたときの状態です。 なにが問題かお分かりでしょうか。
そう、右上の図からお分かりのとおり、ページ全体をスクロールしたことで、見出しやFlexGrid のヘッダ行が画面外に隠れてしまいました。
ヘッダが隠れたせいで、いま見えているデータ項目が一体何なのか、ぱっと見、わかりづらいよね。
しかも、ヘッダが隠れちゃったせいで、ソートやフィルタ操作が簡単にできなくなっちゃった。
そうそう。
もしフィルタしたくなったら、毎回わざわざスクロールを元の位置に戻さなきゃいけないね。
一方、FlexGrid をウィンドウ内に収まりそうなサイズに固定した場合も、それはそれで問題があります。
百聞は一見に如かずですので実例をお見せしましょう。 もしも下図を見てなにかを察し、「あぁ…… 広げたい……」と思ったアナタは私と同じ感性の持ち主です。
なにこの余白。
大きいモニタのときは、ウィンドウをぐっと拡大して、たくさんのデータを一度に見たいのに……。
FlexGrid のサイズを固定しちゃうと、ウィンドウのサイズによってはページ内に無駄な余白がいっぱい出来ちゃう。
しかも、ユーザが自由にグリッドの高さを変えることはできない。
ボクがエンドユーザだったら、さすがにもうちょっと何とかできませんか? って言うと思う。
……ね?
なんとかしたくなってきたでしょ?
もしも、FlexGrid のサイズがブラウザと連動すれば、こうした操作上の小さなストレスを取り除くことができます。
グリッドをどんなにスクロールしてもヘッダ行が定位置に表示されていますので、常に手の届く場所からソートやフィルタといった操作ができるようになります。
また、より多くの情報を一度に見たければ、ブラウザを広げれば良いだけです。
この挙動が実装されるだけで、アプリの手触りは劇的に良くなりますので、一手間かける価値があると思いませんか?
ところが、僕がわざわざこうして日記に書くくらいですから、話はそう簡単ではないのです。
公式情報だけではうまくいかない
一応、GrapeCity 公式のナレッジベースに「コントロールの高さを%で指定する方法」というソレっぽい情報が公開されているものの、残念ながらこれだけではうまく動かないケースの方が多いのです。
特に、親<div>
に適用したスタイルの height
プロパティを % 単位で相対指定した場合に、以下の状況でFlexGrid が親要素のサイズを突き破り、ブラウザの表示領域からはみ出してしまうことがあるのです。
- うまくいかないケースの一つは、ページ内にヘッダやフッタ、見出しなど、FlexGrid 以外の HTML 要素が同居しているとき
- もう一つは、
<router-outlet>
などで埋め込んだ子コンポーネントの中で FlexGrid を使用しているとき
次節で具体例を説明しましょう。
よくある失敗例
下図はよくあるダメな例です。 右側の開発者ツールを見ると、<wj-flex-grid>
の親<div>
に対して height: 100%;
とサイズ指定していることが確認できます。
にも拘わらず、ページ全体にスクロールバーが表示され、FlexGrid がはみ出してしまっていることが分かります。
この状態で、DOM から <wj-flex-grid>
要素だけを削除すると、親<div>
は意図した通り画面ぴったりの高さ (449.19px) になります。 もちろんスクロールバーも表示されません。
明らかに FlexGrid が反抗して親のサイズを突き破っているのです。
ここが非常に厄介なポイントです。
そういえば、以前もこんな要件が挙がったよね?
そのときはどうしたの?
当時どうしても、この height 突き抜け問題が解決できなかったので、window.onresize
イベントのたびに、強制的に FlexGrid の高さを自前で再計算して height
を書き換えていたっけ。
えー…… 力業にも程があるな。
今回編み出した方法は、CSS の設定だけで FlexGrid の高さがブラウザに連動するようになるので、覚えておくといつかきっと役に立つよ。
ソリューション: CSS を駆使して FlexGrid の反抗を制する
今回のポイント
今回のポイントは、たった 2つです。
- ページ全体のレイアウトを、CSS 3 の FlexBox で組むこと
<wj-flex-grid>
と、その親の<div>
のスタイルには、それぞれ以下のとおりposition
プロパティを明示的に設定すること<wj-flex-grid>
には、position: absolute
を設定する- 親
div
には、position: relative
を設定する
ポイント1: ページ全体のレイアウトは CSS 3 の FlexBox で組む
まず第一に、レイアウトの組み方です。
話を簡単にするため、下図のような簡単なレイアウトを考えましょう。
緑色の矩形領域にはページの見出しが、水色の矩形領域には FlexGrid が、それぞれ配置されることを想定しています。
FlexGrid の高さは、親要素の高さからヘッダやフッタなど他のコンポーネントの高さを引いた値になります。 見出しの高さはブラウザのサイズに係わらず常に一定ですが、FlexGrid の方はブラウザをリサイズすれば伸び縮みする点に注意しましょう。
手始めに、ページ全体に適用する CSS を見てみましょう。
style.css
html, body { width: 100%; height: 100%; margin: 0; padding: 0; border: none; }
<body>
のサイズをウィンドウに一致させるため、width
と height
に 100% を設定しています。 また、念の為、余白(margin
, padding
)や境界(border
)をリセットしています。
これは、CSS でレイアウトを組む際の常套手段ですので、おなじみの方も多いでしょう。
続いて、Flex レイアウト用のスタイルを書いてきます。
app.component.css
.main-container { display: flex; flex-direction: column; /* 縦に並べる */ flex-wrap: nowrap; /* はみ出しても折り返さない */ } .header-container { flex: 0 0 auto; /* 勝手な伸び縮みは許さない */ } .grid-container { width: 100%; height: 100%; flex: 0 3 auto; /* 縮むことは許すが勝手に伸びることは許さない */ overflow: auto; }
header-container
クラスは、flex-grow
プロパティにも flex-shrink
プロパティにも 0 を設定しています。
Flex レイアウトは通常、親要素のサイズが変わると自分自身の影響を受けて、自分もサイズを変えようとします。
ただし、見出しのようにサイズ固定のコンポーネントは、ブラウザのサイズに釣られてメタボになったり潰されたりすることは許されないので、flex-grow
も flex-shrink
も 0 を設定して、そういう外圧に抗うわけです。
一方、grid-container
クラスには、flex-grow
プロパティに 0、flex-shrink
に 3 が設定されています。
これは、本来のサイズを超えて勝手にデカくなることは許さないという戒めのために flex-grow
には 0 を設定しているのです。
しかし、本当は高さ 100% まで大きくなりたいけれど、もし他のコンポーネントが場所を取るならばそちらに譲りましょうという慎ましさのため、flex-shrink
に 3 を設定し、勝手に大きくなることは許されないけど、他の影響を受けて潰されるのはOKという設定にしているのです。
ポイント2: <wj-flex-grid>
とその親の <div>
には、それぞれposition
プロパティを設定する
この対策だけでは不十分で、FlexGrid を包むコンテナ<div>
には position: relative;
を、FlexGrid そのものには width: 100%;
, height: 100%;
に加えて、position: absolute;
をそれぞれ付加します。
app.component.css
(抜粋)
.grid-container { width: 100%; height: 100%; flex: 0 3 auto; overflow: auto; position: relative; /* 追加 */ } .grid{ width: 100%; height: 100%; position: absolute; }
ここまでやって初めて、<wj-flex-grid>
が親<div>
のサイズに押さえつけられるようになります。
最後に、HTML を示します。
app.component.ts
<div class="main-container"> <div class="header-container"> <h2>FlexGrid</h2> </div> <div class="grid-container"> <wj-flex-grid class="grid" [itemsSource]="gridData" [showMarquee]="true" [itemFormatter]="itemFormatter" headersVisibility="Column"> <wj-flex-grid-filter></wj-flex-grid-filter> <wj-flex-grid-column header="ID" binding="id" [width]="60"> </wj-flex-grid-column> <wj-flex-grid-column header="商品名" binding="product" [width]="200"> </wj-flex-grid-column> <wj-flex-grid-column header="受注日" binding="date" [width]="120"> </wj-flex-grid-column> <wj-flex-grid-column header="金額" binding="amount" [width]="100" format="c"> </wj-flex-grid-column> </wj-flex-grid> </div> </div>
これで、ブラウザに連動して高さが変わる FlexGrid が完成しました。
繰り返しになりますが、こうした小さな一手間で、だいぶアプリケーションの完成度というかエンドユーザの手触りが変わってきますので、Wijmo を使っている皆様におかれましては、ぜひ参考になさってください。
おまけ1
ちなみに、<router-outlet>
で埋め込んだ子コンポーネントの中で FlexGrid を使用した場合も、同様の手法で要件を満たせます。
おまけ2
今日のネタは、なぜか土曜日の夜中にいきなりふと思いついて、午前4時頃まで試行錯誤していました。
ブラウザのサイズに応じてグリッドの高さを自動調整するやつ、これでできたんじゃないかな、、、?
— たーせる (@tercel_s) September 18, 2021
単に CSS (FlexBox) でコンテナのガワだけ作ったときは上手く行きそうだったのだが、中に <wj-flex-grid style=“height:100%;”> を配置した途端に親要素の height を突き破ってしまう現象に苦しんだ。 https://t.co/uYdkZUSL7h pic.twitter.com/H5CwNKNymc