[Salesforce]Visualforce開発勉強メモ

#1.Visualforce の概要
Visualforce は、Lightning Platform でホストできるモバイルおよびデスクトップアプリケーション用の高度なカスタムユーザインターフェースを、開発者が作成できるようにする Web 開発フレームワークです。Visualforce を使用して、Lightning Experience のスタイル設定に準じたアプリケーションや、独自の完全カスタムインターフェースを作成できます。

開発者は Visualforce を使用して、Salesforce の組み込み機能の拡張、新機能との置き換え、完全に新しいアプリケーションの作成を実行できます。強力な組み込みの標準コントローラ機能を使用するか、Apex で独自のカスタムビジネスロジックを記述します。組織独自の機能を作成するか、AppExchange で販売するアプリケーションを作成できます。

Visualforce アプリケーション開発は、Web アプリケーションを作成したことがある開発者にはなじみがあります。開発者はコンポーネント、HTML、および必要に応じてスタイル設定要素を使用して Visualforce ページを作成します。Visualforce を任意の標準 Web テクノロジまたは JavaScript フレームワークと統合して、より多くのアニメーション効果を使用した豪華なユーザインターフェースにすることができます。各ページには一意の URL でアクセスできます。誰かがページにアクセスすると、そのページで要求されたデータ処理がサーバで実行され、HTML に変換されたページがブラウザに返されて表示されます。
![visualforce_request_processing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/7eb091b7-b0bd-64ad-c3a2-ec6987e43e05.png)

#2.Visualforce ページとは?
開発者は Visualforce を使用して Visualforce ページ定義を作成できます。ページ定義は次の 2 つの主要な要素で構成されます。

- Visualforce マークアップ
- Visualforce コントローラ

###2.1Visualforce マークアップ
Visualforce マークアップは、Visualforce タグ、HTML、Javascript、または 1 つの <apex:page> タグ内に埋め込まれているその他の Web 対応コードで構成されています。マークアップでは、ページに含める必要のあるユーザインターフェースコンポーネントとその表示方法を定義します。

###2.2Visualforce コントローラ
Visualforce コントローラは、関連付けられた Visualforce マークアップで指定されたコンポーネントをユーザが操作 (ボタンやリンクのクリックなど) したときの動作を指定する命令のセットです。コントローラを使用すると、ページに表示されるデータにアクセスでき、また、コンポーネントの動作を変更できます。

開発者は Lightning プラットフォームが提供する標準コントローラを使用するか、または Apex で記述されたクラスを含むカスタムコントローラロジックを追加できます。

- 標準コントローラは、標準の Salesforce ページで使用されているものと同じ機能およびロジックで構成されます。たとえば、標準取引先コントローラを使用する場合、Visualforce ページで [保存] ボタンをクリックした場合、標準の取引先編集ページで [保存] をクリックした場合と同じ動作が行われます。
ページで標準コントローラを使用した場合に、ユーザにそのオブジェクトへのアクセス権がないと、アクセス権がないというエラーメッセージがページに表示されます。オブジェクトへのユーザのアクセシビリティを確認し、コンポーネントを適切に表示することでこれを回避できます。

- 標準リストコントローラでは、一連のレコードを表示または操作できる Visualforce ページを作成できます。レコードセットを使用する既存の Salesforce ページの例として、リストページ、関連リスト、一括アクションページなどがあります。

- カスタムコントローラは Apex で記述されるクラスで、標準コントローラを使用せずにすべてのページのロジックを実装します。カスタムコントローラを使用する場合、新しいナビゲーション要素または動作を定義できますが、標準コントローラにすでに定義された機能も再実装する必要があります。
その他の Apex クラスと同様に、カスタムコントローラ全体はシステムモードで実行されます。このモードでは現在のユーザのオブジェクトと項目レベルの権限は無視されます。カスタムコントローラ内で、ユーザプロファイルを用いてアクセスするか否かを独自に決定することができます。

- コントローラ拡張は、Apex で記述されるクラスで、標準コントローラまたはカスタムコントローラの動作を追加するか、動作を上書きします。拡張を使用すれば、独自のカスタムロジックを追加する一方で、別のコントローラの機能も使用できます。
標準コントローラはユーザモードで実行し、現在のユーザの権限、項目レベルのセキュリティ、共有ルールが強制されるため、標準コントローラを拡張すると、ユーザ権限を重視する Visualforce ページを構築できます。拡張クラスはシステムモードで実行しますが、標準コントローラはユーザモードで実行します。カスタムコントローラと同様、ユーザプロファイルを参照してプログラムでアクセスさせるか否かを指定できます。

