diff options
Diffstat (limited to 'addons/web/static/src/js/data.js')
| -rw-r--r-- | addons/web/static/src/js/data.js | 204 |
1 files changed, 192 insertions, 12 deletions
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 04f657d91f7..cc595c30688 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -129,11 +129,14 @@ instance.web.Query = instance.web.Class.extend({ 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(grouping.concat(this._fields || [])), + fields: _.uniq(raw_fields), domain: this._model.domain(this._filter), context: ctx, offset: this._offset, @@ -231,14 +234,16 @@ instance.web.QueryGroup = instance.web.Class.extend({ this.model = new instance.web.Model( model, fixed_group.__context, fixed_group.__domain); - var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0; + var raw_field = grouping_field && grouping_field.split(':')[0]; + var group_size = fixed_group[raw_field + '_count'] || fixed_group.__count || 0; var leaf_group = fixed_group.__context.group_by.length === 0; + this.attributes = { folded: !!(fixed_group.__fold), grouped_on: grouping_field, // if terminal group (or no group) and group_by_no_leaf => use group.__count length: group_size, - value: fixed_group[grouping_field], + value: fixed_group[raw_field], // 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']), @@ -447,6 +452,9 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, * @returns {$.Deferred} */ read_ids: function (ids, fields, options) { + if (_.isEmpty(ids)) + return $.Deferred().resolve([]); + options = options || {}; // TODO: reorder results to match ids list return this._model.call('read', @@ -743,7 +751,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend({ }); }, get_domain: function (other_domain) { - this._model.domain(other_domain); + return this._model.domain(other_domain); }, alter_ids: function (ids) { this._super(ids); @@ -780,6 +788,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ this._super.apply(this, arguments); this.reset_ids([]); this.last_default_get = {}; + this.running_reads = []; }, default_get: function(fields, options) { var self = this; @@ -846,6 +855,9 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ 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; @@ -861,10 +873,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ to_get.push(id); } }); - var completion = $.Deferred(); var return_records = function() { var records = _.map(ids, function(id) { - return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": 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)) { @@ -896,10 +908,20 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ }, 0); }); } - completion.resolve(records); + return $.when(records); }; if(to_get.length > 0) { - var rpc_promise = this._super(to_get, fields, options).done(function(records) { + 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;}); @@ -910,13 +932,11 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ cached.values = _.defaults(_.clone(cached.values), record); } }); - return_records(); + return return_records(); }); - $.when(rpc_promise).fail(function() {completion.reject();}); } else { - return_records(); + return return_records(); } - return completion.promise(); }, /** * Invalidates caching of a record in the dataset to ensure the next read @@ -928,6 +948,15 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ * @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<len; ++i) { var record = this.cache[i]; // if record we call the button upon is in the cache @@ -1090,6 +1119,157 @@ instance.web.DropMisordered = instance.web.Class.extend({ } }); +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(); + }, +}); + })(); // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: |
