Lightning基本コンポーネントのライブエディタを作ってみる #salesforce

昨日のエントリで検討した通り、シンプルな画面を新規開発する場合においては、Lightning基本コンポーネントを極力使って開発する、というのも、今後は一つの選択肢なんじゃないかなーという仮説を立ててみました。

Lightning基本コンポーネントについては、以下に書かれています。

developer.salesforce.com

しかし、実際に開発する場合、上記ドキュメント(リファレンス)を参照しながら、ということになると思うのですが、案外しんどいことに気づきます。
それはなぜかというと、ドキュメントがテキストのみであり、結果としてどういうUIになるか、仕上がりのプレビューがないことに起因します。

そのため、ちゃんとしたものを作ろうとすると、開発者ガイド、SLDSのドキュメント、場合によってはちょんプロを作ったりと、試行錯誤して作っていくことになりそうだなと予想しています。
(と、思っているんですけど、皆さんどうしているんでしょうね…?)

というわけで、本題です。

開発のハードルを下げるためには、「ライブエディタ(リアルタイムプレビュー)とかあったらいいんじゃない?」と思い立ち、空き時間を利用して作り始めてみました。
まぁ、車輪の再発明の気もしなくもないですが、練習も兼ねてなのでいいかなと。

作ってる画面の例として、lightning:layout の場合は、以下のようなものです*1

f:id:jappy:20171214005821p:plain

このライブエディタ自体も、極力Lightning基本コンポーネントを使って開発するように、意識しています。

動きとしては、コンポーネントの属性値 horizontalAlign などを入力パラメータとして受け取って、その度にコントローラ側で $A.createComponentsコンポーネントを再作成し、PreviewとCodeに反映しています。
実際に動かしてみると、 horizontalAlign 属性には center, space, spread, endの4つのオプションがあるわけですが、それぞれのオプションで配置にどのような違いがあるのかも一目瞭然で、なかなか便利そうな気がしています。

まだまだ作りが荒いですが、モチベを保つ意味で紹介してみました。 sfdxでCI/CDをやりつつ、年内くらいには形になるといいな…。

*1:PreviewとCodeで数字がズレているのはバグですね…

Lightning Experience流のUIを作る4つの方法と比較 #salesforce

アプリをSalesforce ClassicからLightning Experience(LEX)に移行するにあたり、検討・整理した事項についてのメモです。

Salesforceの新しいUIであるLEXですが、開発する画面のLook & FeelをLEXに合わせるためには、私が知る範囲では少なくとも以下の4つの方法があります。

まぁ、どれも根底のスタイル装飾はSLDSなわけですが、作り方・扱い方の違いという観点で、4つを分けました。

主観でメリット、デメリットを整理するとこんな感じです(デメリットと言ってよいものか迷うものもありますが…)。

方式 メリット デメリット
SLDSスクラッチ 開発方式や表現力の自由度が高い マークアップが辛い。
SLDSのバージョンアップに追随する場合、コストをかけて対応が必要。
Reactライブラリ 充実したフロントエンド開発のエコシステムに乗っかって開発ができる SLDSのバージョンアップにライブラリが対応していく必要がある(利用については自己責任)
Visualforce ある程度成熟したVFの成果物を大きく変えずにLEX化できる 完璧ではないので、SLDSを使って細かい調整が必要になることもある。
Visualforceなので、表示領域の高さを固定にしておく必要がある(必要なコンテンツの高さだけ確保する、というのが難しい)
Lightning基本コンポーネント 継続的なアップデートと後方互換性が見込まれる(多分) 利用可能なコンポーネントには限りがある

汎用性(適用範囲)と生産性の高さのバランスがよいので、総合的には2番目がベターかなー、という気がしています。
もう少し掘り下げると、それぞれの方式が適切な用途というのは、それぞれ次のような時かなと思っています。

方式 あてはまるケース
SLDSスクラッチ 慣れているSPA方式で開発したい場合(React以外のライブラリ利用時。Vue.jsとか…?)
Reactライブラリ 慣れているSPA方式で開発したい場合(React利用時)
Visualforce Visualforceの標準コンポーネントを駆使して作られた既存のVisualforceページをLEX対応したい場合
Lightning基本コンポーネント 新規で作る画面であり、比較的シンプルな場合

また、4つの方式は排他的なものではなく、複数を組み合わせて開発することもあるかもしれません。
分かりにくい表ですが、その場合の組み合わせ方は下表のような手段を利用することになりそうです*1。 1番目と2番めは、成果物はSPA方式の静的リソースであるという観点では一緒なので、同一のものと見なしています。

取り込む側\取り込まれる側 SLDSスクラッチ
Reactライブラリ
(=HTML,JS,CSS)
Visualforce Lightningコンポーネント
SLDSスクラッチ
Reactライブラリ
BrowserifyやWebpack等、普通のJSエコシステムの世界 無理(?) Lightning Out
Visualforce 静的リソースとして読み込み VFコンポーネント化すればVF in VFは可。もしくはiframe Lightning Out
Lightningコンポーネント lightning:container, ltng:require iframe <lightning:コンポーネント名>, <c:コンポーネント名>

