一定時間ドラッグオーバーし続けたら処理を実行する

modest に投稿した記事と同内容です。

拡張機能(XULアプリ)にて、一定時間ドラッグオーバーし続けたときに何らかの処理を実行したい、例えばツールバーに配置したボタン上にブラウザタブを3秒間ドラッグオーバーし続けたら、そのボタンをクリックしたものとみなして処理を実行したいとします。

これは、HTML5のドラッグ&ドロップAPIを使い、ドラッグオーバーし続けた際に dragover イベントが繰り返し発生する特性を利用すると、以下のように実装可能です。

以下は、ボタン上に何かを3秒間ドラッグオーバーし続けると、テキストボックスに現在時刻を表示するサンプルです。なお、サンプルコード全量はこちらに置いてあります。 chrome 権限は不要ですので、ダウンロードして拡張子を.xulにしてFirefoxで開けば、動作確認可能です。

XUL:

<button label="Drag something over here for 3 seconds."
        ondragenter="MyExtension.handleDragEvent(event);"
        ondragover="MyExtension.handleDragEvent(event);"
        oncommand="this.nextSibling.value += new Date() + '
';" />
<textbox multiline="true" flex="1" />

JavaScript:

var MyExtension = {

    _dragStartTime: null,

    handleDragEvent: function(event) {
        event.preventDefault();
        switch (event.type) {
            case "dragenter": 
                // ドラッグオーバー開始時、ドラッグオーバー開始時刻をセット
                this._dragStartTime = Date.now();
                break;
            case "dragover": 
                // ドラッグオーバー中、ドラッグオーバー開始時刻からの経過時間を調べる
                if (this._dragStartTime && Date.now() - this._dragStartTime > 3000) {
                    // 3秒以上経過したら、ドラッグ開始時刻をリセットし、処理を実行する
                    this._dragStartTime = null;
                    event.target.doCommand();
                }
                break;
        }
    }

};

タイマーを用いた実装方式

ドラッグオーバー開始時(dragenter イベント発生時)に setTimeout で一定時間後に処理を実行するためのタイマーを設定し、ドラッグオーバー終了時(dragleave イベント発生時)に clearTimeout でタイマーを解除する、という実装方式ももちろん可能です。

XUL:

<button id="myButton"
        label="Drag something over here for 3 seconds."
        ondragenter="MyExtension.handleDragEvent(event);"
        ondragleave="MyExtension.handleDragEvent(event);"
        oncommand="this.nextSibling.value += new Date() + '
';" />
<textbox multiline="true" flex="1" />

JavaScript:

var MyExtension = {
    _dragOverTimer: null,
    handleDragEvent: function(event) {
        event.preventDefault();
        switch (event.type) {
            case "dragenter": 
                // dragenterイベントが二回連続で発生した場合への対策
                if (this._dragOverTimer)
                    return;
                // ドラッグオーバー開始時にタイマーを設定
                this._dragOverTimer = setTimeout(function() {
                    document.getElementById("myButton").doCommand();
                }, 3000);
                break;
            case "dragleave": 
                // ドラッグオーバー終了時にタイマーを解除
                clearTimeout(this._dragOverTimer);
                this._dragOverTimer = null;
                break;
        }
    }
};

TOP

Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その3~ドロップ処理)

その2~ドラッグ処理から引き続き、ドラッグ元をドロップ先へドロップ可能にするための処理と、実際にドロップしたときの処理を追加する。

ドロップ先へのドロップを可能にする

ドロップ先の要素では、テキストボックス (html:input 要素や xul:textbox 要素) などを除き、通常はいかなる形式の転送データでもドロップ不可となっている。ドロップ可能にするには、 dragenter と dragover の2つのイベントハンドラで event.preventDefault() を呼び出す。

はじめに、転送データの形式によらず常にドロップ可能にするには、 handleDropEvents 関数にて以下のような処理を追加する。

        case "dragenter": 
        case "dragover": 
            event.preventDefault();
            break;

別の書き方として、 XUL / HTML のイベントハンドラ側で ondragenter=”return false;” のようにする方法もある。

実際は特定の転送データ形式、例えばリンクのドロップのみを可能にする場合が多いので、 dragenter と dragover の2つのイベントハンドラで転送データの形式を調べ、条件付きで event.preventDefault() を呼び出すようにする。

