WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

ドラッグで並べ替え可能なグリッドを実現する「draggable_grid.js」を作りました

本日は、久しぶりに JavaScript のライブラリを公開します。よく、画像ギャラリー等でマウスのドラッグ&ドロップで画像の並べ替えができるものがありますが、そんな UI を簡単に実現できるものを作ってみました。以前に公開してご好評をいただいた DragResize.js のノウハウを引き継いでおり、以下の特徴があります。

  • JavaScript の呼び出しひとつで、グリッドにドラッグによる並べ替え機能を追加できる。
  • inline-block もしくは float によるグリッドレイアウトに対応。
  • 要素がグリッドの外枠(もしくはドキュメント)からはみ出さないように補正する。
  • ドラッグ中にマウスホイール等でページをスクロールしても、ドラッグ位置がずれない。
  • 要素が表示領域外に出たときは自動スクロールする。
  • DraggableGrid クラスを除き、グローバルな名前空間を汚染しない。
  • 他のライブラリに依存せず、単独で動作する。
  • 若干の制限はあるものの、概ねクロスブラウザで動作する(動作は IE7, FF3, Opera10, Safari3 にて確認)。

今回も利用制限等はとくにありません。改変や商用アプリへの組み込みなど、自由にお使いください。以下にライブデモや使い方の紹介がありますので、興味のある方はぜひご覧ください。

ライブデモ

百聞は一見にしかずということで、以下にデモを用意しました。ぜひ操作してみてください。

数字の書かれた各マスをマウスドラッグで並べ替えできます。ドラッグ中、マスは外枠から出ないように補正されますし、表示範囲の外に持っていけばページがスクロールします。ドラッグ中にホイールでページをスクロールしたり、(IE 以外では)マウスをブラウザの外に持っていっても、正しく追従するはずです。

すいません、数字の位置がおかしいですね。おそらくページで指定しているスタイルのなにかと干渉しているのだと思います。今日は時間がないので、後ほど調べてみます orz 修正しました。コメントにて修正方法を教えてくださった kohei さん、ありがとうございます!(^^)

ソースコード

ソースコードは以下にあります。

右クリックメニューで保存してご利用ください。

使い方

動作の様子がわかったところで、使い方をご紹介していきます。先ほどのライブデモのようなものは、だいたい以下のようなコードで実現できます(重要でないスタイル指定は省略しています)。

<script type="text/javascript" src="http://webos-goodies.googlecode.com/svn/trunk/products/jslib/draggable_grid/draggable_grid.js"></script>

<style type="text/css">
.draggable {
  display: inline-block;
  vertical-align: top;
  width: 100px;
  height: 100px;
  zoom: 1;
  *display: inline;
}
</style>

<div id="grid">
  <div class="draggable">1</div>
  <div class="draggable">2</div>
  <!-- 20 まで繰り返し... -->
</div>

<script type="text/javascript">
new DraggableGrid('grid', { scroll: true, fence: true });
</script>

グリッド上に並べるためのスタイル指定でだいぶ行を使っていますが、 DraggableGrid の呼び出しは一行で終わっています。とても簡単にドラッグによる並べ替えを実現できるのがおわかりいただけるのではないでしょうか。

それでは、以降でそれぞれの記述を詳しく見ていきましょう。

スクリプトを読み込む

まずは draggable_grid.js をページに読み込みます。依存ライブラリ等はないので、 draggable_grid.js のみを SCRIPT タグで読み込めば OK です。

<script type="text/javascript" src="http://webos-goodies.googlecode.com/svn/trunk/products/jslib/draggable_grid/draggable_grid.js"></script>

グリッドの HTML を記述

ちょっと順番が前後しますが、グリッドとして表示する HTML の方を見ていきましょう。基本的には、グリッドの外枠となる DIV 要素の中に、それぞれのマスとなる DIV 要素を必要な数だけ記述すれば良いでしょう。

<div id="grid">
  <div class="draggable">1</div>
  <div class="draggable">2</div>
  <!-- 20 まで繰り返し... -->
</div>

