まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:bootstrap

Bootstrap はバージョン5のβ版もリリースされ(2021/01/28 時点)、グリッドを使ったページスタイルなどでは今でも人気の UI フレームワームだと思っています:
2021012803


自分自身では、この Bootstrap の Modal 機能をよく使っています。いわゆるモーダルダイアログを実現するもので、別ページに飛ばすほどではない(すぐ元のページ内容に戻るのが容易な)画面をモーダルで表示し、モーダルが消えれば元の画面に戻ります。この間の画面遷移も実質ありません。

今回チャレンジしたのは「モーダルのモジュール化」です。具体的には以下の2点を実現する方法を考えました:

  1. テンプレートエンジンを使うなどしてモーダルダイアログを実現する HTML 部分を切り出し、複数の HTML ページから同じモーダルダイアログを呼び出せるようにする
  2. 呼び出し元の HTML ごとにモーダルダイアログ終了後の処理を変える

1. はテンプレートエンジン(例えば今回は Node.js と EJS を使うので EJS で説明します)の機能を使って HTML テンプレートを分割し、モーダルダイアログに関わる部分だけを別モジュールにする、というものです。これによって異なる呼び出し元(例えば page1 と page2)から同じモーダルダイアログ(mymodal)を利用できるようにします。
2021012808


2. は 1. の更なる応用です。例えば mymodal の中にテキストフィールドが1つあって、そこに任意の文字列を入力できるものとします。
2021012803


そして page1 から mymodal を呼び出した場合、mymodal が終了して元の page2 に戻ったら、mymodal に入力された文字列が "http" で始まる URL 文字列だった場合はその URL へジャンプ、URL 文字列でなかった場合は何もしない、ものとします。
2021012804
  ↓
2021012805


一方、page2 から mymodal を呼び出した場合、mymodal が終了して元の page2 に戻ったら、mymodal に入力された文字列をそのまま画面に表示するという条件分岐を行うものとします。
2021012806
  ↓
2021012807


このように、異なる呼び出し元から同じモーダルダイアログを呼び出すのですが、モーダルダイアログ終了後の挙動は呼び出し元ごとに変えたい、というのが 2 の要件です。
2021012809

2021012810


これらが難しい理由は2つあって、(1)まずモーダルダイアログは $('#modal').modal(); で起動するのですが、モーダルダイアログ自体が終了時に値を返すわけではないので、返り値を受け取ってその値を元に次の処理に移れるわけではない点。(2)そしてモーダルダイアログ終了時の処理を予め記述しておくことはできるのですが、その方法だと異なる呼び出し元ごとに終了後の挙動を変えることができないという点です。

これらの技術的挑戦は(2)でコールバック関数を使うことで((1)とまとめて)解決することができました。つまりモーダルダイアログが終了する際に呼び出し元ごとのコールバック関数が呼ばれるようにしておくことでモーダル終了後の処理を分離することができるようになり、分離できてしまえば(1)は普通に実現できるようになるのでした。


では実際のコードを見てみます。まず Node.js 側ですが、GET /page1 で page1.ejs を、GET /page2 で page2.ejs を呼び出すようなルーティングを定義しています:
var express = require( 'express' ),
    ejs = require( 'ejs' ),
    app = express();

app.use( express.Router() );

app.set( 'views', __dirname + '/views' );
app.set( 'view engine', 'ejs' );

app.get( '/page1', async function( req, res ){
  res.render( 'page1', {} );
});

app.get( '/page2', async function( req, res ){
  res.render( 'page2', {} );
});


var port = process.env.PORT || 8080;
app.listen( port );
console.log( "server starting on " + port + " ..." );



次に page1.ejs 側ではモーダルダイアログを呼び出す画面まではこのテンプレート内で用意しますが、EJS の include 命令を使ってモーダルダイアログ mymodal.ejs を読み込んでいます。
<body>

<div class="container">
  <h1>Page1</h1>
</div>

<div class="container">
<button class="btn btn-primary" onClick="myOpen();">モーダルダイアログ</button>
</div>

<%- include('./mymodal', {}) %>

</body>


page2.ejs 側も同様です。自分たちの画面は自身のテンプレート内に記述して、共通で使うモーダルダイアログ部分だけを外部読み込みする形のテンプレートです。
<body>

<div class="container">
  <h1>Page2</h1>
</div>

<div class="container">
<button class="btn btn-primary" onClick="myOpen();">モーダルダイアログ</button>
</div>

<%- include('./mymodal', {}) %>

</body>


