読者です 読者をやめる 読者になる 読者になる

Umi Uyuraのブログ

Titaniumを使ったスマートフォンアプリ開発を中心に、プログラミング関連の作業ログ。

React + NW.jsでFinderからドラッグ&ドロップ

React NW.js node-webkit

以前node-webkitで作ろうとしていたツールで、MacのFinderからファイルやフォルダをドラッグ&ドロップで指定できるようにしようとしてうまくいかなかったことがあるのですが、ふとGitHubのNW.jsのWikiを見ていたら、 それについて書かれたページ を発見してしまいました。

そこで、とりあえず手元でNW.jsでのサンプルを動かしたついでに、それをReact風に書き換えてみることにしました。

NW.jsでのドラッグ&ドロップ

WikiにはHTML内のコードだけだったので、まずはそれをはめ込むためのindex.htmlと、NW.jsとして動かすための環境を設定したpackage.jsonを用意して、 npm install で環境を構築。

そこにWikiにあったコードをそのままコピペしただけですが、問題なく動きました。

f:id:umi-uyura:20150430153818p:plain

表示されている枠内に、Finderから適当なファイルをドラッグしていくと、枠の色が変わります。

f:id:umi-uyura:20150430153831p:plain

カーソルが枠の中にある状態でドロップすると、コンソールにファイルのパスが出力されていました。

f:id:umi-uyura:20150430153842p:plain

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風に変えた部分としては、以下のあたりでしょうか。

onDragOveronDragLeave のイベント発動に応じて state.hover の値が変化して、その変化に反応して render() が呼び出されるので、そのときの state.hover に対応したスタイルで描画されることになります。

これにより、ドラッグ&ドロップのターゲットとなるコンポーネントが作れることになります。

React.render(
  <Holder />,
  document.getElementById('app')
);

window.ondragoverwindow.ondroppreventDefault() を設定している部分がありますが、そこはコンポーネントの外側なので、そのまま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のサンプルと同等の表示になりました。

f:id:umi-uyura:20150430153902p:plain

ファイルをドラッグ&ドロップすると、ちゃんとパスが取得できていました。

f:id:umi-uyura:20150430153913p:plain

こんな感じで、コンポーネント化して流用しやすくすることができる点が、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 を使うように書かれていました。

f:id:umi-uyura:20150430153927p:plain

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版では削除しています。

f:id:umi-uyura:20150430153937p:plain

Event System | React

参考

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発