#3.コントローラ拡張の作成
コントローラ拡張は、ApexPages.StandardController または CustomControllerName 型の単一の引数を取るコンストラクタが含まれる Apex クラスです。CustomControllerName は、拡張するカスタムコントローラの名前です。
###実装例
```java:CustomControllerName1
public class ExtOne {
    public ExtOne(ApexPages.StandardController acon) { }

    public String getFoo() {
        return 'foo-One';
    }
}
```

```java:CustomControllerName2
public class ExtTwo {
    public ExtTwo(ApexPages.StandardController acon) { }

    public String getFoo() {
        return 'foo-Two';
    }
}
```
```java:VisualPage
<apex:page standardController="Account" 
    extensions="ExtOne,ExtTwo" showHeader="false">
    <apex:outputText value="{!foo}" />
</apex:page>
```
****参照順はExtOne -> ExtTwo -> Account***
#4.Visualforce でのテンプレートの使用
- Lightning Design System
  - https://lightningdesignsystem.com/

Visualforce では、複数の Visualforce ページ間で類似の内容を再利用する方法が複数用意されています。どのメソッドを選択するかは、再利用するテンプレートに必要な柔軟性に応じて異なります。テンプレートメソッドが柔軟なほど、そのメソッドを使用するテンプレートの実装が変更しやすくなります。次のテンプレートメソッドを使用できます (柔軟性の高い順)。

- apex:component
- apex:include
- apex:insert および apex:composition
- apex:composition および apex:define

***カスタムコンポーネントの定義***

メソッドでコードをカプセル化すると、プログラムでそのメソッドを複数回利用できるのと同様に、カスタムコンポーネントで共通のデザインパターンをカプセル化することにより 1 つ以上の Visualforce ページでそのコンポーネントを複数回利用することができます。カスタムコンポーネントの定義は最も柔軟なテンプレートメソッドです。これは、有効な Visualforce タグであればどれでも含めることができ、制限なしでどの Visualforce ページにもインポートできるためです。ただし、カスタムコンポーネントは、再利用可能な Visualforce ページの定義には使用しないでください。Visualforce ページ全体の内容を再利用する場合、他の 2 つのテンプレートメソッドのいずれかを選択してください。

<apex:composition> を使用したテンプレートの定義
基本テンプレートを定義して、テンプレートの一部を実装ごとに変更できるようにする場合、<apex:composition> コンポーネントを使用します。このテンプレートメソッドは、ページの全体的な構造を維持し、個々のページの内容を変える場合に適しています。例として、同じページレイアウトでさまざまな記事を表示する必要があるニュース記事用の Web サイトなどがあります。
この技法によって、コントローラが返す PageReference からテンプレートを定義することもできます。

<apex:include> を使用する既存ページの参照
Visualforce ページの内容全体を別のページに挿入する場合、<apex:include> コンポーネントを使用します。このテンプレートメソッドは、複数の領域で同じ内容を複製する場合に適しています。例として、Web サイトのどのページにも表示されるフィードバックフォームなどがあります。

<apex:insert> および <apex:composition> で作成したテンプレートは、すでに存在する Visualforce ページを参照する場合にのみ使用する必要があります。1 つのコンポーネントセットのみを複製する必要がある場合は、カスタムコンポーネントを使用します。

###実装例
```java:Comp_Header.component
<apex:component>

</apex:component>
```

```java:Comp_Footer.component
<apex:component>

</apex:component>
```

```java:Page_CSS.page
<apex:page >
    <style>

    </style>
</apex:page>
```

```java:Page_Javascript.page
<apex:page>
    <script language="javascript">

    </script>
</apex:page>
```

```java:Page_Template.page
<apex:page showHeader="false" standardStylesheets="false" sidebar="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0">
    <html xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <head>
            <title>Visualforce SLDS Sample</title>
            <meta charset="utf-8" />
            <apex:slds />
            <apex:include pageName="Page_CSS" />
            <apex:include pageName="Page_Javascript" />
        </head>
        <body>
            <div class="slds-scope">
                <c:Comp_Header />
                <apex:insert name="content"/>
                <c:Comp_Footer />
            </div>
        </body>
    </html>
</apex:page>
```

