<!DOCTYPE html>
<html>

  <head>
    <script data-require="angular.js@1.2.28" data-semver="1.2.28" src="https://code.angularjs.org/1.2.28/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="angular-indexed-db.js"></script>
    <script src="script.js"></script>
  </head>

  <body ng-app="ngapp">
    <h1>Promises Controller</h1>
    <div ng-controller="CtrlPromises">
      
    </div>
    <h1>IndexedDB Controller</h1>
    <div ng-controller="CtrlIndexed">
      
    </div>
  </body>

</html>
angular.module('ngapp', [
  'indexedDB'
  ])
  
.config(function($indexedDBProvider) {
    $indexedDBProvider
      .connection('myIndexedDB')
      .upgradeDatabase(1, function(event, db, tx){
        var objStore = db.createObjectStore('people', {keyPath: 'code'});
        objStore.createIndex('name_idx', 'name', {unique: false});
      });  
})  

.factory('SlowService', function($q, $timeout) {
  var service = {
    slow: function(myNumber) {
      var deferred = $q.defer();
      // do some slow stuff here
      $timeout(function() {
        deferred.resolve(myNumber + 1);
      }, 5000);
      return deferred.promise;
    },
    slower: function(myNumber) {
      var deferred = $q.defer();
      service.slow(myNumber).then(function(response) {
        deferred.resolve(response);
      });
      return deferred.promise;
    },
    saveMe: function(obj) {
      var deferred = $q.defer();
      obj.id = 345;
      deferred.resolve(obj);
      return deferred.promise;
    }
  };
  return service;
})

.factory('Data', function($q, $log) {
  var service = {
    data: {},
    save: function(data) {
      service.data[data._id] = data;
    },
    get: function(_id) {
      return service.data[_id];
    },
    saveUsingInternal: function(response, _id) {
      // locate existing object using internal id
      // update with new fields (really just the external id)
      $log.info('save Using internal response:');
      $log.info(response); 
      $log.info(_id); 
    },
    f: function(_id) {
      return function(response) {
        $log.info('save Using internal response:');
        $log.info(response); 
        $log.info(_id);
        response._id = _id;
      }
    }
  };
  return service;
})

.controller('CtrlPromises', function($scope, $log, $q, SlowService, Data) {
  // Chaining
  var counter = 0;
  var deferred = $q.defer();
  var obj = {
    _id: 123,
    name: 'foo',
    value: 'bar'
  };
  var tempid = obj._id;
  delete obj._id;
  
  SlowService.saveMe(obj).then(Data.f(tempid));

})
  
.controller('CtrlIndexed', function($scope, $indexedDB, $log, $q) {
  $scope.people = [];
  
  $indexedDB.openStore('people', function(store) {
    
    var person = {
      code: "abcd",
      name: "Fred",
      age: "22"
    };
    
    store.upsert(person).then(function(e) {
      //$log.info(e);
    });
    
    store.getAll().then(function(people) {
      $log.info('got all people:');
      $log.info(people);
      $scope.people = people;
    });
    
  });
})

;
/* Styles go here */


/**
 @license $indexedDBProvider
 (c) 2014 Bram Whillock (bramski)
 Forked from original work by clements Capitan (webcss)
 License: MIT
 */

