複数選択ファイルを保存する

<?php
	  for ($i=0; $i<count($_FILES&#91;'files'&#93;&#91;'name'&#93;) and $i < 2; $i++) {
    $file_ext = pathinfo($_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93;, PATHINFO_EXTENSION);
    if (FileExtensionGetAllowUpload($file_ext) &&  is_uploaded_file($_FILES&#91;"files"&#93;&#91;"tmp_name"&#93;&#91;$i&#93;)) {
      if(move_uploaded_file($_FILES&#91;"files"&#93;&#91;"tmp_name"&#93;&#91;$i&#93;, "upload/img/".$_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93;)) {
          $message .= $_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93; . "を送信しました。<br>";
          $message .= "fileのパスは upload/img/".$_FILES["files"]["name"][$i]." です。<br>";
      } else {
        $message = "ファイルをアップロードできません。<br>";
      }
    } else {
      $message = "ファイルが選択されていません。<br>";
    }
  }
  function FileExtensionGetAllowUpload($ext){
    $allow_ext = array("gif","jpg","jpeg","png");
    foreach($allow_ext as $v){
      if ($v === $ext){
        return 1;
      }
    }
    return 0;
  }

?>
<style>
.thumb {
  height: 100px;
  width: 100px;
  border: 1px solid #000;
  margin: 10px 5px 0 0;
}
h1 {
  margin:0px;
}
#caution {
  color:gray;
  font-size:small;
}
</style>
<h1>添付ファイルを送信</h1>
<div id="caution">
※送信できるファイルの拡張子は"gif","jpg","jpeg","png"の何れかです。<br>
※ファイル送信は最大2つまでです。3つ以上選択しても、3つ目以降は送れません。また、二つのファイルを送信する場合は、キーボードの"ctl"ボタンなどで二つ選択した状態で開いてください。</div><br>
<form action="#" method="post" enctype="multipart/form-data">
<input type="file" id="files" name="files&#91;&#93;" multiple />
<input type="submit" value="送信">
</form>
<?php echo $message; ?>
<output id="list"></output>
<script>
  function handleFileSelect(evt){
    var files = evt.target.files;
    if(files.length > 2){
      files = files.slice(0, 2);
    }    

    for (var i = 0, f; f = files[i]; i++) {
    // for (var i = 0, f; f = files[i]; i++) {

      if (!f.type.match('image.*')) {
            continue;
          }

      var reader = new FileReader();

      reader.onload = (function(theFile){
        return function(e){
              var span = document.createElement('span');
              span.innerHTML = ['<img class="thumb" src="', e.target.result,
                                '" title="', escape(theFile.name), '"/>'].join('');
              document.getElementById('list').insertBefore(span, null);
            };
          })(f);

      reader.readAsDataURL(f);
    }

    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
    // for (var i = 0, f; f = files[i]; i++) {

      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate.toLocaleDateString(), '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>

同じファイル名を送信すると、ファイルが上書きされてしまうので、保存するディレクトリを送信ごとにユニークにする必要がありますね。

選択ファイルのjavascript側の表示数をsliceで制御

適当に数時間試していたら上手くいきました。
変数のlengthをcountして条件分岐で欲しい数だけsliceしてます。

<script>
  function handleFileSelect(evt){
    var files = evt.target.files;
    if(files.length > 2){
      files = files.slice(0, 2);
    }    

    for (var i = 0, f; f = files[i]; i++) {
    // for (var i = 0, f; f = files[i]; i++) {

      if (!f.type.match('image.*')) {
            continue;
          }

      var reader = new FileReader();

      reader.onload = (function(theFile){
        return function(e){
              var span = document.createElement('span');
              span.innerHTML = ['<img class="thumb" src="', e.target.result,
                                '" title="', escape(theFile.name), '"/>'].join('');
              document.getElementById('list').insertBefore(span, null);
            };
          })(f);

      reader.readAsDataURL(f);
    }

    var output = [];
    for (var i = 0, f; f = files[i]; i++) {
    // for (var i = 0, f; f = files[i]; i++) {

      output.push('<li><strong>', escape(f.name), '</strong> (', f.type || 'n/a', ') - ',
                  f.size, ' bytes, last modified: ',
                  f.lastModifiedDate.toLocaleDateString(), '</li>');
    }
    document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
  }

  document.getElementById('files').addEventListener('change', handleFileSelect, false);
</script>

OK! Nice!

さて、問題は、送信ファイルをどうやって保存するかですね。
gmailやhotmailなどは、ファイル名は変わらずに送られてくるので、拡張子にバリデーションをかけて、ファイル名はそのままにしたいと思います。

php側のファイルのアップロードの数に制限をかける

for文の繰り返し処理をand $i < 2とすることで、3つ以上のファイルがpostされても、処理されるのは2つに制限できる。

	  for ($i = 0; $i < count($_FILES['files']['name']) and $i < 2; $i++) {
    $file_ext = pathinfo($_FILES["files"]["name"][$i], PATHINFO_EXTENSION);
    if (FileExtensionGetAllowUpload($file_ext) &&  is_uploaded_file($_FILES["files"]["tmp_name"][$i])) {
      if(move_uploaded_file($_FILES["files"]["tmp_name"][$i], "upload/img/".$_FILES["files"]["name"][$i])) {
          echo $_FILES["files"]["name"][$i] . "をアップロードしました。
“; } else { echo “ファイルをアップロードできません。
“; } } else { echo “ファイルが選択されていません。
“; } }

問題はjavascript側なんだが、
var i = 0, f; f = files[i], i < 2; i++ と書いても、制御ができない。。。 しかも、画像とテキストで二つのfor文を回しているのだが、 二つ目のfor文が上手く動かない。困った。 [code] function handleFileSelect(evt){ var files = evt.target.files; for (var i = 0, f; f = files[i], i < 2; i++) { // for (var i = 0, f; f = files[i]; i++) { if (!f.type.match('image.*')) { continue; } var reader = new FileReader(); reader.onload = (function(theFile){ return function(e){ var span = document.createElement('span'); span.innerHTML = ['‘].join(”);
document.getElementById(‘list’).insertBefore(span, null);
};
})(f);

reader.readAsDataURL(f);
}

var output = [];
for (var i = 0, f; f = files[i]; i++) {
// for (var i = 0, f; f = files[i]; i++) {
output.push(‘

  • ‘, escape(f.name), ‘(‘, f.type || ‘n/a’, ‘) -‘,
    f1.size, ‘ bytes, last modified: ‘,
    f1.lastModifiedDate.toLocaleDateString(), ‘
  • ‘);
    }
    document.getElementById(‘list’).innerHTML = ‘

      ‘ + output.join(”) + ‘

    ‘;
    }
    [/code]

    ファイルアップロードの進捗(%)をjavascriptで表示

    コードでは一番下に来ていますが、addEventListenerで、getElementById(‘files’).addEventListener(‘change’, handleFileSelect, false)として、handleFileSelectの関数を実行します。
    handleFileSelectでは、new FileReader();とした後、errorHandlerとupdateProgressを実行します。
    %は、Math.round((evt.loaded / evt.total) * 100);

    <style>
    #progress_bar {
    	margin: 10px 0;
    	padding: 3px;
    	border: 1px solid #000;
    	font-size: 14px;
    	clear: both;
    	opacity: 0;
    	-moz-transition: opacity ls linear;
    	-o-transition: opacity ls linear;
    	-webkit-transition: opacity ls linear;
    }
    #progress_bar.loading {
    	opacity: 1.0;
    }
    #progress_bar .percent {
    	background-color: #99ccff;
    	height: auto;
    	width: 0;
    }
    </style>
    
    <input type="file" id="files" name="file" />
    <button onclick="abortRead();">Cancel read</button>
    <div id="progress_bar"><div class="percent">0%</div></div>
    
    <script>
    	var reader;
    	var progress = document.querySelector('.percent');
    
    	function abortRead(){
    		reader.abort();
    	}
    
    	function errorHandler(evt){
    		switch(evt.target.error.code){
    			case evt.target.error.NOT_FOUND_ERR:
    				alert('File Not Found!');
    				break;
    			case evt.target.error.NOT_READABLE_ERR:
    				alert('File is not readable');
    				break;
    			case evt.target.error.ABORT_ERR:
    				break;
    			default:
    				alert('An error occurred reading this file.');
    		};
    	}
    
    	function updateProgress(evt) {
        if (evt.lengthComputable) {
          var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
          if (percentLoaded < 100) {
            progress.style.width = percentLoaded + '%';
            progress.textContent = percentLoaded + '%';
          }
        }
      }
    
    
    		function handleFileSelect(evt) {
    	    progress.style.width = '0%';
    	    progress.textContent = '0%';
    
    	    reader = new FileReader();
    	    reader.onerror = errorHandler;
    	    reader.onprogress = updateProgress;
    	    reader.onabort = function(e) {
    	      alert('File read cancelled');
    	    };
    	    reader.onloadstart = function(e) {
    	      document.getElementById('progress_bar').className = 'loading';
    	    };
    	    reader.onload = function(e) {
    	      progress.style.width = '100%';
    	      progress.textContent = '100%';
    	      setTimeout("document.getElementById('progress_bar').className='';", 2000);
    	    }
    
    		reader.readAsBinaryString(evt.target.files&#91;0&#93;);
    	}
    	document.getElementById('files').addEventListener('change', handleFileSelect, false);
    </script>
    

    容量が小さいと、直ぐに100%となるので、最近downloadして重かったJavaの”spring-tool-suite-3.9.2.RELEASE-e4.7.2-win32-x86_64.zip”で試してみます。

    いや、参りました。って感じですかね。参ってばっかりですが。。

    FileReader()

    FileReader クラスは、HTML5 世代の機能

    File APIで出来ること
    -ローカルにある画像をアップロードする前にプレビューを表示する
    -ローカルにある画像をブラウザで加工してダウンロードする
    -テキストファイルをブラウザで解析する
    -データをJSONファイルとしてエクスポートして、インポートする
    -大きなファイルを分割してサーバーに送信する

    特に、「ローカルにある画像をアップロードする前にプレビューを表示する」ですね。

    readAsDataURL メソッドは、指定された Blob ないし File オブジェクトを読み込むために使用
    reader.readAsDataURL(file);
    escape():16 進数エスケープシーケンスに置換
    insertBefore:指定のノードを現在のノードの子ノードとして参照要素の前に挿入

    inputで選択した複数画像をブラウザ上で表示させる

    <style>
    .thumb {
    	height: 100px;
    	width: 100px;
    	border: 1px solid #000;
    	margin: 10px 5px 0 0;
    }
    </style>
    
    <input type="file" id="files" name="files&#91;&#93;" multiple /><br>
    <output id="list"></output>
    <script>
    	function handleFileSelect(evt){
    		var files = evt.target.files;
    
    		for (var i = 0, f; f = files[i]; i++) {
    
    			if (!f.type.match('image.*')) {
    		        continue;
    		      }
    
    			var reader = new FileReader();
    
    			reader.onload = (function(theFile){
    				return function(e){
    							var span = document.createElement('span');
    		          span.innerHTML = ['<img class="thumb" src="', e.target.result,
    		                            '" title="', escape(theFile.name), '"/>'].join('');
    		          document.getElementById('list').insertBefore(span, null);
    		        };
    		      })(f);
    
    			reader.readAsDataURL(f);
    		}
    	}
    
    	document.getElementById('files').addEventListener('change', handleFileSelect, false);
    </script>
    

    初期

    画像を選択

    選択後
    なんだこりゃーー!!すげー

    では繋げてみましょう。

    <?php
    	  for ($i=0; $i<count($_FILES&#91;'files'&#93;&#91;'name'&#93;); $i++) {
        $file_ext = pathinfo($_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93;, PATHINFO_EXTENSION);
        if (/*FileExtensionGetAllowUpload($file_ext) && */ is_uploaded_file($_FILES&#91;"files"&#93;&#91;"tmp_name"&#93;&#91;$i&#93;)) {
          if(move_uploaded_file($_FILES&#91;"files"&#93;&#91;"tmp_name"&#93;&#91;$i&#93;, "upload/img/".$_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93;)) {
              echo $_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93; . "をアップロードしました。<br>";
          } else {
            echo "ファイルをアップロードできません。<br>";
          }
        } else {
          echo "ファイルが選択されていません。<br>";
        }
      }
      //アップロードできるファイルに拡張子の制限をかけたい時
      function FileExtensionGetAllowUpload($ext){
        $allow_ext = array("gif","jpg","jpeg","png","eps");
        foreach($allow_ext as $v){
          if ($v === $ext){
            return 1;
          }
        }
        return 0;
      }
    
    ?>
    <style>
    .thumb {
      height: 100px;
      width: 100px;
      border: 1px solid #000;
      margin: 10px 5px 0 0;
    }
    </style>
    <form action="#" method="post" enctype="multipart/form-data">
    <input type="file" id="files" name="files&#91;&#93;" multiple />
    <input type="submit" value="Send">
    </form>
    <output id="list"></output>
    <script>
      function handleFileSelect(evt){
        var files = evt.target.files;
    
        for (var i = 0, f; f = files[i]; i++) {
    
          if (!f.type.match('image.*')) {
                continue;
              }
    
          var reader = new FileReader();
    
          reader.onload = (function(theFile){
            return function(e){
                  var span = document.createElement('span');
                  span.innerHTML = ['<img class="thumb" src="', e.target.result,
                                    '" title="', escape(theFile.name), '"/>'].join('');
                  document.getElementById('list').insertBefore(span, null);
                };
              })(f);
    
          reader.readAsDataURL(f);
        }
    
        var output = [];
        for (var i = 0, f; f = files[i]; i++) {
          output.push('<li><strong>', escape(f.name), '</strong>(', f.type || 'n/a', ') -',
            f.size, ' bytes, last modified: ',
            f.lastModifiedDate.toLocaleDateString(), '</li>');
        }
        document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
      }
    
      document.getElementById('files').addEventListener('change', handleFileSelect, false);
    </script>
    

    ほお

    php側にもちゃんと渡されています。

    ファイルをdrag & drop

    <style>
    #drop_zone {
    	height:100px;
    	width:400px;
    	background-color:#EEEEEE;
    	display: flex;
    	align-items: center;
    	justify-content: center;
    	color:white;
    
    }
    </style>
    Drop files here
    <div id="drop_zone">ここに添付ファイルをドロップ</div>
    <output id="list"></output>
    
    <script>
    function handleFileSelect(evt){
    	evt.stopPropagation();
    	evt.preventDefault();
    
    	var files = evt.dataTransfer.files;
    
    	var output = [];
    	for (var i = 0, f; f = files[i]; i++) {
    		output.push('<li><strong>', escape(f.name), '</strong>(', f.type || 'n/a', ') -',
    			f.size, ' bytes, last modified: ',
    			f.lastModifiedDate.toLocaleDateString(), '</li>');
    	}
    	document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
    }
    
    	function handleDragOver(evt){
    		evt.stopPropagation();
    		evt.preventDefault();
    		evt.dataTransfer.dropEffect = 'copy';
    	}
    
    	var dropZone = document.getElementById('drop_zone');
    	dropZone.addEventListener('dragover', handleDragOver, false);
    	dropZone.addEventListener('drop', handleFileSelect, false);
    </script>
    

    あ、これマジ凄い

    preventDefault() は、その要素のイベントをキャンセルし、stopPropagation()は、親要素への伝播をキャンセル

    dataTransfer:ドラッグを開始した時に、DataTransfer オブジェクトを取得する
    addEventListener(‘dragover’,”,false)ドロップ領域に入っている間連続して発生
    addEventListener(‘drop’,”,false)ドロップされた時に発生

    jsで複数添付ファイルのtype, size, lastModifiedDateを表示する

    var file = event.target.files[0]; で、ファイルオブジェクトを取り出す。

    <input type="file" id="files" name="files&#91;&#93;" multiple />
    <output id="list"></output>
    
    <script>
    	function handleFileSelect(evt){
    		var files = evt.target.files;
    
    		var output = [];
    		for (var i = 0, f; f = files[i]; i++) {
    			output.push('<li><strong>', escape(f.name), '</strong>(', f.type || 'n/a', ') -',
    				f.size, ' bytes, last modified: ',
    				f.lastModifiedDate.toLocaleDateString(), '</li>');
    		}
    		document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
    	}
    
    	document.getElementById('files').addEventListener('change', handleFileSelect, false);
    </script>
    

    jsで複数の初期化式

    <script>
    document.write("<p>");
    
    for(var i = 1, j=10; i < 10; i++, j++){
    	document.write("i = " + i + ", j = " + j + "<br>");
    }
    
    document.write("</p>");
    </script>
    

    toLocaleDateString()
    ->日付のみ抽出

    <script>
    var date = new Date();
    
    document.write(date.toLocaleDateString());
    </script>
    

    addEventListener
    イベント追加

    <form>
    <input type="button" id="ev" value="button" onclick="func1()">
    </form>
    <script>
    function func1(){ alert("関数1が呼び出されました");}
    var obj = document.getElementById("ev");
    
    obj.addEventListener("click", func2, false);
    function func2(){ alert("関数2が呼び出されました");}
    
    obj.addEventListener("click", function(){
    	alert("関数3が呼び出されました")
    }, false);
    </script>
    

    つなげると、

    <?php
    	  for ($i=0; $i<count($_FILES&#91;'files'&#93;&#91;'name'&#93;); $i++) {
        $file_ext = pathinfo($_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93;, PATHINFO_EXTENSION);
        if (/*FileExtensionGetAllowUpload($file_ext) && */ is_uploaded_file($_FILES&#91;"files"&#93;&#91;"tmp_name"&#93;&#91;$i&#93;)) {
          if(move_uploaded_file($_FILES&#91;"files"&#93;&#91;"tmp_name"&#93;&#91;$i&#93;, "upload/img/".$_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93;)) {
              echo $_FILES&#91;"files"&#93;&#91;"name"&#93;&#91;$i&#93; . "をアップロードしました。<br>";
          } else {
            echo "ファイルをアップロードできません。<br>";
          }
        } else {
          echo "ファイルが選択されていません。<br>";
        }
      }
      //アップロードできるファイルに拡張子の制限をかけたい時
      function FileExtensionGetAllowUpload($ext){
        $allow_ext = array("gif","jpg","jpeg","png","eps");
        foreach($allow_ext as $v){
          if ($v === $ext){
            return 1;
          }
        }
        return 0;
      }
    
    ?>
    <form action="#" method="post" enctype="multipart/form-data">
    <input type="file" id="files" name="files&#91;&#93;" multiple />
    <input type="submit" value="Send">
    </form>
    <output id="list"></output>
    
    <script>
      function handleFileSelect(evt){
        var files = evt.target.files;
    
        var output = [];
        for (var i = 0, f; f = files[i]; i++) {
          output.push('<li><strong>', escape(f.name), '</strong>(', f.type || 'n/a', ') -',
            f.size, ' bytes, last modified: ',
            f.lastModifiedDate.toLocaleDateString(), '</li>');
        }
        document.getElementById('list').innerHTML = '<ul>' + output.join('') + '</ul>';
      }
    
      document.getElementById('files').addEventListener('change', handleFileSelect, false);
    </script>
    

    いいですね!

    onChange()メソッド

    onChangeとは、フォーム内のエレメント(要素)の内容が変更された時に起こるイベント処理

    sample

    <form name="link">
    <select name="linkselect" onChange="confirm()">
    <option value="">選択してください</option>
    <option value="https://products.office.com/ja-jp/exchange/exchange-online">Microsoft Exchange Online</option>
    <option value="https://willcloud.jp/">Will Mail</option>
    <option value="http://www.cybersaas.jp/">CYBERMAIL Σ</option>
    </select>
    </form>
    <script>
    function confirm(){
    	obj = document.link.linkselect;
    
    	index = obj.selectedIndex;
    	if(index != 0){
    		href = obj.options[index].value;
    		location.href = href;
    	}
    }
    </script>
    

    multiple属性による複数ファイルアップロードが上手くいかない?

    up.phpファイル
    postでphp側に渡して、var_dumpします。

    <?php
    print_r('<pre>');
    var_dump($_FILES);
    print_r('</pre>');
    ?>
    
    <html>
    <head>
        <title>multiple upload</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    </head>
    <body>
    <form action="#" method="post" enctype="multipart/form-data">
    Files:<input type="file" name="upfile&#91;&#93;" multiple>
    <input type="submit" value="submit">
    </form>
    </body>
    </html>
    

    初期状態 ※ファイルは選択していません。

    01.gifを選択

    選択されてます。

    続いて02.gifを選択

    02.gifも選択されています。

    post(submit)します。

    何故だ? 02.gifしかアップロードされていない!?

    multipleが効いていないのでは?と、multipleを、multiple=”multiple”に変えたり、phpファイルとhtmlファイルに分けたり、サーバー環境を変えたり、html5shivをヘッダーに入れたりしたが全くダメで、ずっと悩み続け、
    ふと、画像選択時にctlコマンド押下で二つ選んだら、

    おいおいおい、まじかよ?

    今日は実質朝からこれしかやってないんだけど。。。