HTML へのローカルファイルの Drag & Drop のメモです。
JavaScript は CoffeeScript で記述しています。jQeury を使用しています。
ondrop イベント
ファイル Drag&Drop の処理の要点をまとめると、以下のようでした。
- ファイルをHTML要素にドロップすると、ondrop イベントがあがる
- それをハンドルする
- ただし、デフォルト動作(ファイルを表示するなど)が好ましくないので、それは無効にする
なので、それ用のハンドラのラッパを作りました。
dnd = (f = ()->) ->
(e) ->
e.stopPropagation() # イベントの親要素への伝播を無効にする
e.preventDefault() # イベント発生時のデフォルト動作を無効にする
f.apply $(this), [ # f はハンドラ本体
e # 引数は扱いやすいように適当に並び替えました
e.originalEvent.dataTransfer.files[0]
e.originalEvent.dataTransfer.files
]
想定する使い方は以下のような感じです。
ondrop だけでなく dragover も抑止しないといけないので、その2つのイベントに上のラッパをかませます。
$ '#drop-here'
.on 'dragover', dnd()
.on 'drop', dnd (e) -> console.log e # この例の処理は適当
ファイルの読み込み
上で Drag & Drop はできるようになったので、ファイルの中身を読む処理を追加しました。
ファイルを読む処理の要点は以下です。
- new FileReader してリーダオブジェクトを作る
- ReadAsText などのメソッドでファイル読み込み処理を行う
-
- の処理は非同期処理なので、完了時等のハンドラを事前にセットしておく
リーダをつくる関数をつくりました。
create_reader = (done, fail, method = 'readAsText') ->
(e, file, files) ->
rdr = new FileReader()
rdr.onload = (e) -> done e, file, e.target.result
rdr.onerror = (e) -> fail e, file, e.target.result
rdr[method] file
onload には成功時の、onerror には失敗時のハンドラをセットします。
ハンドラに渡されるイベントの e.target にリーダ自身がセットされています。
読み込んだ内容は、e.target.result に格納されています。
読み込んだテキストが JSON の場合、JSON.parse(text) でパースできます。
想定する使い方は以下です。
fail = (e, file, result) -> ... 失敗時の処理
done = (e, file, result) -> ... 成功時の処理
reader = create_reader done, fail
$ '#drop-here'
.on 'dragover', dnd()
.on 'drop', dnd reader
サンプル: ドロップされた JSON データをプロットする
上の記事で作ったスクリプトを流用して、JSON形式でドロップされたデータをプロットするスクリプトを作りました。(CoffeeScript と Haml で記述しています)
渡される JSON形式のデータは、X座標とY座標の組(配列)の配列を想定しています。
[ [x1,y1], [x2,y2], ..., [xn, yx]]
実は、どこかの座標変換が何かがバグっているようで、少し太ったシダになります。(色をランダムにだしているのでぼやけて見えます)
ですが、ここではファイルDrop&Dragのテストが目的なので、そのバグ追跡は打ち切りました。
$ ->
# ---- 色 --------------------------------------------------
green = () -> [0, 0xff, 0, 0xff] # 単色 緑
rand = (max = 0xff, min = 0) -> min + Math.random() * (max - min + 1) |0
random_color = () -> [rand()/4, rand(), rand()/2, 0xff - rand()/8]
#
# ---- プロッタ (Canvas) -----------------------------------
#
plotter = (color = random_color, canvas = $('#drop-here')[0]) ->
ctx = canvas.getContext '2d'
img = ctx.createImageData canvas.width, canvas.height
put = (xy) ->
x = xy[0] * (+1) * (canvas.width - 10) + (canvas.width / 2) |0
y = xy[1] * (-1) * (canvas.height - 10) + (canvas.height ) |0
offset = (canvas.width * y + x) * 4
color().forEach (c, i) -> img.data[offset + i] = c |0
plot: (data) ->
[0...(canvas.width * canvas.height * 4)].forEach (i) -> img.data[i] = 0
data.forEach put
ctx.putImageData img, 0, 0
#
# ---- ファイル Drag & Drop ------------------------------
#
dnd = (f = ()->) ->
(e) ->
e.stopPropagation()
e.preventDefault()
f.apply $(this), [
e
e.originalEvent.dataTransfer.files[0]
e.originalEvent.dataTransfer.files
]
create_reader = (done, fail, method = 'readAsText') ->
(e, file, files) ->
rdr = new FileReader()
rdr.onload = (e) -> done e, file, e.target.result
rdr.onerror = (e) -> fail e, file, e.target.result
rdr[method] file
fail = (e, file, result) -> console.log 'Fail: ' + e
done = (e, file, result) ->
console.log 'ファイル読み込み: ' + file.size + ' バイト'
console.log '描画開始'
plotter().plot JSON.parse result
console.log '描画終了'
reader = create_reader done, fail
$ '#drop-here'
.on 'dragenter' +'.style', () -> $(this).addClass 'active'
.on 'dragleave' +'.style', () -> $(this).removeClass 'active'
.on 'drop' +'.style', () -> $(this).removeClass 'active'
.on 'dragover', dnd()
.on 'drop', dnd reader
!!!
%meta(charset="UTF-8")
%title ファイル Drag & Drop
:css
canvas {
background-color: #000;
}
.active {
background-color: #333;
}
%p データファイルをドロップしてください。
%canvas#drop-here(style="width: 500px; height: 500px")
このブラウザは HTML5 Canvas に対応していません。
%script(src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js")
%script(src="myscript.js")
Haml->HTML、CoffeeScript -> JavaScript の変換方法です。
$ haml -q sample.haml sample.html
$ coffee -b -c myscript.coffee
ドロップするデータの作成には以下のいずれかの方法があります。
ひとつは、以下の RubyスクリプトでJSONを出力する方法です。
#
# ---- 「シダ」のアルゴリズム ----------------------------
#
module Fern
extend Enumerable
extend self
def each(k = 20)
w1x = -> x, y { 0.836 * x + 0.044 * y }
w1y = -> x, y { -0.044 * x + 0.836 * y + 0.169 }
w2x = -> x, y { -0.141 * x + 0.302 * y }
w2y = -> x, y { 0.302 * x + 0.141 * y + 0.127 }
w3x = -> x, y { 0.141 * x - 0.302 * y }
w3y = -> x, y { 0.302 * x + 0.141 * y + 0.169 }
w4x = -> x, y { 0.0 }
w4y = -> x, y { 0.175337 * y }
f = -> k, x, y do
if 0 < k
f.(k - 1, w1x.(x, y), w1y.(x, y))
f.(k - 1, w2x.(x, y), w2y.(x, y)) if Random.rand < 0.3
f.(k - 1, w3x.(x, y), w3y.(x, y)) if Random.rand < 0.3
f.(k - 1, w4x.(x, y), w4y.(x, y)) if Random.rand < 0.3
else
yield [x, y]
end
end
f.(k, 0, 0)
end
end
if $0 == __FILE__
require 'json'
puts JSON.dump Fern.entries
end
使い方。
$ ruby fern.rb >shida.json
もうひとつは、以下の記事で作った Erlang プログラムで「シダ」データを YAMLで出力して、それをJSONに変換する方法です。
$ escript shida.erl | yj >shida.json