このとき、以下のことに注意してください。

  • 外側の DIV (コンテナ要素)には ID をふっておいてください。名前はなんでもかまいません。
  • 内側の DIV (ドラッグ対象要素)のクラス名に "draggable" を含めておいてください(クラス名は変更もできます)。このクラスに含まれていない要素はドラッグ対象になりません。
  • ドラッグ対象要素は外側のコンテナ要素の直接の子でなければなりません。余計なラッパー要素は入れないでください。

また、ドラッグ対象要素はいつでもスクリプトで増減できます。 DOM API の appendElement などで外側の DIV に要素を追加するだけで、ドラッグ可能になります。

スタイル指定

スタイル指定では、 display: inline-block もしくは float を使ってドラッグ対象要素がグリッド状に表示されるようにしてください。 inline-block については上記サンプルの記述で IE7, Opera, FF3, Safari で対応できることを確認しています。最後の "*display" と "zoom" は IE 対応のためのもので、詳細は Mozilla Webdev さんの記事を参照してください。

float を使う場合は以下の記述で良いと思います。

.draggable {
  float: left;
  width: 100px;
  height: 100px;
}

float による方法は各マスの高さが違うと表示が崩れるという欠点がありますが、 IE6, FF2 など多くのブラウザに対応できるので、互換性を重視する場合はこちらを使うのが無難です。

また、ドラッグ対象要素には margin, padding, border などのボックスサイズが変化するスタイルは指定しない方が無難です。内側にもうひとつ DIV を作り、そちらに指定するのが良いと思います。

DraggableGrid インスタンスの作成

以上できちんとグリッド状に表示されるようになったら、あとは DraggableGrid クラスのインスタンスを作成するだけです。書式は以下になります。

new DraggableGrid(外側DIVのID, オプション);

第一引数はコンテナ要素の ID 、第二引数は後述のオプションを指定したオブジェクトです。

オプションには以下のものがあります。

scroll
true にすると、ドラッグ中の要素が表示範囲外に出たときにページをスクロールします。デフォルトは false です。
fence
true にすると、ドラッグ中の要素が外側の DIV の外に出ないようになります。デフォルトは false です。
handleClass
ドラッグ対象要素内の特定の要素でのみドラッグを開始したい場合は、このオプションにその要素のクラスを指定してください。
draggableClass
ドラッグ対象要素を判別するクラス名を指定します。デフォルトは "draggable" です。
proxyHtml
プロキシー要素(ドラッグ中、要素の挿入位置に表示されるピンクの矩形)の内部に設定する HTML をテキストで指定します。デフォルトは "&nbsp;" です。
proxyClass
プロキシー要素に設定するクラス名です。デフォルトは "draggable-proxy" です。
proxyStyle
プロキシー要素の STYLE 属性に設定する文字列です。デフォルトは "background-color: #fcc;" です。
opacity
ドラッグ中の要素の透明度です(IE では機能しません)。 0.0 〜 1.0 の数値で指定してください。デフォルトは 0.7 です。
offset
ドラッグを開始した際に、ドラッグ対象要素を右下にずらすドット数を指定します。これを指定することで、ドラッグが開始されたことをユーザーがわかりやすくなります。デフォルトは 0 です。
ignoreTags
ドラッグ対象要素内でもドラッグを開始しないタグ名を文字列配列で指定します。例えば ["A"] を指定すれば、ドラッグ対象要素内のリンクがクリックできるようになります。
ignoreClasses
ignoreTags と同じ機能をクラス名で指定できます。例えば ["clickable"] を指定すれば、このクラスを持つタグの内部はクリックができるようになります。
onBegin
ドラッグが開始された際に呼ばれるコールバック関数を指定します。
onDrop
ドラッグが終了した際に呼ばれるコールバック関数を指定します。

