Bootstrap 3からBootstrap 5に移行してみた

これまでずっと、Bootstrap3で使っていたのですが、そろそろ新しいバージョンに切り替えようと思います。
ちょうど、Bootstrap 5でJQueryに依存しなくなったので、よい機会かなあと。

ですが、うわさに聞いていた通り、Bootstrap 3から4への移行で細かな差異があり、移行は結構手間がかかりました。また、Bootstrap 5になってから、JQueryがなくなったので、それの影響もありました。

今回は既存のBootstrap 3ベースで動いているものをBootstrap 5に移行するにあたり、変更した部分を整理しておきます。

ちなみに、マニュアルはそこまで詳しくは読んでおらず、実動作で試行錯誤しながら直したので、もっと良い直し方があるかもしれません。

# 参考URL

Bootstrap 5.1.x
　https://getbootstrap.com/docs/5.1/getting-started/introduction/

Bootstrap 3.4.x
　https://getbootstrap.com/docs/3.4/getting-started/

以下のコンテンツを移行しました。
Bootstrap3.4.1版とBootstrap5版の両方を置きましたので、比較してみてください。
また、実際にWebページとして見れますので、見た目も比較してみてください。

Bootstrap 3.4.1で動かしていたもの
　GitHub：https://github.com/poruruba/utilities5/tree/main/utilities
　Webページ：https://poruruba.github.io/utilities5/utilities

Bootstrap 5に移行後
　GitHub：https://github.com/poruruba/utilities5/tree/main/utilities5
　Webページ：https://poruruba.github.io/utilities5/utilities5