page1 および page2 から(ejs の include 命令で埋め込まれて)呼び出される側の mymodal.ejs です。Bootstrap の Modal による画面定義が行われていますが、今回はシンプルに id="text" のテキストフィールドを1つ配置しているだけです。ここまでで page1 および page2 の見た目に関する部分(HTML および CSS)は準備できました。
<div class="modal bd-example-modal-lg fade" id="myModal" tabindex="-1" role="dialog" aria-labbelledby="myModal" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h4 class="modal-title" id="myModalLabel">共有ダイアログ</h4>
      </div>
      <div class="modal-body" id="mymodal-body">
        <div>
          <input type="text" name="text" class="form-control" id="text"/>
        </div>
      </div>
      <div class="modal-footer btn-center">
        <button type="button" class="btn modal_button" data-toggle="modal" onClick="myModalClose();">OK</button>
      </div>
    </div>
  </div>
</div>


次にそれぞれの挙動に関する部分を見てみます。page1 の「モーダルダイアログを呼び出すボタン」をクリックすると myOpen() という関数が実行されるように定義されています。
<button class="btn btn-primary" onClick="myOpen();">モーダルダイアログ</button>


この関数は page1.ejs 内に定義されていて、その中では modalCallback という関数をパラメータにして myModalOpen() 関数が実行されています。
<script>
function myOpen(){
  myModalOpen( modalCallback );
}

function modalCallback( text ){
  if( text.startsWith( 'http' ) ){
    window.location.href = text;
  }else{
    //. Do nothing ?
  }
}
</script>


この myModalOpen() 関数は mymodal.ejs 内に定義されていて、その内容は後述します。また modalCallback 関数は myOpen のすぐ下に定義されています。実はこれがコールバック関数になっていて、mymodal.ejs の表示が終了したタイミングで、テキストフィールドに入力された値をパラメータにして実行されます。page1 ではこの値を調べて、"http" という文字で始まっていた場合は URL とみなし、その URL へ移動するような処理が記述されています。


同様にして、page2 の同部分ではコールバックされた関数内で alert() を使って、テキストフィールドに入力された値をそのまま表示するような処理が記述されています。
<script>
function myOpen(){
  myModalOpen( modalCallback );
}

function modalCallback( text ){
  alert( text );
}
</script>


最後に mymodal.ejs 内の JavaScript を見てみます。page1, page2 からモーダルダイアログ表示時に呼び出される myModalOpen() 関数内で、まずコールバック関数を変数に退避してから $('#myModal').modal() を実行してモーダルダイアログを画面に表示しています。
モーダルダイアログの終了ボタンがクリックされると myModalClose() 関数が実行されます。この関数内ではモーダルを非表示にする(戻す)ための処理が行われ、その後テキストフィールドに入力された値を取り出し、その値をパラメータに退避していたコールバック関数が実行されます。
<script>
var __callback = null;

function myModalOpen( func ){
  __callback = func;
  $('#myModal').modal();
}

function myModalClose(){
  $('body').removeClass( 'modal-open' );
  $('.modal-backdrop').remove();
  $('#myModal').modal( 'hide' );

  var text = $('#text').val();

  __callback( text );
}
</script>

こうすることでモーダルダイアログ終了時に、呼び出し元( page1 や page2 )で myModalOpen() 実行時に指定されたコールバック関数へ処理を戻すことができるようになります。またその際にダイアログ内のテキストフィールドの値も引き渡すことができるので、ダイアログで指定した値を使ってそれぞれの処理を行うことが可能になりました。


もう少し綺麗にモジュール化できるかもしれませんが、おおまかな考え方はこんな感じで実現できました。ソースコードは以下で公開しておきます:
https://github.com/dotnsf/bootstrap_modals



Bootstrap を使うと、簡単にタブを実装することができます:

タブA

タブB

タブC




この中の特定のタブの選択したり、有効化/無効化を JavaScript(jQuery) で切り替える方法を調べ、シンプルな実装方法を見つけたので共有します。また上のタブとボタンで実際の挙動を確認できるようにしています。

まず、特定のタブを選択するにはタブのアンカー(<a id="XXX">)をセレクトして、
$('.tav-tabs a[href="#XXX"]').show( 'tab' );
というメソッドを実行することで選択できます。

またタブのアンカーにつける data-toggle="tab" という属性を取り除くことでタブとしての挙動を無効化することができます。逆に data-toggle="tab" という属性を付けてあげることで、無効化されていたタブを再び有効化することができるようになります:
//. 無効化
$('.tav-tabs a[href="#XXX"]').removeAttr( 'data-toggle' );

//. 有効化
$('.tav-tabs a[href="#XXX"]').attr( 'data-toggle', 'tab' );