転送データの形式を調べるには、 DataTransfer オブジェクトの types プロパティを使用する。
ひとまずは、 dragenter と dragover イベント発生時に転送データに含まれるすべての形式を列挙してみる。

        case "dragenter": 
        case "dragover": 
            for (var i = 0; i < event.dataTransfer.types.length; i++) {
                dump("    " + event.dataTransfer.types.item(i) + "
");
            }
            break;

DataTransfer の types プロパティに特定のデータ形式が含まれるかどうかは、以下のように contains メソッドを使用すると良い。

        case "dragenter": 
        case "dragover": 
            if (event.dataTransfer.types.contains("text/url-list") || 
                event.dataTransfer.types.contains("text/plain"))
                event.preventDefault();
            break;

動作確認 (1)

ドラッグ元をドラッグし、マウスの右クリックを放さずにドロップ先の枠内へ入ったり出たりすると dragenter, dragover, dragleave の3つのイベントが発生することを確認してください。
なお、 XUL / HTML をブラウザタブで開いている場合、マウスの右クリックを放してドロップすると、ブラウザタブ側でドロップイベントが発生して URL を開く動作となります。

ドロップされたデータを取得する

次に、 drop イベント発生時にドロップされた転送データを取得する処理を追加する。
ドロップされた転送データを取得するには、まず dragenter, dragover イベントハンドラでやったように DataTransfer オブジェクトの types プロパティから転送データに目的の形式が含まれることをチェックした上で、DataTransfer オブジェクトの getData メソッドで指定した形式の転送データを取得する。
なお、下記のサンプルコードでは、 XUL / HTML をブラウザタブで開いている場合を考慮し、 event.preventDefault() を呼び出して、ブラウザタブ側でドロップイベントが発生するのを阻止している。

        case "drop": 
            event.preventDefault();
            var data = null;
            if (event.dataTransfer.types.contains("text/url-list"))
                data = event.dataTransfer.getData("text/url-list");
            else if (event.dataTransfer.types.contains("text/plain"))
                data = event.dataTransfer.getData("text/plain");
            alert("Dropped URL: " + data);
            break;

以下のように、あらかじめドロップ可能なデータ形式を配列で定義しておいたほうが見通しの良いソースコードとなるかもしれない。

        case "drop": 
            event.preventDefault();
            var data = null;
            var supportedTypes = ["text/url-list", "text/plain"];
            for each (type in supportedTypes) {
                if (event.dataTransfer.types.contains(type)) {
                    data = event.dataTransfer.getData(type);
                    break;
                }
            }
            alert("Dropped URL: " + data);
            break;

動作確認 (2)

ドラッグ元をドラッグしてドロップ先へドロップすると drop イベントが発生することを確認してください。
また、ドロップされたURLがメッセージボックスで表示されることを確認してください。

参考

Drag Operations – MDC

関連記事

Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その1~イベントハンドラの追加)
Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その2~ドラッグ処理)
Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その3~ドロップ処理)

TOP

Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その2~ドラッグ処理)

その1~イベントハンドラの追加から引き続き、ドラッグ元の要素をドラッグ開始した際に、転送データをセットする処理を追加する。

DataTransfer オブジェクト

ドラッグした際に転送データをセットする処理や、ドロップした際に転送データを取得する処理は、 DataTransfer オブジェクト (event.dataTransfer) によって行う。

ドラッグ元をドラッグ開始時、つまり dragstart イベント発生時、転送データをセットするには、 DataTransfer オブジェクトの setData メソッドを使用する。 setData メソッドの第1引数は転送データの形式、第2引数は転送データの値(文字列に限る)である。転送データの形式は、単純な文字列であれば「text/plain」、URL(複数も可)であれば「text/url-list」といった値を用いる。もちろん、一度のドラッグで複数の形式の転送データをセットすることも可能である。

サンプルコード (JavaScript)

handleDragEvents 関数へ以下のような処理を追加する。

        case "dragstart": 
            // 転送データをセットする
            event.dataTransfer.setData("text/url-list", "http://www.mozilla.org/");
            event.dataTransfer.setData("text/plain", "http://www.mozilla.org/");
            break;

動作確認

ドラッグ元をドラッグして dragstart, drag, dragend の3つのイベントが発生することを確認してください。
現段階ではドロップ先の処理が未完ですので、ドラッグ時の転送データが正しくセットされていることを確認するために、 Firefox のロケーションバーなどにドロップするか、メモ帳などの別アプリへドロップして、「http://www.mozilla.org/」という文字列が貼り付けされることを確認してください。

参考

DataTransfer オブジェクトの詳細:
DataTransfer – MDC
nsIDOMDataTransfer.idl

転送データの形式の詳細:
Recommended Drag Types – MDC

関連記事

Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その1~イベントハンドラの追加)
Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その2~ドラッグ処理)
Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その3~ドロップ処理)

TOP

Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その1~イベントハンドラの追加)

Firefox 3.1 (b1pre) にて HTML 5 のドラッグ&ドロップ API が実装され、 HTML の Web アプリ、 XUL の拡張機能のいずれからも同じように利用可能となった。

