From e8a00bc50da435d3859118ca127d20a2eb9a0cab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Mon, 9 Mar 2015 16:34:27 +0100 Subject: [REF] web: add module system to the web client This commit introduces a new module system for the javascript code. Instead of using global (namespaced) variables everywhere (like openerp.web.whatever, ...), each file declare one (or more) module with its dependencies. Each module will be loaded after its dependencies, and will be given a reference to the return values. This change is a massive departure from the way the client worked previously. It will sadly break some/most history, but I am sure that the future git ecosystem will allow you to easily browse the history past this revision (one can dream). This commit feature: * main namespace is now odoo instead of openerp * a way to define modules and dependencies (inspired by requirejs) * many files have been splitted in simpler subfiles * basic support for debugging in odoo.__DEBUG__ * no global variable (i.e. nothing in window.openerp) * many bug fixes, many new bugs Future vision: this modularization should simplify future code development, simplify debugging and will allow a serious testing effort --- addons/web/static/src/js/data.js | 1344 -------------------------------------- 1 file changed, 1344 deletions(-) delete mode 100644 addons/web/static/src/js/data.js (limited to 'addons/web/static/src/js/data.js') diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js deleted file mode 100644 index 82edbc2f35b..00000000000 --- a/addons/web/static/src/js/data.js +++ /dev/null @@ -1,1344 +0,0 @@ - -(function() { - -var instance = openerp; -openerp.web.data = {}; - -/** - * Serializes the sort criterion array of a dataset into a form which can be - * consumed by OpenERP's RPC APIs. - * - * @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting - * @returns {String} SQL-like sorting string (``ORDER BY``) clause - */ -instance.web.serialize_sort = function (criterion) { - return _.map(criterion, - function (criteria) { - if (criteria[0] === '-') { - return criteria.slice(1) + ' DESC'; - } - return criteria + ' ASC'; - }).join(', '); -}; - -/** - * Reverse of the serialize_sort function: convert an array of SQL-like sort - * descriptors into a list of fields prefixed with '-' if necessary. - */ - instance.web.deserialize_sort = function (criterion) { - return _.map(criterion, function (criteria) { - var split = _.without(criteria.split(' '), ''); - return (split[1] && split[1].toLowerCase() === 'desc' ? '-' : '') + split[0]; - }); -}; - -instance.web.Query = instance.web.Class.extend({ - init: function (model, fields) { - this._model = model; - this._fields = fields; - this._filter = []; - this._context = {}; - this._lazy = true; - this._limit = false; - this._offset = 0; - this._order_by = []; - }, - clone: function (to_set) { - to_set = to_set || {}; - var q = new instance.web.Query(this._model, this._fields); - q._context = this._context; - q._filter = this._filter; - q._lazy = this._lazy; - q._limit = this._limit; - q._offset = this._offset; - q._order_by = this._order_by; - - for(var key in to_set) { - if (!to_set.hasOwnProperty(key)) { continue; } - switch(key) { - case 'filter': - q._filter = new instance.web.CompoundDomain( - q._filter, to_set.filter); - break; - case 'context': - q._context = new instance.web.CompoundContext( - q._context, to_set.context); - break; - case 'lazy': - case 'limit': - case 'offset': - case 'order_by': - q['_' + key] = to_set[key]; - } - } - return q; - }, - _execute: function (options) { - var self = this; - options = options || {}; - return instance.session.rpc('/web/dataset/search_read', { - model: this._model.name, - fields: this._fields || false, - domain: instance.web.pyeval.eval('domains', - [this._model.domain(this._filter)]), - context: instance.web.pyeval.eval('contexts', - [this._model.context(this._context)]), - offset: this._offset, - limit: this._limit, - sort: instance.web.serialize_sort(this._order_by) - }, options).then(function (results) { - self._count = results.length; - return results.records; - }, null); - }, - /** - * Fetches the first record matching the query, or null - * - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred} - */ - first: function (options) { - var self = this; - return this.clone({limit: 1})._execute(options).then(function (records) { - delete self._count; - if (records.length) { return records[0]; } - return null; - }); - }, - /** - * Fetches all records matching the query - * - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred>} - */ - all: function (options) { - return this._execute(options); - }, - /** - * Fetches the number of records matching the query in the database - * - * @returns {jQuery.Deferred} - */ - count: function () { - if (this._count !== undefined) { return $.when(this._count); } - return this._model.call( - 'search_count', [this._filter], { - context: this._model.context(this._context)}); - }, - /** - * Performs a groups read according to the provided grouping criterion - * - * @param {String|Array} grouping - * @returns {jQuery.Deferred> | null} - */ - group_by: function (grouping) { - var ctx = instance.web.pyeval.eval( - 'context', this._model.context(this._context)); - - // undefined passed in explicitly (!) - if (_.isUndefined(grouping)) { - grouping = []; - } - - if (!(grouping instanceof Array)) { - grouping = _.toArray(arguments); - } - if (_.isEmpty(grouping) && !ctx['group_by_no_leaf']) { - return null; - } - var raw_fields = _.map(grouping.concat(this._fields || []), function (field) { - return field.split(':')[0]; - }); - - var self = this; - return this._model.call('read_group', { - groupby: grouping, - fields: _.uniq(raw_fields), - domain: this._model.domain(this._filter), - context: ctx, - offset: this._offset, - lazy: this._lazy, - limit: this._limit, - orderby: instance.web.serialize_sort(this._order_by) || false - }).then(function (results) { - return _(results).map(function (result) { - // FIX: querygroup initialization - result.__context = result.__context || {}; - result.__context.group_by = result.__context.group_by || []; - _.defaults(result.__context, ctx); - var grouping_fields = self._lazy ? [grouping[0]] : grouping; - return new instance.web.QueryGroup( - self._model.name, grouping_fields, result); - }); - }); - }, - /** - * Creates a new query with the union of the current query's context and - * the new context. - * - * @param context context data to add to the query - * @returns {openerp.web.Query} - */ - context: function (context) { - if (!context) { return this; } - return this.clone({context: context}); - }, - /** - * Creates a new query with the union of the current query's filter and - * the new domain. - * - * @param domain domain data to AND with the current query filter - * @returns {openerp.web.Query} - */ - filter: function (domain) { - if (!domain) { return this; } - return this.clone({filter: domain}); - }, - /** - * Creates a new query with the provided parameter lazy replacing the current - * query's own. - * - * @param {Boolean} lazy indicates if the read_group should return only the - * first level of groupby records, or should return the records grouped by - * all levels at once (so, it makes only 1 db request). - * @returns {openerp.web.Query} - */ - lazy: function (lazy) { - return this.clone({lazy: lazy}); - }, - /** - * Creates a new query with the provided limit replacing the current - * query's own limit - * - * @param {Number} limit maximum number of records the query should retrieve - * @returns {openerp.web.Query} - */ - limit: function (limit) { - return this.clone({limit: limit}); - }, - /** - * Creates a new query with the provided offset replacing the current - * query's own offset - * - * @param {Number} offset number of records the query should skip before starting its retrieval - * @returns {openerp.web.Query} - */ - offset: function (offset) { - return this.clone({offset: offset}); - }, - /** - * Creates a new query with the provided ordering parameters replacing - * those of the current query - * - * @param {String...} fields ordering clauses - * @returns {openerp.web.Query} - */ - order_by: function (fields) { - if (fields === undefined) { return this; } - if (!(fields instanceof Array)) { - fields = _.toArray(arguments); - } - if (_.isEmpty(fields)) { return this; } - return this.clone({order_by: fields}); - } -}); - -instance.web.QueryGroup = instance.web.Class.extend({ - init: function (model, grouping_fields, read_group_group) { - // In cases where group_by_no_leaf and no group_by, the result of - // read_group has aggregate fields but no __context or __domain. - // Create default (empty) values for those so that things don't break - var fixed_group = _.extend( - {__context: {group_by: []}, __domain: []}, - read_group_group); - - var count_key = (grouping_fields[0] && grouping_fields[0].split(':')[0]) + '_count'; - var aggregates = {}; - _(fixed_group).each(function (value, key) { - if (key.indexOf('__') === 0 - || _.contains(grouping_fields, key) - || (key === count_key)) { - return; - } - aggregates[key] = value || 0; - }); - - this.model = new instance.web.Model( - model, fixed_group.__context, fixed_group.__domain); - - var group_size = fixed_group[count_key] || fixed_group.__count || 0; - var leaf_group = fixed_group.__context.group_by.length === 0; - - var value = (grouping_fields.length === 1) - ? fixed_group[grouping_fields[0]] - : _.map(grouping_fields, function (field) { return fixed_group[field]; }); - var grouped_on = (grouping_fields.length === 1) - ? grouping_fields[0] - : grouping_fields; - this.attributes = { - folded: !!(fixed_group.__fold), - grouped_on: grouped_on, - // if terminal group (or no group) and group_by_no_leaf => use group.__count - length: group_size, - value: value, - // A group is open-able if it's not a leaf in group_by_no_leaf mode - has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']), - - aggregates: aggregates - }; - }, - get: function (key) { - return this.attributes[key]; - }, - subgroups: function () { - return this.model.query().group_by(this.model.context().group_by); - }, - query: function () { - return this.model.query.apply(this.model, arguments); - } -}); - -instance.web.Model.include({ - /** - new openerp.web.Model([session,] model_name[, context[, domain]]) - - @constructs instance.web.Model - @extends instance.web.Class - - @param {openerp.web.Session} [session] The session object used to communicate with - the server. - @param {String} model_name name of the OpenERP model this object is bound to - @param {Object} [context] - @param {Array} [domain] - */ - init: function() { - var session, model_name, context, domain; - var args = _.toArray(arguments); - args.reverse(); - session = args.pop(); - if (session && ! (session instanceof openerp.web.Session)) { - model_name = session; - session = null; - } else { - model_name = args.pop(); - } - context = args.length > 0 ? args.pop() : null; - domain = args.length > 0 ? args.pop() : null; - - this._super(session, model_name, context, domain); - this._context = context || {}; - this._domain = domain || []; - }, - session: function() { - if (! this._session) - return instance.session; - return this._super(); - }, - /** - * @deprecated does not allow to specify kwargs, directly use call() instead - */ - get_func: function (method_name) { - var self = this; - return function () { - return self.call(method_name, _.toArray(arguments)); - }; - }, - /** - * Call a method (over RPC) on the bound OpenERP model. - * - * @param {String} method name of the method to call - * @param {Array} [args] positional arguments - * @param {Object} [kwargs] keyword arguments - * @param {Object} [options] additional options for the rpc() method - * @returns {jQuery.Deferred<>} call result - */ - call: function (method, args, kwargs, options) { - args = args || []; - kwargs = kwargs || {}; - if (!_.isArray(args)) { - // call(method, kwargs) - kwargs = args; - args = []; - } - instance.web.pyeval.ensure_evaluated(args, kwargs); - return this._super(method, args, kwargs, options); - }, - /** - * Fetches a Query instance bound to this model, for searching - * - * @param {Array} [fields] fields to ultimately fetch during the search - * @returns {instance.web.Query} - */ - query: function (fields) { - return new instance.web.Query(this, fields); - }, - /** - * Executes a signal on the designated workflow, on the bound OpenERP model - * - * @param {Number} id workflow identifier - * @param {String} signal signal to trigger on the workflow - */ - exec_workflow: function (id, signal) { - return this.session().rpc('/web/dataset/exec_workflow', { - model: this.name, - id: id, - signal: signal - }); - }, - /** - * Fetches the model's domain, combined with the provided domain if any - * - * @param {Array} [domain] to combine with the model's internal domain - * @returns {instance.web.CompoundDomain} The model's internal domain, or the AND-ed union of the model's internal domain and the provided domain - */ - domain: function (domain) { - if (!domain) { return this._domain; } - return new instance.web.CompoundDomain( - this._domain, domain); - }, - /** - * Fetches the combination of the user's context and the domain context, - * combined with the provided context if any - * - * @param {Object} [context] to combine with the model's internal context - * @returns {instance.web.CompoundContext} The union of the user's context and the model's internal context, as well as the provided context if any. In that order. - */ - context: function (context) { - return new instance.web.CompoundContext( - instance.session.user_context, this._context, context || {}); - }, - /** - * Button action caller, needs to perform cleanup if an action is returned - * from the button (parsing of context and domain, and fixup of the views - * collection for act_window actions) - * - * FIXME: remove when evaluator integrated - */ - call_button: function (method, args) { - instance.web.pyeval.ensure_evaluated(args, {}); - return this.session().rpc('/web/dataset/call_button', { - model: this.name, - method: method, - // Should not be necessary anymore. Integrate remote in this? - domain_id: null, - context_id: args.length - 1, - args: args || [] - }); - }, -}); - -instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, { - /** - * Collection of OpenERP records, used to share records and the current selection between views. - * - * @constructs instance.web.DataSet - * - * @param {String} model the OpenERP model this dataset will manage - */ - init: function(parent, model, context) { - instance.web.PropertiesMixin.init.call(this); - this.model = model; - this.context = context || {}; - this.index = null; - this._sort = []; - this._model = new instance.web.Model(model, context); - }, - previous: function () { - this.index -= 1; - if (!this.ids.length) { - this.index = null; - } else if (this.index < 0) { - this.index = this.ids.length - 1; - } - return this; - }, - next: function () { - this.index += 1; - if (!this.ids.length) { - this.index = null; - } else if (this.index >= this.ids.length) { - this.index = 0; - } - return this; - }, - select_id: function(id) { - var idx = this.get_id_index(id); - if (idx === null) { - return false; - } else { - this.index = idx; - return true; - } - }, - get_id_index: function(id) { - for (var i=0, ii=this.ids.length; i= ids.length - 1) { - this.index = ids.length - 1; - } - }, - unlink: function(ids) { - this.set_ids(_.without.apply(null, [this.ids].concat(ids))); - this.trigger('unlink', ids); - return $.Deferred().resolve({result: true}); - }, -}); - -instance.web.DataSetSearch = instance.web.DataSet.extend({ - /** - * @constructs instance.web.DataSetSearch - * @extends instance.web.DataSet - * - * @param {Object} parent - * @param {String} model - * @param {Object} context - * @param {Array} domain - */ - init: function(parent, model, context, domain) { - this._super(parent, model, context); - this.domain = domain || []; - this._length = null; - this.ids = []; - this._model = new instance.web.Model(model, context, domain); - }, - /** - * Read a slice of the records represented by this DataSet, based on its - * domain and context. - * - * @params {Object} options - * @param {Array} [options.fields] fields to read and return, by default all fields are returned - * @param {Object} [options.context] context data to add to the request payload, on top of the DataSet's own context - * @param {Array} [options.domain] domain data to add to the request payload, ANDed with the dataset's domain - * @param {Number} [options.offset=0] The index from which selected records should be returned - * @param {Number} [options.limit=null] The maximum number of records to return - * @returns {$.Deferred} - */ - read_slice: function (fields, options) { - options = options || {}; - var self = this; - var q = this._model.query(fields || false) - .filter(options.domain) - .context(options.context) - .offset(options.offset || 0) - .limit(options.limit || false); - q = q.order_by.apply(q, this._sort); - - return q.all().done(function (records) { - // FIXME: not sure about that one, *could* have discarded count - q.count().done(function (count) { self._length = count; }); - self.ids = _(records).pluck('id'); - }); - }, - get_domain: function (other_domain) { - return this._model.domain(other_domain); - }, - alter_ids: function (ids) { - this._super(ids); - if (this.index !== null && this.index >= this.ids.length) { - this.index = this.ids.length > 0 ? this.ids.length - 1 : 0; - } - }, - remove_ids: function (ids) { - var before = this.ids.length; - this._super(ids); - if (this._length) { - this._length -= (before - this.ids.length); - } - }, - unlink: function(ids, callback, error_callback) { - var self = this; - return this._super(ids).done(function(result) { - self.remove_ids( ids); - self.trigger("dataset_changed", ids, callback, error_callback); - }); - }, - size: function () { - if (this._length !== null) { - return this._length; - } - return this._super(); - } -}); - -instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ - virtual_id_prefix: "one2many_v_id_", - debug_mode: true, - init: function() { - this._super.apply(this, arguments); - this.reset_ids([]); - this.last_default_get = {}; - this.running_reads = []; - }, - default_get: function(fields, options) { - var self = this; - return this._super(fields, options).done(function(res) { - self.last_default_get = res; - }); - }, - create: function(data, options) { - var cached = { - id:_.uniqueId(this.virtual_id_prefix), - values: _.extend({}, data, (options || {}).readonly_fields || {}), - defaults: this.last_default_get - }; - this.to_create.push(_.extend(_.clone(cached), {values: _.clone(data)})); - this.cache.push(cached); - return $.Deferred().resolve(cached.id).promise(); - }, - write: function (id, data, options) { - var self = this; - var record = _.detect(this.to_create, function(x) {return x.id === id;}); - record = record || _.detect(this.to_write, function(x) {return x.id === id;}); - var dirty = false; - if (record) { - for (var k in data) { - if (record.values[k] === undefined || record.values[k] !== data[k]) { - dirty = true; - break; - } - } - $.extend(record.values, data); - } else { - dirty = true; - record = {id: id, values: data}; - self.to_write.push(record); - } - var cached = _.detect(this.cache, function(x) {return x.id === id;}); - if (!cached) { - cached = {id: id, values: {}}; - this.cache.push(cached); - } - $.extend(cached.values, _.extend({}, record.values, (options || {}).readonly_fields || {})); - if (dirty) - this.trigger("dataset_changed", id, data, options); - return $.Deferred().resolve(true).promise(); - }, - unlink: function(ids, callback, error_callback) { - var self = this; - _.each(ids, function(id) { - if (! _.detect(self.to_create, function(x) { return x.id === id; })) { - self.to_delete.push({id: id}); - } - }); - this.to_create = _.reject(this.to_create, function(x) { return _.include(ids, x.id);}); - this.to_write = _.reject(this.to_write, function(x) { return _.include(ids, x.id);}); - this.cache = _.reject(this.cache, function(x) { return _.include(ids, x.id);}); - this.set_ids(_.without.apply(_, [this.ids].concat(ids))); - this.trigger("dataset_changed", ids, callback, error_callback); - return $.async_when({result: true}).done(callback); - }, - reset_ids: function(ids) { - this.set_ids(ids); - this.to_delete = []; - this.to_create = []; - this.to_write = []; - this.cache = []; - this.delete_all = false; - _.each(_.clone(this.running_reads), function(el) { - el.reject(); - }); - }, - read_ids: function (ids, fields, options) { - var self = this; - var to_get = []; - _.each(ids, function(id) { - var cached = _.detect(self.cache, function(x) {return x.id === id;}); - var created = _.detect(self.to_create, function(x) {return x.id === id;}); - if (created) { - _.each(fields, function(x) {if (cached.values[x] === undefined) - cached.values[x] = created.defaults[x] || false;}); - } else { - if (!cached || !_.all(fields, function(x) {return cached.values[x] !== undefined;})) - to_get.push(id); - } - }); - var return_records = function() { - var records = _.map(ids, function(id) { - var c = _.find(self.cache, function(c) {return c.id === id;}); - return _.isUndefined(c) ? c : _.extend({}, c.values, {"id": id}); - }); - if (self.debug_mode) { - if (_.include(records, undefined)) { - throw "Record not correctly loaded"; - } - } - var sort_fields = self._sort, - compare = function (v1, v2) { - return (v1 < v2) ? -1 - : (v1 > v2) ? 1 - : 0; - }; - // Array.sort is not necessarily stable. We must be careful with this because - // sorting an array where all items are considered equal is a worst-case that - // will randomize the array with an unstable sort! Therefore we must avoid - // sorting if there are no sort_fields (i.e. all items are considered equal) - // See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11 - // http://code.google.com/p/v8/issues/detail?id=90 - if (sort_fields.length) { - records.sort(function (a, b) { - return _.reduce(sort_fields, function (acc, field) { - if (acc) { return acc; } - var sign = 1; - if (field[0] === '-') { - sign = -1; - field = field.slice(1); - } - if(!a[field] && a[field] !== 0){ return sign} - if(!b[field] && b[field] !== 0){ return (sign == -1) ? 1 : -1} - //m2o should be searched based on value[1] not based whole value(i.e. [id, value]) - if(_.isArray(a[field]) && a[field].length == 2 && _.isString(a[field][1])){ - return sign * compare(a[field][1], b[field][1]); - } - return sign * compare(a[field], b[field]); - }, 0); - }); - } - return $.when(records); - }; - if(to_get.length > 0) { - var def = $.Deferred(); - self.running_reads.push(def); - def.always(function() { - self.running_reads = _.without(self.running_reads, def); - }); - this._super(to_get, fields, options).then(function() { - def.resolve.apply(def, arguments); - }, function() { - def.reject.apply(def, arguments); - }); - return def.then(function(records) { - _.each(records, function(record, index) { - var id = to_get[index]; - var cached = _.detect(self.cache, function(x) {return x.id === id;}); - if (!cached) { - self.cache.push({id: id, values: record}); - } else { - // I assume cache value is prioritary - cached.values = _.defaults(_.clone(cached.values), record); - } - }); - return return_records(); - }); - } else { - return return_records(); - } - }, - /** - * Invalidates caching of a record in the dataset to ensure the next read - * of that record will hit the server. - * - * Of use when an action is going to remote-alter a record which will then - * need to be reloaded, e.g. action button. - * - * @param {Object} id record to remove from the BDS's cache - */ - evict_record: function (id) { - // Don't evict records which haven't yet been saved: there is no more - // recent data on the server (and there potentially isn't any data), - // and this breaks the assumptions of other methods (that the data - // for new and altered records is both in the cache and in the to_write - // or to_create collection) - if (_(this.to_create.concat(this.to_write)).find(function (record) { - return record.id === id; })) { - return; - } - for(var i=0, len=this.cache.length; i self.rsn) { - self.rsn = seq; - res.resolve.apply(res, arguments); - } else if (self.failMisordered) { - res.reject(); - } - }).fail(function () { - res.reject.apply(res, arguments); - }); - - return res.promise(); - } -}); - -instance.web.SimpleIndexedDB = instance.web.Class.extend({ - /** - * A simple wrapper around IndexedDB that provides an asynchronous - * localStorage-like Key-Value api that persists between browser - * restarts. - * - * It will not work if the browser doesn't support a recent version - * of IndexedDB, and it may fail if the user refuses db access. - * - * All instances of SimpleIndexedDB will by default refer to the same - * IndexedDB database, if you want to pick another one, use the 'name' - * option on instanciation. - */ - init: function(opts){ - var self = this; - var opts = opts || {}; - - this.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; - this.db = undefined; - this._ready = new $.Deferred(); - - if(this.indexedDB && this.indexedDB.open){ - var request = this.indexedDB.open( opts.name || "SimpleIndexedDB" ,1); - - request.onerror = function(event){ - self.db = null; - self._ready.reject(event.target.error); - }; - - request.onsuccess = function(event){ - self.db = request.result; - self._ready.resolve(); - }; - - request.onupgradeneeded = function(event){ - self.db = event.target.result; - - var objectStore = self.db.createObjectStore("keyvals", { keyPath:"key" }); - self._ready.resolve(); - }; - }else{ - this.db = null; - this._ready.reject({type:'UnknownError', message:'IndexedDB is not supported by your Browser'}); - } - }, - - /** - * returns true if the browser supports the necessary IndexedDB API - * (but doesn't tell if the db can be created, check ready() for that) - */ - isSupportedByBrowser: function(){ - return this.indexedDB && this.indexedDB.open; - }, - - /** - * returns a deferred that resolves when the db has been successfully - * initialized. done/failed callbacks are optional. - */ - ready: function(done,failed){ - this._ready.then(done,failed); - return this._ready.promise(); - }, - - /** - * fetches the value associated to 'key' in the db. if the key doesn't - * exists, it returns undefined. The returned value is provided as a - * deferred, or as the first parameter of the optional 'done' callback. - */ - getItem: function(key,done,failed){ - var self = this; - var def = new $.Deferred(); - def.then(done,failed); - - this._ready.then(function(){ - var request = self.db.transaction(["keyvals"],"readwrite") - .objectStore("keyvals") - .get(key); - - request.onsuccess = function(){ - def.resolve(request.result ? request.result.value : undefined); - }; - - request.onerror = function(event){ - def.reject(event.target.error); - }; - },function(){ - def.reject({type:'UnknownError', message:'Could not initialize the IndexedDB database'}); - }); - - return def.promise(); - }, - - /** - * Associates a value to 'key' in the db, overwriting previous value if - * necessary. Contrary to localStorage, the value is not limited to strings and - * can be any javascript object, even with cyclic references ! - * - * Be sure to check for failure as the user may refuse to have data localy stored. - */ - setItem: function(key,value,done,failed){ - var self = this; - var def = new $.Deferred(); - def.then(done,failed); - - this._ready.then(function(){ - var request = self.db.transaction(["keyvals"],"readwrite") - .objectStore("keyvals") - .put( {key:key, value:value} ); - - request.onsuccess = function(){ - def.resolve(); - }; - - request.onerror = function(event){ - def.reject(event.target.error); - }; - },function(){ - def.reject({type:'UnknownError', message:'Could not initialize the IndexedDB database'}); - }); - - return def.promise(); - }, - - /** - * Removes the value associated with 'key' from the db. - */ - removeItem: function(key,done,failed){ - var self = this; - var def = new $.Deferred(); - def.then(done,failed); - - this._ready.then(function(){ - var request = self.db.transaction(["keyvals"],"readwrite") - .objectStore("keyvals") - .delete(key); - - request.onsuccess = function(){ - def.resolve(); - }; - - request.onerror = function(event){ - def.reject(event.target.error); - }; - },function(){ - def.reject({type:'UnknownError', message:'Could not initialize the IndexedDB database'}); - }); - - return def.promise(); - }, -}); - -})(); -- cgit v1.2.3