Resumable upload and JavaScript resumable upload of front-end files based on javascript

Source: Internet
Author: User

Resumable upload and JavaScript resumable upload of front-end files based on javascript

Take the image as an example to see the final look.

I. Prepare some knowledge

Resumable data transfer, since there is a break, there should be a file separation process, a paragraph of transmission.

In the past, files could not be separated. However, with the introduction of new HTML5 features, we can use the slice Method to separate files, similar to common strings and arrays.

Therefore, the most basic implementation of resumable data transfer is that the front-end obtains the corresponding file through the FileList object, segments the large file according to the specified segmentation method, and then transmits it to the backend in a segment, the backend then Concatenates the files in the sequence segment.

We need to modify the FileList object before submitting the file. In the previous article, we learned some notes about this submission. Because the FileList object cannot be changed directly, it cannot be directly submitted through the form. the submit () method is used to upload and submit data. A new data is generated based on the FormData object and uploaded through Ajax.

II. Implementation Process

In this example, the basic function of resumable upload is implemented. However, the manual pause upload operation is not successful yet. You can refresh the page during the upload process to simulate the upload interruption, experience "resumable upload ",

There may be other minor bugs, but the basic logic is roughly the same.

1. frontend implementation

Select a file, list the information of the selected file list, and perform custom upload operations.

(1) set the page DOM structure first.

<! -- Upload form --> <form method = "post" id = "myForm" action = "/fileTest. php "enctype =" multipart/form-data "> <input type =" file "id =" myFile "multiple> <! -- Upload file list --> <table id = "upload-list"> <thead> <tr> <th width = "35%"> file name </th> <th width = "15%"> file type </th> <th width = "15%"> file size </th> <th width = "20%"> upload progress </th> <th width = "15%"> <input type = "button" id = "upload-all-btn" value = "upload all"> </th> </tr> </thead> <tbody> </table> </form> <! -- Upload the information template of each file in the file list --> <script type = "text/template" id = "file-upload-tpl"> <tr> <td >{{ fileName }}</td> <td >{{ fileType }}</td> <td >{{ fileSize }}</td> <td class = "upload-progress"> {progress }}</td> <input type = "button" class = "upload-item-btn" data-name = "{fileName}" data -size = "{totalSize }}" data-state =" default "value =" {uploadVal }}"> </td> </tr> </script>

Here, the CSS style is thrown out.