あとはこれらを組み合わせることで特定のタブを選択したり、その際に他のタブを有効化/無効化したりすることで実現できるようになります。

上述の画面では以下のようなコードを実装しています。サンプルとしてどうぞ:
(HTML)
<div  class="container">
  <ul  class="nav nav-tabs">
    <li  class="nav-item"><a  href="#tab-a" data-toggle="tab" class="nav-link active">A</a></li>
    <li  class="nav-item"><a  href="#tab-b" data-toggle="tab" class="nav-link">B</a></li>
    <li  class="nav-item"><a  href="#tab-c" data-toggle="tab" class="nav-link">C</a></li>
  </ul>
  <div  class="tab-content">
    <div  id="tab-a" class="tab-pane active">
      <p  class="text-left">タブA</p>
    </div>
    <div  id="tab-b" class="tab-pane">
      <p  class="text-center">タブB</p>
    </div>
    <div  id="tab-c" class="tab-pane">
      <p  class="text-right">タブC</p>
    </div>
  </div>
</div>

<div  class="container">
<button  onclick="selectX();" class="btn btn-xs btn-primary" id="btnX">タブAを選択&タブAのみ有効</button>
<button  onclick="selectY();" class="btn btn-xs btn-success" id="btnY">タブBを選択&タブBとCのみ有効</button>
<button  onclick="selectZ();" class="btn btn-xs btn-warning" id="btnZ">タブCを選択&タブA, B, Cすべて有効</button>
</div>
(JavaScript)
functon activeTab( tabid ){
  $('.nav-tabs a[href="#' + tabid + '"]').tab( 'show' );
}
function enableTab( tabid ){
  $('.nav-tabs a[href="#' + tabid + '"]').attr( 'data-toggle', 'tab' );
}
function disableTab( tabid ){
  $('.nav-tabs a[href="#' + tabid + '"]').removeAttr( 'data-toggle' );
}

function selectX(){
  //. A を選択して A のみを有効にする
  activeTab( 'tab-a' );
  enableTab( 'tab-a' );
  disableTab( 'tab-b' );
  disableTab( 'tab-c' );
}
function selectY(){
  //. B を選択して B, C のみを有効にする
  activeTab( 'tab-b' );
  disableTab( 'tab-a' );
  enableTab( 'tab-b' );
  enableTab( 'tab-c' );
}
function selectZ(){
  //. C を選択して A, B, C すべてを有効にする
  activeTab( 'tab-c' );
  enableTab( 'tab-a' );
  enableTab( 'tab-b' );
  enableTab( 'tab-c' );
}



Web アプリでドラッグ&ドロップ(以下 DnD と表記)すること自体はできるようになりました。HTML5 では DnD できる要素が多く定義されていたり、HTML5 以前にも mouseup イベントや mousedown イベントをハンドリングすることで独自に実装することは可能でした。 

ただモバイル Web で、つまりスマホのウェブブラウザから DnD を行うことはまだ困難が伴います。そもそもスマホの小さい画面で DnD を行うことが使いやすいかどうか、という根本的な問題もあると思っていて、それが理由かどうかは定かではありませんが、HTML5 の DnD API の多くはスマホブラウザからは使えないことが多いようです。

そういった事情を理解した上で、それでも現状でどこまでできるだろうか? という観点で実現方法を考えて実装し、公開してみました:
https://dotnsf.github.io/mobile_dnd_sample/


上記 URL にスマホのブラウザでアクセスすると以下のような画面になります。4角の枠内に5枚の付箋が貼られているイメージです:
2019111701


各付箋は指でドラッグして位置を変えることができるようにしています。下図はピンクの「ハロー」と書かれた付箋をドラッグして位置を変更した後の様子です:
2019111702


また各付箋はダブルタップすると編集モードになり、書かれた文字を編集することができるようになります。「ハロー」を編集して「ハロー!」にしてみました:
2019111703


編集モードで「OK」をタップすると編集後の文字列が反映されます:
2019111704


付箋を削除する場合は、一度ダブルタップして編集モードにして、「DELETE」をタップします:
2019111705


確認後に削除されて元の画面に戻ります。「abc」と書かれていた付箋が削除されました:
2019111706


新しい付箋を追加する場合は「NEW」をタップし、編集モードで内容を入力します:
2019111707


そして「OK」をタップすると新しい付箋が追加されます。この付箋も同様にドラッグして位置を変えたり、再度編集して内容を変更することができます:
2019111708



