jQuery の sortable を iPad 対応する

jQuery の sortable を使った かんばん風の TodoList を GoogleAppEngine 上で作成して、iPad 対応してみた。

動作例(KanbanList)

ハマったポイントを忘れないうちにメモっておく。

iPadでは画面タッチ時に mousedownイベントではなく touchstart イベントが飛んで来るのでそれをマウスイベントに変換してあげないといけない。

以下のような touchHandler()関数を sortable 対象に addEventListener してあげる。

function touchHandler(event)
{
    var touches = event.changedTouches;
    event.preventDefault();        
    var first = touches[0];
    var type = "";
    switch(event.type)
    {
        case "touchstart": 
        	type = "mousedown"; 
        	break;
        case "touchend":   
    		type = "mouseup";   
        	break;
        case "touchmove":  
        	type = "mousemove"; 
        	break;
        default: 
        	return;
    }
    dispatchMouseEvent(type, first);
}

function dispatchMouseEvent( event_type, touch_event ){
    var simulatedEvent = document.createEvent("MouseEvent");
    simulatedEvent.initMouseEvent(
			event_type, true, true, window, 1, 
			touch_event.screenX, touch_event.screenY, 
			touch_event.clientX, touch_event.clientY, false, 
	        false, false, false, 0, null);

	touch_event.target.dispatchEvent(simulatedEvent);
}

これでとりあえず sortable オブジェクトにマウスイベントが到着し、ドラッグ&ドロップが有効になる。
しかし、sortable オブジェクト(リスト要素)の中でクリックイベントを受け取りたい時にはこれでは足りない。

touchstart と touchend のイベントのデフォルト処理を破棄(event.preventDefault())しなければ click イベントは発生するがタッチ時にリスト要素全体にフォーカスが当たったようになるのが気持ちわるい。

以下の方法を試した。

  • 自分で click イベントを発行(touchstart と touchend の時間を計って短かったら発行)
  • リスト要素の中でもボタンの部分と本当にドラッグしたい部分を table の td で分け、td のみタッチ変換リスナーを登録した

自分で click イベントを発行する方法でもとりあえず動いたが、微妙な操作をするとすぐにおかしな挙動(リストが変な場所に留まるなど)が発生した。また、リスト内のボタン「Edit」をタッチするとテキストエリアが表示されてフォーカスが当たり、iPadのソフトキーボードが出て来るはずが出てこなかった。
どうやら自前 click イベントでは反応しないらしい。

上記理由で後者の実装とした。クリックイベントが必要なボタン類以外のラベル部分の td に class名を設定し、イベントリスナー登録した。
「Edit」ボタンタッチ時のソフトキーボードもちゃんと出るようになった。

function touch_init() 
{
	label_elemens = document.getElementsByClassName("taskLabel");
	for(i=0;i < label_elemens.length;i++){
		label_elemens(i).addEventListener("touchstart",  touchHandler, true);
		label_elemens(i).addEventListener("touchmove",   touchHandler, true);
		label_elemens(i).addEventListener("touchend",    touchHandler, true);
		label_elemens(i).addEventListener("touchcancel", touchHandler, true);    
	}
}

しかしまだ問題はある。マルチタッチしてタスクをドラッグした際に時々リストが壊れる。(DBは大丈夫)
ブラウザ再読み込みをすれば回復するが未だ原因不明。マルチタッチをガードするような処理が必要か。

ここまでやってみて思ったことは、タッチイベント→マウスイベントの単純変換の方法では限界がありそう。iPhone safari 用に開発されている様々な javascript ライブラリを探してはいるけど sortable に対応しているのは見つからなく。探し方が甘いか。

使えそうなモノは以下