オブジェクトの詳細で表示される関連リスト部分、要するにそのレコードを参照しているレコード、を一覧表示からインライン編集出来るようにしたかったので、インライン編集の基本的な部分から調べてみた。
参考
詳細レコード
該当のレコードの詳細を表示し、表示している項目をインライン編集出来るようにする。
作成しているVFページのURLにて、IDが指定されている + 指定されているstandardController
のオブジェクトであれば、Visualforceだけで表示させることが可能。
https://Salesforce_instance/apex/myPage?id=001x000xxx3Jsxb
この場合、IDが001x000xxx3Jsxb
の取引先(Account
)が表示される。
apex:detail
タグを使うと、このレコードの詳細がそのまま表示される。
その際に、属性inlineEdit
をtrue
で指定するとインライン編集が可能となる。
1
2
3
<apex:page standardController="Account">
<apex:detail subject="{!account.Id}" relatedList="false" inlineEdit="true"/>
</apex:page>
特定のレコードの詳細をそのまま表示したい時とかには便利。
一覧表示
詳細ではなく、一覧表示で複数件いっぺんに編集したい場合。
apex:page
の属性で、recordSetVar
を指定してやれば、standardController
で指定したオブジェクトのリストを表示出来る。
standardControllerとrecordSetVar – Qiita
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<apex:page standardController="Account" recordSetVar="records" id="thePage">
<apex:form id="theForm">
<apex:pageBlock id="thePageBlock">
<apex:pageBlockTable value="{!records}" var="record" id="thePageBlockTable">
<apex:column >
<apex:outputField value="{!record.Name}" id="AccountNameDOM" />
<apex:facet name="header">Name</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!record.Type}" id="AccountTypeDOM" />
<apex:facet name="header">Type</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!record.Industry}"
id="AccountIndustryDOM" />
<apex:facet name="header">Industry</apex:facet>
</apex:column>
<apex:inlineEditSupport event="ondblClick"
showOnEdit="saveButton,cancelButton" hideOnEdit="editButton" />
</apex:pageBlockTable>
<apex:pageBlockButtons >
<apex:commandButton value="Edit" action="{!save}" id="editButton" />
<apex:commandButton value="Save" action="{!save}" id="saveButton" />
<apex:commandButton value="Cancel" action="{!cancel}" id="cancelButton" />
</apex:pageBlockButtons>
</apex:pageBlock>
</apex:form>
</apex:page>
コードは公式ドキュメントのママ。
formなど各タグ内のIDは別にあってもなくてもよさそう。
以下のように表示される。
apex:column
で囲まれてた部分が1つの項目になっており、<apex:facet name="header">
タグの内容がそれぞれの項目の見出しとなる。
このままページを表示してインライン編集後、saveボタンをクリックすると確かに更新はされるがホームへ遷移してしまう。
(キャンセルをクリックしても同様)
これを解消するために、カスタムコントローラーを割り当ててみた。
recordSetVar
を使っていると、割り当てたカスタムコントローラ側で取得したレコードリストを使用するために、StandardSetController
を使う必要がある、とのこと。
SFDC:recordSetVarとextensions – tyoshikawa1106のブログ
これを、StandardController
をコントローラ側で使っていると下記のようなエラーがでる。
エラーメッセージだけでは非常にわかりにくそうなので注意。
common.apex.runtime.bytecode.BytecodeApexObjectType cannot be cast to common.apex.runtime.impl.ApexType
で、カスタムコントローラ内で、ボタンを押された際のアクションを作成し、null
を返す事でページ遷移をしないようにする。
ただし、保存するsave
ボタンはこれをしてしまうと保存されなくなってしまったので、キャンセルボタンだけにしておいた。
VF
1
<apex:page standardController="Account" recordSetVar="records" extensions="VfInlineEditSample" id="thePage">
apex (VfInlineEditSample.apxc
)
1
2
3
4
5
6
7
8
9
public class VfInlineEditSample {
public VfInlineEditSample(ApexPages.StandardSetController stdController){
List<Account> lists = (List<Account>)stdController.getRecords();
}
public PageReference cancel(){
return null;
}
}
Classe StandardSetController
関連リスト
とあるレコードの関連リストを一覧表示からインライン編集したい場合。
上記の一覧表示と同じようにすればインライン編集出来るテーブルを作る事は可能。
IDはVFページのパラメータから取得する。
例)
VfInlineEditSamplePage.vfp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<apex:page standardController="Account" extensions="VfInlineEditSample" id="thePage">
<apex:form id="theForm">
<apex:pageBlock title="商談" >
<apex:pageBlockTable value="{!opp_records}" var="opp">
<apex:column >
<apex:outputField value="{!opp.Name}" />
<apex:facet name="header">商談名</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!opp.Amount}" />
<apex:facet name="header">金額</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!opp.StageName}" />
<apex:facet name="header">フェーズ</apex:facet>
</apex:column>
<apex:column >
<apex:outputField value="{!opp.NextStep}" />
<apex:facet name="header">次回アクション</apex:facet>
</apex:column>
<apex:inlineEditSupport event="ondblClick" />
</apex:pageBlockTable>
<apex:pageBlockButtons >
<apex:commandButton value="Save" action="{!save}" />
<apex:commandButton value="Cancel" action="{!cancel}" />
</apex:pageBlockButtons>
</apex:pageBlock>
</apex:form>
</apex:page>
VfInlineEditSample.apxc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class VfInlineEditSample {
public List<Opportunity> opp_records{get; set;}
public VfInlineEditSample(ApexPages.StandardController stdController){
Account acc = (Account)stdController.getRecord();
Id aid = acc.id;
Account record = [SELECT id, name, (SELECT id,name,StageName,NextStep,Amount FROM Opportunities) FROM Account WHERE id=:aid];
opp_records = record.Opportunities;
}
public PageReference cancel(){
return null;
}
}
以下のようになる。
このままだとSaveをクリックしても保存はされない。
なので、cancelと同じようにカスタムコントローラ内でアクションを受け取り、編集した内容はVFへ引き渡している、 opp_records
に入っているので、それをupdateすれば更新される。
apexクラスに以下を追加。
1
2
3
4
public PageReference save(){
update opp_records;
return null;
}
しかしこうすると、編集されたされてないにかかわらず、関連リストに並んでいるオブジェクト(この場合は商談)のレコード全てが更新されてしまう。
ガバナ制限は、1万レコードまで大丈夫なので、問題になることは少ないと思うが、最終更新日付が全て更新されてしまうのが都合が悪いかもしれない。
Apex ガバナ制限
更新対象を選ぶ
ワークフローであれば、ISCHANGED
を使えば、その項目が変更されたかどうかをチェック出来るが、apexではそのメソッドはない。
(機能追加が要望があがってたりする(トリガの機能としてだけど) IsChanged function in Apex )
なので、更新される項目を一つずつ比較して更新があるかどうかを確認する泥臭い方法を取ってみる。
(他にいいアイデアがあれば教えて欲しい…)
修正後のapex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class VfInlineEditSample {
public List<Opportunity> opp_records{get; set;}
private List<Opportunity> old_records;
public VfInlineEditSample(ApexPages.StandardController stdController){
Account acc = (Account)stdController.getRecord();
Id aid = acc.id;
Account record = [SELECT id, name, (SELECT id,name,StageName,NextStep,Amount FROM Opportunities) FROM Account WHERE id=:aid];
opp_records = record.Opportunities;
old_records = opp_records.deepClone();
}
public PageReference cancel(){
return null;
}
public PageReference save(){
List<Opportunity> update_lists = new List<Opportunity>();
for(Integer i = 0; i< opp_records.size(); i++){
if(opp_records[i].name != old_records[i].name){
update_lists.add(opp_records[i]);
continue;
}else if(opp_records[i].StageName != old_records[i].StageName){
update_lists.add(opp_records[i]);
continue;
}else if(opp_records[i].NextStep != old_records[i].NextStep){
update_lists.add(opp_records[i]);
continue;
}else if(opp_records[i].Amount != old_records[i].Amount){
update_lists.add(opp_records[i]);
continue;
}
}
if(update_lists.size() > 0){
update update_lists;
}
return null;
}
}
リストをコピーする際に、deepClone
を使わないとclone
では浅いコピーとなってしまい、参照しているものが同じになってしまうので注意。
(一方の値を更新するともう一方も同じ値になってしまう)
汎用的にする
上記だと、表示する項目を増やす度に比較の条件文を追加しないといけない。
なので、全項目から取得出来るものだけを比較するようにしてみた。
以下、関数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public List<sObject> isChanged(String object_name, List<sObject> new_records, List<sObject> old_records){
sObject obj = (sObject)Type.forName(object_name).newInstance();
Schema.DescribeSObjectResult descR = obj.getsObjectType().getDescribe();
Set<String> fields = descR.fields.getMap().keySet();
Map<String, Schema.SObjectField> fmap = descR.fields.getMap();
List<sObject> update_records = new List<sObject>();
for(Integer i = 0; i< new_records.size(); i++){
for(String field : fields){
try{
Schema.SObjectField f = fmap.get(field);
Schema.DescribeFieldResult fr = f.getDescribe();
if(!fr.isUpdateable()) continue; //更新出来ない項目は飛ばす
//比較
if(new_records[i].get(field) != old_records[i].get(field)){
update_records.add(new_records[i]);
continue;
}
}catch(SObjectException e){
//取得していない項目
}
}
}
return update_records;
}
getDescribe()
で指定したオブジェクトの全項目名を取得出来る
取得した項目名を使ってオブジェクトからget()
で取得。例外が発生したものはselectで取得していない項目、となる
更新可能かどうかは、Describe
で取得出来る項目の情報内を見れば判定可能
isUpdateable
がtrueなら更新が出来る項目となるので、これがtrueのもののみ比較している
使用の際は、第一引数にオブジェクトの参照名、第二引数に更新後のレコードリスト、第三引数に更新前のレコードリストを指定してやる。
1
List<Opportunity> update_lists = isChanged('Opportunity', opp_records, old_records);
これで項目が増えても安心。
参考