項目セットを使ってみよう

皆さん、項目セット使ってますか。
結構前に追加された機能ではあるんですが、私の周りでは使っている人をあんまり見かけないです。
新機能として追加された時に、あんまり脚光を浴びるようなものではなかったせいでしょうか。
いや、きっとみんな存在を知らないだけで、実はこんな機能あったらなーと思っているかもしれません。
そこで、今回は項目セットの使い方をまとめてみたいと思います。

項目セットを使う理由

項目セットとは1つのオブジェクト内の項目を1つのまとまりとし、それをApexやVisualforceから扱うことのできる機能です。

例えば、Salesforceと言えば、項目が簡単に追加できると言われており、ユーザーやお客様なんかは
「後から簡単に項目追加できるんでしょ?この属性も追加してよ」なんてことはよくあります。
たしかにオブジェクトに項目を追加すること自体は簡単にできますし、標準レイアウトであれば項目を追加することも問題ありません。

ただ、そこにApexやVisualforceが絡んでくると修正範囲は広がってしまい、そう簡単なものではなくなってきます。
運用を考えると単にプログラムを修正するだけではなく、ドキュメントの修正、再テスト、承認、リリース判定、デプロイ・・・などなど

また、プログラムに手を入れるということは、IT部門を持たないユーザーの場合はなかなか難しく、項目1個増やすだけなのに
ベンダーへの発注が必要になってしまいます。

こういったことを避けるために、常に開発者は「メンテナンスビリティの高いシステムを作る」という意識を持つべきです。
Force.comで言うと、今回の「項目セット」や「カスタム設定」「カスタム表示ラベル」「静的リソース」あたりを使っている組織は
メンテナンスがしやすい傾向があります。

項目セットを作成する

項目セットはまず箱となるセット自体を作成します。

次に項目セットに追加する項目を入れていきます。
ページレイアウト編集画面のような同じみの画面ですね。
各項目はここで並べた順番で取得できるので、並び順も考えて配置しましょう。

ここで項目をダブルクリックすると、必須の定義ができます。
これはページレイアウトと同じで、この項目セット上で必須かどうか定義するものです。
ただし、ここで必須にしたからと言って、この項目セットを使った時に必ず必須になるというものではなく、必須にするにはVisualforce上でrequiredを指定します。
サンプルではとりあえず商談名だけ必須にしておきます。

ここまでやると項目セットの作成は完了です。
次は各パターンに分けて使い方を紹介します。

outputFieldとinputFieldの場合

StandardControllerでoutputFieldとinputFieldを使った単純なパターンです。

<apex:page standardController="Opportunity">
  <apex:form >
    <apex:pageBlock mode="edit" title="商談 項目セットサンプル画面">

      <apex:pageMessages />
      <apex:pageBlockButtons >
        <apex:commandButton value="保存" action="{!save}"/>
        <apex:commandButton value="キャンセル" action="{!cancel}" />
      </apex:pageBlockButtons>

      <apex:pageBlockSection columns="2" title="outputFieldのサンプル">
        <apex:repeat value="{!$ObjectType.Opportunity.FieldSets.Set01}" var="s"> 
          <apex:outputField value="{!Opportunity[s]}"/>
        </apex:repeat>
      </apex:pageBlockSection>

      <apex:pageBlockSection columns="2" title="inputFieldのサンプル">
        <apex:repeat value="{!$ObjectType.Opportunity.FieldSets.Set01}" var="s"> 
          <apex:inputField value="{!Opportunity[s]}" required="{!s.Required}"/>
        </apex:repeat>
      </apex:pageBlockSection>

    </apex:pageBlock>
  </apex:form>
</apex:page>

項目セットには$ObjectType.[オブジェクト名].FieldSets.[項目セット名]でアクセスしています。
また、inputfiledにはrequiredを指定して、先ほど作った項目セット側で必須定義したものを必須入力にしています。
項目側が必須になっているようなフェーズ項目等は、項目セット側で必須じゃなく設定することもできますが、
当然ながら保存時にエラーになります。
inputField側でrequiredを指定しなければ、項目側の必須設定によって、赤マークが出たりするようです。

Visualforceページ名に?id=[商談SFID] を付けて見るとこんな感じ

outputTextとinputTextの場合

outputTextとinputTextを使う場合はラベル名を引っ張ってこないといけないので、$ObjectType.Opportunity.Fields[s].labelとかで取ります。

<apex:page standardController="Opportunity">
  <apex:form >
    <apex:pageBlock mode="edit" title="商談 項目セットサンプル画面">

      <apex:pageMessages />
      <apex:pageBlockButtons >
        <apex:commandButton value="保存" action="{!save}"/>
        <apex:commandButton value="キャンセル" action="{!cancel}" />
      </apex:pageBlockButtons>

      <apex:pageBlockSection columns="2" title="outputTextのサンプル">
        <apex:repeat value="{!$ObjectType.Opportunity.FieldSets.Set01}" var="s"> 
          <apex:outputText value="{!Opportunity[s]}" label="{!$ObjectType.Opportunity.Fields[s].label}" />
        </apex:repeat>
      </apex:pageBlockSection>

      <apex:pageBlockSection columns="2" title="inputTextのサンプル">
        <apex:repeat value="{!$ObjectType.Opportunity.FieldSets.Set01}" var="s"> 
          <apex:inputText value="{!Opportunity[s]}" label="{!$ObjectType.Opportunity.Fields[s].label}" required="{!s.Required}"/>
        </apex:repeat>
      </apex:pageBlockSection>

    </apex:pageBlock>
  </apex:form>
</apex:page>

画面はこんな感じ。

ご覧のとおり、そのまま出しただけだと日付や数値のフォーマットが出来てないし、選択リストや日付型の入力補助もない。
フォーマットはapex:paramとか使ったり、選択リストなんかも出来なくはないけど、項目セットにどんな型が入って来るのかわからないから分岐が面倒くさそう。
じゃあ、inputFieldとoutputField使えばいいじゃんってなるけど、画面のパフォーマンスを考えるとTextを使ったり、あるいは直に書いた方が早いらしい。
その辺は@xlouderさんの「Visualforceの性能検証」が詳しいので、そちらを見て欲しい。
http://www.slideshare.net/hyoshita/performance-of-visualforce-lt-version20121031

用途によって使い分けてもらえばいいと思います。

Apexで使う場合

Schema.FieldSetとSchema.FieldSetMemberメソッドを使って取得することが可能。
使うとこだけ見ると以下のような感じ。

	public List<Schema.FieldSetMember> getFields() {
		return SObjectType.Opportunity.FieldSets.Set01.getFields();
	}

	private Opportunity getOpportunity() {
		String query = 'SELECT ';
		for(Schema.FieldSetMember f : this.getFields()) {
			query += f.getFieldPath() + ', ';
		}
		query += 'Id, Name FROM Opportunity LIMIT 1';
		return Database.query(query);
	}

以上となりますが、このような感じで項目セットを駆使し、メンテナンスがしやすいシステムを作っておけば「Salesforceなんだから項目の追加が簡単なんでしょ?」と言われた時にも、すぐに対応することが可能です。
ぜひ皆様も項目セットを使い倒してみてください!


ちなみにこの記事はForce.com Advent Calendarに参加しています。
http://atnd.org/events/33649

他の方のエントリーもぜひ見てみてください。