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

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

タグ:jquery

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 で行うようにしています。これでモーダルダイアログから別のモーダルダイアログを表示することができるようになります。



HTML (特に jQueryBootstrap を併用する HTML)で表を扱う場合に便利&有名な DataTables という jQuery プラグインがあります:
2019031901


元の HTML にほんの数行追加するだけで、簡単に各列でソートできるようにしたり、ページネーションを追加したり、検索フィルタを追加することができるようになる、というスグレモノです:
2019031902


ほんの数行で簡単にできてしまう点はいいのですが、簡単に実現できるせいか、その見た目をカスタマイズしようとすると、ちとややこしめです。色々調べてみた限りでのカスタマイズ(特に検索フィルタ部分のカスタマイズ)を紹介します。


【基本 HTML】
今回カスタマイズする表は以下のような内容とします:
<table id="mytable" class="table table-bordered">
  <thead>
    <tr><th>#</th><th>name</th><th>age</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>鈴木</td><td>30</td></tr>
    <tr><td>2</td><td>山本</td><td>31</td></tr>
    <tr><td>3</td><td>佐藤</td><td>33</td></tr>
    <tr><td>4</td><td>木村</td><td>28</td></tr>
    <tr><td>5</td><td>井上</td><td>22</td></tr>
    <tr><td>6</td><td>船橋</td><td>20</td></tr>
    <tr><td>7</td><td>近藤</td><td>30</td></tr>
    <tr><td>8</td><td>藤原</td><td>31</td></tr>
    <tr><td>9</td><td>吉田</td><td>40</td></tr>
    <tr><td>10</td><td>鈴木</td><td>35</td></tr>
    <tr><td>11</td><td>山木</td><td>31</td></tr>
    <tr><td>12</td><td>島田</td><td>33</td></tr>
    <tr><td>13</td><td>上原</td><td>19</td></tr>
    <tr><td>14</td><td>野島</td><td>20</td></tr>
    <tr><td>15</td><td>野崎</td><td>41</td></tr>
    <tr><td>16</td><td>奥村</td><td>39</td></tr>
    <tr><td>17</td><td>佐原</td><td>32</td></tr>
    <tr><td>18</td><td>千葉</td><td>22</td></tr>
    <tr><td>19</td><td>池上</td><td>25</td></tr>
    <tr><td>20</td><td>水森</td><td>29</td></tr>
  </tbody>
</table>


Bootstrap を併用して、table & table-bordered クラスを適用しています(<table> の id は "mytable" としています)。ちなみにこのままだと見た目は以下のようになります:
#nameage
1鈴木30
2山本31
3佐藤33
4木村28
5井上22
6船橋20
7近藤30
8藤原31
9吉田40
10鈴木35
11山木31
12島田33
13上原19
14野島20
15野崎41
16奥村39
17佐原32
18千葉22
19池上25
20水森29

ではこの表を DataTables プラグインを使ってカスタマイズしていきます。


【普通に適用】
特別なカスタマイズなしに DataTables を適用します。まずは DataTables の JavaScript と CSS を CDN からロードします(この中に Bootstrap も含まれているようです):
<link rel="stylesheet" href="https://cdn.datatables.net/t/bs-3.3.6/jqc-1.12.0,dt-1.10.11/datatables.min.css"/>
<script src="https://cdn.datatables.net/t/bs-3.3.6/jqc-1.12.0,dtー1.10.11/datatables.min.js"></script>

次に表の id(="mytable")を指定して、DataTable を適用します。この時点では特にオプションは指定していません:
<script>
$(function(){
  $('#mytable').DataTable({});
});
</script>

すると上記の表は以下のようになります:
#nameage
1鈴木30
2山本31
3佐藤33
4木村28
5井上22
6船橋20
7近藤30
8藤原31
9吉田40
10鈴木35
11山木31
12島田33
13上原19
14野島20
15野崎41
16奥村39
17佐原32
18千葉22
19池上25
20水森29



特別に何を指定したわけでもないのですが、これだけで指定した表の各列の見出し部分をクリックすることでその列値によるソートが可能になります。またデフォルトで 10 件ずつのページネーションが行われ、次のページや前のページを少しずつ表示することができるようになります。更に表の右上でリアルタイム検索フィルタリングが有効になり、ここに入力した文字を含むレコードだけがフィルタリングされて表示されるようになります。ここまでが実質一行の追加でできてしまいました。