一連の機能紹介は以上です。一応スマホのウェブブラウザでもドラッグ&ドロップによる UI が実現できました。サンプルでは全て JavaScript を使ってフロントエンドだけで実現していますが、データベースとのバックエンド連携を加えることで永続化なども実現できると思っています。

詳しくは後述のソースコードを参照いただきたいのですが、今回のサンプルでは HTML5 Canvas を使って、Canvas 内に付箋に相当するオブジェクトを描画して実現しています。Canvas 内の Touch イベントを監視してドラッグを処理しています。また編集モード画面は Bootstrap のモーダルダイアログを使っています。


ソースコードはこちらです。実態は index.html ファイル1つで、全ての HTML と JavaScript がこの中に含まれています:
https://github.com/dotnsf/mobile_dnd_sample



ふと思い立って「モールモール」というパズルゲームを作ってみました。ただし「ゲーム内で使う画像はいらすとや様から入手できるものに限る」という制約を自分に課してみました。つまりいらすとや様から提供されている画像のみを、そのまま(書き足したり、一部だけ切り取ったりせずに)使ってゲームを作る、という条件にしてみました。

「モールモール」はもともと 1985 年に当時のビクター音楽産業(現JVCケンウッド・ビクターエンタテインメント)から MSX 向けにリリースされた、主人公のモグラを操作するパズルゲームでした。その後、他機種への移植や続編のリリースなどもされましたが、シンプルなルールや操作性もあってか、当時の素人プログラマー達が自分の所有していた機種に勝手に移植して雑誌で発表されていることも珍しくありませんでした。

※雑誌にプログラムコードが数ページに渡って記載されていたり、「ソノシート」と呼ばれる片面レコードにプログラム情報が記載されて付録になっていたりした時代がありました。。

実は自分もその一人 σ(^^) です。僕はパソコンの所有が大学4年生になってからと比較的遅かったのですが、高校生の頃にポケットコンピュータ(当時の通称は「ポケコン」)と呼ばれる、コンピュータというよりもプログラミング機能付き電卓を持っていました。その中の1つがシャープの PC-1350 で、当時では画期的な縦4行表示とグラフィック機能を備えており、BASIC によるプログラミングもできるものでした。この「縦複数行表示」が当時のポケコンでは珍しく、広い画面を使って「モールモール」をはじめとするゲームを勝手に移植して遊んでいたのでした。その意味で今でも思い入れあるゲームの1つです。

この「モールモール」、キャラクターとして必要な画像は6種類(ドア、はしご、土、石、イモ、主人公)と比較的少なく、また基本操作も(パズルをギブアップする時などは例外ですが)上下左右の移動のみなので矢印キーだけで実現できます。敵という概念もないので考えている間にやられてしまったり、敵を撃つ必要もありません。リソース的にも非力なポケコンにピッタリのゲームでした(笑)。

で、今回のゲーム制作にあたり、いらすとや様から以下6個の画像を使わせていただいております:

ドア =いろいろな状態のドアのイラスト



はしご =木のはしごのイラスト



 =デジタルデータ風の背景素材(緑)



 =石垣のイラスト(背景素材)



イモ =スネークフルーツのイラスト



主人公 =もぐらのイラスト