###サンプルソース
```java:xxx_Page_Test.page
<apex:page>
    <apex:composition template="Page_Template">
        <apex:define name="content">
            <!-- 画面コンテンツ -->
            <ul class="slds-timeline">
                <li class="slds-timeline__item">
                    <span class="slds-assistive-text">Task</span>
                    <div class="slds-media slds-media--reverse">
                    <div class="slds-media__figure">
                        <div class="slds-timeline__actions">
                        <p class="slds-timeline__date">Feb 24</p>
                        <button class="slds-button slds-button--icon-border-filled">
                            <svg aria-hidden="true" class="slds-button__icon">
                                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/utility-sprite/svg/symbols.svg#switch')}"></use>
                            </svg>
                            <span class="slds-assistive-text">Switch</span>
                        </button>
                        </div>
                    </div>
                    <div class="slds-media__body">
                        <div class="slds-media slds-media--timeline slds-timeline__media--task">
                        <div class="slds-media__figure">
                            <svg aria-hidden="true" class="slds-icon slds-icon-standard-task slds-timeline__icon">
                                <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{!URLFOR($Asset.SLDS, '/assets/icons/standard-sprite/svg/symbols.svg#task')}"></use>
                            </svg>
                        </div>
                        <div class="slds-media__body">
                            <div class="slds-media slds-tile slds-media--small">
                            <div class="slds-media__figure">
                                <label class="slds-checkbox" for="mark-complete">
                                <input name="checkbox" type="checkbox" id="mark-complete" />
                                <span class="slds-checkbox--faux"></span>
                                <span class="slds-form-element__label slds-assistive-text">mark-complete</span>
                                </label>
                            </div>
                            <div class="slds-media__body">
                                <p class="slds-tile__title slds-truncate"><a href="#">Review proposals for EBC deck with larger team and have marketing review this</a></p>
                                <ul class="slds-tile__detail slds-list--horizontal slds-text-body--small">
                                <li class="slds-list__item slds-m-right--large">
                                    <dl class="slds-dl--inline">
                                    <dt class="slds-dl--inline__label">Contact:</dt>
                                    <dd class="slds-dl--inline__detail"><a href="#">Lei Chan</a></dd>
                                    </dl>
                                </li>
                                <li class="slds-list__item">
                                    <dl class="slds-dl--inline">
                                    <dt class="slds-dl--inline__label">Assigned to:</dt>
                                    <dd class="slds-dl--inline__detail"><a href="#">Betty Mason</a></dd>
                                    </dl>
                                </li>
                                </ul>
                            </div>
                            </div>
                        </div>
                        </div>
                    </div>
                    </div>
                </li>
            </ul>
        </apex:define>
    </apex:composition>
</apex:page>
```

#5.Visualforceのビューステートおよび画面間情報維持について
###5.1.ビューステートについて
簡単に言うと、ビューステートはクライアント側とサーバ側の間にメンテナンスしたい情報の格納場所です。

- formタグ必要
- Base64で暗号化
- クライアント側保存

　　`<input type=“hidden” id=“com.salesforce.visualforce.ViewState” name=“com.salesforce.visualforce.ViewState” value=“Base64で暗号化された内容”/>`

ビューステートを理解するには、post backリクエストを理解する必要があります。
post backリクエストとは、Visualforceページにおいてformタグから情報を送信した際に、情報送信後の次の遷移ページが送信前と同一ページである場合のポストリクエストのことを指します。

###5.2.ビューステートサイズの確認方法
#####開発者コンソール
![https___qiita-image-store.s3.amazonaws.com_0_332907_d4916d8a-5c84-1144-f0f6-72fde81da209.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/73221556-91eb-ede5-6d85-5cbc11e84fe4.jpeg)
#####Visualforce実行画面
![https___qiita-image-store.s3.amazonaws.com_0_332907_62b335ff-7e2c-d07c-ee57-dda87ca19396.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/349ea865-4723-828f-5826-8a6a30e74804.jpeg)

