1. Dot vs. Array-Access Property Notation

    If you’re just learning JavaScript, you probably know that the dot property notation is always the preferred way while the array-access notation should be used only when we cannot use the dot notation.

    This is definitely true if you simply include your source JavaScript files in the page. Your production code should, however, be compiled or minified to save the user some time and CPU cycles needed to download and parse the code.

    The most advanced JavaScript compiler out there is probably the Google Closure Compiler . Its true power is noticable when you set the compilation mode to advanced optimizations .

    What it does is that it…

    1. flattens nested properties
      object.property.property > object$property$property > a
    2. renames variables and properties unless they are recognized as standard
      object.property > a.b
    3. removes dead code which would never get executed by your app

    These is genuinely awesome stuff but it can break your application if you’re not careful. One thing to be aware of is that array-access notation cannot be flattened .

    Most of the time, it is better to use the dot notation because the intelligent static analysis performed by the compiler makes sure that connections are not broken. The situation where you want to be careful and use array-access notation is when you handle externally received data . If you work with an external (meaning not included in the compilation) API, you need to use array-access notation because the keys in the response would otherwise be different from what the application expected. Here is an example:

    Consider this the response to the HTTP request made by the script below:

    { "username": "jankuca" }

    The following code works as it should when it is not compiled:

    var xhr = new XMLHttpRequest();
    xhr.open('GET', '…', true);
    xhr.responseType = 'json';
    xhr.onload = function () {
      var res = xhr.response;
      alert(res.username);
    };
    xhr.send(null);

    But when compiled, the username property gets renamed to save space:

    var a = new XMLHttpRequest;
    a.open("GET", "…", !0);
    a.responseType = "json";
    a.onload = function() {
      alert(a.response.a)
    };
    a.send(null);

    This would obviously not work. To fix the code, just use the array-access notation (res['username']) and it’s going to be work as expected.

    A good thing is that the compiler warns us about the possible bug:

    JSC_INEXISTENT_PROPERTY:
    Property username never defined on res at line 5 character 6
    alert(res.username);

    To sum up, the golden rule is: Never use dot notation if you work with 3rd party code or data. This usually applies to external APIs and 3rd party libraries that are not included in the compilation. For instance, database table column names (or document field names in case of document-oriented databases) should always be quoted.

    Follow this rule in all your code, not only when you want it to compile. Using array-access notation should mean that you (or the application) are not the one defining the property.

    There is one special situation in which you do not have to follow this and that is when you declare the external API as externs .

  2. Google Closure Development Environment

    I am a big fan of the Google Closure Tools, mainly the Google Closure Compiler. They help me a lot; my production code is minified, obfuscated and my source files are consistent in code-style BUT the main advantage of using Google Closure Tools is that it helps me to find many subtle errors that would otherwise cause my code to behavior to be wrong.

    Compiler

    The core component of the environment is the Google Closure Compiler. There are three compilation modes of the compiler—white-space-only, simple optimizations and advanced optimizations. The mode I almost always go for is the advanced optimizations mode. In combination with the compiler’s verbose warning level , it can catch (to some extent) anything you can think of.

    The advanced optimization algorithm does variable name flattening and then it renames all of these to make the code smaller. These two processes are very powerful but there are some caveats one has to be aware of to write code that is compilable in this mode.

    One of the most important things to be aware of is that no string is ever compiled. That means writing a.b is way different from writing a['b'] because the former one can be flattened and renamed whereas the latter one cannot.

    HTML

    The same applies to declarative templates where there are references to JavaScript in the HTML. One example of this are Angular.js templates which are simply namespaced attributes in the HTML code. If you write ng:controller="SidebarController" and have the corresponding constructor in your JavaScript, the HTML will remain the same while the constructor name is changed.

    I thought a bit about this problem and come up with a simple solution. The goog.exportSymbol method lets me keep the original name and I know all the names because there are written in my HTML. (This, of course, isn’t true in case of generated HTML.) I wrote a little script that goes through every .html file, looks for values of the given attributes and outputs a JavaScript file with all the goog.exportSymbol invocations required for my application to work. I then feed the compiler this file along with the rest of my app and get a fully-functional compilation.

    Source Maps

    Let’s move to debugging of the compiled application code. If you’ve ever tried to find a bug in a compiled code, you most definitely had a hard time figuring out what is the source code corresponding to the part of the compiled code where an exception was thrown.

    Believe it or not, there is a really sweet solution to this issue and it’s source maps (which the compiler can optionally create). These are files containing connections between the compiled and the original code. The sweetest thing about this is that these mappings are recognized by some browsers. Such browsers then let you debug the compiled code from the original. As of right now, only Firebug and Chrome Dev Tools support source mappings and the implementation is not nearly perfect. However, it is definitely a very useful thing to have.

    Summary

    To sum up, here is a list of the components of my environment:

    • Google Closure Linter – Checks my JavaScript syntax and JSDoc comments.
    • Google Closure Compiler - Compiles my JavaScript code.
    • Google Closure Library - a set of high quality well-tested cross-browser-compatible code; I, however, usually use only the base.js file that defines methods such as goog.require , goog.provide or the aforementioned goog.exportSymbol .
    • compile-html.js – Extracts JavaScript references from my HTML.
    • fix-source-maps.js – Fixes file paths in my source maps.
    • node.js – Runs some of the build scripts.

    Boilerplate

    I’ve put this boilerplate together and published it on GitHub . For installation, follow the instructions there.

    The boilerplate includes two bash scripts:

    • build/lint.sh – Runs the Google Closure Linter
    • build/compile.sh – Runs the compile.js script, the Google Closure Compiler and the fix-source-maps.js script.

    Be sure to configure them according to your directory structure.

    I am a fan of Sublime Text which is why there is also a .sublime-project file prepared for you. It includes the two scripts each as a build system. You can switch between them in the Tools > Build Systems menu and run them with Cmd+B .

  3. Templating without the with statement

    One of the limitations that ECMAScript 5 strict mode brings is the removal of the with statement. This is great news in general as the statement just makes code less readable.

    var x = 6;
    with (obj) {
      // You cannot be certain that the "x" you use in this block
      // is the one you want since obj.x can be present.
      alert(x);
    }
    

    There is, however, one type of libraries that are built upon this statement. Can you guess what libraries I’m talking about? (Yeah, I know—the title is a big hint.)

    It’s templating libraries! Almost every JavaScript templating library I have seen made a use of the with statement. And what is the issue that makes all of the libraries use the statement, you ask? Let’s say that you have an object that defines all variables specific to a template instance and you have the template string (HTML with variables for instance). In such case, the with statement is the simplest way to map the key-value storage (the object) to multiple local variables (one for each key). To demonstrate this situation, I’m going to borrow the EJS syntax.

    // Here I have the object that holds template variables...
    var model = {
      'title': 'Lorem ipsum'
    };
    // ...and here is a simple EJS template...
    var tpl = '<h1><%= title %></h1>';
    // ...from which I get the following JS code:
    var js = "'<h1>' + title + '</h1>'";
    
    with (model) {
      // I can access "model.title" as "title".
      eval('return ' + js);
    }
    

    There is one exception I found and it is the ECO (Embedded CoffeeScript) library:

    // We have the same template object as we had above...
    var model = {
      'title': 'Lorem ipsum'
    };
    // ...but the template looks slightly different:
    var tpl = '<h1><%= @title %></h1>';
    // Following the CoffeeScript syntax, this could be expressed as
    var js = "'<h1>' + this.title + '</h1>'";
    
    // By using "this" instead of a variable number of local variables,
    // the template JS code can be executed without the with statement:
    (function () { return eval(js); }).call(model);
    
    // We could also create a detached anonymous function
    // to execute the code in a limited scope:
    var execute = new Function(js);
    return execute.call(model);
    

    I consider this a nice workaround. However, if we want to use the EJS syntax (or any syntax other than ECO with the @ prefixes), the with statement is still very useful.

    So what is the solution if we need a future-proof library in which we cannot use the with statement? We just need to realize that the Function constructor takes more than one argument—one for each of the new function’s arguments.

    var fn = new Function('a', 'b', 'return a + b');
    fn(2, 3); // 5
    

    It is also possible to call the call and apply methods of the constructor:

    var fn = Function.apply(null, [ 'a', 'b', 'return a + b' ]);
    fn(2, 3); // 5
    

    With this knowledge, I’m going to fix the code above.

    var model = {
      'title': 'Lorem ipsum'
    };
    var tpl = '<h1><%= title %></h1>';
    var js = "'<h1>' + title + '</h1>'";
    
    // First, I get the model's keys and values:
    var keys = [];
    var values = [];
    for (var key in model) {
      if (model.hasOwnProperty(key)) {
        keys.push(key);
        values.push(model[key]);
      }
    }
    
    // Then, I push the template JS code (function body) to the keys:
    keys.push('return ' + js);
    
    var execute = Function.apply(null, keys);
    return execute.apply(null, values);
    

    What do you think about this approach? If you know about a better way to achieve the same result, let me know! Thanks.

  4. Client-side Rendering Engine, Take 1

    Since two days ago, I have been recreating the Google Feedback Tool. It is a pretty big challenge. One of the key features is taking screenshots of the page on which is being given feedback.

    When Elliott Sprehn, the tech lead behind the Google Feedback Tool, commented on my previous post saying that Google developed their own client-side rendering engine which does not require Flash or ActiveX components, my jaw dropped all the way down to the floor from the respect. I thought that nobody would be that crazy to write one of those. However, Google did and it’s amazingly accurate. (Good job!)

    Not that I want to prove that I’m as good as Google is (which I’m not), I’m trying to develop one of those rendering engines on my own and it’s been a great deal of pain so far and I haven’t had the balls to check the solution in other browsers than Chrome. Here is how it snapshot the Last.fm profile of the band The Cinematics.

    Even though there are several horrible bugs and it ignores a lot of CSS properties, when I look at the fact that it took me only a few hours, I find the result rather impressive :-)

  5. “Hey, Google+ Feedback Tool, I’m stealing you” – Part 1

    Have you noticed the feedback tool that Google placed on every page in Google+?. (It is actually available in other Google products as well.)

    I would say it’s the best feedback tool out there in terms of simplicity, user-friendliness and the overall idea. When I saw it, I was like “Dude… I’m stealing that!” …and I am. I mean, I’m not actually stealing their code or something—that would just be impolite at least—I’m trying to recreate the tool on my own.

    DOM Tree Snapshot

    From developer’s point of view, the tool allows people to write a little message to Google, highlight areas and DOM elements on the page that are relevant to the problem and send a screenshot of the page with those areas highlighted.

    Even though that is a very good solution, it requires a Flash object or an ActiveX component to do the actual screenshot. (Update: As Elliott Sprehn mentions in the comments, Google generates the screenshot client-side using their own rendering engine that scans the DOM and redraws the page on a canvas.) However, we can just take a snapshot of the DOM tree with stylesheets and recreate the state afterwards. Great thing about this approach is that you can browse the actual DOM tree the user encountered rather than guessing.

    var snapshot = document.documentElement.innerHTML;
    

    Note: You only get the HTML from <head> to </body>. You could use the outerHTML property to get the whole <html> to </html>. However, for one reason mentioned below in the post, we will just use innerHTML.

    This could, however, end up being pretty messy if you have some JavaScript that manipulates the DOM and expects the elements to have a different state. Due to those dangerous situations (we could not debug wery well in such case), we have to strip the JavaScript included in the page.

    snapshot = snapshot.replace(/<script[^>]*?>[\s\S]*<\/script>/gi, '');
    

    Note: I put [\s\S] instead of a period (.) because in JavaScript, there is no modifier that would make periods include new line characters.

    Another thing you might want to do is to alter resource paths in the snapshot so that you can load the snapshot on a different domain or generate an image using wkhtmltoimage or some similar solution.

    var origin = location.protocol + '//' + location.host;
    var dirs = location.pathname.split(/\//g).slice(1, -1);
    console.log(dirs);
    
    var global_rx = /\s(type|href|src)="?(\.\.?|\/)[^\s">]*/gi;
    var single_rx = /(\s)(type|href|src)(="?)((?:\.\.?|\/)[^\s">]*)/i;
    
    snapshot = snapshot.replace(global_rx, function (match) {
      if (match.search('://') !== -1) { // absolute path
        return match;
      }
    
      match = match.match(single_rx);
      var prefix = match[1] + match[2] + match[3];
      var link = match[4];
    
      if (link[0] === '/') { // absolute path, same domain
        return prefix + origin + link;
      }
    
      link = link.split(/\//g);
      var i = 0
      for (var i = 0; link[i] === '..' && i < link.length; ++i) {}
      console.log(link, i);
      return prefix + origin + (i !== dirs.length ? '/' : '') +
        dirs.slice(0, dirs.length - i).join('/') + '/' +
        link.slice(i, link.length).join('/');
    });
    

    User Interface

    Now that we have the snapshot, we can start building the UI. We are going to have the original page used as background. On top of that, there are going to be several additinal layers.

    The layer right on top of the background (the original content) is going to be a canvas element which will eventually create the mask (highlights). I’ll get to this layer later.

    The most important layer in terms of usability is an iframe containing a copy of the original page. We already have the snapshot which we are going to place inside the iframe. The tricky thing about this is that this layer is going to remain invisible to the user (via opacity) but it’s going to handle DOM events and pass them to our script in the original window.

    var width = document.documentElement.scrollWidth;
    var height = Math.max(
      window.innerHeight,
      document.documentElement.scrollHeight
    );
    
    var iframe = document.createElement('iframe');
    iframe.src = './dom-feedback-copy.html';
    s.style.width = width + 'px';
    s.style.height = height + 'px';
    document.body.appendChild(iframe);
    
    iframe {
      display: none;
      position: absolute;
      top: 0;
      left: 0;
      opacity: 0;
      border: none;
    }
    

    We are loading the page dom-feedback-copy.html that will be filled with the snapshot and will pass DOM events to our script. Unfortunately, we cannot generate this page dynamically (either using a data URI or altering the contentWindow of the iframe) due to the cross-origin policy. You can put this file wherever you want as long as you keep it on the same domain (second level).

    Let’s see what can the file look like if we only want to pass click events. We can even use the widely implemented cross-document messaging.

    <!DOCTYPE html>
    <html>
    <script>
    (function () {
    
      // Loosen up the cross-origin policy
      document.domain = location.hostname;
    
      var origin = location.protocol + '//' + location.host;
    
      var getOffset = function (el) {
        var x = 0;
        var y = 0;
        do {
          x += el.offsetLeft;
          y += el.offsetTop;
        } while (el = el.offsetParent);
        return [x, y];
      };
      var broadcastEvent = function (e) {
        var target = e.target;
        var offset = getOffset(target);
        var data = {
          type: e.type,
          tagName: target.tagName,
          offsetX: offset[0],
          offsetY: offset[1],
          x: e.pageX,
          y: e.pageY
        };
        window.parent.postMessage(data, origin);
      };
      window.addEventListener('click', broadcastEvent, false);
    
    }());
    </script>
    </html>
    

    Notes: 1.) Make sure that doctypes match to prevent minor rendering differences. 2.) The script element will be overwritten by the snapshot, but the functionallity will remain because the snapshot will be injected during the load event which gets fired after the whole HTML code is parsed.

    So let’s check out what are we getting from the iframe:

    • event type (only click at this point)
    • tag name of the clicked element
    • offset of the element within the page
    • size of the element

    This data is enough for us to highlight the element within the canvas layer.

    Canvas, DOM element highlighting

    We are going to create a canvas element at the initialization and fill it with semi-transparent black rectangle.

    var canvas = document.createElement('canvas');
    // size from the code above
    canvas.width = width;
    canvas.height = height;
    
    var ctx = canvas.getContext('2d');
    ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
    ctx.fillRect(0, 0, width, height);
    

    Next this we need is a message event listener to process the click events received from the iframe and highlight (cut) regions in the canvas based on the data in the messages.

    var highlight = function (x, y, width, height) {
      ctx.globalCompositeOperation = 'destination-out';
      ctx.fillStyle = '#FFFFFF';
      ctx.fillRect(x, y, width, height);
    };
    var receiveMessage = function (e) {
      var data = e.data;
      highlight(data.offsetX, data.offsetY, data.width, data.height);
    };
    window.addEventListener('message', receiveMessage, false);
    

    More is coming soon; stay tuned. In the meantime, you can check out my github repository where is the source code described in this post as well as a demo of the product.

  6. Offline File Uploads Using File API Explained

    With the growing number of offline-capable web applications also grow the requirements developers have. One of those is to be able to upload files while the user is offline or their connection crashes a lot and larger uploads fail. Fortunately, W3C defined a great set of standards that allow web apps to do just that.

    This post explains what is happening behind the scenes of this ultimate solution to file uploads.

    HTML

    HTML required for offline uploads is very simple:

    <div class="button disabled">
      <span>Upload a file</span>
      <input type="file" name="files" disabled />
    </div>
    

    However, we are going to ensure successful uploads in browsers that do not implement the File API (those will not work while offline, though). The best possible way to do this is to make use of the commonly known iframe trick—that is that we submit a form into an iframe—which spares us page reloading. The HTML structure allowing both offline and online uploads looks like this:

    <div id="uploader"> <!-- root element -->
      <form method="post" action="./upload" enctype="multipart/form-data"
        target="upload-target" class="button disabled">
        <span>Upload a file</span>
        <input type="file" name="files" disabled />
      </form>
      <iframe name="upload-target" style="display: none;"></iframe>
    </div>
    

    With some styling we can get a pretty good-looking button.

    Button preview

    Basic Event Binding

    We are going to observe the change event on the input element and initiate the upload process if the user chooses a file.

    // Our whole "application" is going to be stored in one global namespace object.
    var uploader = {};
    
    window.onload = function () {
      var root_el = document.getElementById('uploader');
      uploader.init(root_el);
    };
    
    uploader.init = function (root_el) {
      this.input_el = root_el.getElementsByTagName('input')[0];
    
      this.input_el.onchange = function () {
        uploader.upload();
      };
    };
    

    This script is enough to check for files in non-IE browsers. IE requires the user to click the button of the input but we need to have the whole form element clickable. Another way to let the user choose a file is to call the click method of the input element (this even works when the element is hidden).

    We could listen to click events on the form element and call the click method of the input when a click occurs. Then, we could submit the form using the submit method. That is, however, not possible in IE due to security reasons. This fact complicates the process a bit because we need to add a submit button element. The button will cover the whole form element and will be hidden using opacity: 0 (that way it remains clickable).

    <div id="uploader">
      <form method="post" action="./upload" enctype="multipart/form-data"
        target="upload-target" class="button disabled">
        <span>Upload a file</span>
        <input type="file" name="files" disabled />
        <button type="submit" disabled>Upload a file</button>
      </form>
      <iframe name="upload-target" style="display: none;"></iframe>
    </div>
    

    In the script, we need to detect IE and set event listeners accordingly. The onchange handler is going to be set only for non-IE browsers and for IE, there is going to be an onclick handler on the new button element in which we let the user choose a file.

    // Detect IE
    var IE = (navigator.userAgent.search('MSIE') !== -1);
    
    uploader.init = function (root_el) {
      this.form_el = root_el.getElementsByTagName('form')[0];
      this.input_el = root_el.getElementsByTagName('input')[0];
      this.button_el = root_el.getElementsByTagName('button')[0];
    
      if (IE) {
        this.button_el.onclick = function () {
          // Let the user choose a file
          uploader.input_el.click();
          // Prevent form submission is no file was chosen
          return uploader.input_el.value;
        };
      } else {
        this.input_el.onchange = function () {
          uploader.upload();
        };
      }
    };
    

    Client-side Database

    To ensure support across all modern browsers (WebKit-based, Gecko-based and Opera), we have to use both IndexedDB and WebSQL with IndexedDB being the primary choice. The idea is that when the users chooses a file, its contents are read via the File API (FileReader) and stored in one of the database storage solutions. The application then tries to push the file to the server until it is successfully uploaded.

    First, we need to detect which database storage is available.

    // Since the IndexedDB standard has not been finished yet, there are several implementations.
    var INDEXEDDB_SUPPORT = ('indexedDB' in window)
      || ('webkitIndexedDB' in window) || ('mozIndexedDB' in window);
    // Web SQL implementations, on the other hand, use the same property.
    var WEBSQL_SUPPORT = ('openDatabase' in window);
    
    // Together with File API support, we can say that a given browser
    // is capable of offline file uploads.
    var OFFLINE_MODE = (INDEXEDDB_SUPPORT || WEBSQL_SUPPORT)
      && ('files' in document.createElement('input'));
    

    As step 2, we need to connect to the database storage (and build its structure on the first use). Our database will be called uploader and it will have one object store (table) called files.

    uploader.init = function (root_el) {
      // ... the same as above ...
    
      if (OFFLINE_MODE) {
        this._initDatabase();
      } else {
        this.enable();
      }
    };
    
    // The control is initially disabled. (See the HTML.)
    // We call this method after the initialization process.
    uploader.enable = function () {
      this.input_el.disabled = false;
      this.button_el.disabled = false;
      this.form_el.className = 'button';
    };
    
    uploader._initDatabase = function () {
      if (INDEXEDDB_SUPPORT) {
      var req = (window.indexedDB || window.webkitIndexedDB
        || window.mozIndexedDB).open('uploader');
        req.onsuccess = function (e) {
          uploader.db = e.target.result;
          uploader._buildDatabase();
        };
        // If the connection fails, we fallback to regular online uploads.
        req.onfailure = function () {
          OFFLINE_MODE = false;
          uploader.enable();
        };
      } else if (WEBSQL_SUPPORT) {
        this.db = window.openDatabase('uploader', '', 'Pending uploads',
          10 * 1024 * 1024); // 10 MB should be enough
        this._buildDatabase();
      }
    };
    
    uploader._buildDatabase = function () {
      if (this.db.version === '1.0') {
        this.enable();
      } else if (INDEXEDDB_SUPPORT) {
        var req = this.db.setVersion('1.0');
        req.onsuccess = function (e) {
          uploader.db.createObjectStore('files', {
            autoIncrement: true
          });
          uploader.enable();
        };
        // If we are unable to set up the object store,
        // we fallback to regular online uploads.
        req.onfailure = function (e) {
          OFFLINE_MODE = false;
          uploader.enable();
        };
      } else if (WEBSQL_SUPPORT) {
        this.db.transaction(function (tx) {
          tx.executeSql("CREATE TABLE [files] ( " +
            "[id] INTEGER PRIMARY KEY AUTOINCREMENT, " +
            "[name], [data] " +
          )", [], function () { // success
            uploader.enable();
          }, function () { // failure; fallback to online uploads
            OFFLINE_MODE = false;
            uploader.enable();
          });
        });
      }
    };
    

    The target page (./upload) should return an HTML page with a callback that enables the control after the upload finishes. (This approach is generally called JSONP). Such response might look like the following code:

    <script>
    window.top.uploader.enable();
    </script>
    

    Offline uploading

    Now that we have the basic environment initialized, we can get into the actual offline uploading. One thing we need is a way to disable the control while there is a running upload operation.

    uploader.disable = function () {
      this.input_el.disabled = true;
      this.button_el.disabled = true;
      this.form_el.className = 'button disabled';
    };
    

    We already set up the handler that calls out upload method in the initialization so we only need to define this method (and its subsequential methods).

    uploader.upload = function () {
      if (OFFLINE_MODE) { // Use File API
        var files = this.input_el.files;
        if (files.length !== 0) { // files selected
          uploader._uploadViaFileAPI(files);
          this.disable();
        }
      }
    };
    
    uploader._uploadViaFileAPI = function (files) {
      // Our file input can be set up to accept multiple files.
      Array.prototype.forEach.call(files, function (file) {
        var reader = new FileReader();
        reader.onloadend = function (e) {
          uploader.store(file.name, e.target.result);
        };
        reader.readAsDataURL(file);
      });
    };
    

    The two methods above get file contents and pass them to the store method which actually stores them in the database storage.

    // Read/write IndexedDB transaction factory method
    uploader.createIndexedDBTransaction = function () {
      return this.db.transaction(
        ['files'],
        (window.IDBTransaction || window.webkitIDBTransaction
          || window.mozIDBTransaction).READ_WRITE,
        0
      );
    };
    
    uploader.store = function (name, data) {
      if (INDEXEDDB_SUPPORT) {    
        var tx = this.createIndexedDBTransaction();
        var store = tx.objectStore('files');
        var req = store.add({
          name: name,
          data: data
        });
        req.onsuccess = function () {
          uploader.enable();
          uploader.pushQueueToServer();
        };
        req.onfailure = function () {
          uploader.enable();
        };
      } else if (WEBSQL_SUPPORT) {
        this.db.transaction(function (tx) {
          tx.executeSql("INSERT INTO [files] ([name], [data]) VALUES (?, ?)", [name, data], function () {
            uploader.enable();
            uploader.pushQueueToServer();
          }, function () {
            uploader.enable();
          });
        });
      }
    };
    

    At this point, we have a control that stores uploads in a database storage or submits them directly to the server if no database storage is available. The only missing piece is a method that would push the formed upload queue to the server. Calls of such method (pushQueueToServer) are already implemented in the store method above.

    uploader.pushQueueToServer = function () {
      if (INDEXEDDB_SUPPORT) {
        var tx = this.createIndexedDBTransaction();
        var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.mozIDBKeyRange;
        var range = (IDBKeyRange.lowerBound || IDBKeyRange.leftBound)(0);
        var req = tx.objectStore('files').openCursor(range);
        req.onsuccess = function (e) {
          var result = e.target.result;
          if (result) {
            uploader.uploadAsBase64ViaXHR(result.value, function () {
              var tx = uploader.createIndexedDBTransaction();
              var req = tx.objectStore('files')['delete'](result.key);
              req.onsuccess = function () {
                uploader.pushQueueToServer();
              };
              req.onfailure = function () {
                uploader.pushQueueToServer();
              };
            });
          }
        };
      } else if (WEBSQL_SUPPORT) {
        this.db.transaction(function (tx) {
          tx.executeSql("SELECT * FROM [files] LIMIT 1", [], function (res) {
            var item = res.rows[0];
            if (item) {
              uploader.uploadAsBase64ViaXHR(item, function () {
                tx.executeSql("DELETE FROM [files] WHERE [id] = ?", [item.id], function () {
                  uploader.pushQueueToServer();
                }, function () {
                  uploader.pushQueueToServer();
                });
              });
            }
          });
        });
      }
    };
    

    What we do is that, if there is one, we get the oldest item from the queue (database) and pass it to the uploadAsBase64ViaXHR method and as a callback, we remove the given item/file from the queue.

    The uploadAsBase64ViaXHR method basically just creates a POST request via XMLHttpRequest and passes the execution to a callback function when the request successfully finishes.

    uploader.uploadAsBase64ViaXHR = function (item, callback) {
      var data = [
        'name=' + encodeURIComponent(item.name),
        'data=' + encodeURIComponent(item.data)
      ].join('&');
    
      var xhr = new XMLHttpRequest();
      xhr.open('POST', './upload-base64', true);
      xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest');
      xhr.setRequestHeader('content-type',
        'application/x-www-form-urlencoded; charset=UTF-8');
      xhr.onreadystatechange = function () {
        if (this.readyState === 4) {
          if (this.status < 300) { // success
            callback();
          } else { // failure; try again in a moment
            setTimeout(function () {
              uploader.uploadAsBase64ViaXHR(item, callback);
            }, 5000);
          }
        }
      };
      xhr.send(data);
      xhr = null;
    };
    

    A good thing to do is to call the pushQueueToServer method right after database initialization to check if there are any pending uploads from before.

    uploader.buildDatabase = function () {
      if (this.db.version === '1.0') {
        // ...
        this.pushQueueToServer();
      } else if (INDEXEDDB_SUPPORT) {
        // ...
        req.onsuccess = function (e) {
          // ...
          uploader.pushQueueToServer();
        };
        // ...
      } else if (WEBSQL_SUPPORT) {
        this.db.transaction(function (tx) {
          tx.executeSql(/* ..., ... */, function () {
            // ...
            uploader.pushQueueToServer();
          }, /* ... */);
        });
      }
    };
    

    There is one more thing to do. A few code snippets back, we defined a different behavior for IE. However, the next IE (10) is probably going to feature the File API and IndexedDB storage which would make it possible to perform offline uploads even in IE. To take such future situation into account, we have to modify the onclick handler of the submit button element:

    uploader.init = function (root_el) {
      // ...
    
        this.button_el.onclick = function () {
          // Let the user choose a file
          uploader.input_el.click();
          if (OFFLINE_MODE) {
            uploader.upload();
            // Prevent form submission
            return false;
          } else {
            // Prevent form submission is no file was chosen
            return uploader.input_el.value;
          }
        };
    
      // ...
    };
    

    Also, it might not be a bad idea to possibly broaden the support to older non-IE browsers by extending the upload method a little. (This is also called when database initialization fails.)

    uploader.upload = function () {
      if (OFFLINE_MODE) { // Use File API
        // ...
    
      } else { // Use regular form submission; older non-IE browsers
        var value = this.input_el.value;
        if (value) {
          this.form_el.submit();
          this.disable();
        }
      }
    };
    

    Have a look at the complete solution with some logging and control label changing.

    When it comes to server-side requirements, we have two upload endpoints—upload and upload-base64. Those, of course, can be merged into one. This is just to show the need to have two different server-side handlers, one for regular multipart uploads and one for simple POST requests with base64-encoded file contents in their bodies.

    As to browser support, I tested this in Google Chrome 12, FireFox 4, Opera 11 and IE 8/9. However, it should work in any earlier version of Chrome, FireFox (probably even 1.x but I’m not totally sure about that) and probably in IE 7 but I was not able to start the portable version to check it out.

    I hope you found this post helpful. Feel free to share it with your colleagues.