@blog.justoneplanet.info

日々勉強

もっともっとFile APIを使ってサーバ側で受け取ってみる

前回の記事に以下の質問がありました。

質問なのですがバイナリデータはsendでは送れないのでは?
自分も同じようなことをしているのですがFireFox限定のsendAsBinaryでないと無理でした。
どのようにして転送してるのでしょうか?

えーと。。。(・Θ・;)。。。あの、その。。。うかれててサーバ側を考えてませんでした。すみませんm(_ _)m

■クライアント側のコード

前回のコードとおなじ。

function dragStart(e){
    //e.preventDefault();
}
function dragEnter(e){
    //e.preventDefault();
}
function dragOver(e){
    e.preventDefault();
}
function drop(e){
    var files = e.dataTransfer.files;
    for(var i = 0; i < files.length; i++){
        var request = new XMLHttpRequest();
        request.upload.onprogress = function(e){
            document.getElementById('progress').style.width = ((e.loaded / e.total) * 100 + "%");
        }
        request.upload.onload = function(e){
            document.getElementById('progress').style.width = '0px';
            alert('finished');
        }
        request.open('post', "./index.html");
        request.send(files[i]);
    }
    e.preventDefault();
}

サーバー側

今回のケースは、$_FILESなどでファイルを取得することはできない。以下のようにして生のPOSTデータを取得しなければならない。

$post = fopen("php://input", "r");
file_put_contents('./test.png', $post);//ファイル名はテストなので固定=3
fclose($post);

ちゃんと受け取れるヽ(^◇^*)/。簡易的なコードなので実環境ではフィルタリングしてください。

■sendAsBinaryとsendを比較して使ってみる

以下はsendAsBinaryを使ったサンプルである。

request.open('post', "./index.html");
request.sendAsBinary(files[i].getAsBinary());

上述のコードが以下のコードに対応する。

request.open('post', "./index.html");
request.send(files[i]);

まとめ

  1. 引数がFileオブジェクトの時はsendを使用する
  2. 引数がBinary文字列の時はsendAsBinaryを使用する
  3. 引数がBinary文字列の時にsendを使用すると途中でデータがカットされてしまう

なるほど!

sendAsBinaryとは

A variant of the send() method that sends binary data.

sendAsBinaryはsendメソッドの変異体らしい。

This data is converted to a string of single-byte characters by truncation (removing the high-order byte of each character).

シングルバイト文字列に変換されるらしい。

$_FILESで受け取りたい時

以下のようにsendAsBinaryを使用する。

request.open('post', "./index.php", true);
var boundary = '------multipartformboundary1276452374015';
request.setRequestHeader(
    'content-type',
    'multipart/form-data; boundary=' + boundary
);
request.setRequestHeader(
    'content-length',
    files[i].size
);
request.sendAsBinary(
    '--' + boundary + '\n' +
    'Content-Disposition: form-data; name="file"; filename="test.png"\n\n' +
    'Content-Type: application/octet-stream\n\n' +
    files[i].getAsBinary() + '\n' +
    '--' + boundary + '--'
);

以下のようにsendAsBinaryを使用する。ちなみにboundaryとは

マルチパートのデータは、この例のように「境界(boundary)」となる行でデータ項目が区切られ、それぞれがContent-Dispositionという説明情報などのあとに実際のデータが続くという形を取ります。

とのことである。

PHPでは以下のように受け取れる。

var_dump($_FILES);
/*
array(1) {
  ["file"]=>
  array(5) {
    ["name"]=>
    string(8) "test.png"
    ["type"]=>
    string(0) ""
    ["tmp_name"]=>
    string(14) "/tmp/sdhfhsg"
    ["error"]=>
    int(0)
    ["size"]=>
    int(10)
  }
}
*/
参考

勉強になったー=3

もっとFile APIを使ってXMLHttpRequestと組み合わせてみる