###5.3.画面間情報維持について
各Visualforce 画面は一つのControllerで画面間遷移情報維持
***画面の遷移図***
![https___qiita-image-store.s3.amazonaws.com_0_332907_113b2a37-e574-2e57-00b8-03228efef867.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/c484cdca-639a-a5cc-ba3a-d614eada25f8.jpeg)

***ViewStateの格納状況***
![https___qiita-image-store.s3.amazonaws.com_0_332907_0ce3e735-b7b4-ec10-401b-5ac874a5ad1a.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/c8e958ec-dbd1-6327-2829-315140c95f11.jpeg)
###実装例
```java:AccountSearchController.cls
public with sharing class AccountSearchController {
    public String name {get;set;}
    public List<Account> results { get; set; }
    public AccountSearchController() {
        this.name = '';
        this.results = new List<Account>();
    }
    public PageReference doSearch() {
        try {
            String strSoqlQuery = 'select Name, Phone, Website from Account';
            String strName;
            If (String.IsNotBlank(this.name)) {
                strName = '%' + this.name + '%';
                strSoqlQuery = strSoqlQuery + ' where Name like :strName';
            }
            this.results = database.query(strSoqlQuery);
        } catch (Exception e){
             ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, e.getMessage()));
             return null;
        } 

        return Page.AccountSearchResultPage;
    }

    public PageReference doPrevious(){
        return Page.AccountSearchPage;
    }
}
```
```java:AccountSearchPage.page
<apex:page controller="AccountSearchController">
    <apex:form id="TheForm">
        <apex:pageBlock title="検索条件">
            <apex:pageblockSection id="conditionSection" title="検索" columns="1">
                <apex:pageBlockSectionItem >
                    <apex:outputLabel value="取引先名" />
                    <apex:inputText value="{!name}" />
                </apex:pageBlockSectionItem>
            </apex:pageblockSection>
            <apex:pageBlockButtons id="buttonSection" location="bottom">
                <apex:commandButton value="検索" action="{!doSearch}" style="font-size:12pt;width:100px;height:30px;"/>
            </apex:pageBlockButtons>
        </apex:pageBlock>
    </apex:form>
</apex:page>
```
```java:AccountSearchResultPage.page
<apex:page controller="AccountSearchController">
    <apex:form id="theForm">
        <apex:outputpanel id="searchresult">
            <apex:commandButton value="戻る" action="{!doPrevious}" style="font-size:12pt;width:100px;height:30px;"/>

            <apex:pageBlock title="検索結果：" rendered="{!(results.size == 0)}">検索条件に該当するデータがありません</apex:pageBlock>
            <apex:pageBlock id="resultBlock" title="取引先一覧" rendered="{!(results.size > 0)}">
                <apex:outputtext style="width:110px" value="結果 : {!results.size}件" />
                <apex:pageblockTable id="resultTable" value="{!results}" var="o"
                    frame="box">
                    <apex:column style="width:80px">
                        <apex:facet name="header">取引先名</apex:facet>
                        <apex:outputlink value="/{!o.Id}">
                            <apex:outputField style="width:80px" value="{!o.Name}" />
                        </apex:outputlink>
                    </apex:column>
                    <apex:column style="width:160px">
                        <apex:facet name="header">電話</apex:facet>
                        <apex:outputField style="width:150px" value="{!o.Phone}" />
                    </apex:column>
                    <apex:column style="width:160px">
                        <apex:facet name="header">  Web サイト</apex:facet>
                        <apex:outputField style="width:150px"
                            value="{!o.Website}" />
                    </apex:column>
                </apex:pageblockTable>
            </apex:pageBlock>
        </apex:outputpanel>
    </apex:form>
</apex:page>
```
###5.4.検証
***画面***
![https___qiita-image-store.s3.amazonaws.com_0_332907_cbb8079a-e08e-6044-7047-446e2e7b0f7c.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/c5719cea-bf87-d7a3-e47a-00ff8042c0de.jpeg)

***view state***
![https___qiita-image-store.s3.amazonaws.com_0_332907_f43583ca-d19e-4b71-aab3-2a7cb34450ab.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538011/a902ec55-cb1a-ed2f-d807-11e13e0c0da6.jpeg)

#6.注意事項

- 各Visualforce pageにController(extensions含む)の定義は一致する必要があります。
- Spring ‘19 リリースからビューステートサイズの上限が 135KB から 170KB に増加しました。