201112012147jQuery File Upload - 非同步檔案上傳(解決nested form問題)

最近研究了一下JQuery的file upload元件,要套用這個非同步檔案上載元件個人覺得還複雜的,因此把整個過程寫下來備份一下順便也跟大家分享,這篇裡頭主要寫的是前端顯示的部份,至於server端收檔的部份...再另外寫吧。

因為我的用途主要是再一個表單內要嵌入非同步上載元件,但jquery file upload本身就需要一個form在裡頭,因此整個架構就有點像以下這樣。

<form>

    表單其他欄位

    <form>

        JQuery File Upload

    </form>

</form>

但很明顯地,form是不允許巢狀結構的,因此必需改用iFrame方式嵌入到整體表單內。

先來看看整體表單的內容該怎麼寫吧!

test.jsp:

整體表單:
<p>
<form action="complete.jsp" method="post" onsubmit="getFileNames()">
<table border="1">
<tr><td>
姓名:<input type="text" name="name" value="UserName"/>
<%String dirPath = String.valueOf((int)Math.random()*100000); %>
<input type="hidden" name="dirPath" value="<%=dirPath%>"/>
<span id="fileZone"></span>
</td></tr>
<tr><td>
<iframe src="iframe.jsp?path=<%=dirPath%>" name="jqFile" id="jqFile" width="400" frameborder="1"></iframe>
</td></tr>
<tr><td>
<input type="submit" value="send"/>
</td></tr>
</table>
</form>

可以發現一件事,就是裡頭用了一個隱藏隱藏欄位記錄dirPath,主要是為了記錄要上載到的暫存資料夾位置,其實這個欄位應該也可以放在iFrame裡頭,之後再用javascript抓值,不過在這邊我是把這個dirPath當做參數丟入iframe的網頁裡。

在上頭的code可以發現有個fileZone的span標籤,這部份主要是按下送出後用javascript抓取iframe裡頭檔案位置所要放置隱藏欄位的地方。

再來看看iFrame裡的內容。

iframe.jsp:

<link rel="stylesheet" href="js/jQuery-File-Upload/jquery.fileupload-ui.css">
<link rel="stylesheet" href="js/jquery-ui/css/smoothness/jquery-ui-1.8.16.custom.css">

<%
String dirPath = request.getParameter("dirPath");
%>
<table><tr><td>
<div id="fileupload">
<form action="ws/file/upload" method="POST" enctype="multipart/form-data">
<input type="hidden" name="path" value="<%=dirPath%>"/>
<div class="fileupload-buttonbar">
<label class="fileinput-button">
<span>新增</span>
<input type="file" name="files[]">
</label>
<button type="submit" class="start">開始上傳</button>
<button type="reset" class="cancel">取消</button>
<button type="button" class="delete">刪除</button>
</div>
</form>
<form id="fileList">
<div class="fileupload-content">
<table class="files" id="files">

</table>
<div class="fileupload-progressbar"></div>
</div>
</form>
</div>
</td></tr></table>
<script src="js/jquery-1.7.js"></script>
<script src="js/jquery-ui/js/jquery-ui-1.8.16.custom.min.js"></script>
<script src="js/jquery.tmpl.js"></script>
<script src="js/jQuery-File-Upload/jquery.iframe-transport.js"></script>
<script src="js/jQuery-File-Upload/jquery.fileupload.js"></script>
<script src="js/jQuery-File-Upload/jquery.fileupload-ui.js"></script>
<script src="js/jQuery-File-Upload/example/application.js"></script>
<script id="template-upload" type="text/x-jquery-tmpl">
<tr class="template-upload{{if error}} ui-state-error{{/if}}">
<td class="preview"></td>
<td class="name">{{if name}}${"${name}"}{{else}}Untitled{{/if}}</td>
<td class="size">${"${sizef}"}</td>
{{if error}}
<td class="error" colspan="2">Error:
{{if error === 'maxFileSize'}}File is too big
{{else error === 'minFileSize'}}File is too small
{{else error === 'acceptFileTypes'}}Filetype not allowed
{{else error === 'maxNumberOfFiles'}}Max number of files exceeded
{{else}}${error}
{{/if}}
</td>
{{else}}
<td class="progress"><div></div></td>
<td class="start"><button>Start</button></td>
{{/if}}
<td class="cancel"><button>Cancel</button></td>
</tr>
</script>
<script id="template-download" type="text/x-jquery-tmpl">
<tr class="template-download{{if error}} ui-state-error{{/if}}">
{{if error}}
<td></td>
<td class="name">${"${name}"}</td>
<td class="size">${"${sizef}"}</td>
<td class="error" colspan="2">Error:
{{if error === 1}}File exceeds upload_max_filesize (php.ini directive)
{{else error === 2}}File exceeds MAX_FILE_SIZE (HTML form directive)
{{else error === 3}}File was only partially uploaded
{{else error === 4}}No File was uploaded
{{else error === 5}}Missing a temporary folder
{{else error === 6}}Failed to write file to disk
{{else error === 7}}File upload stopped by extension
{{else error === 'maxFileSize'}}File is too big
{{else error === 'minFileSize'}}File is too small
{{else error === 'acceptFileTypes'}}Filetype not allowed
{{else error === 'maxNumberOfFiles'}}Max number of files exceeded
{{else error === 'uploadedBytes'}}Uploaded bytes exceed file size
{{else error === 'emptyResult'}}Empty file upload result
{{else}}${error}
{{/if}}
</td>
{{else}}
<td class="preview">
{{if thumbnail_url}}
<a href="${"${url}"}" target="_blank"><img src="${"${thumbnail_url}"}" width="100"></a>
{{/if}}
</td>
<td class="name">
<a href="${"${url}"}"{{if thumbnail_url}} target="_blank"{{/if}}>${"${fieldName}"}</a>
<input type="hidden" id="${"${fieldName}"}" name="${"${fieldName}"}" value="${"${name}"}"/>
</td>
<td class="size">${"${sizef}"}</td>
<td colspan="2"></td>
{{/if}}
<td class="delete">
<button data-type="${delete_type}" data-url="${delete_url}">Delete</button>
</td>
</tr>
</script>

