JSFでDropZoneを使ったモダンな画像アップロードを実装する

# はじめに
DropZoneをJSFで実装するのが凄く難しかったので覚書。

スタイルは公式のCSSを使用します。
JSF独自タグではなくHTML5Friendlyに寄せて書いています。
基本的にはクリックかドロップでファイルを選択後そのままアップロード。
「キャンセル」リンクをクリックで画像削除といった流れです。
実装イメージはこんな感じです。

##### 選択前
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246099/71419eda-231e-3aea-6c2a-63ba14979126.png)

##### 選択後

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/246099/b067c2a2-4627-bca2-d646-997c31814cb4.png)

#### XHTML
```image_up.xhtml
<form jsf:id="uploadForm"
      enctype="multipart/form-data"
      class="dropzone" jsf:prependId="true">
    <div class="fallback">
        <input jsf:id="upImage" type="file"
               jsf:value="#{controlBean.upImage}" />
        <button jsf:id="submit">submit</button>
    </div>
</form>
```

##### ポイント
jsから指定するのに必要なのでjsf:id="uploadForm"でformに名前を付けます。
マネージドビーンの画像アップロード処理を指定するのにinputのvalueに#{controlBean.upImage}を指定します。
submitが必要なのでbuttonタグを用意します。

#### Javaソース（アップロード）
```ControlBean.java
@Named
@SessionScoped
public class ControlBean implements Serializable {
	private static final long serialVersionUID = 1L;
/**
 * ファイルアップロード
 *
 * @param file 画像
 */
public void setUpImage(Part file) {
	try {
		InputStream in = file.getInputStream();
		String filepath = "保存するディレクトリのパス";
		String fileName = String.format("%s.%s","ファイル名" , FilenameUtils.getExtension(file.getSubmittedFileName()));
		// ファイルとして保管
		Files.copy(in, new File(filepath, fileName).toPath());
	} catch (IOException e) {
		エラー処理;
	} catch (Exception e) {
		エラー処理;
	}
}
```
##### ポイント
Partで受け取った画像データをNAS等の保存先URLを指定し保存します。
アップロード時にファイル名をfileNameListに保存し削除で使用します。

#### Javaソース（画像削除）
```DeleteServlet.java
@WebServlet("/servlet/delete")
public class DeleteServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	@Inject
	private ControlBean controlBean;

	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {

		String requestFileName = request.getParameter("filename");	// removedfileのdataで設定したパラメータ名
		
		TreeMap<String, String> fileList = controlBean.getFileNameList();	// Sessionスコープで作成したControlBeanから削除対象のファイル名を取得。

		Optional<String> found = fileList.entrySet().stream()
				.filter(name -> name.getKey().equals(requestFileName))
				.map(name -> name.getValue())
				.findFirst();

		if (found.isPresent()) {
			// アップロード済みのファイルリストにファイル名が一致

			String ctx = "画像アップロード先URL" + found.get();
			File imageFile = new File(ctx);

			if (imageFile.exists()) {
				// 削除対象が存在すれば削除

				imageFile.delete();
				controlBean.getFileNameList().remove(requestFileName);
			}
		}
	}
}
```
##### ポイント
削除はサーブレットにして実装しています。
削除対象ファイル名をRequestで受け取ってControlBean（セッションスコープ）で保持してるファイル名と一緒なら削除しています。


#### javascript
```dropzone_util.js
Dropzone.options.uploadForm = {
// DropZoneのパラメータ設定は一部省いております
    paramName                  : "uploadForm:upImage",
    dictInvalidFileType        : "画像ファイルを選択してください。",
    dictDefaultMessage         : 'ここにドロップするか<br />クリック',
    addRemoveLinks             : true,
    dictCancelUpload           : "キャンセル",
    dictRemoveFileConfirmation : "画像を削除しますか？",
    accept                     : function(file, done) {
        return done();
    },
    success                    : function(file, response_text, e) {
        // upload.php で echo された内容が response_text に入ります。
    },
    drop                       : function(e) {
        this.element.classList.remove("selected");
    },
    dragover                   : function(e) {
        this.element.classList.add("selected");
    },
    dragleave                  : function(e) {
        this.element.classList.remove("selected");
    },
    complete                   : function(file, message) {
    },
    error                      : function(file, message) {
        alert(message);
        this.removeFile(file);
    },

    removedfile                : function(file) {
      // 削除時のサーブレットを設定
        var name = file.name;
        jQuery.noConflict()(function($){
        $.ajax({
            type : 'POST', // HTTP通信の種類
            url : '../servlet/delete', // リクエスト送信先URL
            data : "filename=" + name, // サーバに送信する値
            dataType : 'text' // サーバから返されるデータタイプ
        });
        });
        var _ref;
        return (_ref = file.previewElement) != null ? _ref.parentNode
                .removeChild(file.previewElement) : void 0;
    }
}

```
##### ポイント
削除処理はremovedfileで指定します。
urlに作成した削除サーブレットのurlを記載します。
送信するファイル名はdataにパラメータ名（"filename="）と削除したいファイル名を指定します。

### 参考
[Stackoverflow:Use dropzone with JSF](https://stackoverflow.com/questions/38018632/use-dropzone-with-jsf/38021341)