LoginSignup
3
3

More than 5 years have passed since last update.

CoffeeScript: ファイル Drag&Drop

Posted at

HTML へのローカルファイルの Drag & Drop のメモです。

JavaScript は CoffeeScript で記述しています。jQeury を使用しています。

ondrop イベント

ファイル Drag&Drop の処理の要点をまとめると、以下のようでした。

  1. ファイルをHTML要素にドロップすると、ondrop イベントがあがる
  2. それをハンドルする
  3. ただし、デフォルト動作(ファイルを表示するなど)が好ましくないので、それは無効にする

なので、それ用のハンドラのラッパを作りました。

dnd.coffee
  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 はできるようになったので、ファイルの中身を読む処理を追加しました。

ファイルを読む処理の要点は以下です。

  1. new FileReader してリーダオブジェクトを作る
  2. ReadAsText などのメソッドでファイル読み込み処理を行う
  3. 2. の処理は非同期処理なので、完了時等のハンドラを事前にセットしておく

リーダをつくる関数をつくりました。

create_reader.coffee
  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のテストが目的なので、そのバグ追跡は打ち切りました。

myscript.coffee
$ ->
  # ---- 色 --------------------------------------------------
  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
sample.haml
!!!
%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を出力する方法です。

fern.rb
#
# ---- 「シダ」のアルゴリズム ----------------------------
#
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
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3