【日本語化】
上記例ではページネーションや検索フィルタリングが追加されましたが、よく見ると英語になっています。これを日本語化する方法はないでしょうか? 実はこれも簡単で、単に日本語化するだけであれば公開されているリソースファイルを使って以下の赤字部分を追加するだけです:
<script>
$(function(){
  $.extend( $.fn.dataTable.defaults, {
    language: {
      url: 'https://cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/Japanese.json'
    }
  });
  $('#mytable').DataTable({});
});
</script>

すると上記の表は以下のようになります:
#nameage
1鈴木30
2山本31
3佐藤33
4木村28
5井上22
6船橋20
7近藤30
8藤原31
9吉田40
10鈴木35
11山木31
12島田33
13上原19
14野島20
15野崎41
16奥村39
17佐原32
18千葉22
19池上25
20水森29



指定したファイル(Japanese.json)をダウンロードして、この中身を別の日本語で書き換え、そのファイルを指定することで日本語訳を変更したり、リッチテキストにしたり、文字列ではなく画像を使ったりすることも可能です。


【ソート列の指定】
DataTables をオプション無しで適用すると表内のすべての列がソート対応になります。ここを明示的に変更することも可能です。例えば上記例で0列目(#)と2列目(age)ではソート可能にしたいが、1列目(name)ではソートできないようにしたい、という場合は以下のように(1列目のソート機能を無効に)します:
<script>
$(function(){
  $.extend( $.fn.dataTable.defaults, {
    language: {
      url: 'https://cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/Japanese.json'
    }
  });
  $('#mytable').DataTable({
    columnDefs: [{
      targets: [ 1 ],
      orderable: false
    }]
  });
});
</script>

すると上記の表は以下のようになります:
#nameage
1鈴木30
2山本31
3佐藤33
4木村28
5井上22
6船橋20
7近藤30
8藤原31
9吉田40
10鈴木35
11山木31
12島田33
13上原19
14野島20
15野崎41
16奥村39
17佐原32
18千葉22
19池上25
20水森29



本当はもっと別の(パーツの位置変更とか)もできないわけじゃないけど、長くなりそうなので別の機会に。


 

スマホのブラウザで touchstart や touchmove、touchend イベントなどを監視することで各種タッチ処理やドラッグ、そしてスワイプといった操作を感知して処理することができます。

が、これらを使ってスワイプ(横にスライドさせるような処理)を感知しようとすると結構面倒だったりします。機種ごとの機能差についてはもちろんのこと、touchstart 時の指の場所と時間を記憶して、touchend 時の指の場所と時間を記憶し、「一定時間以内にほぼ横向きに指が移動した」ことを検知できればスワイプとみなす、といった具合に処理するわけですが、この「一定時間以内」とか、「ほぼ横向き」とか、厳密に処理するわけにもいかず、かと言ってどの程度の猶予をもたせて判断するべきか迷う要素もあったりするわけです。

そんなスワイプ処理を比較的簡単にハンドリングできるのが jQuery Mobile 内の1ファンクションである jQuery Swipe です:
2019031100


実際に jQuery Swipe を使ってスワイプ判断をするサンプルを用意しました:
<html>
<head>
<meta charset="utf8"/>
<title>swipe</title>
<script type="text/javascript" src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>

<script>
$(function(){
  $('#myimg').on( 'swipe', mySwipeHandler );
  function mySwipeHandler( evt ){
    alert( '#myimg swiped.' );
  }
});
</script>
</head>
<body>
<img src="https://2.bp.blogspot.com/-hvxUlVBfZUI/XG4Ga7pkzxI/AAAAAAABRrE/Q2vhHjh76T8tFTyZ7D8fFE3MCQMqiKIVgCLcBGAs/s450/champion_belt_boxing_man.png" id="myimg" width="50%"/>
</body>
</html>

HTML はシンプルに(いらすとやの)画像を1つ画面に配置しているだけです。その画像に 'swipe' イベントが発生した場合に '#myimg swiped' という警告ダイアログが表示される、というものです。jQuery と jQuery Mobile は CDN を使ってロードしています。

このコードの肝になっているのはこの部分です:
  $('#myimg').on( 'swipe', mySwipeHandler );

'#myimg' は画像に付けられた id を指定しています。つまり画像に 'swipe' イベントが発生したら mySwipeHandler 関数を実行する、という指定をしています(そして mySwipeHandler 関数では警告ダイアログが表示されます)。

この 'swipe' イベントはデフォルトだと以下の条件で発生します:
条件デフォルト値カスタマイズする場合の設定箇所
スワイプにかかる時間1000ms$.event.special.swipe.durationThreshold
横方向の移動ピクセル30ピクセル以上$.event.special.swipe.horizontalDistanceThreshold
縦方向の移動ピクセル30ピクセル以内$.event.special.swipe.verticalDistanceThreshold


つまりデフォルトではタッチ開始から終了(指を離す)まで1秒以内で、縦方向には30ピクセル以内の移動で横方向に30ピクセル以上移動するようなスワイプが発生した時に 'swipe' とみなし、そのイベントを発生させる、というものになります。変更したい場合は上述箇所を書き換えてください。


このページをアップロードし、スマホから表示するとこんな感じになります:
2019031101


イラスト部分を右や左にスワイプするとイベントが発生して、警告メッセージが表示されます:
2019031102


iPhone でも Android でも動いて楽ちん。もちろん画像以外の <div> などのパーツにも使えます。

なお、今回のサンプルでは jQuery 2.0.3 と  jQuery Mobile 1.4.5 を使っています。jQuery は 2.x であれば動くようですが、3.x だとエラーになるっぽいです(ここになかなか気づけずハマりました):
https://stackoverflow.com/questions/40172264/jquery-swipe-event-does-not-work-with-jquery-3

マンホールマップでも使っている、 jqPuzzle を使って任意画像をスライドパズル化する方法を紹介します。

このスライドパズルは「15パズル」とも呼ばれていて、僕くらいのオッサンは↓こんなのがおもちゃ売り場で売られているのをよく目にしました。「懐かしいゲーム」の1つです:
2018083000



この jqPuzzle を使ったスライドパズル機能はマンホールマップ内の全てのマンホール画像で遊べます。例えばこのマンホール画像ページの「スライドゲームに移動」をクリックすると:
2018083001


紹介されているマンホール画像がこんなスライドパズルに早変わり:
2018083002


"shuffle" ボタンをクリックするとランダムにシャッフルされます。16 が空いた状態でパズルスタートです:
2018083003


空いたピースの上下左右にあるピースをクリックすると、そのピースが空いた部分にスライドして移動します。これを繰り返して 1 から 15 までが正しい位置にくる完成を目指す、というものです。個人的な印象としては1、2、3までは簡単だけど、4を揃える所あたりからコツが必要になってくると思ってます:
2018083004


こんな楽しい機能を提供する jqPuzzle は jQuery を併用して、画像にスライドパズルのインターフェースを追加してくれる CSS および JavaScript のセットです。なお、jqPuzzle が対応する jQuery は 1.x までの模様なので、この点のみ注意が必要です:
2018083001


jqPuzzle を使うには公式サイトから zip ファイルをダウンロード&展開して使います(CDN は見当たりませんでした)。なお jqPuzzle の提供ライセンスは 以下の通り、GPL と MIT のデュアルライセンス、だそうです:
2018083002


利用にあたっては jQuery 1.x をロードした後に CSS と JavaScript をロードします。これで準備完了(以下の例では jQuery v1.6.2 を指定しています。また jqPuzzle の両ファイルはこれを記述する HTML と同じ階層に存在しているものとします):
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<link rel="stylesheet" type="text/css" href="jquery.jqpuzzle.css"/>
<script type="text/javascript" src="jquery.jqpuzzle.packed.js"></script>

一番簡単な利用方法は <img> タグに jqPuzzle クラスを指定する方法だと思います。実はこれだけでその画像は 4x4 のスライドパズル化されて表示されます:
<img src="sample.jpg" class="jqPuzzle"/>

2018083005


ちなみに "Original" ボタンをクリックすると完成形が、"Numbers" をクリックすると各ピースの数字の表示/非表示が切り替わります。数字表記がないと難易度は一気に上がります。


カスタマイズの要素を加えることも可能です。例えば以下の例では 4x4 で 16 番目の駒を抜くことは変えずに、ボタンの文字を日本語化し、最初からシャッフル済みになるようにしています(というわけでシャッフルボタンも不要なので非表示にしました):
  :
<script type="text/javascript">
var settings = {
  rows: 4,
  cols: 4,
  hole: 16,
  shuffle: true,
  numbers: true,
  language: 'ja',
  control: {
    shufflePieces: false,
    confirmShuffle: true,
    toggleOriginal: true,
    toggleNumbers: true,
    counter: true,
    timer: true,
    pauseTimer: true
  },
  success: {
    fadeOriginal: false,
    callback: undefined,
    callbackTimeout: 300
  },
  animation: {
    shuffleRounds: 3,
    shuffleSpeed: 800,
    slidingSpeed: 200,
    fadeOriginalSpeed: 600
  },
  style: {
    gridSize: 2,
    overlap: true,
    backgroundOpacity: 0.1
  }
};
var texts = {
  shuffleLabel: 'シャッフル',
  toggleOriginalLabel: '元画像',
  toggleNumbersLabel: '数値表示/非表示',
  confirmShuffleMessage: 'シャッフルしてよろしいですか?',
  movesLabel: '回',
  secondsLabel: '秒'
};

$(function(){
  var t = $('img.jqPuzzle');
  t.jqPuzzle( settings, texts );
});
</script>
 :
2018083006


ちょっとした息抜き機能を追加するのに便利なライブラリです。


EC サイトの商品画像を効率よく表示するテクニックの1つに「カルーセル(carousel、「回転木馬」とか、日本だと「メリーゴーランド」と表現されることも)」と呼ばれる方法があります。スマホなどの限られた画面サイズの中により多くの画面や画像を含ませる技術で、画面・画像を小さくして詰め込むのではなく、個々の画面は横サイズいっぱいに表示させつつ、(一般的には)横スクロールで画面いっぱいの画像を次々に切り替えて表示するテクニックです。

このカルーセルを実装する方法はいくつもありますが、jQuery と組み合わせて使う slick が有名なようでした。というわけで、使い方を調べてみたので以下メモ代わりに残しておきます:
2018021900


CSS と JavaScript の指定
まず slick は jQuery が読み込まれている前提で動くので、最初に jQuery を読み込んでおきます。その後、CSS 、テーマ CSS、そして Slick の JavaScript をロードします。全て CDN を使うとこんな感じになります:
  :
<script type="text/javascript" src="//code.jquery.com/jquery-2.0.3.min.js"></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet"/>
<link href="//cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
  :

カルーセルの中身を作成
次にカルーセルの中身になる部分を、以下のような形で作成します(カルーセル全体となる <div> 要素に myclass という名前を付けて、その中に子 <div> 要素の形で各カルーセル内の要素を配置。子要素は HTML でも画像でも可)。

当たり前ですが、このままだと各要素が縦に5つ並ぶだけの UI になります:
  :
<div class="myclass">
 <div>(1番目のカルーセルの中身)</div>
 <div>(2番目のカルーセルの中身)</div>
 <div>(3番目のカルーセルの中身)</div>
 <div>(4番目のカルーセルの中身)</div>
 <div>(5番目のカルーセルの中身)</div>
</div>
  :

カルーセル化
この部分をカルーセル化します。カルーセル全体の div 要素(上記の myclass クラスを指定している部分)に slick() 関数を実行します。実行時のパラメータでカルーセルの挙動をカスタマイズすることも可能です(以下はその例):
<script>
  :
$(function(){
  $('.myclass').slick({
    infinite: false,    //. 端までスクロールしてもループしない
    slidesToShow: 3,    //. 1度に3つ表示
    slidesToScroll: 1,  //. スクロールは1つ単位
    initialSlide: 0,    //. 最初に表示するのは一番最初(上)の要素
    arrows: true,       //. コントロール用の矢印を表示する
    dots: true          //. 全体の位置がわかるような点コントロールを表示する
  })
});
  :
</script>


試しにこのブログエントリ内に同じような要素を作ってみるとこんな感じになりました:
(1番目のカルーセルの中身)
(2番目のカルーセルの中身)
(3番目のカルーセルの中身)
(4番目のカルーセルの中身)
(5番目のカルーセルの中身)


簡単ね~

このページのトップヘ