<style type="text/css">body {font-family: Arial;}form {margin: 50px auto;width: 600px;}input[type="button"] {cursor: pointer;}table {display: none;margin-top: 15px;border: 1px solid #ddd;border-collapse: collapse;}table th {color: #666;}table td,table th {padding: 5px;border: 1px solid #ddd;text-align: center;font-size: 14px;}</style>

(2) The next step is JS implementation analysis.

Through the FileList object, we can get some information about the file.

The size is the size of the file, which must be dependent on

The size here is the number of bytes, so the file size displayed on the interface can be converted in this way.

// Calculate the file size = file. size> 1024? File. size/1024> 1024? File. size/(1024*1024)> 1024? (File. size/(1024*1024*1024 )). toFixed (2) + 'gb': (file. size/(1024*1024 )). toFixed (2) + 'mb': (file. size/1024 ). toFixed (2) + 'kb': (file. size ). toFixed (2) + 'B ';

Select a file and display the file information. Replace the data in the template.

// Update the file information list uploadItem. push (uploadItemTpl. replace (/{fileName}/g, file. name ). replace ('{fileType}', file. type | file. name. match (/\. \ w + $/) + 'file '). replace ('{fileSize}', size ). replace ('{SS}', progress ). replace ('{totalSize}', file. size ). replace ('{uploadVal}', uploadVal ));

However, when the file information is displayed, the file may have been uploaded before. To resume resumable data transfer, you need to determine and make a prompt on the interface.

Check whether there is corresponding data by querying the local file (when the local file is uploaded 100%, it is directly re-uploaded instead of continuing to upload)

// Check whether the file has uploaded percent = window. localStorage. getItem (file. name + '_ p'); if (percent & percent! = '2014. 0') {progress = 'uploaded '+ percent +' % '; uploadVal = 'continue upload ';}

The file information list is displayed.

Click Start upload to upload the corresponding file.

When uploading a file, you need to multipart the file.

For example, the configuration of each segment is 1024B, the total chunks segment (used to determine whether it is the last segment), the chunk segment, and the current uploaded percentage percent.

I need to mention this pause upload operation. In fact, I haven't implemented it yet, so I can't pause it...

Next is the segmentation process.

// Set the start and end of the shard var blobFrom = chunk * eachSize, // start blobTo = (chunk + 1) * eachSize> totalSize? TotalSize: (chunk + 1) * eachSize, // percent = (100 * blobTo/totalSize) at the end of the segment ). toFixed (1), // percentage of uploaded timeout = 5000, // timeout fd = new FormData ($ ('# myForm') [0]); fd. append ('thefile', findTheFile (fileName ). slice (blobFrom, blobTo); // segment the file fd. append ('filename', fileName); // file name fd. append ('totalsize', totalSize); // total file size fd. append ('islastchunk', isLastChunk); // whether it is the last fd. append ('isfirstupload', times === 'First '? 1: 0); // whether it is the first paragraph (first upload) // check whether the chunk is uploaded and whether it is uploaded before the upload. localStorage. getItem (fileName + '_ chunk') | 0; chunk = parseInt (chunk, 10 );

The file should support overwrite upload. Therefore, if the file is uploaded and uploaded, you should reset the data to support overwrite (otherwise, the backend will directly append blob data)

// If the first upload is the last part, that is, the object has been uploaded, re-overwrite the upload if (times = 'first '& isLastChunk = 1) {window. localStorage. setItem (fileName + '_ chunk', 0); chunk = 0; isLastChunk = 0 ;}

This time is actually a parameter, because it is required to pass the next segment after the previous segment is passed, so the method here is to continue calling this upload operation in the callback.

The next step is the real file upload operation. You can use Ajax to upload files. Because the FormData object is used, do not forget to add this configuration processData: false in $. ajax ({}).

After uploading a segment, the returned result is used to determine whether the upload is complete and whether to continue the upload.

Success: function (rs) {rs = JSON. parse (rs); // if (rs. status = 200) {// record the percentage of uploaded windows. localStorage. setItem (fileName + '_ P', percent); // if (chunk ===( chunks-1) {$ progress. text (msg ['done']); $ this. val ('uploaded '). prop ('Disabled ', true0000.css ('cursor', 'not-allowed'); if (! $ ('# Upload-list '). find ('. upload-item-btn: not (: disabled )'). length) {$ ('# upload-all-btn '). val ('uploaded '). prop ('Disabled ', true0000.css ('cursor', 'not-allowed');} else {// record the uploaded part window. localStorage. setItem (fileName + '_ chunk', ++ chunk); $ progress. text (msg ['in'] + percent + '%'); // This setting can be paused, but dynamic settings cannot be paused after clicking .. // if (chunk = 10) {// isPaused = 1; //} console. log (isPaused); if (! IsPaused) {startUpload () ;}}// Upload Failed. There are many upload failures. You can set else if (rs. status = 500) {$ progress. text (msg ['failed']) ;}, error: function () {$ progress. text (msg ['failed']);}

When the next part is uploaded, recursive operations are performed to upload the next part in sequence.

Screenshot ..

This is the complete JS logic. It's not difficult to understand the code.

<Script type = "text/javascript" src = "jquery. js "> </script> <script type =" text/javascript "> // All upload operations $ (document ). on ('click', '# upload-all-btn', function () {// if (! $ ('# Myfile '). val () {$ ('# myfile '). focus () ;}// simulate clicking another file that can be uploaded, else {$ ('# upload-list. upload-item-btn '). each (function () {$ (this ). click () ;};}}); // select a file-display file information $ ('# myfile '). change (function (e) {var file, uploadItem = [], uploadItemTpl = ('{file-upload-tpl'{.html (), size, percent, SS = 'unupload', uploadVal = 'start upload '; for (var I = 0, j = this. files. length; I <j; ++ I) {file = this. files [I]; percent = undefined; pro Gress = 'unupload'; uploadVal = 'start upload'; // calculate the file size = file. size> 1024? File. size/1024> 1024? File. size/(1024*1024)> 1024? (File. size/(1024*1024*1024 )). toFixed (2) + 'gb': (file. size/(1024*1024 )). toFixed (2) + 'mb': (file. size/1024 ). toFixed (2) + 'kb': (file. size ). toFixed (2) + 'B'; // initially, the local record is used to determine whether the file has been uploaded percent = window. localStorage. getItem (file. name + '_ p'); if (percent & percent! = '2017. 0 ') {progress = 'uploaded' + percent + '%'; uploadVal = 'continue upload';} // update the file information list uploadItem. push (uploadItemTpl. replace (/{fileName}/g, file. name ). replace ('{fileType}', file. type | file. name. match (/\. \ w + $/) + 'file '). replace ('{fileSize}', size ). replace ('{SS}', progress ). replace ('{totalSize}', file. size ). replace ('{uploadVal}', uploadval){{{}('{upload-list'{.children('tbody'{.html (uploadI Tem. join ('')). end (). show () ;});/*** when uploading a file, extract the matched file item * @ param {String} fileName the file name to be matched * @ return {FileList} the matched File Project */function findTheFile (fileName) {var files = $ ('# myfile') [0]. files, theFile; for (var I = 0, j = files. length; I <j; ++ I) {if (files [I]. name = fileName) {theFile = files [I]; break ;}} return theFile? TheFile: [];} // uploads a file $ (document ). on ('click ','. upload-item-btn ', function () {var $ this = $ (this), state = $ this. attr ('data-state'), msg = {done: 'uploaded successfully', failed: 'upload failed', in: 'uploading... ', paused:' paused... '}, fileName = $ this. attr ('data-name'), $ progress = $ this. closest ('tr '). find ('. upload-progress '), eachSize = 1024, totalSize = $ this. attr ('data-size'), chunks = Math. ceil (totalSize/eachSize), percent, chunk, // pause Upload operation isPaused = 0; // paused upload operation // not implemented, here, setting the isPaused value dynamically does not prevent the call of the following ajax request if (state = 'uploading') {$ this. val ('continue upload '). attr ('data-state', 'paused'); $ progress. text (msg ['paused'] + percent + '%'); isPaused = 1; console. log ('pause: ', isPaused);} // start/continue the upload operation else if (state = 'paused' | state = 'default ') {$ this. val ('pause upload '). attr ('data-state', 'uploading'); isPaused = 0;} // first click to upload startUpload ('first'); // upload Operation times: Number of times function startUpload (times) {// query whether or not to upload excessive chunk = window. localStorage. getItem (fileName + '_ chunk') | 0; chunk = parseInt (chunk, 10 ); // determine whether it is the last part var isLastChunk = (chunk = (chunks-1 )? 1: 0); // if the first upload is the last part, that is, the file has been uploaded, then re-overwrite upload if (times = 'first '& isLastChunk = 1) {window. localStorage. setItem (fileName + '_ chunk', 0); chunk = 0; isLastChunk = 0;} // set the start and end of the part var blobFrom = chunk * eachSize, // start blobTo = (chunk + 1) * eachSize> totalSize? TotalSize: (chunk + 1) * eachSize, // percent = (100 * blobTo/totalSize) at the end of the segment ). toFixed (1), // percentage of uploaded timeout = 5000, // timeout fd = new FormData ($ ('# myForm') [0]); fd. append ('thefile', findTheFile (fileName ). slice (blobFrom, blobTo); // segment the file fd. append ('filename', fileName); // file name fd. append ('totalsize', totalSize); // total file size fd. append ('islastchunk', isLastChunk); // whether it is the last fd. append ('isfirstupload', times === 'First '? 1: 0); // whether it is the first paragraph (first upload) // upload $. ajax ({type: 'post', url: '/fileTest. php', data: fd, processData: false, contentType: false, timeout: timeout, success: function (rs) {rs = JSON. parse (rs); // if (rs. status = 200) {// record the percentage of uploaded windows. localStorage. setItem (fileName + '_ P', percent); // if (chunk ===( chunks-1) {$ progress. text (msg ['done']); $ this. val ('uploaded '). prop ('Disabled ', trueapps.css ('cursor', 'no' T-allowed '); if (! $ ('# Upload-list '). find ('. upload-item-btn: not (: disabled )'). length) {$ ('# upload-all-btn '). val ('uploaded '). prop ('Disabled ', true0000.css ('cursor', 'not-allowed');} else {// record the uploaded part window. localStorage. setItem (fileName + '_ chunk', ++ chunk); $ progress. text (msg ['in'] + percent + '%'); // This setting can be paused, but dynamic settings cannot be paused after clicking .. // if (chunk = 10) {// isPaused = 1; //} console. log (isPaused); if (! IsPaused) {startUpload () ;}}// Upload Failed. There are many upload failures. You can set else if (rs. status = 500) {$ progress. text (msg ['failed']) ;}, error: function () {$ progress. text (msg ['failed']) ;}}}}); </script>

2. backend implementation

The backend implementation here is relatively simple. It mainly relies on the file_put_contents and file_get_contents methods.

Note that the object uploaded through the FormData object is also obtained through the $ _ FILES Global Object in PHP, and to avoid Chinese garbled characters in the uploaded file, use iconv

Resumable upload supports file overwriting. If a complete file already exists, delete it.

// If the object already exists during the first upload, delete the object and upload it again if ($ isFirstUpload = '1' & file_exists ('upload /'. $ fileName) & filesize ('upload /'. $ fileName) ==$ totalSize) {unlink ('upload /'. $ fileName );}

Use the above two methods to append the file information. Do not forget to add the FILE_APPEND parameter ~

// Continue to append object data if (! File_put_contents ('upload /'. $ fileName, file_get_contents ($ _ FILES ['thefile'] ['tmp _ name']), FILE_APPEND) {$ status = 501 ;} else {// when uploading the last part, check whether the file is complete (the size is consistent) if ($ isLastChunk === '1 ') {if (filesize ('upload /'. $ fileName) ==$ totalSize) {$ status = 200;} else {$ status = 502 ;}} else {$ status = 200 ;}}

Generally, file verification is required after the file is transferred. Therefore, the file size is verified to be consistent.

There are different error handling methods based on actual needs. Here we will not handle them much.

Complete PHP Section

<? Phpheader ('content-type: text/plain; charset = UTF-8 '); $ files = $ _ FILES ['thefile']; $ fileName = iconv ('utf-8 ', 'gbk', $ _ REQUEST ['filename']); $ totalSize = $ _ REQUEST ['totalsize']; $ isLastChunk = $ _ REQUEST ['islastchunk']; $ isFirstUpload =$ _ REQUEST ['isfirstupload']; if ($ _ FILES ['thefile'] ['error']> 0) {$ status = 500 ;} else {// here is a general file upload operation // if (! Move_uploaded_file ($ _ FILES ['thefile'] ['tmp _ name'], 'upload /'. $ _ FILES ['thefile'] ['name']) {// $ status = 501; //} else {// $ status = 200; /// the following parts are resumable upload operations. // if the object already exists during the first upload, delete the file and re-upload if ($ isFirstUpload = '1' & file_exists ('upload /'. $ fileName) & filesize ('upload /'. $ fileName) ==$ totalSize) {unlink ('upload /'. $ fileName) ;}// otherwise, append the file data if (! File_put_contents ('upload /'. $ fileName, file_get_contents ($ _ FILES ['thefile'] ['tmp _ name']), FILE_APPEND) {$ status = 501 ;} else {// when uploading the last part, check whether the file is complete (the size is consistent) if ($ isLastChunk === '1 ') {if (filesize ('upload /'. $ fileName) ==$ totalSize) {$ status = 200;} else {$ status = 502 ;}} else {$ status = 200 ;}}} echo json_encode (array ('status' => $ status, 'totalsize' => filesize ('upload /'. $ fileName), 'islastchunk' = >$ IsLastChunk);?>

The above section describes how to implement resumable upload of front-end files based on JavaScript. I hope it will be helpful to you. If you have any questions, please leave a message for me, the editor will reply to you in a timely manner. Thank you very much for your support for the help House website!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.