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

dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

follow us in feedly

Numeral.jsのlocaleを変更して桁区切り文字をカンマからピリオドにする

日本だと桁区切り文字は,(カンマ)、小数点は. (ピリオド)だけど世界的に見ると国によって異なる。
世界各国での数字の区切り方 | コリス

JavaScript で数値を扱うときには Numeral.js を使っていたんだけど、Numeral でも(Moment.js などと同じく)locale という概念があり、これを使うと桁区切り文字と小数点を変更することができる。

1.x 系と 2.x 系でやり方がだいぶ変わっていたので、メモ。


v1.5.6

公式ドキュメント は 2.x 系の話しかないので、 https://github.com/adamwdraper/Numeral-js/tree/1.5.6 の README や テストメソッド を参考にする。
locale() ではなく language() らしい。

import numeral from 'numeral';
import it from 'numeral/languages/it';

// locale のセット。必要なものを import して `language` の第二引数に指定する必要がある
numeral.language('it', it);
// 実際の locale の変更のためにもう一度実行
numeral.language('it');

numeral.language(); // 'it'

console.log(numeral(1234.567).format('0,0.0000')); // 1.234,5670

numeral.reset();
numeral.language(); // 'en'

console.log(numeral(1234.567).format('0,0.0000')); // 1,234.5670


v2.0.6

2.x 系だとこんな感じになる。

import numeral from 'numeral';
import 'numeral/locales/it';

// numeral.register() は不要

// locale のセット
numeral.locale('it');
console.log(numeral.locale()); // 'it'

console.log(numeral(1234.567).format('0,0.0000')); // 1.234,5670

numeral.reset();
console.log(numeral.locale()); // 'en'

console.log(numeral(1234.567).format('0,0.0000')); // 1,234.5670

いくつか補足すると

1.x -> 2.x での変更

2.0.0 のときの Changelog を参考にすると、locale まわりに関係のある変更は

Breaking change: All language now renamed to locale and standardized to all lowercase filenames
Breaking change: The locale function no longer loads locales, it only sets the current locale

ということで language -> locale へ変更になった。


numeral.register()

最初、ドキュメントの Locales の項 を読むと
locale の load には numeral.register() を使えと書いてある。

// load a locale
numeral.register('locale', 'fr', {
    delimiters: {
        thousands: ' ',
        decimal: ','
    },
    abbreviations: {
        thousand: 'k',
        million: 'm',
        billion: 'b',
        trillion: 't'
    },
    ordinal : function (number) {
        return number === 1 ? 'er' : 'ème';
    },
    currency: {
        symbol: '€'
    }
});

// switch between locales
numeral.locale('fr');

なので、1.x 系と同じように必要な locale ファイルを import したあと、

import it from 'numeral/locales/it';

numeral.register('locale', 'it', it);

で登録する必要があるのかなと思ったんだけど、違った。

TypeError: it locale already registered.

というエラーが出る。

これは、実際の locales/xx.js ファイル
https://github.com/adamwdraper/Numeral-js/blob/master/locales/it.js

を見るとよくわかるんだけど、読み込むと自動的に register() されるみたい。

なので、用意されている locale を使わずに自分で定義する場合のみ register() を使う。


おわりに

最初、format() 文字列の 0,0.00 とかのカンマやピリオドを変更しないといけないのか!と思っていたけど
そんなことなくてよかった。

import numeral from 'numeral';
numeral.register('locale', 'foo', {
  delimiters: {
    thousands: 'あああ',
    decimal: 'えええ'
  },
});
numeral.locale('foo');
console.log(numeral(1234.567).format('0,0.0000')); // 1あああ234えええ5670

作って学ぶRedux Middleware

はじめに

Redux で非同期処理を行うための Middleware である redux-thunk は、わずか 10 数行のこのようなコードです。

https://github.com/gaearon/redux-thunk/blob/v2.2.0/src/index.js

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

最初にこれを見たとき、「なんでこんなにアロー関数が連続してるんだろう?」とか「 next ってなんだ?」とかさっぱりわかりませんでした。

というわけで公式のドキュメント

Middleware · Redux