XMLHttpRequest Level2ではバイナリデータもアップロードできるようになった!ヽ(=´▽`=)ノ

■ソース

基本的には前回と同じコードを利用している。

<p ondragstart="dragstart(event)" ondragenter="dragenter(event);" ondragover="dragover(event);" ondrop="drop(event);">You drop some images, here!</p>
<div id="preview"></div>

次にJavaScriptコード。

function dragStart(e){
    //e.preventDefault();
}
function dragEnter(e){
    //e.preventDefault();
}
function dragOver(e){
    e.preventDefault();
}
function drop(e){
    var files = e.dataTransfer.files;
    for(var i = 0; i < files.length; i++){
        var request = new XMLHttpRequest();
        request.upload.onprogress = function(e){
            document.getElementById('progress').style.width = ((e.loaded / e.total) * 100 + "%");
        }
        request.upload.onload = function(e){
            document.getElementById('progress').style.width = '0px';
            alert('finished');
        }
        request.open('post', "./index.html");
        request.send(files[i]);
    }
    e.preventDefault();
}

ファイルアップロード部分

onprogressで進捗を把握できる。e.loadedはアップロードを行った容量で、e.totalは全体のアップロード容量である。

var request = new XMLHttpRequest();
request.upload.onprogress = function(e){
    document.getElementById('progress').style.width = ((e.loaded / e.total) * 100 + "%");
}

onload でアップロードの完了を取得できる。

request.upload.onload = function(e){
    document.getElementById('progress').style.width = '0px';
    alert('finished');
}
request.open('post', "./index.html");
request.send(files[i]);

Chromeについて

You can work around this by uploading the file via xmlhttprequest, maybe. I don’t
think Chrome has yet implemented the necessary HTML5 APIs for doing this yet,
unfortunately (they’re called FileStreams I think).

まだサポートしていないんじゃないか?と言ってるみたいだ。

WorkersからのDatabaseへのアクセスや,File API,XMLHttpRequestのFormData送信サポートなどはChrome 5での対応からChrome 6への対応に先送りされています

やっぱりFile APIへの対応はChrome6以降になったらしい。残念。

Web Platform Status

マイルストーン6で対応ですな=3

■サンプル

Firefox3.6以降でないと動かないですが、10MB以下のファイルを点線の中にドロップしてください。

You drop some files, here!

おまけ

jQuery1.4.2のbindを使うと、eventオブジェクトのプロパティdataTransferは、コールバック関数の引数のeventオブジェクトでは受け取れないようだ。従って、以下のコードは使えない。

$(elm).bind(
    'drop',
    function(e){
        e.preventDefault();
        var files = e.dataTransfer.files;
        for(var i = 0; i < files.length; i++){
            var request = new XMLHttpRequest();
            request.upload.onprogress = function(e){
                document.getElementById('progress').style.width = ((e.loaded / e.total) * 100 + "%");
            }
            request.upload.onload = function(e){
                document.getElementById('progress').style.width = '0px';
                alert('finished');
            }
            request.open('post', "./index.html");
            request.send(files[i]);
        }
        e.preventDefault();
    }
);

以下のようにaddEventListenerを使用すれば良い。

elm.addEventListener(
    'drop',
    function(e){
        e.preventDefault();
        var files = e.dataTransfer.files;
        for(var i = 0; i < files.length; i++){
            var request = new XMLHttpRequest();
            request.upload.onprogress = function(e){
                document.getElementById('progress').style.width = ((e.loaded / e.total) * 100 + "%");
            }
            request.upload.onload = function(e){
                document.getElementById('progress').style.width = '0px';
                alert('finished');
            }
            request.open('post', "./index.html");
            request.send(files[i]);
        }
        e.preventDefault();
    },
    false
);

さらにdropだけでなく、input type=”file”のchangeにもハンドリング可能だ。

elm.addEventListener(
    'change',
    function(e){
        e.preventDefault();
        var files = e.dataTransfer.files;
        for(var i = 0; i < files.length; i++){
            var request = new XMLHttpRequest();
            request.upload.onprogress = function(e){
                document.getElementById('progress').style.width = ((e.loaded / e.total) * 100 + "%");
            }
            request.upload.onload = function(e){
                document.getElementById('progress').style.width = '0px';
                alert('finished');
            }
            request.open('post', "./index.html");
            request.send(files[i]);
        }
        e.preventDefault();
    },
    false
);

File APIとドラッグ&ドロップを組み合わせてみる

HTML5では、ユーザが選択したファイルにJavaScriptからアクセスできるようになったヽ(*゚▽゚)ノ

■ソース

イベントハンドラが気に入らないけどまぁ。

<p ondragstart="dragstart(event)" ondragenter="dragenter(event);" ondragover="dragover(event);" ondrop="drop(event);">You drop some images, here!</p>
<div id="preview"></div>

JavaScript

グローバルに書くのが気に入らないけどまぁ。基本的な動作としてブラウザにファイルがドロップされるとブラウザはファイルを元の動作で処理しようとする。つまりJavaScriptに処理を渡すようにはなっていないので、e.preventDefault()でデフォルトのイベントを実行させないようにする。

function dragStart(e){
    //e.preventDefault();
}
function dragEnter(e){
    //e.preventDefault();
}
function dragOver(e){
    e.preventDefault();
}
function drop(e){
    var files = e.dataTransfer.files;
    for(var i = 0; i < files.length; i++){
        if(files[i].type.match(/image\/[a-z]/i)){
            var reader = new FileReader();
            var img = document.createElement('img');
            img.src   = reader.result;
            img.width = 300;
            document.getElementById('preview').appendChild(img);
            reader.onloadend = (function(img){
                return function(e){
                    img.src = e.target.result;
                }
            })(img);
            reader.readAsDataURL(files[i]);
        }
    }
    e.preventDefault();
}

Firefoxで動作確認。

Chromeについて

まだファイルの内容を読み込んだりは出来ないらしい

Chrome has partial support for this. In the test file I’ve attached, I can select a
file via the “Choose File” button and get metadata about it: its name, size and
type. However, I cannot read the contents of the file.

「部分的にサポートしているが内容の読み込みは出来ない」とのこと。まぁ、そのうち。

■サンプル

画像ファイルを点線の中にドロップしてください。

You drop some images, here!

リッチテキスト編集用APIを使ってみる

HTML5ではtinyMCEのような事が簡単にできる。

■編集

まずは以下のようにする。

<div contenteditable="true">編集</div>

サンプル

ただのp要素なのに、文字を入れたり、編集できますよー

<body contenteditable="true">
<p>編集</p>
</div>

上述のようにbodyに対しても可能である。

■装飾

以下のようにすると、編集可能領域において選択範囲を太字にできる。

document.execCommand('bold', false, false);

サンプル


ただのp要素なのに、文字を入れたり、編集できますよー

document.querySelectorをつかう

CSSセレクタは凄く分かりやすくjQueryでも頻繁に使用する。

$('h3.hoge');

これだけのためにjQueryを使いたくはないので、querySelectorメソッドを使用する。

document.querySelector('h3.hoge');

上述だと最初の要素しか選択されないので、下述のようにquerySelectorAllメソッドを使用する。

document.querySelectorAll('h3.hoge');