[REF] web: data.js: refactoring of BufferedDataSet (less code and speed up)

This commit is contained in:
Christophe Matthieu
2015-08-27 15:01:09 +02:00
parent a102a9f3fc
commit 0592305126
3 changed files with 118 additions and 192 deletions
+70 -142
View File
@@ -739,155 +739,93 @@ var BufferedDataSet = DataSetStatic.extend({
self.last_default_get = res;
});
},
get_cache: function (id) {
if (!this.cache[id]) {
this.cache[id] = {
'id': id,
'values': {},
'from_read': {},
'changes': {},
'readonly_fields': {},
'to_create': false,
'to_delete': false};
}
return this.cache[id];
},
_update_cache: function (id, options) {
// One should call this method after modifying this.from_read,
// this.to_create or this.to_write. It updates this.cache and
// this.from_readonly.
var cached = _.detect(this.cache, function(x) {return x.id === id;});
if (!cached) {
this.cache.push(cached = {id: id, values: {}});
}
var record = _.detect(this.from_read, function(x) {return x.id === id;});
if (record) {
cached.values = _.clone(record.values);
} else if (record = _.detect(this.to_create, function(x) {return x.id === id;})) {
cached.values = _.clone(record.values);
cached.values = _.extend(cached.values, record.defaults);
} else {
record = {};
}
var record_to_write = _.detect(this.to_write, function(x) {return x.id === id;});
if (record_to_write) {
_.extend(cached.values, record_to_write.values);
}
var record_from_readonly = _.detect(this.from_readonly, function(x) {return x.id === id;});
if (options && options.readonly_fields) {
if (!record_from_readonly) {
this.from_readonly.push(record_from_readonly = {id: id, values: {}});
}
_.extend(record_from_readonly.values, options.readonly_fields);
}
if (record_from_readonly) {
_.extend(cached.values, record_from_readonly.values);
// this.to_create or this.change. It updates this.cache and
// this.readonly_fields.
var cached = this.get_cache(id);
if (options) {
_.extend(cached.from_read, options.from_read);
_.extend(cached.changes, options.changes);
_.extend(cached.readonly_fields, options.readonly_fields);
if (options.to_create !== undefined) cached.to_create = options.to_create;
if (options.to_delete !== undefined) cached.to_delete = options.to_delete;
}
cached.values = _.extend({'id': id}, cached.from_read, cached.changes, cached.readonly_fields);
return cached;
},
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(_.clone(cached));
this._update_cache(cached.id, options);
return $.Deferred().resolve(cached.id).promise();
create: function(data, options) {
var changes = _.extend({}, this.last_default_get, data);
var cached = this._update_cache(_.uniqueId(this.virtual_id_prefix), _.extend({'changes': changes, 'to_create': true}, options));
this.trigger("dataset_changed", data, options);
return $.Deferred().resolve(cached.id);
},
write: function (id, data, options) {
var self = this;
var cached = this._update_cache(id, options);
var cached = this.get_cache(id);
// read missing field to create the to_write diff
var def_read = $.Deferred();
this.mutex.def.then(function () {
if (typeof id !== "number" && id.indexOf(self.virtual_id_prefix) === 0) {
return def_read.resolve();
}
var data_fields = _.keys(data);
var record_fields = _.keys((_.detect(self.from_read, function(x) {return x.id === id;}) || {}).values);
var fields = _.union(data_fields, record_fields);
if (fields.length !== data_fields.length || fields.length !== record_fields.length) {
self.read_ids(self.ids, fields).always(function () {
def_read.resolve();
});
} else {
def_read.resolve();
}
});
// if update after a remove, it's like an add before updating
cached.to_delete = false;
// apply change
var def = $.Deferred();
def_read.then(function () {
self.mutex.exec(function () {
var dirty = false;
var record_to_create = _.detect(self.to_create, function(x) {return x.id === id;});
if (record_to_create) {
// update record in self.to_create
_.each(data, function (v, k) {
if (!_.isEqual(v, cached.values[k])) {
record_to_create.values[k] = v;
dirty = true;
} else {
delete data[k];
}
});
this.mutex.exec(function () {
var dirty = false;
_.each(data, function (v, k) {
if (!_.isEqual(v, cached.values[k])) {
dirty = true;
if (_.isEqual(v, cached.from_read[k])) { // clean changes
delete cached.changes[k];
} else {
cached.changes[k] = v;
}
} else {
// check if the record has changed, and update self.to_write accordingly
var record_to_write = _.detect(self.to_write, function(x) {return x.id === id;});
if (!record_to_write) {
self.to_write.push(record_to_write = {id: id, values: {}});
}
var record_from_read = _.detect(self.from_read, function(x) {return x.id === id;});
if (!record_from_read) {
record_from_read = {values: {}};
}
_.each(data, function (v, k) {
if (!_.isEqual(v, cached.values[k])) {
dirty = true;
if (_.isEqual(v, record_from_read.values[k])) { // clean changes
delete record_to_write.values[k];
} else {
record_to_write.values[k] = v;
}
} else {
delete data[k];
}
});
if (_.isEmpty(record_to_write.values)) {
self.to_write.splice(self.to_write.indexOf(record_to_write), 1);
}
delete data[k];
}
self._update_cache(id, options);
if (dirty) {
self.trigger("dataset_changed", id, data, options);
}
return def.resolve(data).promise();
});
self._update_cache(id, options);
if (dirty) {
self.trigger("dataset_changed", id, data, options);
}
return def.resolve(data).promise();
});
return def;
},
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});
}
_.each(ids, function (id) {
self.get_cache(id).to_delete = true;
});
this.from_read = _.reject(this.from_read, function(x) { return _.include(ids, x.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.set_ids(_.without(this.ids, _.pluck(_.filter(this.cache, function (c) {return c.to_delete;}), 'id')));
this.trigger("dataset_changed", ids, callback, error_callback);
return $.async_when({result: true}).done(callback);
},
reset_ids: function(ids, options) {
this.set_ids(ids);
this.to_delete = [];
this.to_create = [];
this.to_write = [];
if (!options || !options.keep_read_data) {
this.from_read = [];
this.from_readonly = [];
this.cache = {};
} else {
_.each(this.cache, function (cache) {
cache.changes = {};
cache.to_delete = false;
});
}
this.cache = [];
this.delete_all = false;
_.each(_.clone(this.running_reads), function(el) {
el.reject();
@@ -897,21 +835,15 @@ var BufferedDataSet = DataSetStatic.extend({
// read what is necessary from the server to have ids and the given
// fields in this.from_read
var self = this;
var to_get = [];
options = options || {};
_.each(ids, function(id) {
if (typeof id === 'number' || id.indexOf(self.virtual_id_prefix) === -1) {
var record = _.detect(self.from_read, function(x) {return x.id === id;});
if (!record || !_.all(fields, function(x) {return record.values[x] !== undefined;})) {
to_get.push(id);
}
}
var to_get = _.filter(ids, function(id) {
var cache = self.get_cache(id);
return !cache.to_create && _.any(fields, function(x) {return cache.from_read[x] === undefined;});
});
options = options || {};
var return_records = function() {
var records = _.map(ids, function(id) {
var cache = _.find(self.cache, function(cache) {return cache.id === id;});
return _.extend({}, cache && cache.values, {"id": id}, options.readonly_fields || {});
return self.get_cache(id).values;
});
if (self.debug_mode) {
if (_.include(records, undefined)) {
@@ -970,13 +902,7 @@ var BufferedDataSet = DataSetStatic.extend({
_.each(records, function(record, index) {
// add information into from_read
var id = to_get[index];
var record_from_read = _.detect(self.from_read, function(x) {return x.id === id;});
if (record_from_read) {
_.extend(record_from_read.values, record);
} else {
self.from_read.push({id: id, values: record});
}
self._update_cache(id, options);
self._update_cache(id, _.extend(options, {'from_read': record}));
});
return return_records();
});
@@ -997,10 +923,9 @@ var BufferedDataSet = DataSetStatic.extend({
// 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
// for new and altered records is both in the cache and in the change
// or to_create collection)
this.from_read = _.reject(this.from_read, function(x) { return x.id === id;});
this._update_cache(id);
this._update_cache(id, {'from_read': {}});
},
call_button: function (method, args) {
this.evict_record(args[0][0]);
@@ -1011,8 +936,11 @@ var BufferedDataSet = DataSetStatic.extend({
return this._super(id, signal);
},
alter_ids: function(n_ids) {
var dirty = !_.isEqual(this.ids, n_ids);
this._super(n_ids);
this.trigger("dataset_changed", n_ids);
if (dirty) {
this.trigger("dataset_changed", n_ids);
}
},
});
@@ -442,19 +442,20 @@ var AbstractManyField = common.AbstractField.extend({
this.set('value', []);
this.starting_ids = [];
this.mutex = new utils.Mutex();
this.has_not_committed_changes = false;
this.view.on("load_record", this, this._on_load_record);
this.dataset.on('dataset_changed', this, function() {
self.has_not_committed_changes = true;
var options = _.clone(_.last(arguments));
if (!_.isObject(options) || _.isArray(options)) {
options = {};
}
// don't trigger changes if all commands are not resolved
// the editable lists change the dataset without call AbstractManyField methods
if (self.mutex.def.state() === "resolved" && !self.internal_dataset_changed) {
self.trigger("change:commands");
if (!self.internal_dataset_changed && !options.internal_dataset_changed) {
self.trigger("change:commands", options);
}
});
this.on("change:commands", this, function (options) {
self.has_not_committed_changes = false;
self._inhibit_on_change_flag = !!(options||{})._inhibit_on_change_flag;
self._inhibit_on_change_flag = !!options._inhibit_on_change_flag;
self.set({'value': self.dataset.ids.slice()});
self._inhibit_on_change_flag = false;
});
@@ -463,9 +464,10 @@ var AbstractManyField = common.AbstractField.extend({
_on_load_record: function (record) {
this.starting_ids = [];
// don't set starting_ids for the new record
if (record.id && (!isNaN(record.id) || record.id.indexOf(this.dataset.virtual_id_prefix) === -1)) {
if (record.id && record[this.name] && (!isNaN(record.id) || record.id.indexOf(this.dataset.virtual_id_prefix) === -1)) {
this.starting_ids = record[this.name].slice();
}
this.trigger("load_record", record);
},
set_value: function(ids) {
@@ -574,6 +576,7 @@ var AbstractManyField = common.AbstractField.extend({
var dataset = this.dataset;
var res = true;
options = options || {};
var internal_options = _.extend({}, options, {'internal_dataset_changed': true});
_.each(command_list, function(command) {
self.mutex.exec(function() {
@@ -582,12 +585,12 @@ var AbstractManyField = common.AbstractField.extend({
case COMMANDS.CREATE:
var data = _.clone(command[2]);
delete data.id;
return dataset.create(data, options).then(function (id) {
return dataset.create(data, internal_options).then(function (id) {
dataset.ids.push(id);
res = id;
});
case COMMANDS.UPDATE:
return dataset.write(id, command[2], options).then(function () {
return dataset.write(id, command[2], internal_options).then(function () {
if (dataset.ids.indexOf(id) === -1) {
dataset.ids.push(id);
res = id;
@@ -599,14 +602,14 @@ var AbstractManyField = common.AbstractField.extend({
return dataset.unlink([id]);
case COMMANDS.LINK_TO:
if (dataset.ids.indexOf(id) === -1) {
return dataset.add_ids([id], options);
return dataset.add_ids([id], internal_options);
}
return;
case COMMANDS.DELETE_ALL:
return dataset.reset_ids([], {keep_read_data: true});
case COMMANDS.REPLACE_WITH:
dataset.ids = [];
return dataset.alter_ids(command[2], options);
return dataset.alter_ids(command[2], internal_options);
default:
throw new Error("send_commands to '"+self.name+"' receive a non command value." +
"\n" + JSON.stringify(command_list));
@@ -631,29 +634,27 @@ var AbstractManyField = common.AbstractField.extend({
replace_with_ids = [],
add_ids = [],
command_list = [],
id, index, alter_order;
id, index, record;
function format_many2one (values) {
values = _.clone(values);
for (var k in values) {
if ((values[k] instanceof Array) && values[k].length === 2 && typeof values[k][0] === "number" && typeof values[k][1] === "string") {
values[k] = values[k][0];
}
}
return values;
}
_.each(this.get('value'), function (id) {
index = starting_ids.indexOf(id);
if (index !== -1) {
starting_ids.splice(index, 1);
}
if (alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;})) {
command_list.push(COMMANDS.create(format_many2one(alter_order.values)));
return;
}
if (alter_order = _.detect(self.dataset.to_write, function(x) {return x.id === id;})) {
command_list.push(COMMANDS.update(alter_order.id, format_many2one(alter_order.values)));
var record = self.dataset.get_cache(id);
if (!_.isEmpty(record.changes)) {
var values = _.clone(record.changes);
// format many2one values
for (var k in values) {
if ((values[k] instanceof Array) && values[k].length === 2 && typeof values[k][0] === "number" && typeof values[k][1] === "string") {
values[k] = values[k][0];
}
}
if (record.to_create) {
command_list.push(COMMANDS.create(values));
} else {
command_list.push(COMMANDS.update(record.id, values));
}
return;
}
if (!is_one2many || self.dataset.delete_all) {
@@ -683,7 +684,7 @@ var AbstractManyField = common.AbstractField.extend({
},
is_valid: function () {
return this.mutex.def.state() === "resolved" && !this.has_not_committed_changes && this._super();
return this.mutex.def.state() === "resolved" && this._super();
},
is_false: function() {
@@ -853,9 +854,7 @@ var FieldX2Many = AbstractManyField.extend({
if (view && view.type === "list" && view.controller.__focus) {
var def = $.Deferred();
view.controller._on_blur_one2many(true).always(function () {
setTimeout(function () {def.resolve();},0);
}, function () {
setTimeout(function () {def.reject();},0);
def.resolve();
});
return def;
}
@@ -887,17 +886,7 @@ var X2ManyDataSet = data.BufferedDataSet.extend({
self.context.add(context);
});
return this.context;
},
create: function(data, options) {
var self = this;
var def = this._super(data, options);
def.then(function (id) {
setTimeout(function () {
self.trigger("dataset_changed", id, data, options);
},0);
});
return def;
},
}
});
var X2ManyViewManager = ViewManager.extend({
@@ -1046,6 +1035,7 @@ var X2ManyList = ListView.List.extend({
var One2ManyListView = X2ManyListView.extend({
_template: 'One2Many.listview',
init: function (parent, dataset, view_id, options) {
var self = this;
this._super(parent, dataset, view_id, _.extend(options || {}, {
GroupsType: One2ManyGroups,
ListType: X2ManyList
@@ -1065,6 +1055,13 @@ var One2ManyListView = X2ManyListView.extend({
}
};
$(document).on('mousedown', this._mousedown_blur_line);
this.dataset.on('dataset_changed', this, function () {
self._dataset_changed = true;
});
this.dataset.x2m.on('load_record', this, function () {
self._dataset_changed = false;
});
},
do_add_record: function () {
if (this.editable()) {
@@ -1130,10 +1127,10 @@ var One2ManyListView = X2ManyListView.extend({
return $.when();
}).done(function () {
var ds = self.x2m.dataset;
var cached_records = _.any([ds.to_create, ds.to_delete, ds.to_write], function(value) {
return value.length;
var changed_records = _.find(ds.cache, function(record) {
return record.to_create || record.to_delete || !_.isEmpty(record.changes);
});
if (!self.x2m.options.reload_on_button && !cached_records) {
if (!self.x2m.options.reload_on_button && !changed_records) {
self.handle_button(name, id, callback);
}else {
self.handle_button(name, id, function(){
@@ -1160,6 +1157,7 @@ var One2ManyListView = X2ManyListView.extend({
},
_on_focus_one2many: function () {
this.dataset.x2m.internal_dataset_changed = true;
this._dataset_changed = false;
this.__focus = true;
},
_on_blur_one2many: function (force) {
@@ -1180,11 +1178,12 @@ var One2ManyListView = X2ManyListView.extend({
def = this.cancel_edition();
}
def.then(function () {
if (self.dataset.x2m.has_not_committed_changes) {
def.then(function () {
if (self._dataset_changed) {
self.dataset.trigger('dataset_changed');
}
});
});
return def;
},
_after_edit: function () {
@@ -617,7 +617,6 @@ var FormView = View.extend(common.FieldManagerMixin, {
});
});
var args = _.toArray(arguments);
return mutex.def.then(function () { return self.onchanges_mutex.def; }).then(function() {
var save_obj = self.save_list.pop();
if (save_obj) {