*1:実際に試していないものもあるため、かなり雑なまとめです…。

$A.createComponentsでちょっと複雑なコンポーネントを動的に作成してみる #salesforce

Lightningコンポーネントには $A.createComponent$A.createComponents メソッドを利用して、コントローラ(or ヘルパー)側で動的にコンポーネントを作成する手段が用意されています。

developer.salesforce.com

2つのメソッドの違いは単一のコンポーネントを作るか、複数の(特にネストした)コンポーネントを作るか、にあります。
$A.createComponents メソッドを使って、どれくらい複雑なコンポーネントが作れるものなのか確認してみました。

試したサンプルは、lightning:layoutにあるものです(↓)。

<aura:component>
    <div class="c-container">
        <lightning:layout horizontalAlign="space">
            <lightning:layoutItem flexibility="auto" padding="around-small">
                1
            </lightning:layoutItem>
            <lightning:layoutItem flexibility="auto" padding="around-small">
                2
            </lightning:layoutItem>
            <lightning:layoutItem flexibility="auto" padding="around-small">
                3
            </lightning:layoutItem>
            <lightning:layoutItem flexibility="auto" padding="around-small">
                4
            </lightning:layoutItem>
        </lightning:layout>
    </div>
</aura:component>

そこまで複雑というわけではないですね。。
ただ、公式の例( ui:messageui:outputText )と比べ、複数の子要素( lightning:layoutItem )を含んでいたりするので、これくらいのものができれば、後は応用が効きそうだということで選定しました。

コンポーネント : DynamicNestedComponents.cmp

Lightningコンポーネントはこんな感じになりました。

コントローラ(後述)で作成した lightning:layout, lightning:layoutItem{! v.contents} のところで出力させるようにします。

<aura:component  implements="flexipage:availableForAllPageTypes" access="global">
    <aura:attribute name="contents" type="String" />
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    
    <div class="c-container">
    {! v.contents }
    </div>
</aura:component>

なお、 implements="flexipage:availableForAllPageTypes" access="global" の部分に関しては、動作確認を適当なLightningページでするためのものなので、今回の本筋ではないです。

コントローラ : DynamicNestedComponentsController.js

コンポーネントを作成する肝心のコントローラの doInit は次のようになりました。

({
    doInit : function(component, event, helper) {        
        $A.createComponents([
            ["lightning:layout", {
                "horizontalAlign" : "space"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "1"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "2"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "3"
            }],
            ["lightning:layoutItem", {
                "flexibility" : "auto",
                "padding" : "around-small"
            }],
            ["ui:outputText",{
                "value" : "4"
            }],
        ],
            function(components, status, errorMessage){
                if (status === "SUCCESS") { 
                    var layout = components[0];
                    var layoutItems = [
                        components[1],
                        components[3],
                        components[5],
                        components[7] ];
                    var outputTexts = [
                        components[2],
                        components[4],
                        components[6],
                        components[8] ];
                    
                    // それぞれの lightning:layoutItem に値をセット
                    for (var i=0; i<4; i++) {
                        layoutItems[i].set("v.body", outputTexts[i]);
                    }
                    
                    // lightning:layout の下に lightning:layoutItem をセット
                    var layoutBody = layout.get("v.body");
                    for (var i =0; i<4; i++) {
                        layoutBody.push(layoutItems[i]);
                    }
                    layout.set("v.body", layoutBody);
                    
                    component.set("v.contents", layout);
                }
                else if (status === "INCOMPLETE") {
                    console.log("No response from server or client is offline.")
                    // Show offline error
                }
                else if (status === "ERROR") {
                    console.log("Error: " + errorMessage);
                    // Show error message
                }
            }
        );
    }
})

長いですね…。
子要素が4つあるサンプルのため冗長であることをを抜きにしても、結構長いです。

注意する点として、 set("v.body", ...) で中身をセットしたい場合、セットする値もコンポーネントである必要があります。
なので、lightning:layoutItem の中身は表示したい文字列ではなく、 ui:outputText コンポーネントになっています *1
もし、

layoutItems[i].set("v.body", "" + i);

のようにしてしまうと、

Uncaught Assertion Failed!: Descriptor for Config required for registration : undefined

といったエラーが起きます。

というわけで、複数の子要素を含んだLightningコンポーネントでも一応は動的に作成可能であることが確認できました。

*1:実はこのせいで、出力されるHTMLはオリジナルのものとは微妙に異なります。ui:outputText はspanタグを作ってしまうので。

Salesforce開発デザインパターン集 #salesforce

Salesforce Platformの上でApexクラス・トリガやLightningコンポーネントを開発していく上でのデザインパターンについてのまとめ。
他にもあるかもしれないです(知っていたら教えてください)

先人の知恵、大事。

トリガ

Trigger Frameworks and Apex Trigger Best Practices - developer.force.com

適用していないプロジェクトの方が少ないかもしれないくらい定番かもしれないですね。
複数のオブジェクトを操作するような混み入ったDMLを扱う時に、 bypass, clearBypass が役立ちます。

