diff --git a/js/model.js b/js/model.js index 413f614320..5a2ba9af04 100644 --- a/js/model.js +++ b/js/model.js @@ -48,9 +48,86 @@ Sao.Model = Class(Object, { Sao.Group = function(model, context, array) { array.model = model; array.context = context; + array.parent = undefined; + array.parent_name = ''; + array.child_name = ''; + array.parent_datetime_field = undefined; + array.record_removed = []; + array.record_deleted = []; array.forEach(function(e, i, a) { e.group = a; }); + array.load = function(ids) { + var new_records = []; + for (var i = 0, len = ids.length; i < len; i++) { + var id = ids[i]; + var new_record = this.get(id); + if (!new_record) { + new_record = new Sao.Record(this.model, id); + new_record.group = this; + this.push(new_record); + } + new_records.push(new_record); + } + // Remove previously removed or deleted records + var record_removed = []; + for (var i = 0, len = this.record_removed.length; i < len; i++) { + var record = this.record_removed[i]; + if (ids.indexOf(record.id) < 0) { + record_removed.push(record); + } + } + this.record_removed = record_removed; + var record_deleted = []; + for (var i = 0, len = this.record_deleted.length; i < len; i++) { + var record = thos.record_deleted[i]; + if (ids.indexOf(record.id) < 0) { + record_deleted.push(record); + } + } + this.record_deleted = record_deleted; + }; + array.get = function(id) { + // TODO optimize + for (var i = 0, len = this.length; i < len; i++) { + var record = this[i]; + if (record.id == id) { + return record; + } + } + }; + array.new = function(default_, id) { + var record = Sao.Record(this.name, id); + record.model = this.model; + record.group = this; + if (default_) { + record.default_get(); + } + return record; + }; + array.add = function(record, position) { + if (position == undefined) { + position = -1; + } + if (record.group != this) { + record.group = group; + } + this.splice(position, 0, record); + for (var record_rm in this.record_removed) { + if (record_rm.id == record.id) { + this.record_removed.splice( + this.record_removed.indexOf(record_rm), 1); + } + } + for (var record_del in this.record_deleted) { + if (record_del.id == record.id) { + this.record_deleted.splice( + this.record_deleted.indexOf(record_del), 1); + } + } + record.changed['id'] = true; + return record; + }; return array; }; @@ -69,34 +146,21 @@ Sao.Record = Class(Object, { has_changed: function() { return !jQuery.isEmptyObject(this._changed); }, - _get_values: function(fields) { - if (!fields) { - fields = Object.keys(this._values); - } - var values = {}; - for (var i = 0, len = fields.length; i < len; i++) { - var name = fields[i]; - var field = this.model.fields[name]; - values[name] = field.get(this); - } - return values; - }, save: function() { var context = this.get_context(); var prm, values; if (this.id < 0) { - values = this._get_values(); + values = this.get(); prm = this.model.execute('create', [values], context); var created = function(id) { this.id = id; }; prm.done(created.bind(this)); } else { - if (!this.has_changed()) { - return jQuery.when(); + values = this.get(true); + if (!jQuery.isEmptyObject(values)) { + prm = this.model.execute('write', [this.id, values], context); } - values = this._get_values(Object.keys(this._changed)); - prm = this.model.execute('write', [this.id, values], context); } prm.done(this.reload.bind(this)); return prm; @@ -224,6 +288,23 @@ Sao.Record = Class(Object, { this._loaded[name] = true; } }, + get: function() { + var value = {}; + for (var name in this.model.fields) { + if (!this.model.fields.hasOwnProperty(name)) { + continue; + } + var field = this.model.fields[name]; + if (field.description.readonly) { + continue; + } + if ((this._changed[name] == undefined) && this.id >= 0) { + continue; + } + value[name] = field.get(this); + } + return value; + }, get_context: function() { return this.group.context; }, @@ -238,6 +319,37 @@ Sao.Record = Class(Object, { }, field_set_client: function(name) { this.model.fields[name].set_client(this, value); + }, + default_get: function() { + var prm; + if (!jQuery.isEmptyObject(this.model.fields)) { + prm = this.model.execute('default_get', + [Object.keys(this.model.fields)], this.get_context()); + var force_parent = function(values) { + // TODO + return values; + }; + prm = prm.pipe(force_parent).done(this.set_default.bind(this)); + } else { + prm = jQuery.when(); + } + // TODO autocomplete + return prm; + }, + set_default: function(values) { + for (var fname in values) { + if (!values.hasOwnProperty(fname)) { + continue; + } + var value = values[fname]; + if (!(fname in this.model.fields)) { + continue; + } + // TODO rec_name + this.model.fields[fname].set_default(this, value); + this._loaded[fname] = true; + } + // TODO validate } }); @@ -246,6 +358,32 @@ Sao.field = {}; Sao.field.get = function(type) { switch (type) { + case 'char': + return Sao.field.Char; + case 'selection': + return Sao.field.Selection; + case 'datetime': + return Sao.field.DateTime; + case 'date': + return Sao.field.Date; + case 'time': + return Sao.field.Time; + case 'float': + return Sao.field.Float; + case 'numeric': + return Sao.field.Numeric; + case 'integer': + return Sao.field.Integer; + case 'boolean': + return Sao.field.Boolean; + case 'many2one': + return Sao.field.Many2One; + case 'one2one': + return Sao.field.One2One; + case 'one2many': + return Sao.field.One2Many; + case 'many2many': + return Sao.field.Many2Many; default: return Sao.field.Char; } @@ -274,11 +412,387 @@ Sao.field.Field = Class(Object, { get_client: function(record) { return this.get(record); }, + set_default: function(record, value) { + record._values[this.name] = value; + record._changed[this.name] = true; + }, changed: function(record) { // TODO + }, + get_context: function(record) { + var context = jQuery.extend({}, record.get_context()); + if (record.parent) { + jQuery.extend(context, record.parent.get_context()); + } + // TODO eval context attribute + return context; } }); Sao.field.Char = Class(Sao.field.Field, { _default: '' }); + +Sao.field.Selection = Class(Sao.field.Field, { + _default: null, + get_client: function(record) { + return record._values[this.name]; + } +}); + +Sao.field.DateTime = Class(Sao.field.Field, { + _default: null +}); + +Sao.field.Date = Class(Sao.field.Field, { + _default: null +}); + +Sao.field.Time = Class(Sao.field.Field, { +}); + +Sao.field.Number = Class(Sao.field.Field, { + _default: null, + get: function(record) { + if (record._values[this.name] == undefined) { + return this._default; + } else { + return record._values[this.name]; + } + }, + digits: function(record) { + var default_ = [16, 2]; + // TODO + return default_; + } +}); + +Sao.field.Float = Class(Sao.field.Number, { + set_client: function(record, value) { + if (typeof value == 'string') { + var value = Number(value); // without new for type conversion + if (value == NaN) { + value = this._default; + } + } + Sao.field.Float._super.set_client.call(this, record, value); + }, + get_client: function(record) { + var value = record._values[this.name]; + if (value != undefined) { + var digits = this.digits(record); + return value.toFixed(digits[1]); + } else { + return ''; + } + } +}); + +Sao.field.Numeric = Class(Sao.field.Number, { + set_client: function(record, value) { + if (typeof value == 'string') { + var value = new Number(value); // with new to get an instance + if (value.valueOf() == NaN) { + value = this._default; + } + } + Sao.field.Float._super.set_client.call(this, record, value); + }, + get_client: function(record) { + var value = record._values[this.name]; + if (value != undefined) { + var digits = this.digits(record); + return value.toFixed(digits[1]); + } else { + return ''; + } + } +}); + +Sao.field.Integer = Class(Sao.field.Number, { + set_client: function(record, value) { + if (typeof value == 'string') { + value = parseInt(value); + if (value == NaN) { + value = this._default; + } + } + Sao.field.Integer._super.set_client.call(this, record, value); + }, + get_client: function(record) { + var value = record._values[this.name]; + if (value != undefined) { + return '' + value; + } else { + return ''; + } + }, + digits: function(record) { + return [16, 0]; + } +}); + +Sao.field.Boolean = Class(Sao.field.Field, { + _default: false, + set_client: function(record, value) { + value = Boolean(value); + Sao.field.Boolean._super.set_client.call(this, record, value); + }, + get: function(record) { + return Boolean(record._values[this.name]); + }, + get_client: function(record) { + return Boolean(record._values[this.name]); + } +}); + +Sao.field.Many2One = Class(Sao.field.Field, { + _default: null, + get: function(record) { + var value = record._values[this.name]; + // TODO force parent + return value; + }, + get_client: function(record) { + var rec_name = record._values[this.name + '.rec_name']; + if (rec_name == undefined) { + this.set(record, this.get(record)); + rec_name = record._values[this.name + '.rec_name'] || ''; + } + return rec_name; + }, + set: function(record, value) { + var rec_name = record._values[this.name + '.rec_name'] || ''; + // TODO force parent + var store_rec_name = function(rec_name) { + }; + if (!rec_name && (value >= 0) && (value != null)) { + var prm = record.model.execute('read', [[value], ['rec_name']], + record.get_context()); + prm.done(store_rec_name.bind(this)); + } else { + store_rec_name(rec_name); + } + record._values[this.name] = value; + // TODO force parent + }, + set_client: function(record, value) { + var rec_name; + if (value instanceof Array) { + rec_name = value[1]; + value = value[0]; + } else { + if (value == this.get(record)) { + rec_name = record._values[this.name + '.rec_name'] || ''; + } else { + rec_name = ''; + } + } + record._values[this.name + '.rec_name'] = rec_name; + Sao.field.Many2One._super.set_client.call(this, record, value); + } +}); + +Sao.field.One2One = Class(Sao.field.Many2One, { +}); + +Sao.field.One2Many = Class(Sao.field.Field, { + init: function(description) { + Sao.field.One2Many._super.init.call(this, description); + this.context = {}; + }, + _default: null, + set: function(record, value) { + var group = record._values[this.name]; + var fields = {}; + if (group != undefined) { + fields = jQuery.extend({}, group.model.fields); + // TODO destroy and unconnect + } else if (record.model.name == this.description.relation) { + fields = record.model.fields; + } + group = Sao.Group(new Sao.Model(this.description.relation), + this.context, []); + group.parent = record; + group.parent_name = this.description.relation_field; + group.child_name = this.name; + group.model.fields = fields; + record._values[this.name] = group; + group.load(value); + }, + get: function(record) { + var group = record._values[this.name]; + if (group == undefined) { + return []; + } + var record_removed = group.record_removed; + var record_deleted = group.record_deleted; + var result = [['add', []]]; + var parent_name = this.description.relation_field || ''; + for (var i = 0, len = group.length; i < len; i++) { + var record2 = group[i]; + if ((record_removed.indexOf(record2) >= 0) || + (record_deleted.indexOf(record2) >= 0)) { + continue; + } + if (record2.id >= 0) { + var values = record2.get(); + delete values[parent_name]; + if (record2.has_changed() && !jQuery.isEmptyObject(values)) { + result.push(['write', [record2.id], values]); + } + result[0][1].push(record2.id); + } else { + var values = record2.get(); + delete values[parent_name]; + result.push(['create', values]); + } + } + if (jQuery.isEmptyObject(result[0][1])) { + result.shift(); + } + if (!jQuery.isEmptyObject(record_removed)) { + result.push(['unlink', record_removed.map(function(r) { + return r.id; + })]); + } + if (!jQuery.isEmptyObject(record_deleted)) { + result.push(['delete', record_deleted.map(function(r) { + return r.id; + })]); + } + return result; + }, + set_client: function(record, value) { + }, + get_client: function(record) { + this._set_default_value(record); + return record._values[this.name]; + }, + set_default: function(record, value) { + // value is a list of id + if ((value instanceof Array) && !isNaN(parseInt(value[0]))) { + this.set(record, value); + record._changed[this.name] = true; + return; + } + var group = record._values[this.name]; + var fields = {}; + if (group != undefined) { + fields = jQuery.extend({}, group.mode.fields); + // TODO destroy and unconnect + } else if (record.model.name == this.description.relation) { + fields = record.model.fields; + } + if (!jQuery.isEmptyObject(fields)) { + for (var name in fields) { + if (fields.hasOwnProperty(name)) { + fields[name] = fields[name].description; + } + } + } + var prm = jQuery.when(); + prm.pipe(function() { + return fields; + }); + if (!jQuery.isEmptyObject(value)) { + var context = this.get_context(); + var field_names = {}; + for (var val in value) { + if (!value.hasOwnProperty(val)) { + continue; + } + for (var fieldname in val) { + if (!val.hasOwnProperty(fieldname)) { + continue; + } + field_names[fieldname] = true; + } + } + if (!jQuery.isEmptyObject(field_names)) { + var args = { + 'method': 'model.' + this.description.relation + + '.fields_get', + 'params': [Object.keys(field_names), context] + }; + prm = Sao.rpc(args, record.model.session); + prm.pipe(function(new_fields) { + return jQuery.extend(fields, new_fields); + }); + } + } + var set_value = function(fields) { + var group = Sao.Group(new Sao.Model(this.description.relation), + this.context, []); + group.parent = record; + group.parent_name = this.description.relation_field; + group.child_name = this.name; + group.model.add_fields(fields); + if (record._values[this.name] != undefined) { + for (var i = 0, len = record._values[this.name].length; i < len; + i++) { + var r = record._values[this.name][i]; + if (r.id >= 0) { + group.record_deleted.push(r); + } + } + jQuery.extend(group.record_deleted, + record._values[this.name].record_deleted); + jQuery.extend(group.record_removed, + record._values[this.name].record_removed); + } + record._values[this.name] = group; + for (var val in value) { + if (!value.hasOwnProperty(val)) { + continue; + } + var new_record = group.new(false); + new_record.set_default(val); + group.add(new_record); + } + }; + prm.done(set_value.bind(this)); + }, + _set_default_value: function(record) { + if (record._values[this.name] != undefined) { + return; + } + var group = Sao.Group(new Sao.Model(this.description.relation), + this.context, []); + group.parent = record; + group.parent_name = this.description.relation_field; + group.child_name = this.name; + if (record.model.name == this.description.relation) { + group.fields = record.model.fields; + } + record._values[this.name] = group; + } +}); + +Sao.field.Many2Many = Class(Sao.field.One2Many, { + set: function(record, value) { + var group = record._values[this.name]; + var fields = {}; + if (group != undefined) { + fields = jQuery.extend({}, group.model.fields); + // TODO destroy and unconnect + } else if (record.model.name == this.description.relation) { + fields = record.model.fields; + } + group = Sao.Group(new Sao.Model(this.description.relation), + this.context, []); + group.parent = record; + group.parent_name = this.description.relation_field; + group.child_name = this.name; + group.model.fields = fields; + if (record._values[this.name] != undefined) { + jQuery.extend(group.record_removed, record._values[this.name]); + jQuery.extend(group.record_deleted, + record._values[this.name].record_deleted); + jQuery.extend(group.record_removed, + record._values[this.name].record_removed); + } + record._values[this.name] = group; + group.load(value); + } +}); diff --git a/js/screen.js b/js/screen.js index 1acd330d1d..58728fa17d 100644 --- a/js/screen.js +++ b/js/screen.js @@ -40,6 +40,18 @@ Sao.Screen = Class(Object, { var fields = view.fields; var xml_view = jQuery(jQuery.parseXML(arch)); // TODO loading lazy/eager + var loading = 'eager'; + if (xml_view.children().prop('tagName') == 'form') { + loading = 'lazy'; + } + for (var field in fields) { + if (!(field in this.model.fields) || loading == 'eager') { + fields[field]['loading'] = loading; + } else { + fields[field]['loading'] = this.model.fields[field] + .description.loading; + } + } this.model.add_fields(fields); var view = Sao.View.parse(this, xml_view, view.field_childs); this.views.push(view); diff --git a/js/view.js b/js/view.js index 567da62fdf..278e4c184e 100644 --- a/js/view.js +++ b/js/view.js @@ -183,12 +183,8 @@ Sao.View.Tree.Row = Class(Object, { var children_field = this.children_field; console.log('Load children of ' + this.path); record.load(this.children_field).done(function() { - var children = record.field_get(children_field); - var subgroup = Sao.Group(record.model, record.group.context, - children.map(function(id) { - return new Sao.Record(record.model, id); - })); - subgroup.forEach(add_row); + var children = record.field_get_client(children_field); + children.forEach(add_row); }); } }, diff --git a/tests/sao.js b/tests/sao.js index 71f1282890..afeb235188 100644 --- a/tests/sao.js +++ b/tests/sao.js @@ -42,6 +42,9 @@ QUnit.test('CRUD', function() { user.model.fields['name'].set_client(user, 'Test'); user.model.fields['login'].set_client(user, 'test'); }); + prm = prm.pipe(function() { + return user.default_get(); + }); prm = prm.pipe(function() { return user.save(); }); @@ -90,7 +93,7 @@ QUnit.test('CRUD', function() { }; QUnit.stop(); - QUnit.expect(6); + QUnit.expect(11); var prm = Sao.rpc({ 'method': 'common.db.create', 'params': [SaoTest.dbname, SaoTest.password,