拡張機能開発者としては、ドラッグ&ドロップに関する各種処理を、 XPCOM サービスの nsIDragService や面倒くさい nsDragAndDrop.js を使わずに、単純な DOM の API のみで記述できるようになったので、非常にありがたい。

ドラッグ&ドロップのイベント

ドラッグ&ドロップ操作時、ドラッグ元の要素とドロップ先の要素において、以下のようなイベントが発生する。

イベント名 (event.type) イベント発生対象 (event.target) イベント発生のタイミング
dragstart ドラッグ元 ドラッグ開始時
drag ドラッグ元 ドラッグ中
dragend ドラッグ元 ドラッグ終了時
dragenter ドロップ先 ドラッグオーバー開始時
dragover ドロップ先 ドラッグオーバー中
dragleave ドロップ先 ドラッグオーバー終了時
drop ドロップ先 ドロップ時

サンプルコード (HTML)

2つの div 要素を配置し、一方をドラッグ元、もう一方をドロップ先とする。
ドラッグ元となる div 要素には、「draggable=”true”」属性をセットしなければならない。ただし、リンク(a 要素)や画像 (img 要素)などは、「draggable=”true”」を指定しなくても自動的にドラッグ可能となる。

ドラッグ元の要素には ondragstart, ondrag, ondragend の3つのイベントハンドラを追加し、ドロップ先の要素には ondragenter, ondragover, ondragleave, ondrop の4つのイベントハンドラを追加する。ただし、必要最低限のドラッグ&ドロップを実装するのであれば、 ondrag, ondragend, ondragleave は省略しても問題ない。

<html>
<head>
    <title>Drag and Drop Test</title>
    <script type="text/javascript" src="dragdrop.js"></script>
</head>
<body>
    <div id="DragSource"
         draggable="true"
         ondragstart="handleDragEvents(event);"
         ondrag="handleDragEvents(event);"
         ondragend="handleDragEvents(event);"
         style="border: 1px solid black; padding: 50px; margin: 10px;">Drag Source</div>
    <div id="DropTarget"
         ondragenter="handleDropEvents(event);"
         ondragover="handleDropEvents(event);"
         ondragleave="handleDropEvents(event);"
         ondrop="handleDropEvents(event);"
         style="border: 1px solid black; padding: 50px; margin: 10px;">Drop Target</div>
    <a href="http://www.mozilla.org/">http://www.mozilla.org/</a>
    <img src="http://www.mozilla.org/images/poweredby_200.gif">
</body>
</html>

サンプルコード (XUL)

2つの xul:label 要素を配置し、一方をドラッグ元、もう一方をドロップ先とする。 HTML の場合とほぼ同じであるが、すべての XUL 要素はドラッグ可能となりうるため、「draggable=”true”」属性を指定する必要は無い。

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window title="Drag and Drop Test"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script type="application/x-javascript" src="dragdrop.js" />
    <label id="DragSource"
           value="Drag Source"
           ondragstart="handleDragEvents(event);"
           ondrag="handleDragEvents(event);"
           ondragend="handleDragEvents(event);"
           style="border: 1px solid black; padding: 50px; margin: 10px;" />
    <label id="DropTarget"
           value="Drop Target"
           ondragenter="handleDropEvents(event);"
           ondragover="handleDropEvents(event);"
           ondragleave="handleDropEvents(event);"
           ondrop="handleDropEvents(event);"
           style="border: 1px solid black; padding: 50px; margin: 10px;" />
</window>

サンプルコード (JavaScript)

JavaScript のソースコードは、 HTML と XUL の両者に共通となる。また、動作させるために chrome 権限は必要ない。
今回はとりあえず以下のような雛形的なイベントハンドラの処理を作っておき、あとで処理を追加する。
なお、サンプルコードでは window.dump メソッドを使用する。

function handleDragEvents(event) {
    dump("[" + event.target.id + "] " + event.type + "
");
    switch (event.type) {
        case "dragstart": 
            break;
        case "drag": 
            break;
        case "dragend": 
            break;
    }
}

function handleDropEvents(event) {
    dump("[" + event.target.id + "] " + event.type + "
");
    switch (event.type) {
        case "dragenter": 
        case "dragover": 
            break;
        case "dragleave": 
            break;
        case "drop": 
            break;
    }
}

動作確認

現段階ではドラッグ元をドラッグ開始した際の dragstart イベントしか発生しませんが、実際にドラッグしてイベントが発生することを確認してください。

参考

Drag and Drop – MDC

関連記事

Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その1~イベントハンドラの追加)
Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その2~ドラッグ処理)
Firefox 3.1 の新しいドラッグ&ドロップ API の基本的な使い方 (その3~ドロップ処理)

TOP