http://blogjp.sforce.com/2012/01/spring-12-10-6f25.html
上記URLにあるように、様々な新機能がリリースされました。
今回はこの中でも、Visualforceページに表示するコンポーネントを動的に生成する機能「Dynamic Visualforce Components」を使って以下のような簡単なアンケートシステムを作成してみたいと思います。
一般的なアンケートでは、例えば「氏名」を問う質問であった場合は「任意入力形式」、「性別」を問う質問であった場合は「男性」または「女性」の「単一選択形式」、「好きなスポーツ」を問う質問であった場合は「複数選択形式」といったように、その質問内容によって回答形式が変化することがあると思います。
このように質問によって回答形式が変化するようなアンケート回答フォームをVisualforceページで実装する場合、以前は以下のようなコードで実装していたと思います。
<table>
<tbody>
<tr>
<th>番号</th>
<td><apex:outputText value="{!questionNo}" /></td>
</tr>
<tr>
<th>質問</th>
<td><apex:outputText value="{!survey.question}" /></td>
</tr>
<tr>
<th>回答</th>
<td>
<!-- テキスト -->
<apex:inputText value="{!survey.answer}" rendered="{!survey.type=='1'}" />
<!-- 単一選択式 -->
<apex:selectRadio value="{!survey.answer}" rendered="{!survey.type=='2'}" >
<apex:selectoptions value="{!survey.selectOptionList}" />
</apex:selectRadio>
<!-- 複数選択式 -->
<apex:selectCheckboxes value="{!survey.multipleAnswer}" rendered="{!survey.type=='3'}">
<apex:selectoptions value="{!survey.selectOptionList}" />
</apex:selectCheckboxes>
</td>
</tr>
</tbody>
</table>
必要な回答形式に対応するコンポーネントを列挙しておき、「rendered」プロパティによって各コンポーネントの表示・非表示を制御する方法です。
この方法ですと、Viewに表示制御のロジックが混在しているため、実際動作させた際にどのような見た目になるのかを認識しずらいといったデメリットがありました。
一方「Dynamic Visualforce Components」を使った場合は、どのようになるでしょうか?以下のコードをご覧下さい。
<table>
<tbody>
<tr>
<th>番号</th>
<td><apex:outputText value="{!questionNo}" /></td>
</tr>
<tr>
<th>質問</th>
<td><apex:outputText value="{!survey.question}" /></td>
</tr>
<tr>
<th>回答</th>
<td>
<!-- ダイナミックコンポーネント -->
<apex:dynamicComponent componentValue="{!answerComponent}" />
</td>
</tr>
</tbody>
</table>
回答欄が「apex:dynamicComponent」という1つのタグで表現され、Viewの見通しが良くなったと思います。
「apex:dynamicComponent」タグには「componentValue」というプロパティがあり、このプロパティにはコンポーネントを返却するコントローラのメソッドを指定します。上記サンプルコードでは「getAnswerComponent」というメソッドが返却するコンポーネントが表示されることになります。
では、次にコンポーネントを返却するコントローラのメソッドを見てみましょう。
public Component.Apex.OutputPanel getAnswerComponent() {
Component.Apex.OutputPanel panel = new Component.Apex.OutputPanel();
if (survey.type == Survey_Ctrl.TEXT_SINGLE) {
Component.Apex.InputText inputText = new Component.Apex.InputText();
inputText.expressions.value = '{!survey.answer}';
panel.childComponents.add(inputText);
}
else if (survey.type == Survey_Ctrl.SELECTION_SINGLE) {
Component.Apex.SelectRadio radio = new Component.Apex.SelectRadio();
radio.expressions.value = '{!survey.answer}';
Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
options.expressions.value = '{!survey.selectOptionList}';
radio.childComponents.add(options);
panel.childComponents.add(radio);
}
else if (survey.type == Survey_Ctrl.SELECTION_MULTIPLE) {
Component.Apex.SelectCheckboxes checkbox = new Component.Apex.SelectCheckboxes();
checkbox.expressions.value = '{!survey.multipleAnswer}';
Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
options.expressions.value = '{!survey.selectOptionList}';
checkbox.childComponents.add(options);
panel.childComponents.add(checkbox);
}
return panel;
}
このメソッドは、OutputPanelコンポーネントを返却しています。OutputPanelにはsurvey.type(質問の種類)に応じてInputText、SelectRadio、SelectCheckboxesコンポーネントが追加されるようになっています。
ここでポイントになるのは、実際Visualforceページでタグを書いた場合と同じ構成(階層構造)を生成する必要があるということです。事前にVisualforceページでタグを書いて包含関係を把握しておく必要があります。
では具体的に、SelectCheckboxesコンポーネントを生成するコードを解説します。「apex:dynamicComponent」タグを使わない場合のVisualforceページではSelectCheckboxesコンポーネントを以下のようなタグで表現していました。
<apex:selectCheckboxes value="{!survey.multipleAnswer}" rendered="{!survey.type=='3'}">
<apex:selectoptions value="{!survey.selectOptionList}" />
</apex:selectCheckboxes>
包含関係を見ると、SelectCheckboxesの子要素としてSelectOptionsがあるという構造になっています。この構造をApexコードで表現すると、以下のようになります。
Component.Apex.SelectCheckboxes checkbox = new Component.Apex.SelectCheckboxes();
checkbox.expressions.value = '{!survey.multipleAnswer}';
Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
options.expressions.value = '{!survey.selectOptionList}';
checkbox.childComponents.add(options);
各コンポーネントにはchildComponentsというListのプロパティがあり、このListへ子要素となるコンポーネントを追加することで階層構造を構築します。
また、各コンポーネントでexpressions.valueを設定していますが、これは各タグのvalueプロパティを設定していることになります。今回はrenderedプロパティを使わない代わりに動的にコンポーネントを生成しているので、renderedプロパティは設定していませんが、もしrenderedプロパティを設定する場合は以下のようなコードになります。
checkbox.expressions.rendered = '{!survey.type=='3'}';
このようにVisualforceページにおいてタグで表現していた部分をコントローラ側で動的に生成することができました。Visualforceページ側で多数のコンポーネントに対して複雑な表示制御を行う場合などは、この「Dynamic Visualforce Components」を利用することを検討してみてはいかがでしょうか?
参考までに今回作成したアンケートシステムの全ソースコードを添付します。
*Visualforceページ
<apex:page showHeader="false" sidebar="false" controller="Survey_Ctrl" >
<h1>アンケートシステム</h1>
<hr/>
<apex:form >
<table>
<tbody>
<tr>
<th>番号</th>
<td><apex:outputText value="{!questionNo}" /></td>
</tr>
<tr>
<th>質問</th>
<td><apex:outputText value="{!survey.question}" /></td>
</tr>
<tr>
<th>回答</th>
<td>
<!-- ダイナミックコンポーネント -->
<apex:dynamicComponent componentValue="{!answerComponent}" />
</td>
</tr>
</tbody>
</table>
<hr/>
<apex:commandButton action="{!prev}" value="前の質問" disabled="{!NOT(hasPrev)}" />
<apex:commandButton action="{!next}" value="次の質問" disabled="{!NOT(hasNext)}" />
</apex:form>
</apex:page>
*Apexクラス(コントラーラ)
public with sharing class Survey_Ctrl {
public static final String TEXT_SINGLE = '1';
public static final String SELECTION_SINGLE = '2';
public static final String SELECTION_MULTIPLE = '3';
public String questionNo { get {
return String.valueOf(questionIndex+1);
}}
public Survey survey { get {
return surveyList.get(questionIndex);
} private set;}
public Boolean hasNext { get {
return questionIndex < surveyList.size()-1;
}}
public Boolean hasPrev { get {
return questionIndex > 0;
}}
private List surveyList = null;
private Integer questionIndex = 0;
public Survey_Ctrl() {
initSurvey();
}
public PageReference prev() {
if (hasPrev) questionIndex--;
return null;
}
public PageReference next() {
if (hasNext) questionIndex++;
return null;
}
public Component.Apex.OutputPanel getAnswerComponent() {
Component.Apex.OutputPanel panel = new Component.Apex.OutputPanel();
if (survey.type == Survey_Ctrl.TEXT_SINGLE) {
Component.Apex.InputText inputText = new Component.Apex.InputText();
inputText.expressions.value = '{!survey.answer}';
panel.childComponents.add(inputText);
}
else if (survey.type == Survey_Ctrl.SELECTION_SINGLE) {
Component.Apex.SelectRadio radio = new Component.Apex.SelectRadio();
radio.expressions.value = '{!survey.answer}';
Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
options.expressions.value = '{!survey.selectOptionList}';
radio.childComponents.add(options);
panel.childComponents.add(radio);
}
else if (survey.type == Survey_Ctrl.SELECTION_MULTIPLE) {
Component.Apex.SelectCheckboxes checkbox = new Component.Apex.SelectCheckboxes();
checkbox.expressions.value = '{!survey.multipleAnswer}';
Component.Apex.SelectOptions options = new Component.Apex.SelectOptions();
options.expressions.value = '{!survey.selectOptionList}';
checkbox.childComponents.add(options);
panel.childComponents.add(checkbox);
}
return panel;
}
private void initSurvey() {
surveyList = new List();
Survey survey1 = new Survey(Survey_Ctrl.TEXT_SINGLE, '名前は?');
Survey survey2 = new Survey(Survey_Ctrl.SELECTION_SINGLE, '性別は?');
survey2.addSelection('男');
survey2.addSelection('女');
Survey survey3 = new Survey(Survey_Ctrl.SELECTION_MULTIPLE, '好きなスポーツは?');
survey3.addSelection('野球');
survey3.addSelection('サッカー');
survey3.addSelection('ゴルフ');
survey3.addSelection('マラソン');
surveyList.add(survey1);
surveyList.add(survey2);
surveyList.add(survey3);
}
public class Survey {
public String type {set; get;}
public String question {set; get;}
public String answer {set; get;}
public List multipleAnswer {set; get;}
public List selectOptionList {set; get;}
public Survey(String type, String question) {
this.type = type;
this.question = question;
this.multipleAnswer = new List();
this.selectOptionList = new List();
}
public void addSelection(String selection) {
selectOptionList.add(new SelectOption(selection,selection));
}
}
}
Posted by Oi Jun (Appirio Japan)
続きを読む

































