flowjs/flow.js

View on GitHub
samples/Node.js/public/index.html

Summary

Maintainability
Test Coverage
<!DOCTYPE html>
<html>
  <head>
    <title>Flow.js - Multiple simultaneous, stable and resumable uploads via the HTML5 File API</title>
    <meta charset="utf-8" />
    <link rel="stylesheet" type="text/css" href="style.css" />
  </head>
  <body>
    <div id="frame">

      <h1>Flow.js</h1>
      <p>It's a JavaScript library providing multiple simultaneous, stable and resumable uploads via the HTML5 File API.</p>

      <p>The library is designed to introduce fault-tolerance into the upload of large files through HTTP. This is done by splitting each files into small chunks; whenever the upload of a chunk fails, uploading is retried until the procedure completes. This allows uploads to automatically resume uploading after a network connection is lost either locally or to the server. Additionally, it allows for users to pause and resume uploads without loosing state.</p>

      <p>Flow.js relies on the HTML5 File API and the ability to chunks files into smaller pieces. Currently, this means that support is limited to Firefox 4+ and Chrome 11+.</p>

      <hr/>

      <h3>Demo</h3>
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
      <script src="flow.js"></script>

      <div class="flow-error">
        Your browser, unfortunately, is not supported by Flow.js. The library requires support for <a href="http://www.w3.org/TR/FileAPI/">the HTML5 File API</a> along with <a href="http://www.w3.org/TR/FileAPI/#normalization-of-params">file slicing</a>.
      </div>

      <div class="flow-drop" ondragenter="jQuery(this).addClass('flow-dragover');" ondragend="jQuery(this).removeClass('flow-dragover');" ondrop="jQuery(this).removeClass('flow-dragover');">
        Drop files here to upload or <a class="flow-browse-folder"><u>select folder</u></a> or <a class="flow-browse"><u>select from your computer</u></a> or <a class="flow-browse-image"><u>select images</u></a>
      </div>
      
      <div class="flow-progress">
        <table>
          <tr>
            <td width="100%"><div class="progress-container"><div class="progress-bar"></div></div></td>
            <td class="progress-text" nowrap="nowrap"></td>
            <td class="progress-pause" nowrap="nowrap">
              <a href="#" onclick="r.upload(); return(false);" class="progress-resume-link"><img src="resume.png" title="Resume upload" /></a>
              <a href="#" onclick="r.pause(); return(false);" class="progress-pause-link"><img src="pause.png" title="Pause upload" /></a>
              <a href="#" onclick="r.cancel(); return(false);" class="progress-cancel-link"><img src="cancel.png" title="Cancel upload" /></a>
            </td>
          </tr>
        </table>
      </div>
      
      <ul class="flow-list"></ul>

      <script>
        (function () {
          var r = new Flow({
            target: '/upload',
            chunkSize: 1024*1024,
            testChunks: false
          });
          // Flow.js isn't supported, fall back on a different method
          if (!r.support) {
            $('.flow-error').show();
            return ;
          }
          // Show a place for dropping/selecting files
          $('.flow-drop').show();
          r.assignDrop($('.flow-drop')[0]);
          r.assignBrowse($('.flow-browse')[0]);
          r.assignBrowse($('.flow-browse-folder')[0], true);
          r.assignBrowse($('.flow-browse-image')[0], false, false, {accept: 'image/*'});

          // Handle file add event
          r.on('fileAdded', function(file){
            // Show progress bar
            $('.flow-progress, .flow-list').show();
            // Add the file to the list
            $('.flow-list').append(
              '<li class="flow-file flow-file-'+file.uniqueIdentifier+'">' +
              'Uploading <span class="flow-file-name"></span> ' +
              '<span class="flow-file-size"></span> ' +
              '<span class="flow-file-progress"></span> ' +
              '<a href="" class="flow-file-download" target="_blank">' +
              'Download' +
              '</a> ' +
              '<span class="flow-file-pause">' +
              ' <img src="pause.png" title="Pause upload" />' +
              '</span>' +
              '<span class="flow-file-resume">' +
              ' <img src="resume.png" title="Resume upload" />' +
              '</span>' +
              '<span class="flow-file-cancel">' +
              ' <img src="cancel.png" title="Cancel upload" />' +
              '</span>'
            );
            var $self = $('.flow-file-'+file.uniqueIdentifier);
            $self.find('.flow-file-name').text(file.name);
            $self.find('.flow-file-size').text(readablizeBytes(file.size));
            $self.find('.flow-file-download').attr('href', '/download/' + file.uniqueIdentifier).hide();
            $self.find('.flow-file-pause').on('click', function () {
              file.pause();
              $self.find('.flow-file-pause').hide();
              $self.find('.flow-file-resume').show();
            });
            $self.find('.flow-file-resume').on('click', function () {
              file.resume();
              $self.find('.flow-file-pause').show();
              $self.find('.flow-file-resume').hide();
            });
            $self.find('.flow-file-cancel').on('click', function () {
              file.cancel();
              $self.remove();
            });
          });
          r.on('filesSubmitted', function(file) {
            r.upload();
          });
          r.on('complete', function(){
            // Hide pause/resume when the upload has completed
            $('.flow-progress .progress-resume-link, .flow-progress .progress-pause-link').hide();
          });
          r.on('fileSuccess', function(file,message){
            var $self = $('.flow-file-'+file.uniqueIdentifier);
            // Reflect that the file upload has completed
            $self.find('.flow-file-progress').text('(completed)');
            $self.find('.flow-file-pause, .flow-file-resume').remove();
            $self.find('.flow-file-download').attr('href', '/download/' + file.uniqueIdentifier).show();
          });
          r.on('fileError', function(file, message){
            // Reflect that the file upload has resulted in error
            $('.flow-file-'+file.uniqueIdentifier+' .flow-file-progress').html('(file could not be uploaded: '+message+')');
          });
          r.on('fileProgress', function(file){
            // Handle progress for both the file and the overall upload
            $('.flow-file-'+file.uniqueIdentifier+' .flow-file-progress')
              .html(Math.floor(file.progress()*100) + '% '
                + readablizeBytes(file.averageSpeed) + '/s '
                + secondsToStr(file.timeRemaining()) + ' remaining') ;
            $('.progress-bar').css({width:Math.floor(r.progress()*100) + '%'});
          });
          r.on('uploadStart', function(){
            // Show pause, hide resume
            $('.flow-progress .progress-resume-link').hide();
            $('.flow-progress .progress-pause-link').show();
          });
          r.on('catchAll', function() {
            console.log.apply(console, arguments);
          });
          window.r = {
            pause: function () {
              r.pause();
              // Show resume, hide pause
              $('.flow-file-resume').show();
              $('.flow-file-pause').hide();
              $('.flow-progress .progress-resume-link').show();
              $('.flow-progress .progress-pause-link').hide();
            },
            cancel: function() {
              r.cancel();
              $('.flow-file').remove();
            },
            upload: function() {
              $('.flow-file-pause').show();
              $('.flow-file-resume').hide();
              r.resume();
            },
            flow: r
          };
        })();

        function readablizeBytes(bytes) {
          var s = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'];
          var e = Math.floor(Math.log(bytes) / Math.log(1024));
          return (bytes / Math.pow(1024, e)).toFixed(2) + " " + s[e];
        }
        function secondsToStr (temp) {
          function numberEnding (number) {
            return (number > 1) ? 's' : '';
          }
          var years = Math.floor(temp / 31536000);
          if (years) {
            return years + ' year' + numberEnding(years);
          }
          var days = Math.floor((temp %= 31536000) / 86400);
          if (days) {
            return days + ' day' + numberEnding(days);
          }
          var hours = Math.floor((temp %= 86400) / 3600);
          if (hours) {
            return hours + ' hour' + numberEnding(hours);
          }
          var minutes = Math.floor((temp %= 3600) / 60);
          if (minutes) {
            return minutes + ' minute' + numberEnding(minutes);
          }
          var seconds = temp % 60;
          return seconds + ' second' + numberEnding(seconds);
        }
      </script>

    </div>
  </body>
</html>