スケジュールバッチ

tyoshikawa1106.hatenablog.com

Lightning Component

広範囲のパターン集(全部はまだ追えていません…)

Salesforce Lightning Component Design Patterns and Development Best Practices - Mike Topalovich | Chicago Salesforce Architect and CTO

ApexCallだとこちら。

qiita.com

早速今年のAdvent Calendarからですね。 なるほどー!と思いました(小並感)

番外編(?)HTTP コールアウト

github.com

パターンというよりもガッツリしたライブラリですが。使い所は選びそう。。


以上。
あとは、Apex言語そのものの進化にも期待したいところです。

AWS CodeBuildでいつの間にかDependency Cacheがサポートされてた

re:Inventの流れが早くて見逃していたのですが、AWS CodeBuildで依存関係のキャッシュ機構が使えるようになっていました。

aws.amazon.com

昔、自前で似たようなことをするためのやり方を試行錯誤してたのですが、もう不要ですね。

jappy.hatenablog.com

あとあるとすれば、ビルドにカスタムDockerイメージを使っている場合に、イメージのダウンロードに時間がかかってしまう課題が解消できると、最強な気がします。

SOAP API(wsc)で地理位置情報型や時間型を扱う #salesforce

Advent Calendarの季節になりましたね。世のエンジニアの1年間のノウハウが放出されるイベント、楽しみです。

さてそんな中、Advent Calendarとまったく関係のない記事になりますw(エントリを逃したとも言う…)

SFDC謹製のJavaAPIクライアントであるWSC (Force.com Web Service Connector)を使って、ちょっとクセのある(?)「地理地情報型(Location)」や、Summer'17でパイロットリリースされた「時間データ型(Time)」を操作する時のやり方についてのメモです。

地理位置情報型・時間型の扱い方

先に結論ですが、それぞれ以下のように扱うことで、利用できます。

  • 地理位置情報型
    • 緯度と経度を別々に扱えばOK。例えば項目のAPI参照名が FooLocation__c の場合は、 緯度が FooLocation__Latitude__s, 経度が FooLocation__Longitude__s のようになる
  • 時間型
    • "00:00:00.000Z" の形式

地理位置情報型、以下のIssueにある通り、少し前まではダウンロードしてきたWSDLに手を加えないと使えなかったのですが、今は直っているみたいですね*1

github.com

また、時間データ型についてはベータなので、今後変更になる可能性もある気もしますが、現時点での結果ということで…。

サンプルコード

レコード1件をINSERTする場合の例。

public class Sample {

    private static final Logger log = LoggerFactory.getLogger(Sample.class);

    public static void main(String... args) throws Exception {
        ConnectorConfig config = new ConnectorConfig();
        config.setUsername("[ログイン名]");
        config.setPassword("[パスワード]");

        PartnerConnection connection = Connector.newConnection(config);
        
        SObject obj = new SObject("Hoge__c");
        obj.setField("Name", "test");
        obj.setField("Time1__c", "12:00:00.000Z"); // これで 12:00 として時間が登録される
        obj.setField("Location1__Latitude__s", "35.1");
        obj.setField("Location1__Longitude__s", "135.2");

        SaveResult[] results = connection.create(new SObject[]{obj});

        for (SaveResult result : results) {
            log.info("{} {} {}", result.getId(), result.getSuccess(), result.getErrors());
        }
    }
}

実行環境

  • Java 8
  • WSC : "42.0.0" タグ
  • Partner WSDL : API Version 41.0

*1:しれっと会社のGitHubアカウントで +1 していますが。

Java8のLambda式とGroovy

Spockでテストコードを書く時の備忘録。

Consumer : void accept(T)

メソッド

public void method1(List<String> list, Consumer<List<String>> consumer)

Java

hoge.method1(foo, list -> list.add("aaa"));

Groovy

hoge.method1(foo, { list -> list.add('aaa') })

Predicate : boolean test(T)

メソッド

public void method2(String text, Predicate<String> predicate)

Java

hoge.method2("test", s -> s.length() < 5);

Groovy

hoge.method2('test', { s -> s.length() < 5 }) // size() でもOK

Supplier : T get()

メソッド

public void method3(Supplier<String> supplier)

Java

hoge.method3(() -> "test");

Groovy

hoge.method3({ 'aaa' }) // -> 'aaa'
hoge.method3({ -> 'bbb' }) // -> 'bbb'

Function : R apply(T t)

メソッド

public void method4(String text, Function<String, String> function)

Java

hoge.method4("abcde", s -> s.toUpperCase()); // -> "ABCDE"
hoge.method4("abcde", String::toUpperCase); // -> "ABCDE"

Groovy

 hoge.method4('abcde', { s -> s.toUpperCase() }) // -> 'ABCDE'

ちなみに、Groovy2.6からは、オプションとして -Dgroovy.antlr4=true を指定すると、GroovyでもLambda式がそのまま使えるみたいです(このブログを書いた直後に偶然知りました)