を読んでみたところ、Step by Step で Middleware の概念を説明していてわかりやすかったので、その流れにそって Middleware とは何かを自分用にメモしておきます。

コード

https://github.com/zaki-yama/learn-redux-middleware

公式ドキュメントを写経しただけですが、コードはこちらに置いてあります。
いちおう各章に対応したタグもあります。


Problem: Logging

ここでは redux-logger のような logging 機能の実装を例にして、Middleware の仕組みを学んでいきます。
すなわち、ある action が dispatch されたとき、action の内容と、それによって変更された後の state の情報を console に出力するような機能です。


Attempt #1: Logging Manually

最も単純な実装は、 dispatch(action) のたびに手動で console.log を仕込むことでしょう。

(source)

let action = addTodo('Use Redux')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

当たり前ですがこれを毎回やるのはつらいです。


Attempt #2: Wrapping Dispatch

次に、logging まで含めた一連の処理を wrap した関数を作るというのはどうでしょうか。

(source)

function dispatchAndLog(store, action) {
  console.log('dispatching', action);
  store.dispatch(action);
  console.log('next state', store.getState());
}

dispatchAndLog(store, addTodo('Use Redux'));

先ほどよりはマシになりましたが、dispatch する箇所すべてでこの関数を import してやる必要があります。


Attempt #3: Monkeypatching Dispatch

store の dispatch 関数を別の関数で置き換えてはどうでしょうか?いわゆる monkeypatch です。
store はメソッドがいくつか定義された plain object なので可能です。

(source

let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
}

store.dispatch(addTodo('Use Redux'));

next が登場しました。元の dispatch を指すようですね。
dispatch そのものの定義を書き換えたので、以後どこで dispatch が呼ばれても logging されることになります。


Problem: Crash Reporting

先ほどの monkeypatch でうまくいったように見えましたが、logging と同じようなことを 2 つ以上やろうとするとどうなるでしょうか。
ドキュメントで挙げている例としては、Sentry というエラーレポートサービスへの Crash Reporting です。

こんな感じで patch 用関数を logging と crash reporting それぞれで用意し、順番に store に適用する必要があります。

(source)

function patchStoreToAddLogging(store) {
  let next = store.dispatch;
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action);
    let result = next(action);
    console.log('next state', store.getState());
    return result;
  }
}

// Dummy
const Raven = {
  captureException: function(err, optional) {
    console.error(err);
    console.log(optional);
  },
};

function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch;
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action);
    } catch (err) {
      console.error('Caught an exception!', err);
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState(),
        },
      });
      throw err;
    }
  }
}

patchStoreToAddLogging(store);
patchStoreToAddCrashReporting(store);

store.dispatch(addTodo('Use Redux'));

NOTE:Raven というのが Sentry というサービスを使う際に利用するオブジェクトのようですが、サンプルなので適当なダミーオブジェクトとしています

patch 用関数を順番に store に適用しないといけないあたりがいまいちです。


Attempt #4: Hiding Monkeypatching

メソッドを好きなように置き換えてしまう monkeypatch はハックのようなもので、置換するかわりに新しい dispatch 関数を返すようにしてはどうでしょうか。

(source)

function logger(store) {
  let next = store.dispatch;

  // Previously:
  // store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action);
    let result = next(action);
    console.log('next state', store.getState());
    return result;
  }
}

そして、実際に monkeypatch する部分をこのように定義します。

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();

  middlewares.forEach(middleware => {
    store.dispatch = middleware(store);
  });
}

applyMiddlewareByMonkeypatching(store, [logger, crashReporter]);

middlewares.forEach のところで順番に middleware を適用しており、これが結果的に順番に dispatch を置換するような処理になっています。
うまくまとまってきましたが、monkeypatch していることには変わりません。


Attempt #5: Removing Monkeypatching

そもそもどうして dispatch を overwrite しないといけなかったかというと、(middleware を適用した) dispatch を後で実行するからというのももちろんありますが、重要なのは「すべての middleware は直前の middleware が適用された後の dispatch を参照できる」必要があったからでした。

※訳が怪しい。原文は

Why do we even overwrite dispatch? Of course, to be able to call it later, but there’s also another reason: so that every middleware can access (and call) the previously wrapped store.dispatch:

