標準オブジェクトでの動的参照の使用
アクセスする項目の既知のセットを使用して単純で再利用可能なページを構築するには、動的 Visualforce バインドを使用します。このアプローチには、どの項目がユーザの処理対象となるのかを容易にカスタマイズできるという利点があります。
次の 2 つの例は、説明のために意図的に簡略化されています。動的 Visualforce を十分に活用した高度な例は、「ユーザがカスタマイズ可能なページでの動的参照の使用」を参照してください。
単純な動的フォーム
次の例は、動的参照を使用する Visualforce ページを構築する最も簡単な方法を示します。
1public class DynamicAccountFieldsLister {
2
3 public DynamicAccountFieldsLister(ApexPages.StandardController controller) {
4 controller.addFields(editableFields);
5 }
6
7 public List<String> editableFields {
8 get {
9 if (editableFields == null) {
10 editableFields = new List<String>();
11 editableFields.add('Industry');
12 editableFields.add('AnnualRevenue');
13 editableFields.add('BillingCity');
14 }
15 return editableFields ;
16 }
17 private set;
18 }
19}1<apex:page standardController="Account"
2 extensions="DynamicAccountFieldsLister">
3
4 <apex:pageMessages /><br/>
5
6 <apex:form>
7 <apex:pageBlock title="Edit Account" mode="edit">
8 <apex:pageBlockSection columns="1">
9 <apex:inputField value="{!Account.Name}"/>
10 <apex:repeat value="{!editableFields}" var="f">
11 <apex:inputField value="{!Account[f]}"/>
12 </apex:repeat>
13 </apex:pageBlockSection>
14 </apex:pageBlock>
15 </apex:form>
16
17</apex:page>- DynamicAccountFieldsLister コントローラ拡張は、editableFields という文字列のリストを作成します。各文字列は、Account オブジェクト内の項目名に対応付けられます。
- editableFields リストはハードコードされていますが、項目はクエリや計算から特定したり、カスタム設定から読み取ったりすることが可能です。ハードコードされていない場合は、より動的な操作が提供されます。動的参照の利点は、このようなことが可能であることです。
- DynamicAccountEditor マークアップは、<apex:repeat> タグを使用して editableFields から返された文字列をループ処理します。
- <apex:inputField> タグは、Account の項目名を表す f 反復要素を参照して、editableFields 内の各項目を表示します。動的参照 {!Account[f]} が、実際に値をページに表示します。
標準コントローラによる動的参照の項目の読み込み
Visualforce は、ページの StandardController (または StandardSetController) で実行される SOQL クエリを自動的に最適化し、ページで実際に使用される項目のみを読み込みます。オブジェクトと項目への静的参照を含む Visualforce ページを作成する場合、項目とオブジェクトは前もって知っておく必要があります。ページを保存するときに、Visualforce はどのオブジェクトと項目を SOQL クエリに追加する必要があるかを判別して保存できます。この SOQL クエリを、後でページが要求されたときに StandardController が実行します。
動的参照は実行時に、StandardController が SOQL クエリを実行した後に評価されます。動的参照でのみ使用される項目は、自動的には読み込まれません。動的参照が後で評価されたとき、その項目は存在しないデータとして解決され、結果的に SOQL エラーになります。読み込み対象の項目と関連オブジェクトがわかるように、コントローラに追加情報を指定する必要があります。
1public DynamicAccountFieldsLister(ApexPages.StandardController controller) {
2 controller.addFields(editableFields);
3 }これは、コントローラ拡張のインスタンス化時に読み込むすべての項目のリストがわかるページに適しています。項目のリストが、要求処理における後の時点まで判別できない場合は、コントローラで reset() をコールし、項目を追加できます。これにより、コントローラは修正されたクエリを送信することになります。この技法の例は、「ユーザがカスタマイズ可能なページでの動的参照の使用」を参照してください。
関連オブジェクトへの動的参照
この例では、ケースレコードの Visualforce ページを作成し、特定の項目を編集可能にします。表示される項目の一部は関連オブジェクトのもので、動的参照を使用してリレーションをトラバースする方法を示します。
1public class DynamicCaseLoader {
2
3 public final Case caseDetails { get; private set; }
4
5 // SOQL query loads the case, with Case fields and related Contact fields
6 public DynamicCaseLoader(ApexPages.StandardController controller) {
7 String qid = ApexPages.currentPage().getParameters().get('id');
8 String theQuery = 'SELECT Id, ' + joinList(caseFieldList, ', ') +
9 ' FROM Case WHERE Id = :qid';
10 this.caseDetails = Database.query(theQuery);
11 }
12
13 // A list of fields to show on the Visualforce page
14 public List<String> caseFieldList {
15 get {
16 if (caseFieldList == null) {
17 caseFieldList = new List<String>();
18 caseFieldList.add('CaseNumber');
19 caseFieldList.add('Origin');
20 caseFieldList.add('Status');
21 caseFieldList.add('Contact.Name'); // related field
22 caseFieldList.add('Contact.Email'); // related field
23 caseFieldList.add('Contact.Phone'); // related field
24 }
25 return caseFieldList;
26 }
27 private set;
28 }
29
30 // Join an Apex list of fields into a SELECT fields list string
31 private static String joinList(List<String> theList, String separator) {
32
33 if (theList == null) {
34 return null;
35 }
36 if (separator == null) {
37 separator = '';
38 }
39
40 String joined = '';
41 Boolean firstItem = true;
42 for (String item : theList) {
43 if(null != item) {
44 if(firstItem){
45 firstItem = false;
46 }
47 else {
48 joined += separator;
49 }
50 joined += item;
51 }
52 }
53 return joined;
54 }
55}1<apex:page standardController="Case" extensions="DynamicCaseLoader">
2 <br/>
3 <apex:form >
4 <apex:repeat value="{!caseFieldList}" var="cf">
5 <h2>{!cf}</h2>
6 <br/>
7 <!-- The only editable information should be contact information -->
8 <apex:inputText value="{!caseDetails[cf]}"
9 rendered="{!IF(contains(cf, "Contact"), true, false)}"/>
10 <apex:outputText value="{!caseDetails[cf]}"
11 rendered="{!IF(contains(cf, "Contact"), false, true)}"/>
12 <br/><br/>
13 </apex:repeat>
14 </apex:form>
15</apex:page>
- コントローラ拡張では、オブジェクトを表示できるようにコンストラクタが独自の SOQL クエリを実行します。これは、ページの StandardController がデフォルトでは関連項目を読み込まないためですが、カスタマイズした SOQL クエリが必要になる使用事例は数多くあります。クエリの結果は、プロパティ caseFieldList によってページで使用できるようになります。コンストラクタでクエリを実行するための要件はありません。プロパティの get メソッドに簡単��追加できます。
- SOQL クエリは読み込む項目を指定するため、単純な動的フォームでは必要だった addFields() を使用する必要はありません。
- SOQL クエリは実行時に作成されます。ユーティリティメソッドが項目名のリストを SOQL SELECT ステートメントでの使用に適した文字列に変換します。
- マークアップでは、フォーム項目は、<apex:repeat> を使用する項目名を反復処理し、項目名変数 cf を動的参照で使用して項目値を取得することで表示されます。各項目は、<apex:outputText> と <apex:inputText> の 2 つのコンポーネントで記述される可能性があります。これらのタグ上の表示属性が、2 つのどちらを実際に表示するかを制御します。項目名が文字列「Contact」を含む場合、情報は <apex:inputText> タグに表示され、含まない場合は <apex:outputText> に表示されます。
ユーザがカスタマイズ可能なページでの動的参照の使用
Visualforce 動的バインドの最大の利点は、オブジェクトでどの項目が使用可能かを知らなくてもページを作成できることです。次の例はこの機能を示しています。すべてのオブジェクトで必須の Name 項目を除き、Account オブジェクトの項目を一切知らずにカスタマイズできる取引先のリストが表示されます。これは、Schema.SobjectType.Account.fields.getMap() を使用してオブジェクトに存在する項目のリストを取得し、Visualforce 動的参照を使用することで可能になります。
1public class DynamicCustomizableListHandler {
2
3 // Resources we need to hold on to across requests
4 private ApexPages.StandardSetController controller;
5 private PageReference savePage;
6
7 // This is the state for the list "app"
8 private Set<String> unSelectedNames = new Set<String>();
9 private Set<String> selectedNames = new Set<String>();
10 private Set<String> inaccessibleNames = new Set<String>();
11
12 public DynamicCustomizableListHandler(ApexPages.StandardSetController controller) {
13 this.controller = controller;
14 loadFieldsWithVisibility();
15 }
16
17 // Initial load of the fields lists
18 private void loadFieldsWithVisibility() {
19 Map<String, Schema.SobjectField> fields =
20 Schema.SobjectType.Account.fields.getMap();
21 for (String s : fields.keySet()) {
22 if (s != 'Name') { // name is always displayed
23 unSelectedNames.add(s);
24 }
25 if (!fields.get(s).getDescribe().isAccessible()) {
26 inaccessibleNames.add(s);
27 }
28 }
29 }
30
31 // The fields to show in the list
32 // This is what we generate the dynamic references from
33 public List<String> getDisplayFields() {
34 List<String> displayFields = new List<String>(selectedNames);
35 displayFields.sort();
36 return displayFields;
37 }
38
39 // Nav: go to customize screen
40 public PageReference customize() {
41 savePage = ApexPages.currentPage();
42 return Page.CustomizeDynamicList;
43 }
44
45 // Nav: return to list view
46 public PageReference show() {
47 // This forces a re-query with the new fields list
48 controller.reset();
49 controller.addFields(getDisplayFields());
50 return savePage;
51 }
52
53 // Create the select options for the two select lists on the page
54 public List<SelectOption> getSelectedOptions() {
55 return selectOptionsFromSet(selectedNames);
56 }
57 public List<SelectOption> getUnSelectedOptions() {
58 return selectOptionsFromSet(unSelectedNames);
59 }
60
61 private List<SelectOption> selectOptionsFromSet(Set<String> opts) {
62 List<String> optionsList = new List<String>(opts);
63 optionsList.sort();
64 List<SelectOption> options = new List<SelectOption>();
65 for (String s : optionsList) {
66 options.add(new
67 SelectOption(s, decorateName(s), inaccessibleNames.contains(s)));
68 }
69 return options;
70 }
71
72 private String decorateName(String s) {
73 return inaccessibleNames.contains(s) ? '*' + s : s;
74 }
75
76 // These properties receive the customization form postback data
77 // Each time the [<<] or [>>] button is clicked, these get the contents
78 // of the respective selection lists from the form
79 public transient List<String> selected { get; set; }
80 public transient List<String> unselected { get; set; }
81
82 // Handle the actual button clicks. Page gets updated via a
83 // rerender on the form
84 public void doAdd() {
85 moveFields(selected, selectedNames, unSelectedNames);
86 }
87 public void doRemove() {
88 moveFields(unselected, unSelectedNames, selectedNames);
89 }
90
91 private void moveFields(List<String> items,
92 Set<String> moveTo, Set<String> removeFrom) {
93 for (String s: items) {
94 if( ! inaccessibleNames.contains(s)) {
95 moveTo.add(s);
96 removeFrom.remove(s);
97 }
98 }
99 }
100}- 標準コントローラメソッドの addFields() および reset() が show() メソッドで使用されます。このメソッドにより、リストビューに戻ります。これらが必要なのは、表示する項目のリストが変更された可能性があり、表示用のデータを読み込むクエリを再実行する必要があるためです。
- 2 つの action メソッド customize() と show() がリストビューからカスタマイズフォームに移動し、再び戻ります。
- navigation action メソッド後に行われることはすべて、カスタマイズフォーム関連の処理です。これらのメソッドは、コメントに注記されているように、大きく 2 つのグループに分けられます。最初のグループは、カスタマイズフォームで使用される List<SelectOption> リストを提供し、2 つ目のグループは項目をリスト間で移動する 2 つのボタンを処理します。
1<apex:page standardController="Account" recordSetVar="accountList"
2 extensions="DynamicCustomizableListHandler">
3 <br/>
4 <apex:form >
5
6 <!-- View selection widget, uses StandardController methods -->
7 <apex:pageBlock>
8 <apex:outputLabel value="Select Accounts View: " for="viewsList"/>
9 <apex:selectList id="viewsList" size="1" value="{!filterId}">
10 <apex:actionSupport event="onchange" rerender="theTable"/>
11 <apex:selectOptions value="{!listViewOptions}"/>
12 </apex:selectList>
13 </apex:pageblock>
14
15 <!-- This list of accounts has customizable columns -->
16 <apex:pageBlock title="Accounts" mode="edit">
17 <apex:pageMessages />
18 <apex:panelGroup id="theTable">
19 <apex:pageBlockTable value="{!accountList}" var="acct">
20 <apex:column value="{!acct.Name}"/>
21 <!-- This is the dynamic reference part -->
22 <apex:repeat value="{!displayFields}" var="f">
23 <apex:column value="{!acct[f]}"/>
24 </apex:repeat>
25 </apex:pageBlockTable>
26 </apex:panelGroup>
27 </apex:pageBlock>
28
29 <br/>
30 <apex:commandButton value="Customize List" action="{!customize}"/>
31
32 </apex:form>
33</apex:page>2 つ目の <apex:pageBlock> には、<apex:repeat> で追加された列が含まれる <apex:pageBlockTable> が保持されます。繰り返しコンポーネントのすべての列は、取引先項目 {!acct[f]} への動的参照を使用して、ユーザがカスタム選択した項目を表示します。
1<apex:page standardController="Account" recordSetVar="ignored"
2 extensions="DynamicCustomizableListHandler">
3 <br/>
4 <apex:form >
5
6 <apex:pageBlock title="Select Fields to Display" id="selectionBlock">
7 <apex:pageMessages />
8 <apex:panelGrid columns="3">
9 <apex:selectList id="unselected_list" required="false"
10 value="{!selected}" multiselect="true" size="20" style="width:250px">
11 <apex:selectOptions value="{!unSelectedOptions}"/>
12 </apex:selectList>
13 <apex:panelGroup >
14 <apex:commandButton value=">>"
15 action="{!doAdd}" rerender="selectionBlock"/>
16 <br/>
17 <apex:commandButton value="<<"
18 action="{!doRemove}" rerender="selectionBlock"/>
19 </apex:panelGroup>
20 <apex:selectList id="selected_list" required="false"
21 value="{!unselected}" multiselect="true" size="20" style="width:250px">
22 <apex:selectOptions value="{!selectedOptions}"/>
23 </apex:selectList>
24 </apex:panelGrid>
25 <em>Note: Fields marked <strong>*</strong> are inaccessible to your account</em>
26 </apex:pageBlock>
27
28 <br/>
29 <apex:commandButton value="Show These Fields" action="{!show}"/>
30
31 </apex:form>
32
33</apex:page>- このページは、取引先が表示されていなくても、リストビューと同じ標準コントローラを使用します。これは、表示する項目のリストが含まれるビューステートを維持するために必要です。このフォームでユーザの設定がカスタム設定のような永続的なものに保存された場合、この操作は不要になります。
- 最初のリストは、getUnSelectedOptions() メソッドをコールすることで入力されます。フォームが (2 つの <apex:commandButton> コンポーネントのいずれかを経由して) 送信されると、フォーム送信時に選択されたリストの値が selected プロパティに保存されます。対応するコードが、もう一方のリストを処理します。
- これらの移動する項目の「デルタ」リストは、クリックされたボタンに応じて doAdd() または doRemove() メソッドで処理されます。
- デフォルト状態のカスタマイズ可能なリストに取引先名項目のみが表示されます。
[リストをカスタマイズ] をクリックします。 - 表示設定画面が表示されます。
一部の項目を右側のリストに移動し、[これらの項目を表示] をクリックします。 - カスタマイズされたリストビューが表示されます。