これらの画像に手を加えず、そのまま使っています。モールモールでは「土」がなぜか緑色なのですが、デジタルデータ風背景画像が遠くからみるとそれっぽく見えるのが大発見でした(苦笑)。また「イモ」の画像は数種類あったのですが、一番イモっぽく見えたのが「スネークフルーツ」だったのも新発見でした。 (^^;


で、作ってみました。github リポジトリはこちらです:
https://github.com/dotnsf/molemole


リポジトリには画像は含まれていません。必要な画像はすべて動的にいらすとや様から直接ダウンロードして使っています:
2019071801


実質 index.html ファイルだけなので GitHub Pages でも公開しました。PC ブラウザでこちらのリンク先から遊ぶことができます:
https://dotnsf.github.io/molemole/

2019071701


簡単なルールなどは README.md に記載しているので、上記ページを参照ください。簡単に言うと「すべてのイモを取ってからドアまで行けばクリア」です。ただ重力を意識する必要があることと、落ちてくる石をうまく使わないとクリアできない面があったりします。

例えば上図は第0ステージですが、この面は普通にイモを取りに行って、そのままドアまで向かえばゴールです。よほど捻くれた取り方をしない限りは詰むことはないと思います。

でも第1ステージはこうなります:
2019071703


この面の場合、何も考えずにイモを取りに行ってしまう↓と・・・、ドアにたどり着く術がなくなってしまいます。こうなると "retry" ボタンでリセットするしかありません。石をうまく誘導しながらイモを取る必要があります:
2019071704

 
先に進むともう少し複雑なステージも待っています:
2019071702


README.md にステージ追加のカスタマイズ方法も乗せているので、興味ある人はダウンロード後に自分でステージを考えて追加して遊んでみてください。



Bootstrap を使うとモーダルダイアログを簡単に作ることができます。


モーダルダイアログは親画面の中で表示されるダイアログボックス画面のことで、「このダイアログボックスを閉じるまで親画面や他の画面に移動することができない」という特徴を持っています。利用者に質問を促して「はい」か「いいえ」のボタンを押させる、といった時の、どちらかを押すまで他の処理ができなくなる(どちらかを押す前に他の処理をされては困る)場合などに使われます。

具体的にはこんな感じで実装できます:
<html>
<head>
<meta charset="utf8"/>
<title>Bootstrap モーダルから別のモーダルへ</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>
<body>
  
  <nav class="navbar">
    <a href="#" class="navbar-brand">Bootstrap モーダルから別のモーダルへ</a>
  </nav>

  <!-- Button trigger modal -->
  <div class="container">
    <a href="#" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal1">モーダル1</a><br/>
    <a href="#" class="btn btn-success" data-toggle="modal" data-target="#exampleModal2">モーダル2</a>
  </div>

  <!-- Modal1 -->
  <div class="modal fade" id="exampleModal1" tabindex="-1" role="dialog" aria-labelledby="exampleModal1Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal1Label">モーダル1</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>1番目のモーダル</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>

  <!-- Modal2 -->
  <div class="modal fade" id="exampleModal2" tabindex="-1" role="dialog" aria-labelledby="exampleModal2Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal2Label">モーダル2</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>2番目のモーダル</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

画面内には2つのモーダルダイアログ(便宜上、「モーダル1」と「モーダル2」と呼ぶことにします)が定義されていて、最初はどちらも非表示状態になっています。初期画面では2つのボタンがあり、それぞれモーダル1、モーダル2を表示するためのボタンになっています。モーダル1を表示している間、元の親画面は薄暗くグレーアウトされ、モーダル1を閉じるまではこの画面上に戻って処理を行うことはできません。モーダル2も同様です。HTML を見ていただくとわかるのですが、特別に JavaScript などを定義することもなく、Bootstrap の標準機能の一部として定義されているので、CSS の指定だけで実現できることがわかります。またモーダルダイアログ内を HTML で簡単&自由度高くカスタマイズできる点も便利です:



では「モーダル1を表示し、モーダル1を消すと同時にモーダル2を呼び出す」ということはできるでしょうか? 結論としてはできるのですが、少し JavaScript を使う必要があります。

上記を少し改良しました。モーダル1を閉じる際に「普通に閉じる」ボタンと「閉じてモーダル2を呼び出す」ボタンを用意しています。そして後者がクリックされた時に以下の JavaScript が呼び出されるように定義しました:
<html>
<head>
<meta charset="utf8"/>
<title>Bootstrap モーダルから別のモーダルへ</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script>
function changeModal(){
  $('body').removeClass( 'modal-open' );
  $('.modal-backdrop').remove();
  $('#exampleModal1').modal( 'hide' );
  
  $('#exampleModal2').modal();
}
</script>
</head>
<body>
  
  <nav class="navbar">
    <a href="#" class="navbar-brand">Bootstrap モーダルから別のモーダルへ</a>
  </nav>

  <!-- Button trigger modal -->
  <div class="container">
    <a href="#" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal1">モーダル1</a><br/>
    <a href="#" class="btn btn-success" data-toggle="modal" data-target="#exampleModal2">モーダル2</a>
  </div>

  <!-- Modal1 -->
  <div class="modal fade" id="exampleModal1" tabindex="-1" role="dialog" aria-labelledby="exampleModal1Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal1Label">モーダル1</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>1番目のモーダル</p>
          <a href="#" class="btn btn-success" onClick="changeModal()">モーダル2へ</a>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>

  <!-- Modal2 -->
  <div class="modal fade" id="exampleModal2" tabindex="-1" role="dialog" aria-labelledby="exampleModal2Label" aria-hidden="true">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title" id="exampleModal2Label">モーダル2</h5>
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">×</span>
          </button>
        </div>
        <div class="modal-body">
          <p>2番目のモーダル</p>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

こうすることでモーダル1を閉じて、後始末の処理も行い、その上でモーダル2を表示する、という一連の処理をすべて JavaScript で行うようにしています。これでモーダルダイアログから別のモーダルダイアログを表示することができるようになります。



このページのトップヘ