つまり、先ほどの

function logger(store) {

  let next = store.dispatch;
  ...

store.dispatch は、一番最初の dispatch ではなく、直前の middleware が適用された結果になっていないと chaining が成立しません。

が、dispatch を置き換える方法以外にもこれを実現する手段はあって、それは dispatch を引数として受け取るようにしてしまうというものです。

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

そんでもってこれは ES2015 のアロー関数を使うとこのように書けます。

(source)

const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
}

最初に見た redux-thunk の実装とほとんど同じになりました。

ここまでで middelware は next という dispatch 関数を取り、新しい dispatch 関数を返す関数になりました。
store は別に使ってないので一番外側の関数はいらないんじゃないか?と思いますが
getState() を使って state 全体にアクセスできた方が色々と都合がいいのでそうなっているそうです。


Attempt #6: Naïvely Applying the Middleware

Attempt #4 で実装した applyMiddlewareByMonkeypatching() のかわりに、monkeypatch を排除した middleware を適用するための applyMiddleware() を書いてみます。

(source)

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice();
  middlewares.reverse();

  let dispatch = store.dispatch;
  middlewares.forEach((middleware) => {
    dispatch = middleware(store)(dispatch);
  });

  return Object.assign({}, store, { dispatch });
}

const storeWithMiddlewares = applyMiddleware(store, [logger, crashReporter]);

storeWithMiddlewares.dispatch(addTodo('Use Redux'));

middleware を順次適用している部分の書き方が変わりました。

NOTE:ドキュメントには書いてませんが、この時点で実際の動きを確かめる場合は applyMiddleware() が新しい store を返していることに注意です。↑の例では storeWithMiddlewares に格納しています。

Redux の実際の applyMiddleware() も似たような実装ですが、以下の3点が異なります。

1)middleware の引数になっているのは実際には store そのものではなく、store の API の一部である dispatch(action)getState() です。

NOTE:なので、redux-thunk の一番外側は
return ({ dispatch, getState }) => next => action => { となっていますね。

2)実際の applyMiddleware() では、middleware 内で next(action) ではなく store.dispatch(action) が呼ばれたときも、それまでの middleware が適用された dispatch が実行されるような考慮がされています。

3)store に対して middleware の適用が1回きりになるよう、実際の applyMiddleware() は store そのものに対してではなく createStore() を扱う関数になっています。
つまり

(store, middlewares) => store

(store と middlewares を受け取り、新しい store を返す関数)

ではなく

(...middlewares) => (createStore) => createStore

(middlwares を受け取り、「createStore() 関数を受け取ると middleware が適用された後の createStore() を返す」関数を返す関数)

です。

ただ、実際には使う前に createStore() に関数を適用するのは面倒なので、
createStore() の最後の optional な引数として applyMiddleware() を受け取れるようになっています。

const store = createStore(
  todoApp,
  applyMiddleware(logger, crashReporter), // createStore() 時に middleware を適用してる
);

NOTE:このへんいまいち理解できてませんが


The Final Approach

というわけで最終的にはこのようになりました。

(source)

// middleware
const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action);
  console.log('next state', store.getState());
  return result;
}
// crashReporter は省略
import { createStore, applyMiddleware } from 'redux';
import { addTodo } from './actions';
import todoApp from './reducers';

const store = createStore(
  todoApp,
  applyMiddleware(logger, crashReporter),
);

store.dispatch(addTodo('Use Redux'));

NOTE:Attempt #6 で「1)実際の applyMiddleware() は store そのものではなく dispatchgetState() を受け取る」とあるのにこれでうまく動いているのはなぜ?と思いましたが
インターフェースが store => next => ... でも ({ dispatch, getState }) => next => ... でも
一番外側は object を受け取ってることには変わりないですね。


おわりに

というわけで順を追っていくとなんとなく middleware の仕組みや middleware 関数の読み方が理解できました。

  • middleware は dispatch 関数を拡張する役割
  • next は元の dispatch 関数

ということだけ覚えておいて、あとは「こういう書き方をするんだなー」ぐらいでもいいかなと思いました。

ちなみに公式ドキュメントの最後には Seven Examples という章があり、そこで logger や crashReporter 以外のサンプルも載っているので参考になります。

Salesforce:JavaScript Remoting(@RemoteAction)をPromiseで扱う

はじめに

Visualforce で Angular や React などの JavaScript ライブラリを使ったアプリケーションを作ろうと思った場合、
Apex で定義したメソッドの実行には JavaScript Remoting を使うのが一般的かと思います。

似たようなことを実現するための手段として リモートオブジェクト(Remote Object) というのもありますが、オブジェクトの単純な CRUD 処理以上のことをやろうとすると JavaScript Remoting が必要になります。両者の比較は こちら

JavaScript Remoting は以下のような書き方で、JavaScript から対象の Apex クラスおよびメソッドを指定します。

// 方法1
[namespace.]controller.method(
    [parameters...,]
    callbackFunction,
    [configuration]
);

// 方法2
Visualforce.remoting.Manager.invokeAction(
  '{!$RemoteAction.controller.method}',
  [parameters...,]
  callbackFunction
);

ここで問題点としては、どちらの書き方にも言えることですが、 Apex 側からのレスポンスがきた後の処理をコールバック関数として渡さなければならない点です。

コールバック地獄としてたびたび問題になる話ですが、最近では Promise や async/await 構文といった書き方があるので
今回は Promise でこの部分を書けないか試してみます。


コード

できあがったものを先に置いておきます。
なお、アプリケーション自体は React/Redux で書かれています。


Promise を使わない(= コールバックを使った)書き方

はじめに、Redux で JavaScript Remoting をそのまま使った場合の書き方。

// actions.js
import Remoting from '../service';

export function sayHelloCallback() {
  return (dispatch, getState) => {
    dispatch(loadingStart());
    new Remoting().sayHelloCallback((result) => {
      // 実際にはここでレスポンスの正否を確認する
      console.log(result);
      dispatch(loadingEnd());
    });
  };
}
// service.js
export default class SfRemoting {
  getRemoting() {
    // eslint-disable-next-line
    return __NAMESPACE__RemoteActionController;
  }

  sayHelloCallback(callback) {
    this.getRemoting().sayHello((result, event) => {
      console.log('result:', result);
      console.log('event:', event);
      callback(result);
    });
  }
}

Action と JavaScript Remoting 呼び出しを分離するために service.js というファイルを作っています。
Apex 側には sayHello() という引数を取らない RemoteAction メソッドが定義されているという前提です。

// RemoteActionController.cls
public class RemoteActionController {
    @RemoteAction
    public static String sayHello() {
        return 'Hello, World';
    }
}

また、方法1 の書き方で JavaScript Remoting を実行しようとすると namespace の問題をなんとかしないといけないので
webpack の string-replace-loader で(やや強引に)解決しています。

