Salesforce ファイルアップロード使用事例

Page側にファイルアップロードして、Salesforceオブジェクトに登録する実装例。
-------------------
- Visualforce側Pageの実装(関連部分の実装のみ表現)

```Sample.v
<div class="file_drop_area">
    <div class="file_drop">
        <input type="file" class="file_drop" id="upload" accept="image/*, application/pdf" data-action-name="uploadFileAction" data-drop-area-class="file_drop_area" />
    </div>
    <apex:actionFunction action="{!pageUploadFile}" name="uploadFileAction" oncomplete="completeMsg();" rerender="refreshArea">
        <apex:param name="fileBody" value="" assignTo="{!fileBody}"/>
        <apex:param name="fname" value="" assignTo="{!fileName}" />
        <apex:param name="fileClass" value="" assignTo="{!fileClass}"/>
        <apex:param name="fileSize" value="" assignTo="{!fileSize}" />
    </apex:actionFunction>
</div>
```
-------------------
- JSの実装

```Sample.js
$(function(){
  // ファイルドロップ時の設定
  $(document).on('dragenter', ".file_drop", function (e) {
      e.stopPropagation();
      e.preventDefault();
  });
  $(document).on('dragover', ".file_drop", function (e) {
      e.stopPropagation();
      e.preventDefault();
      e.originalEvent.dataTransfer.dropEffect = 'copy'; 
  });
  $(document).on('drop', ".file_drop", function (e) {
    e.stopPropagation();
    e.preventDefault();
    uploadFile(e.originalEvent.dataTransfer.files.item(0), $(this).data("action-name"), $(this).data("drop-area-class"));
  });

  // ファイル選択ダイアログの設定
  $(document).on('change', ".upload_file", function () {
    uploadFile($(this).prop('files')[0], $(this).data("action-name"), $(this).data("drop-area-class"));
    // ファイル選択状態を解除
    $(this).val("");
  });
});

/**
 * ファイルアップロード処理を行う大本。
 * ファイルアップロード時はこちらをまず呼び出す。
 * file：アップロードするファイル
 * actionName: アップロード時に実行する関数。apex:actionFunctionにて指定。
 * className: ファイルアップロードエリア(ファイルをドラッグするエリア)のクラス名。
 */
function uploadFile(file, actionName, className) {
  if (!validateUploadFile(file, className)) { // validateUploadFileの処理内容はここで割愛
    // バリデーションエラー時の処理
    return;
  }
  // 画像ファイルとPDFは処理が異なる
  if (file.type != "application/pdf") {
    uploadImageFile(actionName, file, className, file.type, file.size);
  } else {
    // 画像を読み込み
    var reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = event => {
      submitFile(actionName, event.target.result, file.name, className, file.type, file.size);
    };
  }
}

/**
 * 画像ファイル用アップロード処理。
 * 画像ファイルはアップロード時にファイルサイズを80%に落とす。
 * actionName: アップロード時に実行する関数。apex:actionFunctionにて指定。
 * file：アップロードするファイル
 * className: ファイルアップロードエリア(ファイルをドラッグするエリア)のクラス名。
 */
function uploadImageFile(actionName, file, className) {
  var reader = new FileReader();

  reader.readAsDataURL(file);
  reader.onload = event => {
    var img = new Image();
    img.src = event.target.result;
    img.onload = () => {
      
      var width = img.width;
      var height = img.height;
      
      var canvas = document.createElement('canvas');
      canvas.width = width;
      canvas.height = height;
      var ctx = canvas.getContext('2d');
      
      // img.width and img.height will contain the original dimensions
      // 画像を再描する
      ctx.drawImage(img, 0, 0, width, height);
      submitFile(actionName, canvas.toDataURL(file.type, 0.8), file.name, className, file.type, file.size);
    },
    reader.onerror = error => console.log(error);
  };
}

/**
 * ファイルアップロード処理。
 * apex:actionFunctionで設定したcontrollerの処理を実行する関数を呼び出す。
 * actionName: アップロード時に実行する関数。apex:actionFunctionにて指定。
 * base64File：アップロードするファイルを変換したbase64文字列。
 * fileName: ユーザーがアップロードするファイルのファイル名。
 * className: ファイルアップロードエリア(ファイルをドラッグするエリア)のクラス名。
 */
function submitFile(actionName, base64File, fileName, className, fileType, fileSize) {
  //サーバ処理pageUploadFileを実行する
  Function('return this')()[actionName](base64File, fileName, fileType, fileSize);
  //ファイルドロップエリアの画像差し替え
  // 処理内容割愛(アップロードされた画像を画面にプレビュー)
  $("." + className).show();
}
```
-------------------
- Apex側の実装<br>
**ガバナ制限を超えないように、ファイル内容を格納するプロパティをtransientで定義**

```Sample.cls
//　※ここが重要※ ガバナ制限を超えないように、ファイル内容を格納するプロパティをtransientで定義
public transient String fileBody {get; set;}// 添付ファイルデータ
public String fileName {get; set;}// 添付ファイル名	
public String fileClass {get; set;}// ファイルタイプ
public String fileSize {get; set;}// ファイルサイズ
/**
 * 画面からアップロード処理を行う.
 * @return PageReference
 */
public PageReference pageUploadFile(){
    try {
        // 添付書類を保存する(カスタムオブジェクトです。Contactとアップロード画像との紐付く関係を持っているためのオブジェクト)
        AttachedFile__c attFile = new AttachedFile__c(
                                          Test_Column__c = 'testColumn',
                                          File_Type__c = 'testType',
                                          Contact_Id__c = contact.Id);
        insert attachedFile;

        // 画像データ保存のため、取得したデータを処理する
        String fileData = fileBody.substring(fileBody.indexOf(',') + 1, fileBody.length());

        // コンテンツバージョンを保存する
        ContentVersion conVer = new ContentVersion();
        conVer.Title = fileName;// タイトル
        conVer.PathOnClient = fileName;// クライアントでのパス
        conVer.VersionData = EncodingUtil.Base64Decode(fileData);// バージョンデータ
        conVer.IsMajorVersion = true;// メジャーバージョン
        insert conVer;

        // ContentDocumentIdを取得する
        String recordId = conVerData.Id;
        String query = 'SELECT ContentDocumentId FROM ContentVersion  WHERE Id IN :recordId';
        ContentVersion conVerNew = Database.query(query);

        // コンテンツドキュメントリンクを保存する
        ContentDocumentLink conDocLink = new ContentDocumentLink();
        conDocLink.ContentDocumentId = conVerNew.ContentDocumentId;
        conDocLink.LinkedEntityId = attFileData.Id;
        conDocLink.ShareType = 'I'; // 必須 V、C、I(詳細の意味はAPI参照)
        conDocLink.Visibility = 'AllUsers'; // (他の設定値はAPI参照)
        insert conDocLink;

        // ContentDocumentオブジェクトの登録処理もありますが、その登録が自動で登録されるので、意識しなくて大丈夫です。
        // Contactデータを保存する(処理を省略)
        return null;
    } catch (Exception e) {
        // エラー処理
    }
```
-------------------