いろいろと柔軟な指定ができるようにと作っているうちに、けっこう増えてしまいました(^^;

以下、使い方がわかりづらいオプションを詳しくご紹介します。

handleClass の指定

handleClass オプションを指定すると、ドラッグするためのハンドルをドラッグ対象要素の一部に限定できます。例えば、以下の例では、マス上部の灰色の帯のみドラッグでき、他の場所をドラッグしてもマスは移動できません。

 
1
 
2

この例のソースコードは以下のような感じです(重要でないスタイルは省略)。

<style type="text/css">
.draggable {
  display: inline-block;
  vertical-align: top;
  width: 100px;
  height: 100px;
  zoom: 1;
  *display: inline;
}
.handle {
  height: 20px;
  background-color: #ccc;
}
</style>

<div id="grid">
  <div class="draggable"><div class="handle">&nbsp;</div><div>1</div></div>
  <div class="draggable"><div class="handle">&nbsp;</div><div>2</div></div>
</div>

<script type="text/javascript">
new DraggableGrid('grid', { handleClass: 'handle' });
</script>

このようにすると、ドラッグ対象要素でも handleClass 以外の場所ではクリックイベントが自由に使えます。

コールバックの指定方法

onBegin, onDrop オプションに指定するコールバックは、いくつか指定法法を用意しています。まずオーソドックスに関数を指定する方法。この場合、コールバック内での this は window オブジェクトになります。

onBegin : function() { ... }

次に、配列渡しすれば、 this を指定することができます。

onBegin : [this_obj, function() { ... }]

配列の代わりにオブジェクトで指定することも可能です。

onBegin : { scope: this_obj, method: function() { ... } }

場合によって使い分けてください。

コールバック関数には引数としてオブジェクトがひとつ渡され、以下のメンバが格納されています。

メンバ名内容
managerDraggableGrid オブジェクト
prevPosドラッグ開始時のドラッグ対象要素の nextSibling

コールバック関数内では、以下のメソッドが利用可能です。

manager.getDraggingElement()
ドラッグ対象要素を返します。
manager.getProxyElement()
プロキシー要素を返します。

このあたりはあまり考えて作っていないので、「こんなメソッドも欲しい!」というご要望がありましたら、ぜひお聞かせください。

ドラッグの中止

もしドラッグ中にドラッグを中止すべきイベントが発生した場合(モーダルダイアログを表示したとか)、 DraggableGrid.cancelDrag() を呼んでください。ただちにドラッグが中止され、ドラッグ中の要素は開始時の位置に戻ります。

並び順をフォームで送信する

この DraggableGrid の使い方で最初に思いつくのは、フォームで画像などの並び順を簡単に指定できるようにすることだと思います。そのとき、フォームデータに並び順の情報をいかにして反映させるかが問題ですが、実はとても簡単な方法があります。以下のように、各マスの中にそれぞれの画像の ID を持つ隠しフィールドを入れてしまうのです。

<form action="...">
<div id="grid">
  <div class="draggable">
    <input type="hidden" name="imageid" value="1">
    <img src="image1.png">
  </div>
  <div class="draggable">
    <input type="hidden" name="imageid" value="2">
    <img src="image2.png">
  </div>
  <!-- ... -->
</div>
<input type="submit">
</form>

こうすると、送信されるフォームデータは以下のようになります。

imageid=先頭の画像ID&imageid=2番目の画像ID...

"imageid" という名前のパラメータで、画像の ID が並べ替えた順番に並んでいるわけです。あとはこれを解析すればサーバー側で簡単に画像の順番が取得できます。 Rails ならば、 Rails のフォームで配列形式のデータを扱う方法でご紹介したテクニックを使って、より簡単に実装できます。

もちろん画像以外でもいろいろ使い道があると思います。ぜひ活用してください。

ご意見・ご要望・バグ報告など

最後にいつもの Google グループの告知です。 draggable_grid.js に関するご意見・ご要望・バグ報告には、以下の Google グループをご利用ください。スクリプトをバージョンアップした際のご報告もこちらで行いますので、登録されることをお勧めします。

Google グループ Beta
WebOS Goodiesに参加
メール アドレス:

以上、本日はドラッグ&ドロップによる並べ替えを簡単に実装できる draggable_grid.js をご紹介しました。この手の UI が欲しい場面はけっこうあると思いますが、手軽に使えるスクリプトがないと、つい次善の策(順番をテキストボックスで指定するとか ^^;)でお茶を濁してしまいがちです。その点、この draggable_grid.js は特定のフレームワークへの依存がないので、あらゆるサイトに簡単に組み込めます。ぜひ活用して、ユーザビリティーの高い UI を構築する助けにしていただければと思います。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles