text: (function () {
    var COMPAT_ENVS = [
      ['Firefox', ">= 16.0"],
      ['Google Chrome',
       ">= 24.0 (you may need to get Google Chrome Canary), NO Blob storage support"]
    ];
    var compat = $('#compat');
    compat.empty();
    compat.append('<ul id="compat-list"></ul>');
    COMPAT_ENVS.forEach(function(val, idx, array) {
      $('#compat-list').append('<li>' + val[0] + ': ' + val[1] + '</li>');
    });
  
    const DB_NAME = 'mdn-demo-indexeddb-epublications';
    const DB_VERSION = 1; // Use a long long for this value (don't use a float)
    const DB_STORE_NAME = 'publications';
  
    var db;
  
    // Used to keep track of which view is displayed to avoid uselessly reloading it
    var current_view_pub_key;
  
    function openDb() {
      console.log("openDb ...");
      var req = indexedDB.open(DB_NAME, DB_VERSION);
      req.onsuccess = function (evt) {
        // Better use "this" than "req" to get the result to avoid problems with
        // garbage collection.
        // db = req.result;
        db = this.result;
        console.log("openDb DONE");
      };
      req.onerror = function (evt) {
        console.error("openDb:", evt.target.errorCode);
      };
  
      req.onupgradeneeded = function (evt) {
        console.log("openDb.onupgradeneeded");
        var store = evt.currentTarget.result.createObjectStore(
          DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
  
        store.createIndex('biblioid', 'biblioid', { unique: true });
        store.createIndex('title', 'title', { unique: false });
        store.createIndex('year', 'year', { unique: false });
      };
    }
  
    /**
     * @param {string} store_name
     * @param {string} mode either "readonly" or "readwrite"
     */
    function getObjectStore(store_name, mode) {
      var tx = db.transaction(store_name, mode);
      return tx.objectStore(store_name);
    }
  
    function clearObjectStore(store_name) {
      var store = getObjectStore(DB_STORE_NAME, 'readwrite');
      var req = store.clear();
      req.onsuccess = function(evt) {
        displayActionSuccess("Store cleared");
        displayPubList(store);
      };
      req.onerror = function (evt) {
        console.error("clearObjectStore:", evt.target.errorCode);
        displayActionFailure(this.error);
      };
    }
  
    function getBlob(key, store, success_callback) {
      var req = store.get(key);
      req.onsuccess = function(evt) {
        var value = evt.target.result;
        if (value)
          success_callback(value.blob);
      };
    }
  
    /**
     * @param {IDBObjectStore=} store
     */
    function displayPubList(store) {
      console.log("displayPubList");
  
      if (typeof store == 'undefined')
        store = getObjectStore(DB_STORE_NAME, 'readonly');
  
      var pub_msg = $('#pub-msg');
      pub_msg.empty();
      var pub_list = $('#pub-list');
      pub_list.empty();
      // Resetting the iframe so that it doesn't display previous content
      newViewerFrame();
  
      var req;
      req = store.count();
      // Requests are executed in the order in which they were made against the
      // transaction, and their results are returned in the same order.
      // Thus the count text below will be displayed before the actual pub list
      // (not that it is algorithmically important in this case).
      req.onsuccess = function(evt) {
        pub_msg.append('<p>There are <strong>' + evt.target.result +
                       '</strong> record(s) in the object store.</p>');
      };
      req.onerror = function(evt) {
        console.error("add error", this.error);
        displayActionFailure(this.error);
      };
  
      var i = 0;
      req = store.openCursor();
      req.onsuccess = function(evt) {
        var cursor = evt.target.result;
  
        // If the cursor is pointing at something, ask for the data
        if (cursor) {
          console.log("displayPubList cursor:", cursor);
          req = store.get(cursor.key);
          req.onsuccess = function (evt) {
            var value = evt.target.result;
            var list_item = $('<li>' +
                              '[' + cursor.key + '] ' +
                              '(biblioid: ' + value.biblioid + ') ' +
                              value.title +
                              '</li>');
            if (value.year != null)
              list_item.append(' - ' + value.year);
  
            if (value.hasOwnProperty('blob') &&
                typeof value.blob != 'undefined') {
              var link = $('<a href="' + cursor.key + '">File</a>');
              link.on('click', function() { return false; });
              link.on('mouseenter', function(evt) {
                        setInViewer(evt.target.getAttribute('href')); });
              list_item.append(' / ');
              list_item.append(link);
            } else {
              list_item.append(" / No attached file");
            }
            pub_list.append(list_item);
          };
  
          // Move on to the next object in store
          cursor.continue();
  
          // This counter serves only to create distinct ids
          i++;
        } else {
          console.log("No more entries");
        }
      };
    }
  
    function newViewerFrame() {
      var viewer = $('#pub-viewer');
      viewer.empty();
      var iframe = $('<iframe />');
      viewer.append(iframe);
      return iframe;
    }
  
    function setInViewer(key) {
      console.log("setInViewer:", arguments);
      key = Number(key);
      if (key == current_view_pub_key)
        return;
  
      current_view_pub_key = key;
  
      var store = getObjectStore(DB_STORE_NAME, 'readonly');
      getBlob(key, store, function(blob) {
        console.log("setInViewer blob:", blob);
        var iframe = newViewerFrame();
  
        // It is not possible to set a direct link to the
        // blob to provide a mean to directly download it.
        if (blob.type == 'text/html') {
          var reader = new FileReader();
          reader.onload = (function(evt) {
            var html = evt.target.result;
            iframe.load(function() {
              $(this).contents().find('html').html(html);
            });
          });
          reader.readAsText(blob);
        } else if (blob.type.indexOf('image/') == 0) {
          iframe.load(function() {
            var img_id = 'image-' + key;
            var img = $('<img id="' + img_id + '"/>');
            $(this).contents().find('body').html(img);
            var obj_url = window.URL.createObjectURL(blob);
            $(this).contents().find('#' + img_id).attr('src', obj_url);
            window.URL.revokeObjectURL(obj_url);
          });
        } else if (blob.type == 'application/pdf') {
          $('*').css('cursor', 'wait');
          var obj_url = window.URL.createObjectURL(blob);
          iframe.load(function() {
            $('*').css('cursor', 'auto');
          });
          iframe.attr('src', obj_url);
          window.URL.revokeObjectURL(obj_url);
        } else {
          iframe.load(function() {
            $(this).contents().find('body').html("No view available");
          });
        }
  
      });
    }
  
    /**
     * @param {string} biblioid
     * @param {string} title
     * @param {number} year
     * @param {string} url the URL of the image to download and store in the local
     *   IndexedDB database. The resource behind this URL is subjected to the
     *   "Same origin policy", thus for this method to work, the URL must come from
     *   the same origin as the web site/app this code is deployed on.
     */
    function addPublicationFromUrl(biblioid, title, year, url) {
      console.log("addPublicationFromUrl:", arguments);
  
      var xhr = new XMLHttpRequest();
      xhr.open('GET', url, true);
      // Setting the wanted responseType to "blob"
      // http://www.w3.org/TR/XMLHttpRequest2/#the-response-attribute
      xhr.responseType = 'blob';
      xhr.onload = function (evt) {
                             if (xhr.status == 200) {
                               console.log("Blob retrieved");
                               var blob = xhr.response;
                               console.log("Blob:", blob);
                               addPublication(biblioid, title, year, blob);
                             } else {
                               console.error("addPublicationFromUrl error:",
                                             xhr.responseText, xhr.status);
                             }
                           };
      xhr.send();
  
      // We can't use jQuery here because as of jQuery 1.8.3 the new "blob"
      // responseType is not handled.
      // http://bugs.jquery.com/ticket/11461
      // http://bugs.jquery.com/ticket/7248
      // $.ajax({
      //   url: url,
      //   type: 'GET',
      //   xhrFields: { responseType: 'blob' },
      //   success: function(data, textStatus, jqXHR) {
      //     console.log("Blob retrieved");
      //     console.log("Blob:", data);
      //     // addPublication(biblioid, title, year, data);
      //   },
      //   error: function(jqXHR, textStatus, errorThrown) {
      //     console.error(errorThrown);
      //     displayActionFailure("Error during blob retrieval");
      //   }
      // });
    }
  
    /**
     * @param {string} biblioid
     * @param {string} title
     * @param {number} year
     * @param {Blob=} blob
     */
    function addPublication(biblioid, title, year, blob) {
      console.log("addPublication arguments:", arguments);
      var obj = { biblioid: biblioid, title: title, year: year };
      if (typeof blob != 'undefined')
        obj.blob = blob;
  
      var store = getObjectStore(DB_STORE_NAME, 'readwrite');
      var req;
      try {
        req = store.add(obj);
      } catch (e) {
        if (e.name == 'DataCloneError')
          displayActionFailure("This engine doesn't know how to clone a Blob, " +
                               "use Firefox");
        throw e;
      }
      req.onsuccess = function (evt) {
        console.log("Insertion in DB successful");
        displayActionSuccess();
        displayPubList(store);
      };
      req.onerror = function() {
        console.error("addPublication error", this.error);
        displayActionFailure(this.error);
      };
    }
  
    /**
     * @param {string} biblioid
     */
    function deletePublicationFromBib(biblioid) {
      console.log("deletePublication:", arguments);
      var store = getObjectStore(DB_STORE_NAME, 'readwrite');
      var req = store.index('biblioid');
      req.get(biblioid).onsuccess = function(evt) {
        if (typeof evt.target.result == 'undefined') {
          displayActionFailure("No matching record found");
          return;
        }
        deletePublication(evt.target.result.id, store);
      };
      req.onerror = function (evt) {
        console.error("deletePublicationFromBib:", evt.target.errorCode);
      };
    }
  
    /**
     * @param {number} key
     * @param {IDBObjectStore=} store
     */
    function deletePublication(key, store) {
      console.log("deletePublication:", arguments);
  
      if (typeof store == 'undefined')
        store = getObjectStore(DB_STORE_NAME, 'readwrite');
  
      // As per spec http://www.w3.org/TR/IndexedDB/#object-store-deletion-operation
      // the result of the Object Store Deletion Operation algorithm is
      // undefined, so it's not possible to know if some records were actually
      // deleted by looking at the request result.
      var req = store.get(key);
      req.onsuccess = function(evt) {
        var record = evt.target.result;
        console.log("record:", record);
        if (typeof record == 'undefined') {
          displayActionFailure("No matching record found");
          return;
        }
        // Warning: The exact same key used for creation needs to be passed for
        // the deletion. If the key was a Number for creation, then it needs to
        // be a Number for deletion.
        req = store.delete(key);
        req.onsuccess = function(evt) {
          console.log("evt:", evt);
          console.log("evt.target:", evt.target);
          console.log("evt.target.result:", evt.target.result);
          console.log("delete successful");
          displayActionSuccess("Deletion successful");
          displayPubList(store);
        };
        req.onerror = function (evt) {
          console.error("deletePublication:", evt.target.errorCode);
        };
      };
      req.onerror = function (evt) {
        console.error("deletePublication:", evt.target.errorCode);
      };
    }
  
    function displayActionSuccess(msg) {
      msg = typeof msg != 'undefined' ? "Success: " + msg : "Success";
      $('#msg').html('<span class="action-success">' + msg + '</span>');
    }
    function displayActionFailure(msg) {
      msg = typeof msg != 'undefined' ? "Failure: " + msg : "Failure";
      $('#msg').html('<span class="action-failure">' + msg + '</span>');
    }
    function resetActionStatus() {
      console.log("resetActionStatus ...");
      $('#msg').empty();
      console.log("resetActionStatus DONE");
    }
  
    function addEventListeners() {
      console.log("addEventListeners");
  
      $('#register-form-reset').click(function(evt) {
        resetActionStatus();
      });
  
      $('#add-button').click(function(evt) {
        console.log("add ...");
        var title = $('#pub-title').val();
        var biblioid = $('#pub-biblioid').val();
        if (!title || !biblioid) {
          displayActionFailure("Required field(s) missing");
          return;
        }
        var year = $('#pub-year').val();
        if (year != '') {
          // Better use Number.isInteger if the engine has EcmaScript 6
          if (isNaN(year))  {
            displayActionFailure("Invalid year");
            return;
          }
          year = Number(year);
        } else {
          year = null;
        }
  
        var file_input = $('#pub-file');
        var selected_file = file_input.get(0).files[0];
        console.log("selected_file:", selected_file);
        // Keeping a reference on how to reset the file input in the UI once we
        // have its value, but instead of doing that we rather use a "reset" type
        // input in the HTML form.
        //file_input.val(null);
        var file_url = $('#pub-file-url').val();
        if (selected_file) {
          addPublication(biblioid, title, year, selected_file);
        } else if (file_url) {
          addPublicationFromUrl(biblioid, title, year, file_url);
        } else {
          addPublication(biblioid, title, year);
        }
  
      });
  
      $('#delete-button').click(function(evt) {
        console.log("delete ...");
        var biblioid = $('#pub-biblioid-to-delete').val();
        var key = $('#key-to-delete').val();
  
        if (biblioid != '') {
          deletePublicationFromBib(biblioid);
        } else if (key != '') {
          // Better use Number.isInteger if the engine has EcmaScript 6
          if (key == '' || isNaN(key))  {
            displayActionFailure("Invalid key");
            return;
          }
          key = Number(key);
          deletePublication(key);
        }
      });
  
      $('#clear-store-button').click(function(evt) {
        clearObjectStore();
      });
  
      var search_button = $('#search-list-button');
      search_button.click(function(evt) {
        displayPubList();
      });
  
    }
  
    openDb();
    addEventListeners();
  
  })(); // Immediately-Invoked Function Expression (IIFE)
  })(); // Immediately-Invoked Function Expression (IIFE)
  