(function() {
  'use strict';
  var __slice = [].slice;

  angular.module('indexedDB', []).provider('$indexedDB', function() {
    var IDBKeyRange, allTransactions, apiDirection, appendResultsToPromise, applyNeededUpgrades, cursorDirection, db, dbMode, dbName, dbPromise, dbVersion, defaultQueryOptions, errorMessageFor, indexedDB, readyState, upgradesByVersion;
    indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
    IDBKeyRange = window.IDBKeyRange || window.mozIDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
    dbMode = {
      readonly: "readonly",
      readwrite: "readwrite"
    };
    readyState = {
      pending: "pending"
    };
    cursorDirection = {
      next: "next",
      nextunique: "nextunique",
      prev: "prev",
      prevunique: "prevunique"
    };
    apiDirection = {
      ascending: cursorDirection.next,
      descending: cursorDirection.prev
    };
    dbName = '';
    dbVersion = 1;
    db = null;
    upgradesByVersion = {};
    dbPromise = null;
    allTransactions = [];
    defaultQueryOptions = {
      useIndex: void 0,
      keyRange: null,
      direction: cursorDirection.next
    };
    applyNeededUpgrades = function(oldVersion, event, db, tx, $log) {
      var version;
      for (version in upgradesByVersion) {
        if (!upgradesByVersion.hasOwnProperty(version) || version <= oldVersion) {
          continue;
        }
        $log.log("$indexedDB: Running upgrade : " + version + " from " + oldVersion);
        upgradesByVersion[version](event, db, tx);
      }
    };
    errorMessageFor = function(e) {
      if (e.target.readyState === readyState.pending) {
        return "Error: Operation pending";
      } else {
        return e.target.webkitErrorMessage || e.target.error.message || e.target.errorCode;
      }
    };
    appendResultsToPromise = function(promise, results) {
      if (results !== void 0) {
        return promise.then(function() {
          return results;
        });
      } else {
        return promise;
      }
    };

    /**
    @ngdoc function
    @name $indexedDBProvider.connection
    @function
    
    @description
    sets the name of the database to use
    
    @param {string} databaseName database name.
    @returns {object} this
     */
    this.connection = function(databaseName) {
      dbName = databaseName;
      return this;
    };

    /**
    @ngdoc function
    @name $indexedDBProvider.upgradeDatabase
    @function
    
    @description provides version number and steps to upgrade the database wrapped in a
    callback function
    
    @param {number} newVersion new version number for the database.
    @param {function} callback the callback which proceeds the upgrade
    @returns {object} this
     */
    this.upgradeDatabase = function(newVersion, callback) {
      upgradesByVersion[newVersion] = callback;
      dbVersion = Math.max.apply(null, Object.keys(upgradesByVersion));
      return this;
    };
    this.$get = [
      '$q', '$rootScope', '$log', function($q, $rootScope, $log) {
        var DbQ, ObjectStore, Query, Transaction, addTransaction, closeDatabase, createDatabaseConnection, keyRangeForOptions, openDatabase, openTransaction, rejectWithError, validateStoreNames;
        rejectWithError = function(deferred) {
          return function(error) {
            return $rootScope.$apply(function() {
              return deferred.reject(errorMessageFor(error));
            });
          };
        };
        createDatabaseConnection = function() {
          var dbReq, deferred;
          deferred = $q.defer();
          dbReq = indexedDB.open(dbName, dbVersion || 1);
          dbReq.onsuccess = function() {
            db = dbReq.result;
            $rootScope.$apply(function() {
              deferred.resolve(db);
            });
          };
          dbReq.onblocked = dbReq.onerror = rejectWithError(deferred);
          dbReq.onupgradeneeded = function(event) {
            var tx;
            db = event.target.result;
            tx = event.target.transaction;
            $log.log("$indexedDB: Upgrading database '" + db.name + "' from version " + event.oldVersion + " to version " + event.newVersion + " ...");
            applyNeededUpgrades(event.oldVersion, event, db, tx, $log);
          };
          return deferred.promise;
        };
        openDatabase = function() {
          return dbPromise || (dbPromise = createDatabaseConnection());
        };
        closeDatabase = function() {
          return openDatabase().then(function() {
            db.close();
            db = null;
            return dbPromise = null;
          });
        };
        validateStoreNames = function(storeNames) {
          var found, storeName, _i, _len;
          found = true;
          for (_i = 0, _len = storeNames.length; _i < _len; _i++) {
            storeName = storeNames[_i];
            found = found & db.objectStoreNames.contains(storeName);
          }
          return found;
        };
        openTransaction = function(storeNames, mode) {
          if (mode == null) {
            mode = dbMode.readonly;
          }
          return openDatabase().then(function() {
            if (!validateStoreNames(storeNames)) {
              return $q.reject("Object stores " + storeNames + " do not exist.");
            }
            return new Transaction(storeNames, mode);
          });
        };
        keyRangeForOptions = function(options) {
          if (options.beginKey && options.endKey) {
            return IDBKeyRange.bound(options.beginKey, options.endKey);
          }
        };
        addTransaction = function(transaction) {
          allTransactions.push(transaction.promise);
          return transaction.promise["finally"](function() {
            var index;
            index = allTransactions.indexOf(transaction.promise);
            if (index > -1) {
              return allTransactions.splice(index, 1);
            }
          });
        };
        Transaction = (function() {
          function Transaction(storeNames, mode) {
            if (mode == null) {
              mode = dbMode.readonly;
            }
            this.transaction = db.transaction(storeNames, mode);
            this.defer = $q.defer();
            this.promise = this.defer.promise;
            this.setupCallbacks();
          }

          Transaction.prototype.setupCallbacks = function() {
            this.transaction.oncomplete = (function(_this) {
              return function() {
                return $rootScope.$apply(function() {
                  return _this.defer.resolve("Transaction Completed");
                });
              };
            })(this);
            this.transaction.onabort = (function(_this) {
              return function(error) {
                return $rootScope.$apply(function() {
                  return _this.defer.reject("Transaction Aborted", error);
                });
              };
            })(this);
            this.transaction.onerror = (function(_this) {
              return function(error) {
                return $rootScope.$apply(function() {
                  return _this.defer.reject("Transaction Error", error);
                });
              };
            })(this);
            return addTransaction(this);
          };

          Transaction.prototype.objectStore = function(storeName) {
            return this.transaction.objectStore(storeName);
          };

          Transaction.prototype.abort = function() {
            return this.transaction.abort();
          };

          return Transaction;

        })();
        DbQ = (function() {
          function DbQ() {
            this.q = $q.defer();
            this.promise = this.q.promise;
          }

          DbQ.prototype.reject = function() {
            var args;
            args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
            return $rootScope.$apply((function(_this) {
              return function() {
                var _ref;
                return (_ref = _this.q).reject.apply(_ref, args);
              };
            })(this));
          };

          DbQ.prototype.rejectWith = function(req) {
            return req.onerror = req.onblocked = (function(_this) {
              return function(e) {
                return _this.reject(errorMessageFor(e));
              };
            })(this);
          };

          DbQ.prototype.resolve = function() {
            var args;
            args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
            return $rootScope.$apply((function(_this) {
              return function() {
                var _ref;
                return (_ref = _this.q).resolve.apply(_ref, args);
              };
            })(this));
          };

          DbQ.prototype.notify = function() {
            var args;
            args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
            return $rootScope.$apply((function(_this) {
              return function() {
                var _ref;
                return (_ref = _this.q).notify.apply(_ref, args);
              };
            })(this));
          };

          DbQ.prototype.dbErrorFunction = function() {
            return (function(_this) {
              return function(error) {
                return $rootScope.$apply(function() {
                  return _this.q.reject(errorMessageFor(error));
                });
              };
            })(this);
          };

          DbQ.prototype.resolveWith = function(req) {
            this.rejectWith(req);
            return req.onsuccess = (function(_this) {
              return function(e) {
                return _this.resolve(e.target.result);
              };
            })(this);
          };

          return DbQ;

        })();
        ObjectStore = (function() {
          function ObjectStore(storeName, transaction) {
            this.storeName = storeName;
            this.store = transaction.objectStore(storeName);
            this.transaction = transaction;
          }

          ObjectStore.prototype.defer = function() {
            return new DbQ();
          };

          ObjectStore.prototype._mapCursor = function(defer, mapFunc, req) {
            var results;
            if (req == null) {
              req = this.store.openCursor();
            }
            results = [];
            defer.rejectWith(req);
            return req.onsuccess = function(e) {
              var cursor;
              if (cursor = e.target.result) {
                results.push(mapFunc(cursor));
                defer.notify(mapFunc(cursor));
                return cursor["continue"]();
              } else {
                return defer.resolve(results);
              }
            };
          };

          ObjectStore.prototype._arrayOperation = function(data, mapFunc) {
            var defer, item, req, results, _i, _len;
            defer = this.defer();
            if (!angular.isArray(data)) {
              data = [data];
            }
            for (_i = 0, _len = data.length; _i < _len; _i++) {
              item = data[_i];
              req = mapFunc(item);
              results = [];
              defer.rejectWith(req);
              req.onsuccess = function(e) {
                results.push(e.target.result);
                defer.notify(e.target.result);
                if (results.length >= data.length) {
                  return defer.resolve(results);
                }
              };
            }
            if (data.length === 0) {
              return $q.when([]);
            }
            return defer.promise;
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.getAllKeys
            @function
          
            @description
            gets all the keys
          
            @returns {Q} A promise which will result with all the keys
           */

          ObjectStore.prototype.getAllKeys = function() {
            var defer, req;
            defer = this.defer();
            if (this.store.getAllKeys) {
              req = this.store.getAllKeys();
              defer.resolveWith(req);
            } else {
              this._mapCursor(defer, function(cursor) {
                return cursor.key;
              });
            }
            return defer.promise;
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.clear
            @function
          
            @description
            clears all objects from this store
          
            @returns {Q} A promise that this can be done successfully.
           */

          ObjectStore.prototype.clear = function() {
            var defer, req;
            defer = this.defer();
            req = this.store.clear();
            defer.resolveWith(req);
            return defer.promise;
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.delete
            @function
          
            @description
            Deletes the item at the key.  The operation is ignored if the item does not exist.
          
            @param {key} The key of the object to delete.
            @returns {Q} A promise that this can be done successfully.
           */

          ObjectStore.prototype["delete"] = function(key) {
            var defer;
            defer = this.defer();
            defer.resolveWith(this.store["delete"](key));
            return defer.promise;
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.upsert
            @function
          
            @description
            Updates the given item
          
            @param {data} Details of the item or items to update or insert
            @returns {Q} A promise that this can be done successfully.
           */

          ObjectStore.prototype.upsert = function(data) {
            return this._arrayOperation(data, (function(_this) {
              return function(item) {
                return _this.store.put(item);
              };
            })(this));
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.insert
            @function
          
            @description
            Updates the given item
          
            @param {data} Details of the item or items to insert
            @returns {Q} A promise that this can be done successfully.
           */

          ObjectStore.prototype.insert = function(data) {
            return this._arrayOperation(data, (function(_this) {
              return function(item) {
                return _this.store.add(item);
              };
            })(this));
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.getAll
            @function
          
            @description
            Fetches all items from the store
          
            @returns {Q} A promise which resolves with copies of all items in the store
           */

          ObjectStore.prototype.getAll = function() {
            var defer;
            defer = this.defer();
            if (this.store.getAll) {
              defer.resolveWith(this.store.getAll());
            } else {
              this._mapCursor(defer, function(cursor) {
                return cursor.value;
              });
            }
            return defer.promise;
          };

          ObjectStore.prototype.eachWhere = function(query) {
            var defer, direction, indexName, keyRange, req;
            defer = this.defer();
            indexName = query.indexName;
            keyRange = query.keyRange;
            direction = query.direction;
            req = indexName ? this.store.index(indexName).openCursor(keyRange, direction) : this.store.openCursor(keyRange, direction);
            this._mapCursor(defer, (function(cursor) {
              return cursor.value;
            }), req);
            return defer.promise;
          };

          ObjectStore.prototype.findWhere = function(query) {
            return this.eachWhere(query);
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.each
            @function
          
            @description
            Iterates through the items in the store
          
            @param {options.beginKey} the key to start iterating from
            @param {options.endKey} the key to stop iterating at
            @param {options.direction} Direction to iterate in
            @returns {Q} A promise which notifies with each individual item and resolves with all of them.
           */

          ObjectStore.prototype.each = function(options) {
            if (options == null) {
              options = {};
            }
            return this.eachBy(void 0, options);
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.eachBy
            @function
          
            @description
            Iterates through the items in the store using an index
          
            @param {indexName} name of the index to use instead of the primary
            @param {options.beginKey} the key to start iterating from
            @param {options.endKey} the key to stop iterating at
            @param {options.direction} Direction to iterate in
            @returns {Q} A promise which notifies with each individual item and resolves with all of them.
           */

          ObjectStore.prototype.eachBy = function(indexName, options) {
            var q;
            if (indexName == null) {
              indexName = void 0;
            }
            if (options == null) {
              options = {};
            }
            q = new Query();
            q.indexName = indexName;
            q.keyRange = keyRangeForOptions(options);
            q.direction = options.direction || defaultQueryOptions.direction;
            return this.eachWhere(q);
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.count
            @function
          
            @description
            Returns a count of the items in the store
          
            @returns {Q} A promise which resolves with the count of all the items in the store.
           */

          ObjectStore.prototype.count = function() {
            var defer;
            defer = this.defer();
            defer.resolveWith(this.store.count());
            return defer.promise;
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.find
            @function
          
            @description
            Fetches an item from the store
          
            @returns {Q} A promise which resolves with the item from the store
           */

          ObjectStore.prototype.find = function(key) {
            var defer, req;
            defer = this.defer();
            req = this.store.get(key);
            defer.rejectWith(req);
            req.onsuccess = (function(_this) {
              return function(e) {
                if (e.target.result) {
                  return defer.resolve(e.target.result);
                } else {
                  return defer.reject("" + _this.storeName + ":" + key + " not found.");
                }
              };
            })(this);
            return defer.promise;
          };


          /**
            @ngdoc function
            @name $indexedDBProvider.store.findBy
            @function
          
            @description
            Fetches an item from the store using a named index.
          
            @returns {Q} A promise which resolves with the item from the store.
           */

          ObjectStore.prototype.findBy = function(index, key) {
            var defer;
            defer = this.defer();
            defer.resolveWith(this.store.index(index).get(key));
            return defer.promise;
          };

          ObjectStore.prototype.query = function() {
            return new Query();
          };

          return ObjectStore;

        })();
        Query = (function() {
          function Query() {
            this.indexName = void 0;
            this.keyRange = void 0;
            this.direction = cursorDirection.next;
          }

          Query.prototype.$lt = function(value) {
            this.keyRange = IDBKeyRange.upperBound(value, true);
            return this;
          };

          Query.prototype.$gt = function(value) {
            this.keyRange = IDBKeyRange.lowerBound(value, true);
            return this;
          };

          Query.prototype.$lte = function(value) {
            this.keyRange = IDBKeyRange.upperBound(value);
            return this;
          };

          Query.prototype.$gte = function(value) {
            this.keyRange = IDBKeyRange.lowerBound(value);
            return this;
          };

          Query.prototype.$eq = function(value) {
            this.keyRange = IDBKeyRange.only(value);
            return this;
          };

          Query.prototype.$between = function(low, hi, exLow, exHi) {
            if (exLow == null) {
              exLow = false;
            }
            if (exHi == null) {
              exHi = false;
            }
            this.keyRange = IDBKeyRange.bound(low, hi, exLow, exHi);
            return this;
          };

          Query.prototype.$desc = function(unique) {
            this.direction = unique ? cursorDirection.prevunique : cursorDirection.prev;
            return this;
          };

          Query.prototype.$asc = function(unique) {
            this.direction = unique ? cursorDirection.nextunique : cursorDirection.next;
            return this;
          };

          Query.prototype.$index = function(indexName) {
            this.indexName = indexName;
            return this;
          };

          return Query;

        })();
        return {

          /**
          @ngdoc method
          @name $indexedDB.objectStore
          @function
          
          @description an IDBObjectStore to use
          
          @params {string} storeName the name of the objectstore to use
          @returns {object} ObjectStore
           */
          openStore: function(storeName, callBack, mode) {
            if (mode == null) {
              mode = dbMode.readwrite;
            }
            return openTransaction([storeName], mode).then(function(transaction) {
              var results;
              results = callBack(new ObjectStore(storeName, transaction));
              return appendResultsToPromise(transaction.promise, results);
            });
          },
          openStores: function(storeNames, callback, mode) {
            if (mode == null) {
              mode = dbMode.readwrite;
            }
            return openTransaction(storeNames, mode).then(function(transaction) {
              var objectStores, results, storeName;
              objectStores = (function() {
                var _i, _len, _results;
                _results = [];
                for (_i = 0, _len = storeNames.length; _i < _len; _i++) {
                  storeName = storeNames[_i];
                  _results.push(new ObjectStore(storeName, transaction));
                }
                return _results;
              })();
              results = callback.apply(null, objectStores);
              return appendResultsToPromise(transaction.promise, results);
            });
          },
          openAllStores: function(callback, mode) {
            if (mode == null) {
              mode = dbMode.readwrite;
            }
            return openDatabase().then((function(_this) {
              return function() {
                var objectStores, results, storeName, storeNames, transaction;
                storeNames = Array.prototype.slice.apply(db.objectStoreNames);
                transaction = new Transaction(storeNames, mode);
                objectStores = (function() {
                  var _i, _len, _results;
                  _results = [];
                  for (_i = 0, _len = storeNames.length; _i < _len; _i++) {
                    storeName = storeNames[_i];
                    _results.push(new ObjectStore(storeName, transaction));
                  }
                  return _results;
                })();
                results = callback.apply(null, objectStores);
                return appendResultsToPromise(transaction.promise, results);
              };
            })(this));
          },

          /**
            @ngdoc method
            @name $indexedDB.closeDatabase
            @function
          
            @description Closes the database for use and completes all transactions.
           */
          closeDatabase: function() {
            return closeDatabase();
          },

          /**
            @ngdoc method
            @name $indexedDB.deleteDatabase
            @function
          
            @description Closes and then destroys the current database.  Returns a promise that resolves when this is persisted.
           */
          deleteDatabase: function() {
            return closeDatabase().then(function() {
              var defer;
              defer = new DbQ();
              defer.resolveWith(indexedDB.deleteDatabase(dbName));
              return defer.promise;
            })["finally"](function() {
              return $log.log("$indexedDB: " + dbName + " database deleted.");
            });
          },
          queryDirection: apiDirection,
          flush: function() {
            if (allTransactions.length > 0) {
              return $q.all(allTransactions);
            } else {
              return $q.when([]);
            }
          },

          /**
            @ngdoc method
            @name $indexedDB.databaseInfo
            @function
          
            @description Returns information about this database.
           */
          databaseInfo: function() {
            return openDatabase().then(function() {
              var storeNames, transaction;
              transaction = null;
              storeNames = Array.prototype.slice.apply(db.objectStoreNames);
              return openTransaction(storeNames, dbMode.readonly).then(function(transaction) {
                var store, storeName, stores;
                stores = (function() {
                  var _i, _len, _results;
                  _results = [];
                  for (_i = 0, _len = storeNames.length; _i < _len; _i++) {
                    storeName = storeNames[_i];
                    store = transaction.objectStore(storeName);
                    _results.push({
                      name: storeName,
                      keyPath: store.keyPath,
                      autoIncrement: store.autoIncrement,
                      indices: Array.prototype.slice.apply(store.indexNames)
                    });
                  }
                  return _results;
                })();
                return transaction.promise.then(function() {
                  return {
                    name: db.name,
                    version: db.version,
                    objectStores: stores
                  };
                });
              });
            });
          }
        };
      }
    ];
  });

}).call(this);