Javascript Remoting で input too long エラーを回避する

#Javascript Remoting の引数の上限文字数は 1,000,000 文字
Visualforce ページから任意のレコードに対し添付ファイルをアップロードしようとすると、「input too long」エラーで怒られてしまう事がよくあります。
Visualforce から非同期で Apex と通信できる　Javascript Remoting では、その引数の長さが 1,000,000 文字を超えることが出来ません。
そのため、base64 エンコードしたファイルなどを渡す際は少し工夫する必要があります。


#Apex の String 型変数の上限文字数は 6,000,000 文字
以下にその対処方法の例を記載していますが、これも万能ではありません。
base64 でエンコーディングした文字列は Apex 上で String として扱われます。Apex の String 型変数の上限文字数は 6,000,000 文字となるため、約 5MB 弱のサイズのファイルしか扱えないことに注意してください。

#input too long エラーを回避するサンプルコード
基本的な対応方針は、multipart リクエストのように、送信するデータを分割し末尾再帰的に複数回に分けて送信します。
それに合わせて、Apex コードではデータ文字列を結合する事で復元しながら保存していきます。

```html:AttachmentUpload.vfp
<apex:page controller="AttachmentUploadController" >
    <input id="js-input-file" type="file" />   
    <input type="button" value="送信" onclick="submit()" />
    
    <script>
    function submit() {
        // ファイルを取得
        var file = document.getElementById('js-input-file').files[0];
        
        // file を読み込む
        var reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = function () {
            // 親レコードの ID、ファイル名、ファイルの data uri を指定する。
            uploadAttachment('0016F00001puC19', file.name, reader.result);
        };
        reader.onerror = function (error) {
            console.log('ERROR: ', error);
        };
    }
    
    
    
    var maxSizeForApexString = 6000000; // Apex の String 型変数の最大文字数
    var chunkSize            =  950000; // Javascript Remoting で一度に送信したい文字数の上限
   
    function uploadAttachment(parentId, fileName, dataURI) {
        var contentType = dataURI.split(',')[0].split(':')[1].split(';')[0]; 
        var dataString  = dataURI.split(',')[1];
        var recordId    = null;
        var curIndex    = 0;
        if(dataString.length < maxSizeForApexString) {
            console.log('Start uploading');
            upload(parentId, recordId, fileName, contentType, dataString, curIndex);
        } else {
            alert("File is too big.");
        }
    }
    
    function upload(parentId, recordId, fileName, contentType, dataString, curIndex) {
        var chunk = dataString.slice(curIndex, curIndex + chunkSize);
        curIndex += chunkSize;
        AttachmentUploadController.uploadAttachment(
            parentId, recordId, fileName, contentType, chunk,
            function(result, event) {
                if (event.status) {
                    if (curIndex > dataString.length) {
                        console.log('Uploading completed');
                    } else {
                        console.log('data sent: ' + curIndex + '/' +  dataString.length);
                        upload(parentId, result, fileName, contentType, dataString, curIndex);
                    } 
                } else if (event.type === 'exception') {
                    console.error("EXCEPTION: " + event.message);
                } else {
                    console.error("ERROR: " + event.message);
                }                
            },
            { buffer: false, escape: true, timeout: 30000 }
        );
    }
    </script>
</apex:page>
```



```java:AttachmentUploadController.apxc
public class AttachmentUploadController {
    @RemoteAction
    public static String uploadAttachment(String parentId, String recordId, String fileName, String contentType, String dataString) {
        Attachment attachment;
        if (String.isEmpty(recordId)) {
            attachment = new Attachment(Name=fileName, ContentType=contentType, parentId=parentId, Body=EncodingUtil.base64Decode(dataString));
        } else {
            attachment = [SELECT Id, Body FROM Attachment WHERE Id =: recordId];
            String currentBody = EncodingUtil.base64Encode(attachment.Body);
            attachment.Body = EncodingUtil.base64Decode(currentBody + dataString);
        }
        upsert attachment;
        return attachment.Id;
    }
}
```


#添付ファイル以外のファイル送信
## Files

```java:FileUploadController.apxc
public class FileUploadController {
    @RemoteAction
    public static String uploadFileAndLinkTo(String parentId, String recordId, String fileName, String dataString) {
        ContentVersion content;
        if (String.isEmpty(recordId)) {
            content = new ContentVersion(Title=fileName, VersionData=EncodingUtil.base64Decode(dataString), PathOnClient='/' + fileName);
            insert content;
            content = [select id, ContentDocumentId from ContentVersion WHERE Id =: content.Id];
            ContentDocumentLink link = new ContentDocumentLink(ContentDocumentId=content.ContentDocumentId, LinkedEntityId=parentId, ShareType='V', Visibility='AllUsers');
            insert link;
        } else {
            ContentVersion partialContent = [SELECT Id, ContentDocumentId, VersionData FROM ContentVersion WHERE Id =: recordId];
            String currentData = EncodingUtil.base64Encode(partialContent.VersionData);
            content = new ContentVersion(Title=fileName, PathOnClient='/' + fileName, ContentDocumentId=partialContent.ContentDocumentId);
            content.VersionData = EncodingUtil.base64Decode(currentData + dataString);
            insert content;
        }
        return content.Id;
    }
}
```