上載後的檔案主要會秀在fileupload-content這個div裡頭,而秀的內容就是template-download這個script裡頭寫的東西,基本上可以加上自己額外要秀的東西,只要在server端的收檔程式有寫好就可以。

因為我是希望使用者選完檔案後就直接上傳(不必在按上傳按鈕),所以必須在jquery file upload的設定檔內改寫application.js內容在function內加入以下這行:

$('#fileupload').fileupload({autoUpload: true});

完成上述過程後,基本上畫面應該會是這樣:

不過因為我的目的是讓使用者選完檔案後自動上傳,如果使用者要刪除檔案的話則是逐一刪除,因此就直接把以下三行mark掉,相對地上圖"開始上傳"、"取消"、"刪除"這三個按鈕也就看不到嚕。

<button type="submit" class="start">開始上傳</button>
<button type="reset" class="cancel">取消</button>
<button type="button" class="delete">刪除</button>

再來看看test.jsp裡頭用來抓取iframe內容的javascript寫法。

test.jsp:

function getFileNames(){
var fileNames = jqFile.document.getElementById('fileList').elements["fNames[]"];
//alert(fileNames.constructor);
        if(fileNames != undefined){
        if(fileNames.constructor == NodeList){
var size = fileNames.length;
for(var i=0;i<size;i++){
text = document.createElement('input');
text.setAttribute('name','fName[]');
text.setAttribute('type','hidden');
text.setAttribute('value',fileNames[i].value);
document.getElementById('fileZone').appendChild(text);
}
}else{
text = document.createElement('input');
text.setAttribute('name','fName[]');
text.setAttribute('type','hidden');
text.setAttribute('value',fileNames.value);
document.getElementById('fileZone').appendChild(text);
}
        }
}

由上面這段javascript就可以將iframe內使用者已上傳的檔案名稱抓到整體表單內並以隱藏欄位的方式放在fileZone標籤裡頭。

另外,還有一種情況,就是類似當使用這之前已經上載過某些檔案,而當他過了一段時間要再修改這個表單時,要將他之前已經上載的檔案秀在jquery file upload的list裡頭,則可以在test.jsp裡頭加入以下這些javascript,相對地要在test.jsp的body加入onload="loadExistReport()"。

test.jsp:

String[] eFiles = {"file1.txt","file2.pdf"};
out.println("<script type=\"text/javascript\">");
out.println("function loadExistReport(){");
out.println("var table = jqFile.document.getElementById('files');");
for(int i=0;i<eFiles.length;i++){

out.println("var row = document.createElement('tr');");
out.println("row.setAttribute('class','template-download');");
out.println("var cell = document.createElement('td');");
out.println("cell.setAttribute('class','preview');");
out.println("cell.innerHTML = '';");

out.println("row.appendChild(cell);");
out.println("cell = document.createElement('td');");
out.println("cell.setAttribute('class','name');");
out.println("cell.innerHTML = '<a href=\"eFile[i]\" id=\"fName[]\" name=\"fName[]\" value=\"eFiles[i]\"/>eFiles[i]</a>'+");
out.println("'<input type=\"hidden\" id=\"fName[]\" name=\"fName[]\" value=\"eFiles[i]\"/>';");
out.println("row.appendChild(cell);");
out.println("cell = document.createElement('td');");
out.println("cell.setAttribute('class','size');");
out.println("cell.innerHTML = '';");
out.println("row.appendChild(cell);");

out.println("cell = document.createElement('td');");
out.println("cell.setAttribute('colspan','2');");
out.println("row.appendChild(cell);");
out.println("cell = document.createElement('td');");
out.println("cell.setAttribute('class','delete');");
out.println("cell.innerHTML = '<button data-type data-url class=\"ui-button ui-widget ui-state-default ui-corner-all'+");
out.println("' ui-button-icon-only\" role=\"button\" aria-disable=\"false\" title=\"Delete\">'+");
out.println("'<span class=\"ui-button-icon-primary ui-icon ui-icon-trash\"></span>'+");
out.println("'<span class=\"ui-button-text\">Delete</span></button>';");

out.println("row.appendChild(cell);");
out.println("table.appendChild(row);");
}
out.println("}");
out.println("</script>");

以上是模擬使用者上一次已經上載過file1.txt與file2.pdf這兩個檔案,因此在一開始就先把這兩個東西列到jquery file upload的上載清單內。

通過以上這些過程後,接收整體表單內容的程式complete.jsp只需要使用一般request.getParameter()這種方式就可以收到整體表單(含非同步上載的檔案名稱)的內容囉,到此為止,前端的部份大致上就差不多完成嚕。

server端REST收檔程式部份內容也蠻長的,有時間再寫另一篇啦~~

回應