// webpack.config.js
module.exports = {
  module: {
    loaders: [
      ...
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'string-replace-loader',
        query:{
          search: '__NAMESPACE__',
          replace: process.env.NAMESPACE ? `${process.env.NAMESPACE}.` : '',
        }
      },

ビルド実行時に NAMESPACE=foo というように環境変数を渡したときだけ namespace を付与します。

Action の

new Remoting().sayHelloCallback((result) => {

のところでコールバックによるネストが発生しており、複数の JavaScript Remoting の呼び出しがあるとネストが深くなります。
また並列に実行するような書き方もできません。(私の知る限りですが)


Promise を使った書き方

service.js 内のメソッドを Promise を使った書き方に直してみます。自信はないですがこんな感じになるはず…!

// service.js
export default class SfRemoting {
  execute(methodName, ...args) {
    return new Promise((resolve, reject) => {
      this.getRemoting()[methodName](...args, (result, event) => {
        if (event.status) {
          resolve(result);
        } else {
          console.log('Remote Action error occured:', event);
          reject({ message: event.message, where: event.where });
        }
      });
    });
  }

  sayHelloPromise() {
    return this.execute('sayHello');
  }
}

RemoteAction メソッドのコールを execute メソッドに集約し、その中で Promise を使っています。
また、コールバック関数内でレスポンスの内容を元に resolve() または reject() を実行しています。

このような書き方にすると、Action 側は以下のように書けます。

export function sayHello() {
  return (dispatch, getState) => {
    dispatch(loadingStart());
    new Remoting().sayHelloPromise()
      .then((result) => {
        console.log(result);
        dispatch(loadingEnd());
      })
      .catch((err) => {
        console.error(err.message, err.where);
        dispatch(loadingEnd());
        dispatch(raiseError(err.message, err.where));
      });
  };
}

ネストすることなく後続の処理を書くことができました。
また sayHelloPromise の後に RemoteAction メソッドの呼び出しが続く場合も

.then((result) => {
  return new Remoting().anotherRemoteActionMethod();
})
.then((result) => {
  ...
})

というように then 内で別の RemoteAction メソッドの呼び出しを行うことで、ネストを深くせずにメソッドをチェインすることができます。


補足:エラーハンドリングについて

JavaScript Remoting のレスポンスは第一引数が Apex メソッドの戻り値、第二引数が以下の項目を含む event オブジェクトです。
(参考:リモート応答の処理 | Visualforce 開発者ガイド | Salesforce Developers

項目 説明
event.status 成功のときは true、エラーのときは false になります。
event.type 応答の種別: 成功したコールは rpc、リモートメソッドが例外を返した場合は exception のようになります。
event.message 返されたエラーメッセージが含まれます。
event.where リモートメソッドにより生成された場合は、Apex スタック追跡が含まれます。

そのため、基本的には event.status の値を見て成功/失敗を判断すれば良いことになります。
また event.message には Apex 側で throw した Exception にセットしたメッセージです。

// RemoteActionController.cls
public class RemoteActionController {

    public class MyException extends Exception {}

    @RemoteAction
    public static void sayHelloError() {
        throw new MyException('Something bad happened!');
    }
}

(画面)

f:id:dackdive:20170505211953p:plain:w320

詳細はサンプルコードをご参照下さい。


おまけ

引数の型や数が間違ったときなどに

Visualforce Remoting: Parameter length does not match remote action parameters: expected 1 parameters, got

のようなエラーがブラウザのコンソールに出力されますが、これは補足できないぽい…?

参考:名前空間および JavaScript Remoting | Visualforce 開発者ガイド | Salesforce Developers

invokeAction のコール時に発生したエラーは JavaScript コンソールでのみレポートされます。たとえば、$RemoteAction で複数の名前空間に一致する @RemoteAction メソッドが見つかった場合、最初に一致したメソッドを返し、JavaScript コンソールに警告を記録します。 一致するコントローラまたはアクションが見つからない場合は、そのコールはエラーを表示することなく失敗し、エラーは JavaScript コンソールに記録されます。


リファレンス

Visualforce 開発者ガイドより、

webpack+ES2015でモダンGoogle Apps Script開発

するための scaffold 的なものを作った。

gapps を使って Google Apps Script をローカルで開発するための方法については 以前 Qiita に書いた し、それにより ES2015 や TypeScript で書いて手元でビルド、みたいなことはみんなやるようになったんだけど
webpack を使った構成が探してもあまり見つからなかったので自分用に作った。


特徴

ES2015 で書いて webpack でビルド、ESLint で構文チェック

業務でも使っているスタックで GAS が書けるようになった。 import/export でファイルを適切に分割できてうれしい。
最終的なビルド結果を GAS で使える用にエクスポートする方法がわからなかったんだけど、gas-webpack-plugin というのを使わせていただいた。

// webpack.config.babel.js
import GasPlugin from 'gas-webpack-plugin';

export default {
...
  plugins: [
    new GasPlugin(),
  ]
}

と書き、スクリプト側は

global.hello = function () {
}

とすれば良い。


.env に定義した環境変数を読み込めるように

GAS で何かしたいときって Slack などの外部サービスと連携することが多く、トークンなどの情報をコード中に埋め込むとスクリプトが公開しづらくなるかと思って .env から読み込めるようにした。


watch モードつき

ファイルの変更監視して再ビルド・アップロードをできるように watch コマンドも作っておいた。
https://github.com/mikeal/watch というのがあるらしい。npm scripts だけでいけるんですね。

Meguro.es x Gotanda.js #1 in Drecomに行ってきた

行ってきました。


hyperapp について Jorge Bucaran

  • https://github.com/hyperapp/hyperapp
  • SPA を作るための 1kb の軽量ライブラリ
  • Virtual DOM + Redux/Elm 的な State 管理 + Router
  • Fast
  • React や Vue は framework っぽすぎ
  • "dependencies": {}


API ドキュメンテーションと mock サーバ mizuki_r


JSerのためのブラウザコードリーディング(テスト編) edwardkenfox


大規模静的サイトのためのビルドツール ktsn


仮想domを実装した感想 brn0227


Single File Components++ kazupon


TypeScriptのプラグインについて Quramy


Cycle.js の紹介 ( 改訂版 ) - リアクティブ・プログラミングに特化した JS フレームワーク wakamsha

  • Cycle.js = Observable x Virtual DOM
  • xstream (RxJS の軽量版) と Snabbdom (仮想DOMを操るシンプルなライブラリ) に依存
    • rxjs は Promise のすごいやつ
    • xstream: github.com/staltz/xstream
  • Viewも純粋なJSの関数で組み立てる。のでtypoするとビルドエラーで気づける
  • アプリケーションを main() と driver() に分ける
    • driver はDOM操作、API実行など副作用を扱う
    • main は state を管理する
    • driver は副作用を main に投げ、main は state を更新した結果を driver に Sink する??このサイクルが名前の由来

React/Reduxの設計に関する参考記事まとめ

最近悩んでいる React/Redux でそれなりの規模のアプリケーションを作るための良い設計について、
日頃ネットで気になる記事を漁っていたんだけど、積読がたまってきた&Twitter やら Pocket やら Qiita に散らばってしまったのでここらで一旦整理します。

(★ はその中でも特に読みたいもの)


Redux の設計

実際に GitHub にあるソースコードを読んで得られた知見について。
「Storeはどんな具合に構成すべきか」「Store初期化(hydration)用データの定義はどうすべきか」「Componentはどう整理すべきか」と目次だけ見ても非常に実用的な内容。

Ducks というディレクトリ構成の考え方。

React/Redux に限らず、複雑なアプリケーションをどう設計するかという点で非常に参考になる。
またこの中の 複雑なJavaScriptアプリケーションを考えながら作る話 で Flux の話をしている。
CQRS を理解したい。

無料の Ebook。


Immutable.js

Redux にモデル層を云々と言われているやつ。

サンプルは簡単な Todo アプリだが、 Immutable.Record を使ったモデルという考え方を理解するのに一番良い記事に見える。

その他、日本語でも入門的記事はいくつか見つかった。


Redux その他


バリデーション

Redux でバリデーション処理をどこに実装するのが正解なのかなーというのは自分も気になっていたので。


非同期処理

いまは redux-thunk 使っているけど、redux-saga 使うことになったら読みたい記事。
後者は Action を UI 寄りの低レベルのものとビジネスロジック寄りの高レベルのものに分けて考えていて、saga を使わなくてもこういった考え方は参考になる。

こちらは async/await 使えば Middleware なくてもきれいに書けるんじゃないか、という話。


Tips 的な

一回目を通すだけで良さそう。

React の様々なパターンやテクニックについて。


番外編:RxJS

興味はあるがおそらく手を出すのはだいぶ先かなー。。。

Lightning Utility Barを作ってみた

Spring'17 から利用可能になった Utility Bar を作ってみたので、その手順を残しておきます。

f:id:dackdive:20170419225949p:plain

とりあえず Hello, World! と表示するところまで。

こちらの記事が参考になりました。

一応、公式の Implementation Guide とやらもあるみたいだけど、
https://resources.docs.salesforce.com/204/latest/en-us/sfdc/pdf/utility_bar_impl_guide.pdf
おそらく情報が古く、REST API 経由で操作するような記述が見られるが実際には必要ありませんでした。

以下、作り方。


Utility Bar のコンテンツとなる Lightning Component を作る

通常の Lightning Component と同じように作ります。
flexipage:availableForAllPageTypes を implement する必要があります。

<aura:component implements="flexipage:availableForAllPageTypes">
  <div class="slds-text-heading--large">Hello, World!</div>
</aura:component>


FlexiPage メタデータを用意する

Lightning Utility Bar も、メタデータ的には App Builder で作ったページと同じ FlexiPage なんだそうです。
FlexiPage のメタデータについては以下。

FlexiPage | Metadata API Developer Guide | Salesforce Developers

実際には、いきなりローカルでメタデータを作るのではなく

設定 > アプリケーション > アプリケーションマネージャ

より任意のアプリケーションの編集画面を開き、「ユーティリティバー」タブから画面上でユーティリティバーを作成し、できたメタデータをダウンロードする方が早いです。
「追加」を押すと先ほどの Lightning Component も表示されるはず。

f:id:dackdive:20170420000102p:plain

保存したら Force.com Migration Tool や jsforce-metadata-tools などを使って CustomApplication と FlexiPage を retrieve してきます。

retrieve してきた FlexiPage の中身はこんな感じ。

<?xml version="1.0" encoding="UTF-8"?>
<FlexiPage xmlns="http://soap.sforce.com/2006/04/metadata">
    <flexiPageRegions>
        <componentInstances>
            <componentInstanceProperties>
                <name>eager</name>
                <type>decorator</type>
                <value>false</value>
            </componentInstanceProperties>
            <componentInstanceProperties>
                <name>height</name>
                <type>decorator</type>
                <value>480</value>
            </componentInstanceProperties>
            <componentInstanceProperties>
                <name>icon</name>
                <type>decorator</type>
                <value>custom_apps</value>
            </componentInstanceProperties>
            <componentInstanceProperties>
                <name>label</name>
                <type>decorator</type>
                <value>UtilityBarSample</value>
            </componentInstanceProperties>
            <componentInstanceProperties>
                <name>scrollable</name>
                <type>decorator</type>
                <value>true</value>
            </componentInstanceProperties>
            <componentInstanceProperties>
                <name>width</name>
                <type>decorator</type>
                <value>440</value>
            </componentInstanceProperties>
            <componentName>UtilityBarSample</componentName>
        </componentInstances>
        <name>utilityItems</name>
        <type>Region</type>
    </flexiPageRegions>
    <masterLabel>MyFirstUtilityBar</masterLabel>
    <pageTemplate>one:utilityBarTemplateDesktop</pageTemplate>
    <type>UtilityBar</type>
</FlexiPage>

ファイル名は [アプリケーション名]_UtilityBar.flexipage とかになってるはずですが、カスタムアプリケーション側の <utilityBar> 名と一致していれば好きな名前を付けられるはず(未確認)。

また、画面から設定できるユーティリティ項目は、XMLcomponentInstanceProperties >name に対応しますが、その対応付けは確認した限りは以下の通りです。

f:id:dackdive:20170420001905p:plain

eager 以外はわかりやすいですね。

また、 <componentName> は Lightning Component 名と揃える必要があります。


CustomApplication のメタデータに追加する

最後に、Utility Bar をアプリケーションに追加した場合の CustomApplication メタデータは以下のようになります。

<?xml version="1.0" encoding="UTF-8"?>
<CustomApplication xmlns="http://soap.sforce.com/2006/04/metadata">
    <brand>
        <headerColor>#0070D2</headerColor>
    </brand>
    <formFactors>Large</formFactors>
    <label>MyLightningApp</label>
    <navType>Standard</navType>
    <uiType>Lightning</uiType>
    <utilityBar>MyLightningApp_UtilityBar</utilityBar>
</CustomApplication>

<utilityBar>MyLightningApp_UtilityBar</utilityBar> の部分です。


おわりに

画面から Utility Bar を作れるようになったことでずいぶん楽になったんでしょう。
今回は Hello, World! を表示するだけの実用性のないコンポーネントでしたが、いろんな可能性のある機能なので引き続き何か作りたいと思います。

冒頭で紹介した記事のほか、こちらにもサンプルコードがあるので参考になりそう。


GitHub

https://github.com/zaki-yama/lightning-component-samples/tree/master/lightning-utility-bar