Archive for 七月, 2011

html5 的拖拽上传文件功能的rails实现

星期日, 七月 3rd, 2011

最近在做一个webim,觉得之前看到的拖拽上传文件很方便,就决定加上这个功能,结果我一半的开发时间都花在这上面了,html5目前还是很坑爹啊。这里面涉及的主要是两个api,fileReader 和 XMLHttpRequest(以下简称xhr), firefox和chrome的较新版本都支持这两个接口。firefox对这两个接口支持的比较完美。而chrome版本的xhr只有send方法,没有sendasbinary方法,导致拖拽上传的二进制文件错乱,只能上传文本文件。有些教程说google的gears支持上传文件功能,试验了一下,用gears上传的时候崩溃,然后将chrome升级到12版本,发现chrome居然不支持gears了,再查了一下,google原来已经放弃了gears了,chrome的最后一根救命稻草是他的file input,原来可以把文件直接拖到他的<input type=file>上,最后的实现是在下层div放一个提示语句,上层div放file input,但是设置透明度为0,这样看起来和firefox的拖拽保持一致。下面放代码:

以下是chrome版本的实现

HTML部分:

<div class=”new_attachment” >

<form accept-charset=”UTF-8″ action=”/talks/1/attachments” enctype=”multipart/form-data” method=”post” target=”form_accepter”>

<div style=”margin:0;padding:0;display:inline”>

<input name=”authenticity_token” type=”hidden” value=”x2MqPKIsC+MgoQpiqx9k2sub62p+pqOcDzdKUXoxicA=” />

<!–这一段看不懂不用管,这个token是rails默认生成的安全策略,用来防止csrf攻击,后起之秀的安全策略做的是真好啊–>

</div>

<div class=”notice”>

<span>请将附件拖到这里上传</span>

</div>

<div class=”file_input”>

<input id=”attachment” name=”attachment” style=”width:100%; height:100%” type=”file” />

</div>

</form>

<iframe name=”form_accepter” style=”display:none”>

</iframe>

</div>

CSS部分:

.new_attachment {

margin: 200px auto 0px auto;

width: 194px;

height: 100px;

float: left;

border:3px dashed silver;

}

.new_attachment form {

height: 100%;

width: 100%;

}

.new_attachment .notice {

position: relative;

left: 0px;

top: 0px;

height: 100%;

}

.new_attachment .file_input {

position: relative;

left: 0px;

top: -100px;

height: 100%;

filter: alpha(opacity=50);

-khtml-opacity: 0;

}

js部分:

New = {

init: function () {

this.newAttachment = $( “div.new_attachment” );

this.attachmentForm = this.newAttachment.children( “form” );

this.fileInput = $( “div.file_input > input” );

this.noticeDiv = $( “div.notice” );

this.fileInput.change( function () {

Sentences.New.noticeDiv.children( “span” ).html( “正在上传请稍后” );

Sentences.New.attachmentForm.submit();

} );

}

$( document ).ready( function () {

Sentences.New.init();

} );

//用到了jquery,如果对jquery不熟悉的只能抱歉了,不过代码大概您应该能看懂

//还有些改进空间,例如gmail实现了上传的进度条,我暂时没找到实现的代码如果谁找到请告诉我一声,另外的提升空间是,监听html的drapenter事件,在有文件拖动到html中时显示那个提示div,诱导用户把文件拖拽到对应的位置。我实现的时候老收到莫名其妙的drapleave事件,所以我放弃了智能提示

firefox的实现

js部分

New = {

buildBody: function ( data, boundary ) {

var dashdash = ‘–‘;

var crlf = ‘\r\n’;

var builder = ”;

/* 下面这段是rails用的,为了加上csrf认证通过,非rails绕过,我注释掉了,去的token的方法是通过页面里现有的form来取

builder += dashdash + boundary + crlf;

builder += “Content-Disposition: form-data; name=\”utf8\”” + crlf + crlf;

builder += ‘Content-Type: application/octet-stream’+ crlf + crlf;

builder += “&#x2713” + crlf;

builder += dashdash + boundary + crlf;

builder += “Content-Disposition: form-data; name=\”authenticity_token”

+ “\”” + crlf + crlf;

builder += $( “div.new_sentence > form > div > input” ).eq( 1 ).val()

+ “\r\n”;

*/

builder += dashdash+boundary+crlf;

builder += “Content-Disposition: form-data;” +

” name=\”attachment\”; filename=\”” +

New.attachment.fileName + “\”” + crlf;

builder += ‘Content-Type: application/octet-stream’+ crlf + crlf;

builder += data + crlf;

builder += dashdash + boundary + dashdash + crlf;

return builder;

},

uploadFile: function( fileData ) {

var boundary = ‘—————–‘ + (new Date).getTime();

var data = New.buildBody( fileData, boundary );

this.xhr = new XMLHttpRequest();

//下面的这个url要您自己定制

var upload_url = “your upload url”;

this.xhr.open( “post”, upload_url, true);

this.xhr.onuploadprogress = New.onUploadProgress;

this.xhr.onreadystatechange = New.onUploadReadyStateChange;

this.xhr.setRequestHeader(“Content-type”, “multipart/form-data; boundary=” + boundary);

this.xhr.overrideMimeType(“text/plain; charset=x-user-defined-binary”);

if ( this.xhr.sendAsBinary ) {

this.xhr.sendAsBinary(data);

}

else {

this.xhr.send(data);

}

},

onUploadProgress: function( e ) {

if ( e.lengthComputable ) {

var percentage = Math.round((e.loaded * 100) / e.total);

$( “.preview” ).html( percentage / 100.0 );

}

},

onUploadReadyStateChange: function ( e ) {

if ( New.xhr.readyState == 4 ) {

//您要加的,上传完成后要做的事情

}

},

init: function () {

$( “div.new_sentence > form” ).submit( function () {

New.onSubmit();

} );

var uploader = $( “div.upload_box” );

$( “div.sentences” ).scrollTop( $( “div.sentences” )[0].

scrollHeight );

document.addEventListener( “dragenter”, function( e ){

uploader.css( “borderColor”, “gray” );

}, false );

document.addEventListener( “dragleave”, function( e ){

uploader.css( “borderColor”, “silver” );

}, false );

uploader.bind( “dragenter”, function( e ){

uploader.css( “borderColor”, “gray” );

uploader.css( “backgroundColor”, “white” );

}, false );

uploader.bind( “dragleave”, function( e ){

uploader.css( “backgroundColor”, “transparent” );

}, false );

uploader.bind( “dragenter”, function( e ){

e.stopPropagation();

e.preventDefault();

}, false );

uploader.bind( “dragover”, function( e ){

e.stopPropagation();

e.preventDefault();

}, false );

uploader[0].addEventListener( “drop”, function ( e ) {

e.stopPropagation();

e.preventDefault();

{

var file = e.dataTransfer.files[0];

New.attachment = file;

var dataReader = new FileReader();

dataReader.onload = function( e ) {

New.uploadFile( e.target.result );

};

dataReader.readAsBinaryString( file );

}

}, false );

}

};

$( document ).ready( function () {

New.init();

} );

html部分

<div class=”upload_box” style=”min-height:100px;border:3px dashed silver;”>

<div class=”preview”>

</div>

</div>

上面的代码在ff5和chrome12下都测试通过,如果有不对的地方,欢迎来邮讨论,shallwe@shallwe.net,或者留言亦可