2016-02-22

ElectronでExactTargetのGUIツール作ってみた

ElectronでExactTargetのGUIツール作ってみました↓ Releases · tzmfreedom/ET_Tool

リファクタリングとかアイコンとか細かいところは完全無視してとりあえずリリース。

機能とか作った経緯とか

GUIで他組織間、ビジネスユニット(BU)間でデータエクステンション(DE)を一括コピーすることができます。

ExactTargetではテスト用BUで検証後に本番用BUに対して各設定をコピーして、本番反映するという非常に原始的な方法で反映することが多いのが現状です(Sandbox環境は機能としてはあるものの、うまく動かないという噂)。また、今までBU間でDEのコピーをするには、”共有フォルダにDEをコピー→共有フォルダから別BUに対してDEをコピー”という段取りを踏む必要がありました。DEに関しては、まだコピーする手段があるので良いですが、共有フォルダの概念のない設定に関しては完全に目視レベルでのデプロイとなります。これはイケていない…ということで、今回のツールを作成する契機になりましたとさ。(現時点ではGUIツールのコピーはDEのみ対応してます)

利用技術ですが、モダンでシャレオツな事がしたかったので、ES6使って、babelでES5にコンバートしてReact+Reduxも使って、gulpのwatchで適宜タスク回してElectronのLivereloadも入れて開発しました。デザインはMaterialUI+Bootstrap(Gridだけ)。

以下、開発関連の備忘録です。

JSXにおけるコンポーネント表示・非表示切り替え

こんな感じでJS直接書いて判定するみたいです。

{ this.props.isShow ? <HogeHoge /> : null}

ifとか書きたい場合は即時実行でラップする感じで

{ (() =>{
if (this.props.isShow) {
  return <HogeHoge />;
} else {
  return null;
}
})()}

複雑になる場合は関数として切り出して実行した方が見通しは良さそうです

renderHogeHoge() {
  if (this.props.isShow) {
    return <HogeHoge />;
  } else {
    return null;
  }
}

render(){
  return (
    <div>{this.renderHogeHoge()}</div>
  )
}

リピートの方法

配列に入れてrender内に入れる感じで

renderList() {
  return this.props.records.map(function(record) {
    return <li>{record.Name}</li>
  });
}

render () {
  return (
    <ul>{this.renderList()}</ul>
  )
}

Reduxによるstate管理

今回はFluxフレームワークとして有名なReduxを利用しました。

onClickなどのユーザインタラクションに対してActionを発行 →reducerがdispatchして処理実行+state変更 →stateの変更が各コンポーネントに伝搬

という感じでFluxの一方向なデータフローを実現しています。

redux-devtools

デバッグ用にElectronでreact-devtoolsがうまく動かなかったため、redux-devtoolsを使いました。

これを入れると、Stateのデバッグ、コミット、ロールバックができます。デバッグの表示方法もカスタマイザブルになっています。ステートの変更を追えてエラー箇所が特定しやすくなるので非常に便利です。使い方はこちらを参照↓

https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md

Material-UI

マテリアルデザインをReactで実現するためのライブラリであるMaterial-UIを使いました。使い方はrequireしてコンポーネントをパラメータ付きで設置するだけ。超絶便利。CSSフレームワークがReactのコンポーネントになっている、という感じ。

ちなみに、SelectFieldコンポーネントを使うときにreact-tap-event-pluginを使わないとうまく動かないので注意。

import injectTapEventPlugin from "react-tap-event-plugin";
injectTapEventPlugin();

詳細はこちら。ただし、上記記述を入れると今度はボタン系コンポーネントのプロパティでkeyboardFocused={true}となっているボタンは2回ボタンをクリックしないと動かなくなるので、keyboardFocusedは外す必要があります。(他に良い解消方法を知っている方がいればご教示ください…)

React-Bootstrap

Material-UIにはGridレイアウトが含まれないので、React用のBootstrapであるReact-BootstrapのGridだけ使いました。ちなみに、コンポーネントのロードだけではなく、cssは自前でロードする必要があります。Gridはこんな感じで設置できます。

render() {
  return (
    <Grid>
      <Row>
        <Col md={8}>8</Col>
        <Col md={4}>4</Col>
      </Row>
    </Grid>
  );
}

ローディングのオーバーレイ

OverLayのコンポーネントはあったんですが、CSSで簡単に実現できそうだったのでローディングはこんな感じで実装してます。CircularProgressはMaterial−UIのコンポーネントです。

get styleForOverlay() {
  return {
    background: 'rgba(0, 0, 0, 0.2)',
    position: 'fixed',
    top: 0,
    left: 0,
    width: '100%',
    height: '100%',
    zIndex: 10,
    display: 'block'
  };
}

render() {
  return (
    ...
    {this.props.deploy.isLoading ?
      <div style={this.styleForOverlay}>
        <CircularProgress style={{top:'40%',left:'45%'}}/>
      </div> : null }
    ...
  )
}

Electron+React+Redux+ES6でのGulpfile

こちらの記事↓を参考に作ってみました。Sassとか使わないので最低限開発できて動けばOKという感じで。 ぼくのかんがえたさいきょうのElectron

Electronでのlivereloadはelectron-connectを利用するのが良いです。あとは普通にBabelやら何やら入れればOK。Redux使うときにdecorator使わない場合は、transform-decorators-legacyは必要ありません。

コンポーネントに関数を渡すときのthis参照

コンポーネントに関数を渡して、コンポーネントが渡された親の関数を呼び出す場合、thisの取り扱いに注意する必要があります。親側の関数で、親側のプロパティを想定していて処理を書く場合は、関数を渡すときにFunction.prototype.bind()でコンテキストを明示する必要があります。

<TextField onChange={this.hogehoge.bind(this)} />

Electronアプリでコピペできない場合

開発時にはコピペできたのに、パッケージングしたらコピペができなくなる…という現象に直面したら、アプリ起動のJSに以下の様な記述を加える必要があります。

app.on('ready', function() {
  Menu.setApplicationMenu(menu);
  // Create the browser window.
  var mainWindow = new BrowserWindow({width: 800, height: 600});
  ...
});

var template = [
  {
    label: "Edit",
    submenu: [
      {
        label: "Copy",
        accelerator: "CmdOrCtrl+C",
        role: "copy",
      },
      {
        label: "Paste",
        accelerator: "CmdOrCtrl+V",
        role: "paste",
      },
      ...
    ]
  }
];

var menu = Menu.buildFromTemplate(template);

こちらのリンクが詳しいです↓ Electronでコピー&ペーストを実装する - Qiita

所感

フロントエンドは初級レベル(というかデベロッパとして初級レベル…orz)なので、とにかく覚えることが多かった…。React、Redux、MaterialUI、Electron周りのハマりどころにはトコトンはまった感じ。Reduxのaction、reducer辺りがスパゲッティになってしまっているので、もうちょいスマートに書ける方法を勉強したいところ。

あと、node_modulesがかさばっているので良い感じにBrowserifyして不要なファイルを削除したとminifyすることで圧縮できるようなので、そこらへんの対応とかアイコンとか開発用のRedux-devtoolsがコメントアウトされてるだけだったり、electron-connectがそのままだったり色々と課題があるんですが、ひとまずリリースということで。Done is better than perfectの精神。

このエントリーをはてなブックマークに追加