※もし上記のページの内容についてご興味がありましたら、以下をご参照ください。
　[便利ページ：Javascriptでちょっとした便利な機能を作ってみた](https://qiita.com/poruruba/items/6c67c7eef34ed302feb3)

# 変更箇所のサマリ

■Bootstrap変更に伴う変更

* CDN取得先の変更
* Glyphiconの代替
* class=nav-linkの追加
* data-toggleからdata-bs-toggleに変更
* class=”fade in”のinの削除
* page-headerの代わりにmodal-header
* panalをcardに置き換え
* labelの太字が無効
* btn-defaultの代わりにbtn-secondary
* class="input-group-btn" が不要
* class=”form-control”の範囲拡大
* class="form-inline"の廃止
* col-xs-*が col-*に変更
* collapseはaccordionで代用
* text-left/rightの変更
* pull-left/rightの変更
* img-responsiveをimg-fluidに変更
* 属性bgcolorをstyleで代用
* マージンは手動指定

■JQuery廃止に伴う変更

* セレクタ$の置き換え
* Toastの代替
* modalの実装追加
* Collapseの実装追加
* Tab選択の実装追加

# Bootstrap変更に伴う変更

## CDN取得先の変更


v3.4系からv5系に変えたための当然の変更です。
注目は、JQueryが不要になったことです。

変更前

```html:index.html
    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
    <!-- Latest compiled and minified CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
    <!-- Optional theme -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
```

変更後

```html:index.html
    <!-- Latest compiled and minified CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <!-- Latest compiled and minified JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
```


## Glyphiconの代替

残念ながら、Glyphiconが同梱されなくなりました。手軽に使えてよかったのですが。。。
そこで、代替として「Open Iconic」を使うようにしました。

OPEN ICONIC
　https://useiconic.com/open

本日時点では、v1.1.0のようです。
ダウンロードしてとりあえず以下に配置します。

open-iconic-master.zip\open-iconic-master\font\fonts\*
→
dist/fonts/*

open-iconic-master.zip\open-iconic-master\font\css\open-iconic-bootstrap.min.css
→
dist/css/open-iconic-bootstrap.min.css

そして、index.htmlで以下を追加で取り込みます。

```html:index.html
    <link rel="stylesheet" href="dist/css/open-iconic-bootstrap.min.css">
```

使うときには以下のように変わります。glyphiconではなくoiになります。

変更前

```html:index.html
<button class="btn btn-default glyphicon glyphicon-paperclip" v-on:click="clip_copy(uuid_uuid)"></button>
```

変更後

```html:index.html
<button class="btn btn-secondary oi oi-paperclip" v-on:click="clip_copy(uuid_uuid)"></button>
```

当然ながら、絵も名前も違うので、目で見て似たような絵を探す必要があります。

## class=nav-linkの追加

Navbarを使っている場合、タブの記述において、aに「class="nav-link"」を付ける必要があります。
また、タブにドロップダウンを付ける場合、キャラット(<span class=”caret”>)の指定は不要になりました。

変更前

```html:index.html
        <ul class="nav nav-tabs">
            <li v-for="(link, index) in favorite_link"><a v-bind:href="'#' + link" data-toggle="tab">{{get_tab_name(link)}}</a></li>
            <li class="dropdown">
                <a class="dropdown-toggle" data-toggle="dropdown">ユーティリティ <span class="caret"></span></a>
                <ul class="dropdown-menu">
                    <li v-for="(link, index) in tab_list"><a v-bind:href="'#' + link.id" data-toggle="tab" v-on:click="favorite_add(link.id)">{{link.name}}</a></li>
                </ul>
            </li>
        </ul>
```

変更後

```html:index.html
        <ul class="nav nav-tabs">
            <li v-for="(link, index) in favorite_link"><a class="nav-link" v-bind:href="'#' + link" data-bs-toggle="tab">{{get_tab_name(link)}}</a></li>
            <li class="dropdown">
                <a class="nav-link dropdown-toggle" data-bs-toggle="dropdown">ユーティリティ</a>
                <ul class="dropdown-menu">
                    <li v-for="(link, index) in tab_list"><a class="dropdown-item" v-bind:href="'#' + link.id" data-bs-toggle="tab" v-on:click="favorite_add(link.id)">{{link.name}}</a></li>
                </ul>
            </li>
        </ul>
```

## data-toggleからdata-bs-toggleに変更

上記のNavbarもそうですが、いろんなところでdata-toggleを使っていると思いますが、data-bs-toggleに変更します。

## class="fade in"のinの削除

fade inと記載していた個所のinは不要になりました。
例えば以下のような場合です。

```html:index.html
            <div id="blecent" class="tab-pane fade in">
                <h2 class="page-header">BLE Central</h2>
```

## page-headerの代わりにmodal-header

page-headerは無くなったので、どれでも良いのですが、とりあえず、modal-headerで代用しました。

変更前

```html:index.html
            <div id="blecent" class="tab-pane fade in">
                <h2 class="page-header">BLE Central</h2>
```

変更後

```html:index.html
            <div id="blecent" class="tab-pane fade">
                <h2 class="modal-header">BLE Central</h2>
```

## panalをcardに置き換え

パネルがなくなり、Cardに代わりました。
Collapseを使っていた場合は、CollapseやAccordionに代替するのもありです。

変更前

```html:index.html
                <div class="panel ">
                    <div class="panel-heading">
                        ヘッダー
                    </div>
                    <div class="panel-body">
                      ボディ
                    </div>
                    <div class="panel-footer">
                       フッター
                    </div>
                </div>
```

変更後

```html:index.html
                <div class="card">
                    <div class="card-header">
                        ヘッダー
                    </div>
                    <div class="card-body">
                      ボディ
                    </div>
                    <div class="card-footer">
                      フッター
                    </div>
                </div>
```


## labelの太字が無効

これまでは、目立たせる意味で<label>で囲って太字にしていましたが、それが無効になりました。
手動で太字にする必要があります。
そこで、cssに記載しておきます。

```css:css/start.css
label.title {
  font-weight: bold;
}
```

使い方(変更前)

```html
<label>タイトル</label>
```

使い方(変更後)

```html
<label class=”title”>タイトル</label>
```


## btn-defaultの代わりにbtn-secondary

btn-defaultなど、****-defaultがなくなりました。とりあえず、btn-secondaryで代用しました。

変更前

```html
<button class="btn btn-default">All Read</button>
```

変更後

```html
<button class="btn btn-secondary">All Read</button>
```

## class="input-group-btn" が不要

Inputにボタンをくっつけるときに、input-group-btnを付けていましたが、不要になりました。

変更前

```html:index.html
                <div class="input-group">
                    <span class="input-group-btn">
                        <button class="btn btn-default glyphicon glyphicon-paperclip" v-on:click="clip_copy(uuid_uuid)"></button>
                    </span>
                    <input type="text" class="form-control" v-model="uuid_uuid" readonly>
                </div>
```

変更後

```html:index.html
                <div class="input-group">
                    <button class="btn btn-secondary oi oi-paperclip" v-on:click="clip_copy(uuid_uuid)"></button>
                    <input type="text" class="form-control" v-model="uuid_uuid" readonly>
                </div>
```

## class=”form-control”の範囲拡大

form-controlを適用できるものが増えたり、見た目がまともになりました。

・type=”file”がclass=”form-control”対象に変更

変更前

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/eaee55a5-479c-f61a-8661-19a428a79659.png)

変更後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/11cc6dfb-14be-026c-1fbf-072edd932457.png)

・type=selectはform-selectに変更

form-controlでも良かったのですが、form-selectにしないと、キャラットが表示されません。

・type=rangeやcolorも見栄え向上

付けないと、横幅を自動的に伸縮してくれないです。

以下を付けるようにします。

```html
class="form-control-color"
class="form-range"
```

## class="form-inline"の廃止

結構form-inlineを多用していたので、これが一番痛かったです。修正も面倒です。

* 明示的にclass="row"で囲う。
* 各要素に、class=”col-auto”を付ける。ただし、インプットやセレクトは、一つ上にdivやspanで囲い、そこにcol-autoを付ける。

変更前

```html:index.html
<div class="form-inline">
    <label>タイトル</label>
    <button class="btn btn-default btn-sm" v-on:click="date_duration_reset">リセット</button>
    <select class="form-control" v-on:change="date_process()" v-model="date_duration_unit">
        <option value="year">年</option>
        <option value="month">月</option>
        <option value="day">日</option>
        <option value="hour">時</option>
        <option value="minute">分</option>
        <option value="second">秒</option>
    </select>
    <input type="number" class="form-control" v-on:change="date_process()" v-model.number="date_duration">
    <button class="btn btn-primary btn-sm" v-on:click="date_process()">経過期間→経過後日時</button>
</div>
```

変更後

```html:index.html
<div class="row">
    <label class="col-auto title">タイトル</label>
    <button class="col-auto btn btn-secondary btn-sm" v-on:click="date_duration_reset">リセット</button>
    <span class="col-auto">
        <select class="form-select" v-on:change="date_process()" v-model="date_duration_unit">
            <option value="year">年</option>
            <option value="month">月</option>
            <option value="day">日</option>
            <option value="hour">時</option>
            <option value="minute">分</option>
            <option value="second">秒</option>
        </select>
    </span>
    <span class="col-auto">
        <input type="number" class="form-control" v-on:change="date_process()" v-model.number="date_duration">
    </span>
    <button class="col-auto btn btn-primary btn-sm" v-on:click="date_process()">経過期間→経過後日時</button>
</div>
```

## class="col-xs-\*"が"col-\*"に変更

グリッド指定でxsを使っていた場合はxsを省きます。

## collapseはaccordionで代用

これは必須ではないですが、collapseも引き続きBootstrap5で使えますが、accordionの場合には、右端にキャラットが付くのでお好みで。

変更前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/f943120e-3d62-33d3-c987-440909e5b864.png)

変更後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/15da6786-5413-9384-b0e3-c5e9e202e75c.png)

使う場合は書き方が特殊で、すぐ忘れるので、以下のようにVueのコンポーネントにしています。

変更前

```js:js/components_bootstrap.js
  'collapse-panel': {
    props: ['id', 'collapse', 'title'],
    template: `
      <div class="panel">
        <div class="panel-heading">
          <div class="panel-title"><a data-toggle="collapse" v-bind:href="'#' + id">{{title}}</a></div>
        </div>
        <div class="panel-collapse" v-bind:class="collapse=='true' ? 'collapse' : 'collapse in'" v-bind:id="id">
          <slot name="content"></slot>
        </div>
      </div>`,
  }
```

変更後

```js:js/components_bootstrap.js
  'collapse-panel': {
    props: ['id', 'collapse', 'title'],
    template: `
      <div class="accordion mt-3">
        <div class="accordion-item">
          <div class="accordion-header">
            <button class="accordion-button" v-bind:class="collapse=='true' ? 'collapsed' : ''" type="button" data-bs-toggle="collapse" v-bind:href="'#' + id"><label class='title'>{{title}}</label></button>
          </div>
          <div class="accordion-collapse" v-bind:class="collapse=='true' ? 'collapse' :	'collapse show'" v-bind:id="id">
            <slot name="content"></slot>
          </div>
        </div>
      </div>`,
  }
```

こんな感じで定義します。

```
  <collapse-panel id="html5_audio" title="Audio" collapse="true">
    <span slot="content">
      <div class="card-body">
        ・・・
      </div>
      <div class="card-footer">
        ・・・
      </div>
    </span>
  </collapse-panel>
```

## 属性bgcolorをstyleで代用

なぜか、HTMLでの属性bgcolorの指定が横取りされてしまうようで、style指定にしました。

変更前

```html:index.html
<td v-bind:bgcolor="color_value">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
```

変更後

```html:index.html
<td v-bind:style="{ 'background-color': color_value }">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</td>
```

## text-left/rightの変更

text-left・text-rightは、それぞれ、text-start・text-endに名前が変更となりました。

## pull-left/rightの変更

pull-left・pull-rightは、それぞれ、float-start・float-endに名前が変更となりました。

## img-responsiveをimg-fluidに変更

img-responsiveがimg-fluidに名前が変更となっています。

## マージンは手動指定

これまでPanelなどを並べて表示しても、マージンが付与されていたので、くっついて表示されることはなかったのですが、Bootstrap5では自分でマージンを指定します。

class=m-X で、Xに数字を入れます。

変更前

```html:index.html
      <div class="panel">
```

変更後

```html:index.html
      <div class="accordion m-3">
```

# JQuery廃止に伴う変更

JQueryに依存しなくなったのは良いのですが、いくつか使えない機能が出てきましたので、代替を用意します。

## セレクタ$の置き換え

JQueryで慣れ親しんできた$(***) によるセレクタは使えなくなるので、ブラウザ標準の書式に変えます。

変更前

```js
$('#id名')
```

変更後

```js
document.querySelector('#id名')
```

## Toastの代替

これまでは、有志のToastのライブラリを使ってきましたが、ほとんどがJQueryを前提としたものでした。もちろん、Bootstrap5にもToastはあるのですが、使いにくいし、Toast表示をスタックできないようです。
そこで、JQueryに依存しないライブラリに切り替えます。

ooyun0/siiimple-toast
　https://github.com/ooyun0/siiimple-toast
　https://www.cssscript.com/demo/animated-queued-toast-messages-pure-javascript-siiimple-toast/

index.htmlで以下をロードします。

```html:index.html
    <link href="https://cdn.jsdelivr.net/npm/siiimple-toast/dist/style.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/siiimple-toast/dist/siiimple-toast.min.js"></script>
```

呼び出しはこんな感じ

```js:js/methods_bootstrap.js
    toast_show: function(message, level = "success"){
        if( level == 'message' )
            siiimpleToast.message(message, { position: 'top|right' });
        else if( level == 'alert' )
            siiimpleToast.alert(message, { position: 'top|right' });
        else
            siiimpleToast.success(message, { position: 'top|right' });
    },
```

## modalの表示/非表示切り替えの実装追加

Javascriptから、Modalを表示したり非表示するためのJavascriptを実装します。

```js:js/methods_bootstrap.js
    dialog_open: function(target, backdrop = 'static'){
        const element = document.querySelector(target);
        let modal = bootstrap.Modal.getInstance(element);
        if( !modal )
            modal = new bootstrap.Modal(element, { backdrop: backdrop, keyboard: false });
        modal.show();
    },
    dialog_close: function(target){
        const element = document.querySelector(target);
        let modal = bootstrap.Modal.getInstance(element);
        if( modal )
            modal.hide();
    },
```

上記の関数はあらかじめ、index.htmlにModalが定義されている前提です。以下は例。

```html:index.html
        <div class="modal" id="binary_image_dialog">
            <div class="modal-dialog modal-lg">
                <div class="modal-content">
                    <div class="modal-header">
                        <h4 class="modal-title">内容表示
                            <span class="pull-right">
                                <button class="btn btn-secondary" v-on:click="dialog_close('#binary_image_dialog')">閉じる</button>
                            </span>
                        </h4>
                    </div>
                    <div class="modal-body">
                        <img v-if="binary_type.startsWith('image/')" v-bind:src="binary_dataurl" class="img-responsive" />
                        <pre v-if="binary_type.startsWith('text/')">{{binary_text}}</pre>
                    </div>
                </div>
            </div>
        </div>
```

ここで、１点注意があります。
Modalでよく使うfadeを使う場合です。
fadeを使うことで、Modal表示・非表示の際にアニメーションを付けることができます。
しかしながら、modal.show()で表示したとき、このアニメーションが終わる前に関数から返ってくるため、すぐにmodal.hide()を呼び出してもModalが表示されたままとなる場合があります。

```js
this.dialog_open('#binary_image_dialog');
this.dialog_close('#binary_image_dialog');
```

表示のアニメーション中に受け取った非表示要求が無視されるためです。
なので、デフォルトでfadeはつけていません。付ける場合は注意して使ってください。

## Collapseの実装追加

Modal同様に、Collapseを開いたり閉じたりする関数をJavascriptで実装します。

```js:js/methods_bootstrap.js
    panel_open: function(target){
        var element = document.querySelector(target);
        var collapse = bootstrap.Collapse.getInstance(element);
        if( !collapse )
            collapse = new bootstrap.Collapse(element, { toggle: false });
        collapse.show();
    },
    panel_close: function(target){
        var element = document.querySelector(target);
        var collapse = bootstrap.Collapse.getInstance(element);
        if( !collapse )
            collapse = new bootstrap.Collapse(element, { toggle: false });
        collapse.hide();
    },
```

## Tab選択の実装追加

Tabも同様です。

```js:js/methods_bootstrap.js
    tab_select: function(target){
        var element = document.querySelector("a[href='" + target + "']");
        var tab = bootstrap.Tab.getInstance(element);
        if( !tab )
            tab = new bootstrap.Tab(element);
        tab.show();
    },
```

# 未解決事項

今回は、既存の実装をBootstrap5に移行しましたので、既存で使っていた部分だけの差分をまとめました。
Bootstrapはいろんな機能がありますので、そのほんの一助になればと思います。

* Navbarのドロップダウンを選択すると、選択状態のままとなってしまいます。表示だけなので問題ないですが。（Bootstrapのバグ？） → 最新のBootstrap5では治っているようです。
* チェックボックスを他のInputやボタンと横並びに表示すると、縦方向に中央配置されていません。直すのが面倒なので直していません。

以上