$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式がそのまま使えるみたいです(このブログを書いた直後に偶然知りました)

Trailheadはじめました(2) #salesforce

10月も終わろうとしていますが、依然とほぼ同じペースで時間を確保することができ、大分進みました。

  • ランク: Ranger
  • バッジ: 202(内1つはハロウィーンのイベントバッヂ)
  • ポイント: 165,325
  • トレイル: 32

50個ごとにSuperbadgeにチャレンジするという計画は、100個取った時点で軌道修正しました。
そもそも今ある全部のバッヂを足しても300行かなそうなので…。というわけで、週末を利用してSuperbadgeはコンプリートしました。

必然的に日本語訳されていないモジュールに取り組むことも多くなりましたが、英語と日本語で内容が違っているのとかは相変わらずでしたね。
特に、Lightningコンポーネント関連のモジュールは多かった気がします。それだけ発展途上ってことなのかなと思いました。

今後は、ちょっとペースを落としつつ、お気に入りしているボリュームのあるモジュール(とプロジェクト)を消化していこうかなぁと思います。

Trailheadはじめました #salesforce

ずっと食わず嫌いで手を付けていなかった、というわけでもないのですが、ドラクエ11も裏ボスまで倒してやることがほぼなくなったということもあり、Trailheadをはじめました。

やってみると、結構楽しいw

今のところのスコアはこんな感じ。

  • ランク: Expeditioner
  • バッジ: 54
  • ポイント: 44,650
  • トレイル: 3

トレイルが少ないのは、手を付けやすそうなモジュールからつまみ食いをしていたためだと思われます。

もうちょっと体系立てて選んでいけばよかったなと反省。。

すすめ方

勉強時間は、平日は22時~1時くらい。休日は日によってまちまちですが、多くても6時間くらい。(大体ドラクエ11をやっていた時間ですね
飽きっぽい性分ですし、このペースを維持するのは無理でしょうが、息切れした後はマイペースにやっていこうかと思ってます。

また、50バッジごとにSuperbadgeにチャレンジしていく、みたいな自分ルールを定めてます。予定だとこんな感じ。

バッジ数 superbadge
50 Apex Specialist
100 Reports & Dashboards Specialist
150 Security Specialist
200 Lightning Experience Specialist
250 Lightning Experience Rollout Specialist
300 Data Integration Specialist

もう一つの自分ルールとして、クリアすると決めたモジュールは必ずクリアして次のモジュールに移る、という風にしています。

Trailheadのよいところ

  • 業務で触る機会のない製品についても学べる
  • 難易度や所要時間など様々なモジュールが自由に選択できる
  • ゲーミフィケーションの要素を取り入れていて、バッジを集めていくのが楽しい
  • 分かったつもりの単元でも、意外と知らない気づきがある

改善点(?)とか

  • 説明は日本語訳されているのに、演習はすべからく英語なのが辛い
  • キャプチャ画像の文字が小さすぎて読めないことがあるので、クリックで拡大されると嬉しい(画像を別タブで開くと大きいキャプチャが見られるのに気づきませんでした…)
  • 演習で失敗した場合のメッセージが分かりにくい時がある