React + NW.jsでFinderからドラッグ&ドロップ
以前node-webkitで作ろうとしていたツールで、MacのFinderからファイルやフォルダをドラッグ&ドロップで指定できるようにしようとしてうまくいかなかったことがあるのですが、ふとGitHubのNW.jsのWikiを見ていたら、 それについて書かれたページ を発見してしまいました。
そこで、とりあえず手元でNW.jsでのサンプルを動かしたついでに、それをReact風に書き換えてみることにしました。
NW.jsでのドラッグ&ドロップ
WikiにはHTML内のコードだけだったので、まずはそれをはめ込むためのindex.htmlと、NW.jsとして動かすための環境を設定したpackage.jsonを用意して、 npm install
で環境を構築。
そこにWikiにあったコードをそのままコピペしただけですが、問題なく動きました。
表示されている枠内に、Finderから適当なファイルをドラッグしていくと、枠の色が変わります。
カーソルが枠の中にある状態でドロップすると、コンソールにファイルのパスが出力されていました。
React + NW.js化
これをReactベースに書き換えて見ることにしました。
サンプルコードでは、ドロップする先は <div id="holder"></div>
というdivタグにCSSを当ててドロップ先を作っていましたので、これを以下のように単独のReact Componentとしてみました。
var Holder = React.createClass({ getInitialState: function() { return { hover: false }; }, doDragOver: function(e) { this.setState({ hover: true }); }, doDragLeave: function() { this.setState({ hover: false }); }, doDrop: function(e) { e.preventDefault(); for (var i = 0; i < e.dataTransfer.files.length; ++i) { console.log(e.dataTransfer.files[i].path); } }, render: function() { var styles = { leave: { border: '10px dashed #ccc', width: '300px', height: '300px', margin: '20px auto' }, hover: { border: '10px dashed #333', width: '300px', height: '300px', margin: '20px auto' } }; var style = this.state.hover ? this.styles.hover : this.styles.leave; return ( <div style={style} onDragOver={this.doDragOver} onDragLeave={this.doDragLeave} onDrop={this.doDrop}></div> ) } });
元の構造からReact風に変えた部分としては、以下のあたりでしょうか。
- 各drag/dropイベントハンドラはdivタグの属性として関数をひもづける
- CSSの切り替えは
className
を使うのではなく、コンポーネントに持たせたstate.hover
の値でおこなう
onDragOver
と onDragLeave
のイベント発動に応じて state.hover
の値が変化して、その変化に反応して render()
が呼び出されるので、そのときの state.hover
に対応したスタイルで描画されることになります。
これにより、ドラッグ&ドロップのターゲットとなるコンポーネントが作れることになります。
React.render( <Holder />, document.getElementById('app') );
window.ondragover
と window.ondrop
に preventDefault()
を設定している部分がありますが、そこはコンポーネントの外側なので、そのままindex.html内に残しています。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>NW.js Drag &anp; Drop with React</title> <script src="node_modules/react/dist/react.js"></script> </head> <body> <div id="app"></div> <script> // prevent default behavior from changing page on dropped file window.ondragover = function(e) { e.preventDefault(); return false }; window.ondrop = function(e) { e.preventDefault(); return false }; </script> <script src="build/index.js"></script> </body> </html>
これをNW.jsとして実行したところ、NW.jsのサンプルと同等の表示になりました。
ファイルをドラッグ&ドロップすると、ちゃんとパスが取得できていました。
こんな感じで、コンポーネント化して流用しやすくすることができる点が、Reactの特徴のひとつですね。
作った2種類のサンプルはGitHubにあげてあります。
umi-uyura/nwjs-dragndrop-sample
細かいメモ
Reactに書き換えるにあたって気になった点をメモ。
CSSの当て方
今回、ドラッグ中にターゲット領域上でマウスオーバーしたときにスタイルを変化させていますが、その部分をどう実装しようか迷いました。
ReactコンポーネントにCSSを適用する場合、 一般的なWebのように class
属性(Reactでは className
属性となる)を使う方法と、JSオブジェクトとして指定する方法( HTMLの style
属性に相当)と大きく2つあるようです。
さらに動的に className
を切り替える方法としては、React標準のアドオンにあるclassSetを使うこともできますが、コンポーネントのコード内で完結できることもあり、今回はJSオブジェクトとして実装することにしました。
規模が大きいアプリケーションなどでCSSを別ファイルとして用意する場合や、コンポーネントのスタイルを外出しにしたい場合など、どちらを主体にするかは都度検討が必要そう。
さらにこれを実装しているときに気付いたのですが、classSetアドオンは将来的に削除されるようで、 JedWatson/classnames を使うように書かれていました。
Class Name Manipulation | React
イベント伝播の挙動
[8050:0428/223353:INFO:CONSOLE(19548)] ""Warning: Returning `false` from an event handler is deprecated and will be ignored in a future release. Instead, manually call e.stopPropagation() or e.preventDefault(), as appropriate."", source: file:///Users/myhome/work/nwjs-dragndrop-sample/nwjs-dragndrop-with-react/node_modules/react/dist/react.js (19548)
これもドキュメントに記載がありましたが、すでに return false
を使ってイベント伝播をコントロールする方法は非推奨のようで、 e.stopPropagation()
や e.preventDefault()
を手動で呼び出して管理した方が良いもよう。
そのため、元のコードでは入っていた doDrop
の最後の return false;
の行は、React版では削除しています。
参考
- Dragging files into page · nwjs/nw.js Wiki
- ならばCSS in JS だ
- JedWatson/classnames
- umi-uyura/nwjs-dragndrop-sample
入門 React ―コンポーネントベースのWebフロントエンド開発
- 作者: Frankie Bagnardi,Jonathan Beebe,Richard Feldman,Tom Hallett,Simon HØjberg,Karl Mikkelsen,宮崎空
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/04/03
- メディア: 大型本
- この商品を含むブログ (2件) を見る