CSS Grid Layoutでメンテしやすいタイムテーブルを作る

## 自己紹介
Qiita初投稿なので。(ソワソワ)
私は某通信会社でかれこれ5年少し法務やっていますが、法務の前はUXデザイナーをしていた関係から、"コーディング"と"デザイン"の多少の経験がある人間です。

## はじめに
HTMLで表といえばTableタグを使うのが普通なのですが、複数の行・列にまたがるときにいちいち"colspan"とか"rowspan"を設定しなければならず、かつTableは基本的にRow方向にしか管理ができないので、結合なんかした時にはHTMLも非常に見辛くて、個人的にはあまり使いたいものではありませんでした。
そこでGrid Layoutをうまく使えないかと、最近制作を担当させてもらったNTT [NTT Tech Conference #4のイベントページ](https://ntt-developers.github.io/ntt-tech-conference/04/)で「Grid Layoutを使ったタイムテーブル」を作ったので、備忘録もかねてここに書いておきます。

## タイムテーブルの作成方法
[NTT Tech Conference #4のイベントページ](https://ntt-developers.github.io/ntt-tech-conference/04/)のTime Tableのエリアにある表がそれです。
見たらわかる通り、ところどころExcelでいうところの「セルが結合している状態」になっています。
この状態をtableタグで管理するのは大変ですが、Grid Layoutで管理するとCol方向(縦方向)で管理できるようになるので、スマホ表示も含めて管理がしやすくなります。
以下、細かいStyleは省略して、Grid Layoutの部分だけ。
まず、HTMLはtableタグ使用時とは違い、表の中身は全て縦方向にdivでコンテンツを並べ、それらをtimeTable__gridのClassで括っています。

```html:index.html
<div  class="timeTable__grid">
<!-- Time -->
<div  class="timeTable__item timeTable__head timeItem_1">時間</div>
<div  class="timeTable__item timeTable__time timeItem_2"><span  class="timeTable__time">9:30</span></div>
<div  class="timeTable__item timeTable__time timeItem_3"><span  class="timeTable__time">10:00</span></div>
<!-- 中略 -->
<!-- 301 -->
<div  class="timeTable__item timeTable__head venue1 venue1_1">301</div>
<div  class="timeTable__item venue1 venue1_2"><span  class="timeTable__txt">受付開始</span></div>
<div  class="timeTable__item venue1 venue1_3 handson"><span  class="timeTable__link">LEGOで学ぶクラフト機械学習</span></div>
<!-- 中略 -->
<!-- 302 -->
<div  class="timeTable__item timeTable__head venue2 venue1_1">302</div>
<div  class="timeTable__item venue2 venue2_2"><span  class="timeTable__txt">受付開始</span></div>
<div  class="timeTable__item venue2 venue2_3 handson"><span  class="timeTable__link">超高校級の攻撃-さよならEmpire-</span></div>
<!-- 中略 -->
<!-- 303 -->
<div  class="timeTable__item timeTable__head venue3 venue1_1">303</div>
<div  class="timeTable__item venue3 venue3_2"><span  class="timeTable__txt">受付開始</span></div>
<div  class="timeTable__item venue3 venue3_3 handson"><span  class="timeTable__link">エンジニアのキャリアをレゴで考えるワークショップ</span></div>
</div>
```

SCSS側はまず親要素でGrid Layoutとするために、

```scss:style.scss
.timeTable {
  &__grid {
    width: 100%;
    display: grid;
    grid-template-columns: 1fr  3fr  3fr  3fr;
    grid-template-rows: repeat(auto-fill, minmax(27px, 1fr));
    border-top: 1px  solid  #ddd;
    border-left: 1px  solid  #ddd;
  }
}
```

とし、次に必要な表をまずは結合していない状態で作り上げるために繰り返し処理でrowを設定していきます。

```scss:style.scss
// Time
@for  $i  from  1  through  13 {
  .timeItem_#{$i} {
    grid-row: #{$i} / #{$i+1};
    grid-column: 1 / 2;
  }
}
// 301
@for  $i  from  1  through  13 {
  .venue1_#{$i} {
    grid-row: #{$i} / #{$i+1};
    grid-column: 2 / 3;
  }
}
// 302
@for  $i  from  1  through  13 {
  .venue2_#{$i} {
    grid-row: #{$i} / #{$i+1};
    grid-column: 3 / 4;
  }
}
// 303
@for  $i  from  1  through  13 {
  .venue3_#{$i} {
    grid-row: #{$i} / #{$i+1};
    grid-column: 4 / 5;
  }
}
```

これで表自体は完成。
あとはここから結合したい部分だけをOverrideします。

```scss:style.scss
// Time Table Override
.venue2_5 {
  grid-row: 5 / 7;
}
.venue2_7 {
  grid-row: 7 / 12;
}
.venue2_12 {
  grid-row: 12 / 14;
}
.venue3_5 {
  grid-row: 5 / 7;
}
.venue3_10 {
  grid-row: 10 / 13;
}
```

これでGrid Layoutを使った表が完成します。
この表は縦方向で管理しているので、セルの結合をCSSでOverrideするだけで調整できるのが個人的には便利だと思っています。
あと、コンテンツの並び順としても、結合をするとRow方向にはズレが出ますが、Col方向の並びにはズレが出ません。(上から順に並ぶことには変わりない。)
このため、HTMLだけを見ても、表のイメージがしやすいという点もGrid Layoutの活用のいいところだと思います。

## でもコンテンツの増減でdivの数が変わるのでは？
HTMLとCSSだけの構成だとその通りです。
つまり、新たに結合されるところが増えたり減ったりすると、その度にdivを増やしたり削ったりしなければなりません。
これだとHTML側のメンテコストが若干上がるので、そこはjs側で制御しています。(jQuery使用)

```javascript:script.js
// Time Tableの空欄箇所には自動でhiddenを付与
$('.timeTable__item:empty').addClass("hidden");
$('.timeTable__link:empty, .timeTable__txt:empty').parent().addClass("hidden");
```

各Classの中身が空の時はClassにhidden(hiddenにはdisplay: none;が書かれています。)を入れるようにしています。
これでいちいち「コンテンツなくなったから空のdivは削除して、こっちを増やして...」としなくて良い形にしています。

## レスポンシブ対応
Col方向で管理しているので、Media Queryを使えばタブ切り替えの実装も比較的簡単です。
HTML側には切り替えようのタブを準備します。

```html:inde.html
<ul  class="timeTable__tabList">
  <li  class="timeTable__tabItem selected"  id="tab1"><span>301</span></li>
  <li  class="timeTable__tabItem"  id="tab2"><span>302</span></li>
  <li  class="timeTable__tabItem"  id="tab3"><span>303</span></li>
</ul>
```

そして、親要素は２列にしてしまいます。

```scss:style.scss
// Time table
@media  screen and (max-width: 768px){
.timeTable {
  &__grid {
    grid-template-columns: 1fr  3fr;
  }
}
```

次に各列を全て同じ列にあてがって、最初に表示させておくもの以外はdisplay: none;で消しておきます。

```scss:style.scss
// 301
@for  $i  from  1  through  13 {
  .venue1_#{$i} {
    grid-column: 2 / 3;
  }
}
// 302
@for  $i  from  1  through  13 {
  .venue2_#{$i} {
    grid-column: 2 / 3;
  }
}
// 303
@for  $i  from  1  through  13 {
  .venue3_#{$i} {
    grid-column: 2 / 3;
  }
}
.venue2, .venue3 {
  display: none;
}
```

タブ切り替えの動作はjsで制御します。

```javascript:script.js
// Smartphone UI用 Tab切り替え
$(".timeTable__tabItem").on("click", function(){
  var  ID = $(this).attr("id");
  if(ID == "tab1"){
  if(!$("#tab1").hasClass("selected")){
    $(".timeTable__tabItem").removeClass("selected");
    $("#tab1").addClass("selected");
    $(".venue1, .venue2, .venue3").fadeOut();
    $(".venue1").fadeIn()
}
} else  if(ID == "tab2"){
  if(!$("#tab2").hasClass("selected")){
    $(".timeTable__tabItem").removeClass("selected");
    $("#tab2").addClass("selected");
    $(".venue1, .venue2, .venue3").fadeOut();
    $(".venue2").fadeIn()
}
} else {
  if(!$("#tab3").hasClass("selected")){
    $(".timeTable__tabItem").removeClass("selected");
    $("#tab3").addClass("selected");
    $(".venue1, .venue2, .venue3").fadeOut();
    $(".venue3").fadeIn()
  }
}
});

// Tabの切り替えを行った後に、ブラウザ幅を広げるとStyle display noneが邪魔をするため、ブラウザ幅が広がった時にStyleをRemoveする
var  mediaQuery = matchMedia('(min-width: 768px)'); 

// ページが読み込まれた時に実行
handle(mediaQuery);

// ウィンドウサイズが変更されても実行されるように
mediaQuery.addListener(handle);

function  handle(mq) {
  if (mq.matches) {
    $(".venue1, .venue2, .venue3").removeAttr("style")
  };
};
```

## まとめ
Grid Layoutを使ってタイムテーブルを作ったので、備忘録的に残しておきました。
HTML的に正しいかどうかや速度がどうか(無駄な処理がないか)よりはメンテナンス重視の書き方になるので、良し悪しはあると思いますが、Grid Layoutが表の作成にも応用できるということの一例として参考になれば。（もっといい書き方あるよもWelcomeです。）