色々と調べたがまとまっている記事が無かったので、備忘録としてメモ。
まだまだ改善の余地、というかダメな部分も多々あるが、ひとまずそれぞれの仕組みの細かいことは置いておいて、手順だけ。
##全体の構成##
1.<textarea>
は使わずに、<div>
を使う。
contenteditable="true"
を指定することで中身が編集できるようになるので、これをテキストエリアと見なす。
2.テキストエリアにドロップされた画像をFileReaderで読み込み、<div contenteditable="true">
の中に<img>
要素を生成してプレビューする。
3.アップロードの際は、<img>
要素をFormDataに組み込んでXMLHttpRequest.send()
で送信する。
##ソース##
<form action="upload/path" id="" method="post">
<div id="alt_textarea" contenteditable="true" style="height: auto; min-height: 250px;">
</div>
<input type="submit" id="submit_btn" name="submit">
</form>
/***
***********************************************
** ドラッグアンドドロップで画像をアップした時の動き **
***********************************************
****/
//ブラウザデフォルトのドラッグ&ドロップ機能を無効化
document.addEventListener('dragover',function(event){
event.preventDefault();
}, false);
document.addEventListener('drop',function(event){
event.preventDefault();
}, false);
//div要素にドラッグしていった時の挙動
var div = document.getElementById('alt_textarea');
div.addEventListener('dragover', function(){
div.style.border = '3px Aqua dashed';
}, false);
//ドラッグで離れた時の挙動
div.addEventListener('dragleave',function(){
div.style.border = '';
}, false);
//ドロップした時の挙動
div.addEventListener('drop', function(event){
//現在のカーソル位置
var sel = window.getSelection();
if (sel.anchorNode === null) {
alert('テキストエリアにカーソルを合わせてください');
div.style.border = '';
return;
}
event.preventDefault();
div.style.border = '';
//DataTransfer オブジェクト、ファイルリストを取得する
var files = event.dataTransfer.files;
if(!files) { return; }
for(var i = 0; i < files.length;i++){
//ファイルを取得する
if (!files[i].type.match('image.*')) {
alert('画像をアップしてください');
return;
}
var reader = new FileReader();
//エラー処理
reader.addEventListener('error', function (event){
console.log('error' + event.target.error.code);
}, false);
//読み込み後の処理
reader.addEventListener('load', function (event){
var img = document.createElement('img');
img.src = event.target.result;
img.width = 300;
var range = sel.getRangeAt(0);
range.insertNode(img);
}, false);
reader.readAsDataURL(files[i]);
}
}, false);
/***
***********************************************
** submitボタンを押された時の挙動
***********************************************
****/
var form = document.forms[0];
form.addEventListener('submit', function(event) {
event.preventDefault();
this.submit.disabled = true;
var defaultValue = this.submit.value;
this.submit.value = '送信中';
var div = document.getElementById('alt_textarea');
var div_tmp = document.createElement('div');
div_tmp.innerHTML = div.innerHTML;
var images = Array.prototype.slice.call(div_tmp.getElementsByTagName('img'));
var form_data = new FormData(this);
for (var i = 0; i < images.length; i++) {
var altImage = document.createTextNode("articleImage_" + i);
images[i].parentNode.insertBefore(altImage,images[i]);
images[i].parentNode.removeChild(images[i]);
form_data.append('eyecatch[]',images[i].src);
};
var xhr = new XMLHttpRequest();
form_data.append('body',div_tmp.innerHTML);
//エラー処理
xhr.addEventListener('error', function (e){
console.log('error' + e.target.error.code);
},false);
xhr.addEventListener('load', function (e){
if (this.status == 200) {
var url = 'redirect/url';
window.location.href = url;
} else {
form.submit.disabled = false;
form.submit.value = defaultValue;
console.log('入力に間違いがあります。');
}
}, false);
xhr.open("POST" , this.getAttribute('action'));
xhr.send(form_data);
}, false);
##補足1##
for (var i = 0; i < images.length; i++) {
var altImage = document.createTextNode("articleImage_" + i);
images[i].parentNode.insertBefore(altImage,images[i]);
images[i].parentNode.removeChild(images[i]);
form_data.append('eyecatch[]',images[i].src);
};
FormDataに画像を追加しつつ、テキストエリアの文字列を置き換えているのは、アップされた記事を後に表示する際にサーバーサイドで処理しやすくするためである。
articleImage_1 => <img src="hogehoge1.jpg">
articleImage_2 => <img src="hogehoge2.jpg">
表示する際は、記事の内容中の文字列を上記のように該当の<img>
で置き換え、<?php>echo htmlspecialchars_decode()<?>
とかで出力すれば画像が途中に埋め込まれた形で表示出来る。
何かもっと賢い方法が見つかったら変更予定。
##補足2##
var images = Array.prototype.slice.call(div_tmp.getElementsByTagName('img'));
にてArray.prototype.slice.call()
を使っているのは、取得した要素の追従をなくすため。
以前書いた記事でykztsさんに教えていただきました。ありがとうございます。
ここで追従を無くさないと、補足1で書いた操作をした時に<img>
要素そのものが消えてしまい、FormDataに追加できなくなってしまう。
##まとめ##
ひとまずこれでそれっぽい動きはするようになった。
Laravelを使っているのでバリデーションはサーバーサイドでやりたいし、Flash Messageも使いたいのでできればAjaxでのsubmitではなく、もっと静的に送信したいが何か方法あるのかな?