Lightningコンポーネントには $A.createComponent
や $A.createComponents
メソッドを利用して、コントローラ(or ヘルパー)側で動的にコンポーネントを作成する手段が用意されています。
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:message
と ui: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タグを作ってしまうので。