diff options
| author | Kunal Chavda (OpenERP) <kch@tinyerp.com> | 2011-10-17 16:07:40 +0530 |
|---|---|---|
| committer | Kunal Chavda (OpenERP) <kch@tinyerp.com> | 2011-10-17 16:07:40 +0530 |
| commit | f76bdfb1fa4a2de837eacf619a211c805d23a226 (patch) | |
| tree | 4bdcb2ef8e8b8e199b11574da215576f9ee29eef /addons/web/static/src | |
| parent | c5f5637e0992158734adda1782b725e24546dc6e (diff) | |
| parent | f1f48e69f0e654487234ced0537a03dd46fb3e12 (diff) | |
[MERGE]merge main view editor branch upto 884 revision.
bzr revid: kch@tinyerp.com-20111017103740-v7lrybz3gkq2ujxa
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/base.css | 20 | ||||
| -rw-r--r-- | addons/web/static/src/img/box-a-header-a.gif | bin | 0 -> 91 bytes | |||
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 75 | ||||
| -rw-r--r-- | addons/web/static/src/js/core.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/data.js | 172 | ||||
| -rw-r--r-- | addons/web/static/src/js/formats.js | 15 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 71 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_editor.js | 575 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 504 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 87 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 36 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_tree.js | 42 | ||||
| -rw-r--r-- | addons/web/static/src/js/views.js | 272 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 83 |
14 files changed, 1190 insertions, 776 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 7f5b7f5f41b..a814ddbba1b 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -300,7 +300,7 @@ label.error { text-shadow: 0 1px 0 #333; text-align: center; font-size: 18px; - line-height: 14px; + line-height: 18px; right: 0; } .openerp .secondary_menu.oe_folded .oe_toggle_secondary_menu { @@ -325,6 +325,7 @@ label.error { background: #5A5858; vertical-align: top; height: 100%; + display: block; position: relative; } .openerp .secondary_menu.oe_folded { @@ -721,11 +722,15 @@ label.error { .openerp .oe-listview .oe-field-cell { cursor: pointer; } +.openerp .oe-listview .oe-field-cell progress { + width: 100%; +} .openerp .oe-listview .oe-field-cell button { + margin: 0; padding: 0; border: none; background: none; - width: 100%; + width: 16px; } .openerp .oe-listview .oe-field-cell button:active { opacity: 0.5; @@ -767,6 +772,10 @@ label.error { .openerp .oe-listview .oe-list-footer span { margin: 0 1em; } +.openerp .oe-listview .oe-list-footer progress { + vertical-align:-10% !important; + width: 100%; +} /** list rounded corners @@ -828,6 +837,9 @@ label.error { } /* Form */ +.openerp .oe_frame.oe_forms { + clear: both; +} .openerp table.oe_frame td { color: #4c4c4c; } @@ -855,7 +867,7 @@ label.error { cursor: help; } -.openerp .oe_form_field label.oe_label, .openerp .oe_form_field label.oe_label_help { +.openerp .oe_forms label.oe_label, .openerp .oe_forms label.oe_label_help { text-align: right; margin: 3px 0 0 10px; } @@ -943,7 +955,7 @@ label.error { position: relative; } .openerp input.oe-binary-file { - z-index: 2; + z-index: 0; line-height: 0; font-size: 50px; position: absolute; diff --git a/addons/web/static/src/img/box-a-header-a.gif b/addons/web/static/src/img/box-a-header-a.gif Binary files differnew file mode 100644 index 00000000000..6b461b42bfe --- /dev/null +++ b/addons/web/static/src/img/box-a-header-a.gif diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 1955298ba27..69c9466a9ce 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -5,15 +5,16 @@ openerp.web.chrome = function(openerp) { var QWeb = openerp.web.qweb; openerp.web.Notification = openerp.web.Widget.extend(/** @lends openerp.web.Notification# */{ - /** - * @constructs openerp.web.Notification - * @extends openerp.web.Widget - * - * @param parent - * @param element_id - */ - init: function(parent, element_id) { - this._super(parent, element_id); + template: 'Notification', + identifier_prefix: 'notification-', + + init: function() { + this._super.apply(this, arguments); + openerp.notification = this; + }, + + start: function() { + this._super.apply(this, arguments); this.$element.notify({ speed: 500, expires: 1500 @@ -28,9 +29,12 @@ openerp.web.Notification = openerp.web.Widget.extend(/** @lends openerp.web.Not warn: function(title, text) { this.$element.notify('create', 'oe_notification_alert', { title: title, - text: text + text: text, + }, { + expires: false, }); - } + }, + }); openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{ @@ -369,7 +373,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database } $db_list.find(':selected').remove(); self.db_list.splice(_.indexOf(self.db_list, db, true), 1); - self.notification.notify("Dropping database", "The database '" + db + "' has been dropped"); + self.do_notify("Dropping database", "The database '" + db + "' has been dropped"); }); } }); @@ -454,7 +458,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database self.display_error(result); return; } - self.notification.notify("Changed Password", "Password has been changed successfully"); + self.do_notify("Changed Password", "Password has been changed successfully"); }); } }); @@ -462,7 +466,7 @@ openerp.web.Database = openerp.web.Widget.extend(/** @lends openerp.web.Database }); openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ - remember_creditentials: true, + remember_credentials: true, template: "Login", identifier_prefix: 'oe-app-login-', @@ -480,13 +484,22 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ this.selected_db = null; this.selected_login = null; - if (this.has_local_storage && this.remember_creditentials) { + if (this.has_local_storage && this.remember_credentials) { this.selected_db = localStorage.getItem('last_db_login_success'); this.selected_login = localStorage.getItem('last_login_login_success'); if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) { this.selected_password = localStorage.getItem('last_password_login_success'); } } + + var qs = jQuery.deparam(jQuery.param.querystring()); + if (qs.db) { + this.selected_db = qs.db; + } + if (qs.login) { + this.selected_login = qs.login; + } + }, start: function() { var self = this; @@ -537,7 +550,7 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ this.session.session_login(db, login, password, function() { if(self.session.session_is_valid()) { if (self.has_local_storage) { - if(self.remember_creditentials) { + if(self.remember_credentials) { localStorage.setItem('last_db_login_success', db); localStorage.setItem('last_login_login_success', login); if (jQuery.deparam(jQuery.param.querystring()).debug != undefined) { @@ -779,9 +792,8 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{ }, start: function() { this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded'); - this.reload(); }, - reload: function() { + do_reload: function() { this.rpc("/web/menu/load", {}, this.on_loaded); }, on_loaded: function(data) { @@ -823,7 +835,9 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{ this.session.active_id = id; this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded); } - ev.stopPropagation(); + if (ev) { + ev.stopPropagation(); + } return false; }, do_menu_click: function($clicked_menu, manual) { @@ -866,6 +880,7 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{ $sub_menu.hide(); return true; } + return manual; } else { return true; } @@ -927,14 +942,12 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie } this.$element.html(QWeb.render("Interface", params)); + this.notification = new openerp.web.Notification(); this.session = new openerp.web.Session(); this.loading = new openerp.web.Loading(this,"oe_loading"); this.crashmanager = new openerp.web.CrashManager(this); this.crashmanager.start(); - // Do you autorize this ? will be replaced by notify() in controller - openerp.web.Widget.prototype.notification = new openerp.web.Notification(this, "oe_notification"); - this.header = new openerp.web.Header(this); this.login = new openerp.web.Login(this); this.header.on_logout.add(this.login.on_logout); @@ -954,12 +967,27 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie }, start: function() { + this._super.apply(this, arguments); + this.notification.prependTo(this.$element); this.header.appendTo($("#oe_header")); this.session.start(); this.login.appendTo($('#oe_login')); this.menu.start(); }, + do_reload: function() { + this.session.session_restore(); + this.menu.do_reload(); + }, + do_notify: function() { + var n = this.notification; + n.notify.apply(n, arguments); + }, + do_warn: function() { + var n = this.notification; + n.warn.apply(n, arguments); + }, on_logged: function() { + this.menu.do_reload(); if(this.action_manager) this.action_manager.stop(); this.action_manager = new openerp.web.ActionManager(this); @@ -1046,7 +1074,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie this.action_manager.do_action(action); }, do_about: function() { - } + }, + }); }; diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index 9d4d2124008..c3ff924c8e9 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -287,14 +287,12 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry */ get_any: function (keys) { for (var i=0; i<keys.length; ++i) { - try { - return this.get_object(keys[i]); - } catch (e) { - if (e instanceof openerp.web.KeyNotFound) { - continue; - } - throw e; + var key = keys[i]; + if (key === undefined || !(key in this.map)) { + continue; } + + return this.get_object(key); } throw new openerp.web.KeyNotFound(keys.join(',')); }, @@ -480,7 +478,7 @@ openerp.web.Session = openerp.web.CallbackEnabled.extend( /** @lends openerp.web session_login: function(db, login, password, success_callback) { var self = this; var params = { db: db, login: login, password: password }; - this.rpc("/web/session/login", params, function(result) { + return this.rpc("/web/session/login", params, function(result) { self.session_id = result.session_id; self.uid = result.uid; self.user_context = result.context; diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index fcf6a65dcf5..1eaffcd0e30 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -610,40 +610,120 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ on_default_get: function(res) { this.last_default_get = res; }, + /** + * Makes sure this dataset has the fields_get for its model stored locally, + * so create/write methods are able to determine if written fields are m2os + * and can name_get those fields if that's the case (in order to cache a + * correct m2o value for them) + * + * @returns {$.Deferred} whether the fields_get is done executing, provides the fields_get value to its callbacks + */ + ensure_has_fields: function () { + var self = this; + + if (this.fields_get) { + var d = $.Deferred(); + setTimeout(function () { d.resolve(self.fields_get, true); }, 0); + return d.promise(); + } else { + return self.call('fields_get', [], function (fields_get) { + self.fields_get = fields_get; + }); + } + }, + /** + * Relational fields may not be written in the same format they are read. + * + * Since the BufferedDataSet is a bit dumb, it returns what it's got stored + * in its cache, which for modified value is what it was given. This breaks + * some basic contracts of openerp such as m2o read being in the + * ``name_get`` format. + * + * Because create & write are supposed to be asynchronous, though, it + * should be easy to just name_get all incorrect values. + * + * @param {Object} data form-data to write to the cache + * @returns {$.Deferred} resolved with a fixed data dictionary + */ + fix_relational: function (data) { + var self = this; + var results = $.Deferred(); + this.ensure_has_fields().then(function (fields) { + var fields_to_fix = _(fields).chain() + .map(function (d, k) { return {key: k, descriptor: d}; }) + .filter(function (field) { + // keep m2o fields which are in the data dict + return field.descriptor.type === 'many2one' && + data[field.key]; + }).pluck('key') + .value(); + var name_gets = _(fields_to_fix).map(function (field) { + return new openerp.web.DataSet(self, self.fields_get[field].relation) + .name_get([data[field]], null); + }); + // null-concat forces stupid bastard `when` to always return an + // array containing the Array-ified results of all the name_gets. + // otherwise, if there's a single field to fix it returns the + // results of the unique name_get directly. + $.when.apply(null, name_gets.concat([null])).then(function () { + var record = _.extend({}, data); + for(var i=0; i<fields_to_fix.length; ++i) { + // Each argument is [[name_get], success, jqXhr] and we + // want just the name_get pair + record[fields_to_fix[i]] = arguments[i][0][0]; + } + results.resolve(record); + }); + }); + return results; + }, create: function(data, callback, error_callback) { - var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data, - defaults: this.last_default_get}; - this.to_create.push(cached); - this.cache.push(cached); - this.on_change(); + var self = this; var prom = $.Deferred().then(callback); - setTimeout(function() {prom.resolve({result: cached.id});}, 0); + this.fix_relational(data).then(function (fixed_m2o_data) { + var cached = { + id:_.uniqueId(self.virtual_id_prefix), + values: fixed_m2o_data, + defaults: self.last_default_get + }; + self.to_create.push(_.extend(_.clone(cached), { + values: _.clone(data)})); + self.cache.push(cached); + self.on_change(); + prom.resolve({result: cached.id}); + }); return prom.promise(); }, write: function (id, data, options, callback) { 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; + var to_return = $.Deferred().then(callback); + this.fix_relational(data).then(function (fixed_m2o_data) { + var record = _.detect(self.to_create, function(x) {return x.id === id;}); + record = record || _.detect(self.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); } - $.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;}); - $.extend(cached.values, record.values); - if (dirty) - this.on_change(); - var to_return = $.Deferred().then(callback); - to_return.resolve({result: true}); + var cached = _.detect(self.cache, function(x) {return x.id === id;}); + if (!cached) { + cached = {id: id, values: {}}; + this.cache.push(cached); + } + _.extend(cached.values, fixed_m2o_data); + if (dirty) + self.on_change(); + to_return.resolve({result: true}); + }); return to_return.promise(); }, unlink: function(ids, callback, error_callback) { @@ -695,7 +775,7 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ throw "Record not correctly loaded"; } } - setTimeout(function () {completion.resolve(records);}, 0); + completion.resolve(records); }; if(to_get.length > 0) { var rpc_promise = this._super(to_get, fields, function(records) { @@ -720,27 +800,51 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ }); openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/; -openerp.web.ReadOnlyDataSetSearch = openerp.web.DataSetSearch.extend({ +openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({ + init: function() { + this._super.apply(this, arguments); + this.create_function = null; + this.write_function = null; + this.read_function = null; + }, + read_ids: function () { + if (this.read_function) { + return this.read_function.apply(null, arguments); + } else { + return this._super.apply(this, arguments); + } + }, default_get: function(fields, callback) { return this._super(fields, callback).then(this.on_default_get); }, on_default_get: function(result) {}, create: function(data, callback, error_callback) { this.on_create(data); - var to_return = $.Deferred().then(callback); - setTimeout(function () {to_return.resolve({"result": undefined});}, 0); - return to_return.promise(); + if (this.create_function) { + return this.create_function(data, callback, error_callback); + } else { + console.warn("trying to create a record using default proxy dataset behavior"); + var to_return = $.Deferred().then(callback); + setTimeout(function () {to_return.resolve({"result": undefined});}, 0); + return to_return.promise(); + } }, on_create: function(data) {}, write: function (id, data, options, callback) { this.on_write(id, data); - var to_return = $.Deferred().then(callback); - setTimeout(function () {to_return.resolve({"result": true});}, 0); - return to_return.promise(); + if (this.write_function) { + return this.write_function(id, data, options, callback); + } else { + console.warn("trying to write a record using default proxy dataset behavior"); + var to_return = $.Deferred().then(callback); + setTimeout(function () {to_return.resolve({"result": true});}, 0); + return to_return.promise(); + } }, on_write: function(id, data) {}, unlink: function(ids, callback, error_callback) { this.on_unlink(ids); + console.warn("trying to unlink a record using default proxy dataset behavior"); var to_return = $.Deferred().then(callback); setTimeout(function () {to_return.resolve({"result": true});}, 0); return to_return.promise(); diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index e7feedf26a5..94b2c7085db 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -69,6 +69,13 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { } catch (e) { return value.format("%H:%M:%S"); } + case 'selection': + // Each choice is [value, label] + var result = _(descriptor.selection).detect(function (choice) { + return choice[0] === value; + }); + if (result) { return result[1]; } + return; default: return value; } @@ -179,9 +186,13 @@ openerp.web.auto_date_to_str = function(value, type) { * @param {String} [column.string] button label * @param {String} [column.icon] button icon * @param {String} [value_if_empty=''] what to display if the field's value is ``false`` + * @param {Boolean} [process_modifiers=true] should the modifiers be computed ? */ -openerp.web.format_cell = function (row_data, column, value_if_empty) { - var attrs = column.modifiers_for(row_data); +openerp.web.format_cell = function (row_data, column, value_if_empty, process_modifiers) { + var attrs = {}; + if (process_modifiers !== false) { + attrs = column.modifiers_for(row_data); + } if (attrs.invisible) { return ''; } if (column.tag === 'button') { return [ diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index db91d6bfd27..2769a860d2a 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -13,24 +13,35 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search * @param view_id * @param defaults */ - init: function(parent, dataset, view_id, defaults) { + init: function(parent, dataset, view_id, defaults, hidden) { this._super(parent); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; this.defaults = defaults || {}; + this.has_defaults = !_.isEmpty(this.defaults); this.inputs = []; this.enabled_filters = []; this.has_focus = false; + this.hidden = !!hidden; + this.headless = this.hidden && !this.has_defaults; + this.ready = $.Deferred(); }, start: function() { this._super(); - this.rpc("/web/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + if (this.hidden) { + this.$element.hide(); + } + if (this.headless) { + this.ready.resolve(); + } else { + this.rpc("/web/searchview/load", {"model": this.model, "view_id":this.view_id}, this.on_loaded); + } return this.ready.promise(); }, show: function () { @@ -105,8 +116,9 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search */ make_field: function (item, field) { try { - return new (openerp.web.search.fields.get_object(field.type)) - (item, field, this); + return new (openerp.web.search.fields.get_any( + [item.attrs.widget, field.type])) + (item, field, this); } catch (e) { if (! e instanceof openerp.web.KeyNotFound) { throw e; @@ -137,10 +149,7 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search 'defaults': this.defaults }); - // We don't understand why the following commented line does not work in Chrome but - // the non-commented line does. As far as we investigated, only God knows. - //this.$element.html(render); - jQuery(render).appendTo(this.$element); + this.$element.html(render); this.$element.find(".oe_search-view-custom-filter-btn").click(ext.on_activate); var f = this.$element.find('form'); @@ -246,10 +255,13 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search * @param e jQuery event object coming from the "Search" button */ do_search: function (e) { + if (this.headless && !this.has_defaults) { + return this.on_search([], [], []); + } // reset filters management var select = this.$element.find(".oe_search-view-filters-management"); select.val("_filters"); - + if (e && e.preventDefault) { e.preventDefault(); } var data = this.build_search_data(); @@ -324,10 +336,10 @@ openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.Search * @param {Array} errors a never-empty array of error objects */ on_invalid: function (errors) { - this.notification.notify("Invalid Search", "triggered from search view"); + this.do_notify("Invalid Search", "triggered from search view"); }, do_clear: function () { - this.$element.find('.filter_label').removeClass('enabled'); + this.$element.find('.filter_label, .filter_icon').removeClass('enabled'); this.enabled_filters.splice(0); var string = $('a.searchview_group_string'); _.each(string, function(str){ @@ -748,9 +760,44 @@ openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends * @extends openerp.web.search.Field */ openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends openerp.web.search.SelectionField# */{ + // This implementation is a basic <select> field, but it may have to be + // altered to be more in line with the GTK client, which uses a combo box + // (~ jquery.autocomplete): + // * If an option was selected in the list, behave as currently + // * If something which is not in the list was entered (via the text input), + // the default domain should become (`ilike` string_value) but **any + // ``context`` or ``filter_domain`` becomes falsy, idem if ``@operator`` + // is specified. So at least get_domain needs to be quite a bit + // overridden (if there's no @value and there is no filter_domain and + // there is no @operator, return [[name, 'ilike', str_val]] template: 'SearchView.field.selection', + init: function () { + this._super.apply(this, arguments); + // prepend empty option if there is no empty option in the selection list + this.prepend_empty = !_(this.attrs.selection).detect(function (item) { + return !item[1]; + }); + }, get_value: function () { - return this.$element.val(); + var index = parseInt(this.$element.val(), 10); + if (isNaN(index)) { return null; } + var value = this.attrs.selection[index][0]; + if (value === false) { return null; } + return value; + }, + /** + * The selection field needs a default ``false`` value in case none is + * provided, so that selector options with a ``false`` value (convention + * for explicitly empty options) get selected by default rather than the + * first (value-holding) option in the selection. + * + * @param {Object} defaults search default values + */ + render: function (defaults) { + if (!defaults[this.attrs.name]) { + defaults[this.attrs.name] = false; + } + return this._super(defaults); } }); openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @lends openerp.web.search.BooleanField# */{ diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js index 87744825207..774d1872c29 100644 --- a/addons/web/static/src/js/view_editor.js +++ b/addons/web/static/src/js/view_editor.js @@ -32,241 +32,247 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ this.xml_id = 0; this.property = openerp.web.ViewEditor.property_widget; }, - start: function() { this.View_editor(); }, - - View_editor : function(){ - var self = this; - var action = { - name:'ViewEditor', - context:this.session.user_context, - domain: [["model", "=", this.dataset.model]], - res_model : 'ir.ui.view', - views : [[false, 'list']], - type: 'ir.actions.act_window', - target: "current", - limit : 80, - auto_search : true, - flags: { - sidebar: false, - views_switcher: false, - action_buttons:false, - search_view:false, - pager:false, - radio:true - }, - }; - var action_manager = new openerp.web.ActionManager(this); - this.dialog = new openerp.web.Dialog(this,{ - modal: true, - title: 'ViewEditor', - width: 750, - height: 500, - buttons: { - "Create": function(){ - //to do - }, - "Edit": function(){ - self.xml_id = 0 ; - self.get_data(); + View_editor : function() { + var self = this; + var action = { + name:'ViewEditor', + context:this.session.user_context, + domain: [["model", "=", this.dataset.model]], + res_model : 'ir.ui.view', + views : [[false, 'list']], + type: 'ir.actions.act_window', + target: "current", + limit : 80, + auto_search : true, + flags: { + sidebar: false, + views_switcher: false, + action_buttons:false, + search_view:false, + pager:false, + radio:true + }, + }; + var action_manager = new openerp.web.ActionManager(this); + this.view_edit_dialog = new openerp.web.Dialog(this,{ + modal: true, + title: 'ViewEditor', + width: 750, + height: 500, + buttons: { + "Create": function(){ + //to do + }, + "Edit": function(){ + self.xml_id = 0 ; + self.get_data(); + }, + "Close": function(){ + $(this).dialog('destroy'); + } }, - "Close": function(){ - $(this).dialog('destroy'); - } - }, - }); - this.dialog.start(); - this.dialog.open(); - action_manager.appendTo(this.dialog); - action_manager.do_action(action); + }); + this.view_edit_dialog.start(); + this.view_edit_dialog.open(); + action_manager.appendTo(this.view_edit_dialog); + action_manager.do_action(action); }, - - check_attr:function(xml,tag,level){ + check_attr: function(xml, tag, level) { var obj = new Object(); obj.child_id = []; obj.id = this.xml_id++; - obj.level = level; - var vidhin = xml; - var att_list = []; + obj.level = level+1; var render_name = "<" + tag; - var xml_tag = "<" + tag; + obj.att_list = []; + obj.att_list.push(tag); $(xml).each(function() { - att_list = this.attributes; - att_list = _.select(att_list, function(attrs){ - xml_tag += ' ' +attrs.nodeName+'='+'"'+attrs.nodeValue+'"'; - if (tag != 'button'){ - if(attrs.nodeName == "string" || attrs.nodeName == "name" || attrs.nodeName == "index"){ - render_name += ' ' +attrs.nodeName+'='+'"'+attrs.nodeValue+'"';} - }else{ - if(attrs.nodeName == "name"){ - render_name += ' ' +attrs.nodeName+'='+'"'+attrs.nodeValue+'"';} - } - }); - render_name+= ">"; - xml_tag+= ">"; + _.each(this.attributes, function(attrs){ + if (tag != 'button') { + if (attrs.nodeName == "string" || attrs.nodeName == "name" || attrs.nodeName == "index") { + render_name += ' ' + attrs.nodeName + '=' + '"' + attrs.nodeValue + '"' ; + } + } else if (attrs.nodeName == "name") { + render_name += ' ' + attrs.nodeName + '=' + '"' + attrs.nodeValue + '"'; + } + if (attrs.nodeName != "position") { + obj.att_list.push( [attrs.nodeName,attrs.nodeValue] ); + } + }); + render_name+= ">"; }); - obj.main_xml = xml; obj.name = render_name; return obj; }, - - save_object : function(val,parent_list,child_obj_list){ + save_object: function(val, parent_list, child_obj_list) { var self = this; var check_id = parent_list[0]; var p_list = parent_list.slice(1); - if(val.child_id.length != 0){ - $.each(val.child_id, function(key,val) { - if(val.id==check_id){ - if(p_list.length!=0){ - self.save_object(val,p_list,child_obj_list); - }else{ + if (val.child_id.length != 0) { + _.each(val.child_id, function(val, key) { + if (val.id==check_id) { + if (p_list.length!=0) { + self.save_object(val, p_list, child_obj_list); + } else { val.child_id = child_obj_list; - return; + return false; } } }); - }else{ + } else { val.child_id = child_obj_list; } }, - - children_function : function(xml,root,parent_list,parent_id,main_object,parent_child_id){ + xml_node_create: function(xml, root, parent_list, parent_id, main_object){ var self = this; var child_obj_list = []; - var parent_child_id = parent_child_id; - var parent_list = parent_list; - var main_object = main_object; var children_list = $(xml).filter(root).children(); var parents = $(children_list[0]).parents().get(); - _.each(children_list, function(child_node){ - var string = self.check_attr(child_node,child_node.tagName.toLowerCase(),parents.length); - child_obj_list.push(string); + _.each(children_list, function (child_node) { + child_obj_list.push(self.check_attr(child_node,child_node.tagName.toLowerCase(),parents.length)); }); - - if(children_list.length != 0){ - var child_ids = _.map(child_obj_list ,function(num){return num.id;}); - parent_child_id.push({'key': parent_id, 'value': child_ids}); - var parents = $(children_list[0]).parents().get(); - if(parents.length <= parent_list.length){ - parent_list.splice(parents.length-1); + if (children_list.length != 0) { + if (parents.length <= parent_list.length) { + parent_list.splice(parents.length - 1); } parent_list.push(parent_id); - $.each(main_object, function(key,val) { - self.save_object(val,parent_list.slice(1),child_obj_list); - }); + self.save_object(main_object[0], parent_list.slice(1), child_obj_list); } - - for(var i=0;i<children_list.length;i++){ - self.children_function - (children_list[i],children_list[i].tagName.toLowerCase(), - parent_list,child_obj_list[i].id,main_object,parent_child_id); + for (var i=0; i<children_list.length; i++) { + self.xml_node_create + (children_list[i], children_list[i].tagName.toLowerCase(), + parent_list, child_obj_list[i].id, main_object); } - return {"main_object":main_object,"parent_child_id":parent_child_id}; + return main_object; }, - - parse_xml :function(arch,view_id){ - var self = this; + parse_xml: function(arch, view_id) { var root = $(arch).filter(":first")[0]; var tag = root.tagName.toLowerCase(); - var root_object = self.check_attr(root,tag,this.xml_id); - return self.children_function(arch,tag,[],this.xml_id-1,[root_object],[]); + var obj ={'child_id':[],'id':this.xml_id++,'level':0,'att_list':[],'name':"<view view_id='"+view_id+"'>"}; + var root_object = this.check_attr(root,tag,0); + obj.child_id = this.xml_node_create(arch, tag, [], this.xml_id-1, [root_object], []) + return [obj]; }, - - get_data : function(){ + get_data: function() { var self = this; - var view_id =(($("input[name='radiogroup']:checked").parent()).parent()).attr('data-id'); - var ve_dataset = new openerp.web.DataSet(this,'ir.ui.view'); - ve_dataset.read_ids([parseInt(view_id)],['arch'],function (arch){ + var view_id =((this.view_edit_dialog.$element.find("input[name='radiogroup']:checked").parent()).parent()).attr('data-id'); + var ve_dataset = new openerp.web.DataSet(this, 'ir.ui.view'); + ve_dataset.read_ids([parseInt(view_id)], ['arch'], function (arch) { one_object = self.parse_xml(arch[0].arch,view_id); one_object.arch = arch[0].arch; - dataset = new openerp.web.DataSetSearch(self,'ir.ui.view', null, null); - dataset.read_slice([],{domain : [['inherit_id','=',parseInt(view_id)]]},function (result) { - _.each(result,function(res){ - self.inherit_view(one_object,res); + dataset = new openerp.web.DataSetSearch(self, 'ir.ui.view', null, null); + dataset.read_slice([], {domain : [['inherit_id','=', parseInt(view_id)]]}, function (result) { + _.each(result, function(res) { + self.inherit_view(one_object, res); }); - return self.edit_view(one_object); + return self.edit_view({"main_object": one_object, + "parent_child_id": self.parent_child_list(one_object, [])}); }); }); }, - inherit_view : function(one_object,result){ + parent_child_list : function(one_object, p_list) { + var self = this; + _.each(one_object , function(element){ + if(element.child_id.length != 0){ + p_list.push({"key":element.id,"value":_.pluck(element.child_id, 'id')}); + self.parent_child_list(element.child_id, p_list); + } + }); + return p_list; + }, + inherit_view : function(one_object, result) { var self = this; var root = $(result.arch).filter('*'); - var xpath_list = []; - var part_expr = []; - var position ; - if(root[0].tagName.toLowerCase() == "data"){ - _.each($(root).find('xpath'),function(xpath){ - xpath_list.push(xpath); - }); - } else if(root[0].tagName.toLowerCase() == "xpath"){ - xpath_list.push(root[0]); + var xml_list = []; + if (root[0].tagName.toLowerCase() == "data") { + xml_list = $(root[0]).children(); + } else { + xml_list.push(root[0]); } - _.each(xpath_list,function(element){ - var xpath_object = self.parse_xml(element,result.id); - var expr = $(element).attr('expr'); - var position = $(element).attr('position'); - part_expr = expr.split("/"); - if(part_expr[0]=="" && part_expr[1]==""){ - part_expr = part_expr.splice(2); - }else if(part_expr[0]==""){ - part_expr = part_expr.splice(1); - } - if(part_expr[part_expr.length-1].search("@")!=-1){ - var part = part_expr[part_expr.length-1]; - var xpath_list = $.trim(part.replace(/[^a-zA-Z 0-9 _]+/g,' ')).split(" "); - one_object['parent_child_id'].push(xpath_object['parent_child_id'][0]); - $.each(one_object['main_object'], function(key,val) { - var id = self.search_object(val,xpath_list,[],position,xpath_object['main_object'],[]); - _.detect(one_object['parent_child_id'],function(res){ - if(res.key==id){ - res.value.push(xpath_object['main_object'][0].id); - } - }); + _.each(xml_list , function(xml){ + var parent_id; + var check_list = []; + var xpath_object = self.parse_xml(xml,result.id); + if (xml.tagName.toLowerCase() == "xpath" ) { + var part_expr = _.without($(xml).attr('expr').split("/"),""); + _.each(part_expr,function(part){ + check_list.push(_.without($.trim(part.replace(/[^a-zA-Z 0-9 _]+/g,'!')).split("!"),"")); }); + } else { + check_list = [_.flatten(xpath_object[0].child_id[0].att_list)]; } + self.full_path_search(check_list ,one_object ,xpath_object); }); }, - search_object:function(val,list,p_list,position,xpath_object,r_list){ + full_path_search: function(check_list ,val ,xpath_object) { var self = this; - var return_list = r_list; - var main_list = $.trim(val.name.replace(/[^a-zA-Z 0-9 _]+/g,' ')).split(" "); - var insert = _.intersection(main_list,list); - var check = _.indexOf(p_list.child_id,xpath_object[0]); - if(check == -1){ - if(insert.length == list.length){ - var level = val.level; - $.each(xpath_object, function(key,val) { - self.increase_level(val,level) - }); - var index = _.indexOf(p_list.child_id,val); - if(position == "before"){ - if(index!=0){index--;} - }else if(position == "after"){ - index++; + if(xpath_object.length!=0){ + var check = check_list[0]; + var obj; + switch (check.length) { + case 2: + if(parseInt(check[1])){ + var list_1 = _.select(val,function(element){ + var main_list = _.flatten(element.att_list); + return _.include(main_list, check[0]); + }); + obj = val[_.indexOf(val,list_1[parseInt(check[1])-1])]; + } else { + obj = _.detect(val, function(element){ + var main_list = _.flatten(element.att_list); + return _.include(main_list, check[0]); + }); + } + break; + case 3: + obj = _.detect(val,function(element){ + var main_list = _.flatten(element.att_list); + check = _.uniq(check); + var insert = _.intersection(main_list,check); + if(insert.length == check.length ){return element;} + }); + break; + case 1: + var list_1 = _.select(val,function(element){ + var main_list = _.flatten(element.att_list); + return _.include(main_list, check[0]); + }); + if(list_1 != 0){ + (check_list.length == 1)? obj = list_1[0] : check_list.shift(); + } + break; + } + if(obj) { + check_list.shift(); + if (check_list.length !=0){ + self.full_path_search(check_list ,obj.child_id ,xpath_object); + } else { + var level = obj.level+1; + self.increase_level(xpath_object[0], level) + obj.child_id.push(xpath_object[0]); + xpath_object.pop(); + return; } - p_list.child_id.splice(index,0,xpath_object[0]); - return_list.push(p_list.id); - }else{ - if(val.child_id.length!=0){p_list = val;} - $.each(val.child_id, function(key,val) { - self.search_object(val,list,p_list,position,xpath_object,return_list); + } + else { + _.each(val,function(element){ + self.full_path_search(check_list ,element.child_id ,xpath_object); }); } } - return return_list; }, - increase_level :function(val,level){ + increase_level: function(val, level) { var self = this; val.level = level; - $.each(val.child_id, function(key,val) { - self.increase_level(val,level+1); + _.each(val.child_id, function(val, key) { + self.increase_level(val, level + 1); }); }, - edit_view : function(one_object){ + edit_view: function(one_object) { var self = this; - this.dialog = new openerp.web.Dialog(this,{ + this.edit_xml_dialog = new openerp.web.Dialog(this,{ modal: true, title: 'Edit Xml', width: 750, @@ -283,135 +289,134 @@ openerp.web.ViewEditor = openerp.web.Widget.extend({ } } }); - this.dialog.start().open(); - this.dialog.$element.html(QWeb.render('view_editor', { - 'data': one_object['main_object'], - })); - $("tr[id^='viewedit-']").click(function() { - $("tr[id^='viewedit-']").removeClass('ui-selected'); - $(this).addClass('ui-selected'); - }); - - $("img[id^='parentimg-']").click(function() { - if ($(this).attr('src') == '/web/static/src/img/collapse.gif'){ - $(this).attr('src', '/web/static/src/img/expand.gif'); - self.on_expand(this); - }else{ - $(this).attr('src', '/web/static/src/img/collapse.gif'); - var id = this.id.split('-')[1]; - self.on_collapse(this,one_object['parent_child_id'],one_object['main_object']); - } - }); - $("img[id^='side-']").click(function() { - var side = $(this).closest("tr[id^='viewedit-']") - var id_tr = (side.attr('id')).split('-')[1]; - var img = side.find("img[id='parentimg-"+id_tr+"']").attr('src'); ; - var level = side.attr('level'); - var list_shift =[]; - var last_tr; - var cur_tr = side; - list_shift.push(side); - var next_tr; - switch (this.id) - { - case "side-add": - break; - case "side-remove": - break; - case "side-edit": - var tag, fld_name; - var tr = $(this).closest("tr[id^='viewedit-']").find('a').text(); - var tag_fld = tr.split(" "); - if (tag_fld.length > 3){ - tag = tag_fld[1].replace(/[^a-zA-Z 0-9]+/g,''); - fld_name = tag_fld[2].split("=")[1].replace(/[^a-zA-Z _ 0-9]+/g,''); - }else{ - tag = tag_fld[1].replace(/[^a-zA-Z 0-9]+/g,''); - } - var properties = _PROPERTIES[tag]; - self.on_edit_node(properties,fld_name); - break; - case "side-up": - while(1){ - var prev_tr = cur_tr.prev(); - if(level >= prev_tr.attr('level') || prev_tr.length==0){ - last_tr = prev_tr; - break; + this.edit_xml_dialog.start().open(); + this.edit_xml_dialog.$element.html(QWeb.render('view_editor', { + 'data': one_object['main_object'], + })); + this.edit_xml_dialog.$element.find("tr[id^='viewedit-']").click(function() { + $("tr[id^='viewedit-']").removeClass('ui-selected'); + $(this).addClass('ui-selected'); + }); + this.edit_xml_dialog.$element.find("img[id^='parentimg-']").click(function() { + if ($(this).attr('src') == '/web/static/src/img/collapse.gif') { + $(this).attr('src', '/web/static/src/img/expand.gif'); + self.on_expand(this); + } else { + $(this).attr('src', '/web/static/src/img/collapse.gif'); + var id = this.id.split('-')[1]; + self.on_collapse(this,one_object['parent_child_id'], one_object['main_object']); + } + }); + this.edit_xml_dialog.$element.find("img[id^='side-']").click(function() { + var side = $(this).closest("tr[id^='viewedit-']") + var id_tr = (side.attr('id')).split('-')[1]; + var img = side.find("img[id='parentimg-"+id_tr+"']").attr('src'); ; + var level = side.attr('level'); + var list_shift =[]; + var last_tr; + var cur_tr = side; + list_shift.push(side); + var next_tr; + switch (this.id) { + case "side-add": + break; + case "side-remove": + break; + case "side-edit": + var tag, fld_name; + var tr = $(this).closest("tr[id^='viewedit-']").find('a').text(); + var tag_fld = tr.split(" "); + if (tag_fld.length > 3){ + tag = tag_fld[1].replace(/[^a-zA-Z 0-9]+/g,''); + fld_name = tag_fld[2].split("=")[1].replace(/[^a-zA-Z _ 0-9]+/g,''); + }else{ + tag = tag_fld[1].replace(/[^a-zA-Z 0-9]+/g,''); } - cur_tr = prev_tr; - } - if(img){ - while(1){ - next_tr = side.next(); - if(next_tr.attr('level') <= level || next_tr.length==0){ + var properties = _PROPERTIES[tag]; + self.on_edit_node(properties,fld_name); + break; + case "side-up": + while (1) { + var prev_tr = cur_tr.prev(); + if(level >= prev_tr.attr('level') || prev_tr.length == 0) { + last_tr = prev_tr; + break; + } + cur_tr = prev_tr; + } + if (img) { + while (1) { + next_tr = side.next(); + if (next_tr.attr('level') <= level || next_tr.length == 0) { + break; + } else { + list_shift.push(next_tr); + side = next_tr; + } + } + } + if (last_tr.length != 0 && last_tr.attr('level') == level) { + _.each(list_shift, function(rec) { + $(last_tr).before(rec); + }); + } + break; + case "side-down": + if (img) { + while (1) { + next_tr = cur_tr.next(); + if (next_tr.attr('level') <= level || next_tr.length == 0) { + last_tr = next_tr; break; - }else{ + } else { list_shift.push(next_tr); - side = next_tr; + cur_tr = next_tr; } - } + } } - if(last_tr.length!=0 && last_tr.attr('level') == level){ - _.each(list_shift,function(rec){ - $(last_tr).before(rec); - }); + else { + last_tr = cur_tr.next(); } - break; - case "side-down": - if(img){ - while(1){ - next_tr = cur_tr.next(); - if(next_tr.attr('level') <= level || next_tr.length==0){ - last_tr = next_tr; - break; - }else{ - list_shift.push(next_tr); - cur_tr = next_tr; - } - } - } - else{ - last_tr = cur_tr.next(); - } - if(last_tr.length != 0 && last_tr.attr('level')==level){ - var last_tr_id = (last_tr.attr('id')).split('-')[1]; - img = last_tr.find("img[id='parentimg-"+last_tr_id+"']").attr('src'); - if(img){ - $("img[id='parentimg-"+last_tr_id+"']").attr('src', '/web/static/src/img/expand.gif'); - while(1){ - var next_tr = last_tr.next(); - if (next_tr.attr('level') <= level || next_tr.length==0){break;} - next_tr.hide(); - last_tr = next_tr; + if (last_tr.length != 0 && last_tr.attr('level') == level) { + var last_tr_id = (last_tr.attr('id')).split('-')[1]; + img = last_tr.find("img[id='parentimg-" + last_tr_id + "']").attr('src'); + if (img) { + this.edit_xml_dialog.$element.find("img[id='parentimg-" + last_tr_id + "']").attr('src', '/web/static/src/img/expand.gif'); + while (1) { + var next_tr = last_tr.next(); + if (next_tr.attr('level') <= level || next_tr.length == 0) break; + next_tr.hide(); + last_tr = next_tr; + } } + list_shift.reverse(); + _.each(list_shift, function(rec) { + $(last_tr).after(rec); + }); } - list_shift.reverse(); - _.each(list_shift,function(rec){ - $(last_tr).after(rec); - }); + break; } - break; - } - }); + }); }, - on_expand: function(self){ - var level = $(self).closest("tr[id^='viewedit-']").attr('level'); - var cur_tr = $(self).closest("tr[id^='viewedit-']"); - while (1){ + on_expand: function(expand_img){ + var level = parseInt($(expand_img).closest("tr[id^='viewedit-']").attr('level')); + var cur_tr = $(expand_img).closest("tr[id^='viewedit-']"); + while (1) { var nxt_tr = cur_tr.next(); - if (nxt_tr.attr('level') > level){ + if (parseInt(nxt_tr.attr('level')) > level){ cur_tr = nxt_tr; nxt_tr.hide(); - }else return nxt_tr; + } else return nxt_tr; } }, - on_collapse: function(self,parent_child_id,id,main_object){ - var id = self.id.split('-')[1]; - var datas = _.detect(parent_child_id,function(res){ + on_collapse: function(collapse_img, parent_child_id, id, main_object) { + var self = this; + var id = collapse_img.id.split('-')[1]; + var datas = _.detect(parent_child_id,function(res) { return res.key == id; }); - _.each(datas.value,function(rec){ - var tr = $("tr[id='viewedit-"+rec+"']"); + _.each(datas.value, function(rec) { + var tr = self.edit_xml_dialog.$element.find("tr[id='viewedit-"+rec+"']"); tr.find("img[id='parentimg-"+rec+"']").attr('src','/web/static/src/img/expand.gif'); tr.show(); }); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index e184424fbd6..d55ecdbdc45 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -11,6 +11,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */ searchable: false, form_template: "FormView", + identifier_prefix: 'formview-', /** * @constructs openerp.web.FormView * @extends openerp.web.View @@ -32,7 +33,6 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.widgets_counter = 0; this.fields = {}; this.datarecord = {}; - this.ready = false; this.show_invalid = true; this.dirty_for_user = false; this.default_focus_field = null; @@ -43,6 +43,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.translatable_fields = []; _.defaults(this.options, {"always_show_new_button": true, "not_interactible_on_create": false}); + this.mutating_lock = $.Deferred(); + this.initial_mutating_lock = this.mutating_lock; + this.on_change_lock = $.Deferred().resolve(); + this.reload_lock = $.Deferred().resolve(); }, start: function() { this._super(); @@ -118,7 +122,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id); this.sidebar.start(); this.sidebar.do_unfold(); - this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this.sidebar.add_section('attachments', "Attachments"), this); + this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this); this.sidebar.add_toolbar(this.fields_view.toolbar); this.set_common_sidebar_sections(this.sidebar); } @@ -179,7 +183,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } } this.on_form_changed(); - this.show_invalid = this.ready = true; + this.initial_mutating_lock.resolve(); + this.show_invalid = true; this.do_update_pager(record.id == null); if (this.sidebar) { this.sidebar.attachments.do_update(); @@ -222,69 +227,78 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# $pager.find('span.oe_pager_count').html(this.dataset.ids.length); }, do_onchange: function(widget, processed) { - processed = processed || []; - if (widget.node.attrs.on_change) { - var self = this; - this.ready = false; - var onchange = _.trim(widget.node.attrs.on_change); - var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/); - if (call) { - var method = call[1], args = []; - var context_index = null; - var argument_replacement = { - 'False' : function() {return false;}, - 'True' : function() {return true;}, - 'None' : function() {return null;}, - 'context': function(i) { - context_index = i; - var ctx = widget.build_context ? widget.build_context() : {}; - return ctx; - } - }; - var parent_fields = null; - _.each(call[2].split(','), function(a, i) { - var field = _.trim(a); - if (field in argument_replacement) { - args.push(argument_replacement[field](i)); - return; - } else if (self.fields[field]) { - var value = self.fields[field].get_on_change_value(); - args.push(value == null ? false : value); - return; - } else { - var splitted = field.split('.'); - if (splitted.length > 1 && _.trim(splitted[0]) === "parent" && self.dataset.parent_view) { - if (parent_fields === null) { - parent_fields = self.dataset.parent_view.get_fields_values(); - } - var p_val = parent_fields[_.trim(splitted[1])]; - if (p_val !== undefined) { - args.push(p_val == null ? false : p_val); - return; + var self = this; + var act = function() { + try { + processed = processed || []; + if (widget.node.attrs.on_change) { + var onchange = _.trim(widget.node.attrs.on_change); + var call = onchange.match(/^\s?(.*?)\((.*?)\)\s?$/); + if (call) { + var method = call[1], args = []; + var context_index = null; + var argument_replacement = { + 'False' : function() {return false;}, + 'True' : function() {return true;}, + 'None' : function() {return null;}, + 'context': function(i) { + context_index = i; + var ctx = widget.build_context ? widget.build_context() : {}; + return ctx; + } + }; + var parent_fields = null; + _.each(call[2].split(','), function(a, i) { + var field = _.trim(a); + if (field in argument_replacement) { + args.push(argument_replacement[field](i)); + return; + } else if (self.fields[field]) { + var value = self.fields[field].get_on_change_value(); + args.push(value == null ? false : value); + return; + } else { + var splitted = field.split('.'); + if (splitted.length > 1 && _.trim(splitted[0]) === "parent" && self.dataset.parent_view) { + if (parent_fields === null) { + parent_fields = self.dataset.parent_view.get_fields_values(); + } + var p_val = parent_fields[_.trim(splitted[1])]; + if (p_val !== undefined) { + args.push(p_val == null ? false : p_val); + return; + } } } - } - throw "Could not get field with name '" + field + - "' for onchange '" + onchange + "'"; - }); - var ajax = { - url: '/web/dataset/call', - async: false - }; - return this.rpc(ajax, { - model: this.dataset.model, - method: method, - args: [(this.datarecord.id == null ? [] : [this.datarecord.id])].concat(args), - context_id: context_index === null ? null : context_index + 1 - }, function(response) { - self.on_processed_onchange(response, processed); - }); - } else { - console.log("Wrong on_change format", on_change); + throw "Could not get field with name '" + field + + "' for onchange '" + onchange + "'"; + }); + var ajax = { + url: '/web/dataset/call', + async: false + }; + return self.rpc(ajax, { + model: self.dataset.model, + method: method, + args: [(self.datarecord.id == null ? [] : [self.datarecord.id])].concat(args), + context_id: context_index === null ? null : context_index + 1 + }).pipe(function(response) { + return self.on_processed_onchange(response, processed); + }); + } else { + console.log("Wrong on_change format", on_change); + } } - } + } catch(e) { + console.error(e); + return $.Deferred().reject(); + } + }; + this.on_change_lock = this.on_change_lock.pipe(act, act); + return this.on_change_lock; }, on_processed_onchange: function(response, processed) { + try { var result = response; if (result.value) { for (var f in result.value) { @@ -317,7 +331,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# if (result.domain) { // TODO: } - this.ready = true; + return $.Deferred().resolve(); + } catch(e) { + console.error(e); + return $.Deferred().reject(); + } }, on_button_new: function() { var self = this; @@ -364,42 +382,50 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */ do_save: function(success, prepend_on_create) { var self = this; - if (!this.ready) { - return $.Deferred().reject(); - } - var form_dirty = false, - form_invalid = false, - values = {}, - first_invalid_field = null; - for (var f in this.fields) { - f = this.fields[f]; - if (!f.is_valid()) { - form_invalid = true; - f.update_dom(); - if (!first_invalid_field) { - first_invalid_field = f; + var action = function() { + try { + if (!self.initial_mutating_lock.isResolved() && !self.initial_mutating_lock.isRejected()) + return; + var form_dirty = false, + form_invalid = false, + values = {}, + first_invalid_field = null; + for (var f in self.fields) { + f = self.fields[f]; + if (!f.is_valid()) { + form_invalid = true; + f.update_dom(); + if (!first_invalid_field) { + first_invalid_field = f; + } + } else if (f.is_dirty()) { + form_dirty = true; + values[f.name] = f.get_value(); } - } else if (f.is_dirty()) { - form_dirty = true; - values[f.name] = f.get_value(); } - } - if (form_invalid) { - first_invalid_field.focus(); - this.on_invalid(); - return $.Deferred().reject(); - } else { - console.log("About to save", values); - if (!this.datarecord.id) { - return this.dataset.create(values).pipe(function(r) { - return self.on_created(r, undefined, prepend_on_create); - }).then(success); + if (form_invalid) { + first_invalid_field.focus(); + self.on_invalid(); + return $.Deferred().reject(); } else { - return this.dataset.write(this.datarecord.id, values, {}).pipe(function(r) { - return self.on_saved(r); - }).then(success); + console.log("About to save", values); + if (!self.datarecord.id) { + return self.dataset.create(values).pipe(function(r) { + return self.on_created(r, undefined, prepend_on_create); + }).then(success); + } else { + return self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { + return self.on_saved(r); + }).then(success); + } } - } + } catch (e) { + console.error(e); + return $.Deferred().reject(); + } + }; + this.mutating_lock = this.mutating_lock.pipe(action, action); + return this.mutating_lock; }, do_save_edit: function() { this.do_save(); @@ -417,7 +443,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } }); msg += "</ul>"; - this.notification.warn("The following fields are invalid :", msg); + this.do_warn("The following fields are invalid :", msg); }, on_saved: function(r, success) { if (!r.result) { @@ -425,7 +451,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# return $.Deferred().reject(); } else { this.reload(); - return $.Deferred().then(success).resolve(r); + return $.when(r).then(success); } }, /** @@ -460,12 +486,9 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# } console.debug("The record has been created with id #" + this.datarecord.id); this.reload(); - return $.Deferred().then(success).resolve(_.extend(r, {created: true})); + return $.when(_.extend(r, {created: true})).then(success); } }, - do_search: function (domains, contexts, groupbys) { - console.debug("Searching form"); - }, on_action: function (action) { console.debug('Executing action', action); }, @@ -473,11 +496,16 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# console.debug("Cancelling form"); }, reload: function() { - if (this.dataset.index == null || this.dataset.index < 0) { - this.on_button_new(); - } else { - this.dataset.read_index(_.keys(this.fields_view.fields), this.on_record_loaded); - } + var self = this; + var act = function() { + if (self.dataset.index == null || self.dataset.index < 0) { + return $.when(self.on_button_new()); + } else { + return self.dataset.read_index(_.keys(self.fields_view.fields), self.on_record_loaded); + } + }; + this.reload_lock = this.reload_lock.pipe(act, act); + return this.reload_lock; }, get_fields_values: function() { var values = {}; @@ -543,8 +571,12 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({ openerp.web.form = {}; openerp.web.form.SidebarAttachments = openerp.web.Widget.extend({ - init: function(parent, element_id, form_view) { - this._super(parent, element_id); + init: function(parent, form_view) { + var $section = parent.add_section(_t('Attachments'), 'attachments'); + this.$div = $('<div class="oe-sidebar-attachments"></div>'); + $section.append(this.$div); + + this._super(parent, $section.attr('id')); this.view = form_view; }, do_update: function() { @@ -562,7 +594,7 @@ openerp.web.form.SidebarAttachments = openerp.web.Widget.extend({ }, on_attachments_loaded: function(attachments) { this.attachments = attachments; - this.$element.html(QWeb.render('FormView.sidebar.attachments', this)); + this.$div.html(QWeb.render('FormView.sidebar.attachments', this)); this.$element.find('.oe-binary-file').change(this.on_attachment_changed); this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); }, @@ -584,7 +616,7 @@ openerp.web.form.SidebarAttachments = openerp.web.Widget.extend({ ids: [parseInt($e.attr('data-id'))] }, function(r) { $e.parent().remove(); - self.notification.notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); + self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); }); } } @@ -688,7 +720,8 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form. this.width = this.node.attrs.width; }, start: function() { - this.$element = this.view.$element.find('.' + this.element_class); + this.$element = this.view.$element.find( + '.' + this.element_class.replace(/[^\r\n\f0-9A-Za-z_-]/g, "\\$&")); }, process_modifiers: function() { var compute_domain = openerp.web.form.compute_domain; @@ -702,6 +735,51 @@ openerp.web.form.Widget = openerp.web.Widget.extend(/** @lends openerp.web.form. render: function() { var template = this.template; return QWeb.render(template, { "widget": this }); + }, + _build_view_fields_values: function() { + var a_dataset = this.view.dataset; + var fields_values = this.view.get_fields_values(); + var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {}; + fields_values.parent = parent_values; + return fields_values; + }, + _build_eval_context: function() { + var a_dataset = this.view.dataset; + return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values()); + }, + /** + * Builds a new context usable for operations related to fields by merging + * the fields'context with the action's context. + */ + build_context: function() { + var f_context = (this.field || {}).context || {}; + if (!!f_context.__ref) { + var fields_values = this._build_eval_context(); + f_context = new openerp.web.CompoundDomain(f_context).set_eval_context(fields_values); + } + // maybe the default_get should only be used when we do a default_get? + var v_contexts = _.compact([this.node.attrs.default_get || null, + this.node.attrs.context || null]); + var v_context = new openerp.web.CompoundContext(); + _.each(v_contexts, function(x) {v_context.add(x);}); + if (_.detect(v_contexts, function(x) {return !!x.__ref;})) { + var fields_values = this._build_eval_context(); + v_context.set_eval_context(fields_values); + } + // if there is a context on the node, overrides the model's context + var ctx = v_contexts.length > 0 ? v_context : f_context; + return ctx; + }, + build_domain: function() { + var f_domain = this.field.domain || []; + var n_domain = this.node.attrs.domain || null; + // if there is a domain on the node, overrides the model's domain + var final_domain = n_domain !== null ? n_domain : f_domain; + if (!(final_domain instanceof Array)) { + var fields_values = this._build_eval_context(); + final_domain = new openerp.web.CompoundDomain(final_domain).set_eval_context(fields_values); + } + return final_domain; } }); @@ -757,6 +835,10 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ var type = {}; if (node.tag == 'field') { type = this.view.fields_view.fields[node.attrs.name] || {}; + if (node.attrs.widget == 'statusbar') { + // This way we can retain backward compatibility between addons and old clients + node.attrs.nolabel = '1'; + } } var widget = new (this.view.registry.get_any( [node.attrs.widget, type.type, node.tag])) (this.view, node); @@ -792,7 +874,7 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ for (var i = 0; i < node.children.length; i++) { var n = node.children[i]; if (n.tag == "page") { - var page = new openerp.web.form.WidgetNotebookPage( + var page = new (this.view.registry.get_object('notebookpage'))( this.view, n, this, this.pages.length); this.pages.push(page); } @@ -811,7 +893,7 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ page.id = self.pages[index].element_id; }); this.$element.tabs(); - this.view.on_button_new.add_last(this.do_select_first_visible_tab); + this.view.on_button_new.add_first(this.do_select_first_visible_tab); }, do_select_first_visible_tab: function() { for (var i = 0; i < this.pages.length; i++) { @@ -898,11 +980,11 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ self.on_confirmed().then(function() { def.resolve(); }); - $(self).dialog("close"); + $(this).dialog("close"); }, Cancel: function() { def.resolve(); - $(self).dialog("close"); + $(this).dialog("close"); } } }); @@ -911,7 +993,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ return self.on_confirmed(); } }; - if ((!this.node.attrs.special && this.view.dirty_for_user) || !this.view.datarecord.id) { + if (!this.node.attrs.special) { return this.view.recursive_save().pipe(exec_action); } else { return exec_action(); @@ -920,8 +1002,15 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ on_confirmed: function() { var self = this; + var context = this.node.attrs.context; + if (context && context.__ref) { + context = new openerp.web.CompoundContext(context); + context.set_eval_context(this._build_eval_context()); + } + return this.view.do_execute_action( - this.node.attrs, this.view.dataset, this.view.datarecord.id, function () { + _.extend({}, this.node.attrs, {context: context}), + this.view.dataset, this.view.datarecord.id, function () { self.view.reload(); }); }, @@ -1068,46 +1157,6 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f this.invalid = false; }, focus: function() { - }, - _build_view_fields_values: function() { - var a_dataset = this.view.dataset; - var fields_values = this.view.get_fields_values(); - var parent_values = a_dataset.parent_view ? a_dataset.parent_view.get_fields_values() : {}; - fields_values.parent = parent_values; - return fields_values; - }, - _build_eval_context: function() { - var a_dataset = this.view.dataset; - return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values()); - }, - /** - * Builds a new context usable for operations related to fields by merging - * the fields'context with the action's context. - */ - build_context: function() { - var f_context = this.field.context || null; - // maybe the default_get should only be used when we do a default_get? - var v_contexts = _.compact([this.node.attrs.default_get || null, - this.node.attrs.context || null]); - var v_context = new openerp.web.CompoundContext(); - _.each(v_contexts, function(x) {v_context.add(x);}); - if (_.detect(v_contexts, function(x) {return !!x.__ref;})) { - var fields_values = this._build_eval_context(); - v_context.set_eval_context(fields_values); - } - // if there is a context on the node, overrides the model's context - var ctx = v_contexts.length > 0 ? v_context : f_context; - return ctx; - }, - build_domain: function() { - var f_domain = this.field.domain || null; - var v_domain = this.node.attrs.domain || []; - if (!(v_domain instanceof Array) || true) { //TODO niv: remove || true - var fields_values = this._build_eval_context(); - v_domain = new openerp.web.CompoundDomain(v_domain).set_eval_context(fields_values); - } - // if there is a domain on the node, overrides the model's domain - return f_domain || v_domain; } }); @@ -1153,7 +1202,7 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ }, on_button_clicked: function() { if (!this.value || !this.is_valid()) { - this.notification.warn("E-mail error", "Can't send email to invalid e-mail address"); + this.do_warn("E-mail error", "Can't send email to invalid e-mail address"); } else { location.href = 'mailto:' + this.value; } @@ -1168,7 +1217,7 @@ openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({ }, on_button_clicked: function() { if (!this.value) { - this.notification.warn("Resource error", "This resource is empty"); + this.do_warn("Resource error", "This resource is empty"); } else { window.open(this.value); } @@ -1787,6 +1836,9 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ result.result.context = _.extend(result.result.context || {}, additional_context); self.do_action(result.result); }); + }, + focus: function () { + this.$input.focus(); } }); @@ -1842,6 +1894,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ this._super(view, node); this.is_started = $.Deferred(); this.form_last_update = $.Deferred(); + this.init_form_last_update = this.form_last_update; this.disable_utility_classes = true; }, start: function() { @@ -1883,7 +1936,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ form: 'openerp.web.form.One2ManyFormView' }); var once = $.Deferred().then(function() { - self.form_last_update.resolve(); + self.init_form_last_update.resolve(); }); this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { if (view_type == "list") { @@ -1896,6 +1949,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ self.save_form_view(); }); controller.$element.find(".oe_form_button_save_edit").hide(); + } else if (view_type == "graph") { + self.reload_current_view() } self.is_started.resolve(); }); @@ -1915,9 +1970,12 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ if (this.dataset.index === null && this.dataset.ids.length >= 1) { this.dataset.index = 0; } - this.form_last_update.then(function() { - this.form_last_update = view.do_show(); - }); + var act = function() { + return view.do_show(); + } + this.form_last_update = this.form_last_update.pipe(act, act);; + } else if (self.viewmanager.active_view === "graph") { + view.do_search(this.build_domain(), this.dataset.get_context(), []); } }, set_value: function(value) { @@ -2006,9 +2064,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ var view = this.viewmanager.views[this.viewmanager.active_view].controller; if (this.viewmanager.active_view === "form") { var res = $.when(view.do_save()); - if (!res.isResolved() && !res.isRejected()) { - throw "Asynchronous get_value() is not supported in form view."; - } + // it seems line there are some cases when this happens + /*if (!res.isResolved() && !res.isRejected()) { + console.warn("Asynchronous get_value() is not supported in form view."); + }*/ return res; } } @@ -2020,6 +2079,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ }, validate: function() { this.invalid = false; + if (!this.viewmanager.views[this.viewmanager.active_view]) + return; var view = this.viewmanager.views[this.viewmanager.active_view].controller; if (this.viewmanager.active_view === "form") { for (var f in view.fields) { @@ -2062,11 +2123,14 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({ pop.select_element(self.o2m.field.relation,{ initial_view: "form", alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, - create_function: function(data) { - return self.o2m.dataset.create(data, function(r) { + create_function: function(data, callback, error_callback) { + return self.o2m.dataset.create(data).then(function(r) { self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); self.o2m.dataset.on_change(); - }); + }).then(callback, error_callback); + }, + read_function: function() { + return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); }, parent_view: self.o2m.view, form_view_options: {'not_interactible_on_create':true} @@ -2203,6 +2267,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope * - parent_view * - form_view_options * - list_view_options + * - read_function */ select_element: function(model, options, domain, context) { var self = this; @@ -2211,7 +2276,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.context = context || {}; this.options = _.defaults(options || {}, {"initial_view": "search", "create_function": function() { return self.create_row.apply(self, arguments); - }}); + }, read_function: null}); this.initial_ids = this.options.initial_ids; this.created_elements = []; openerp.web.form.dialog(this.render(), {close:function() { @@ -2221,8 +2286,18 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }, start: function() { this._super(); - this.dataset = new openerp.web.ReadOnlyDataSetSearch(this, this.model, + var self = this; + this.dataset = new openerp.web.ProxyDataSet(this, this.model, this.context); + this.dataset.create_function = function() { + return self.options.create_function.apply(null, arguments).then(function(r) { + self.created_elements.push(r.result); + }); + }; + this.dataset.write_function = function() { + return self.write_row.apply(self, arguments); + }; + this.dataset.read_function = this.options.read_function; this.dataset.parent_view = this.options.parent_view; this.dataset.on_default_get.add(this.on_default_get); if (this.options.initial_view == "search") { @@ -2243,11 +2318,11 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }); this.searchview.on_search.add(function(domains, contexts, groupbys) { if (self.initial_ids) { - self.view_list.do_search.call(self, domains.concat([[["id", "in", self.initial_ids]], self.domain]), + self.do_search(domains.concat([[["id", "in", self.initial_ids]], self.domain]), contexts, groupbys); self.initial_ids = undefined; } else { - self.view_list.do_search.call(self, domains.concat([self.domain]), contexts, groupbys); + self.do_search(domains.concat([self.domain]), contexts, groupbys); } }); this.searchview.on_loaded.add_last(function () { @@ -2274,15 +2349,30 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }).pipe(function() { self.searchview.do_search(); }); - }); this.searchview.appendTo($("#" + this.element_id + "_search")); }, - create_row: function(data) { + do_search: function(domains, contexts, groupbys) { + var self = this; + this.rpc('/web/session/eval_domain_and_context', { + domains: domains || [], + contexts: contexts || [], + group_by_seq: groupbys || [] + }, function (results) { + self.view_list.do_search(results.domain, results.context, results.group_by); + }); + }, + create_row: function() { + var self = this; + var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); + wdataset.parent_view = this.options.parent_view; + return wdataset.create.apply(wdataset, arguments); + }, + write_row: function() { var self = this; var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; - return wdataset.create(data); + return wdataset.write.apply(wdataset, arguments); }, on_select_elements: function(element_ids) { }, @@ -2313,33 +2403,25 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self})); var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save-new"); $nbutton.click(function() { - self._created = $.Deferred().then(function() { - self._created = undefined; - self.view_form.on_button_new(); + $.when(self.view_form.do_save()).then(function() { + self.view_form.reload_lock.then(function() { + self.view_form.on_button_new(); + }); }); - self.view_form.do_save(); }); var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save"); $nbutton.click(function() { - self._created = $.Deferred().then(function() { - self._created = undefined; - self.check_exit(); + $.when(self.view_form.do_save()).then(function() { + self.view_form.reload_lock.then(function() { + self.check_exit(); + }); }); - self.view_form.do_save(); }); var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close"); $cbutton.click(function() { self.check_exit(); }); }); - this.dataset.on_create.add(function(data) { - self.options.create_function(data).then(function(r) { - self.created_elements.push(r.result); - if (self._created) { - self._created.resolve(); - } - }); - }); this.view_form.do_show(); }, check_exit: function() { @@ -2401,7 +2483,6 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp this.setup_form_view(); }, on_write: function(id, data) { - this.stop(); if (!this.options.auto_write) return; var self = this; @@ -2424,7 +2505,9 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp $buttons.html(QWeb.render("FormOpenPopup.form.buttons")); var $nbutton = $buttons.find(".oe_formopenpopup-form-save"); $nbutton.click(function() { - self.view_form.do_save(); + self.view_form.do_save().then(function() { + self.stop(); + }); }); var $cbutton = $buttons.find(".oe_formopenpopup-form-close"); $cbutton.click(function() { @@ -2436,7 +2519,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp } }); -openerp.web.form.FormOpenDataset = openerp.web.ReadOnlyDataSetSearch.extend({ +openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({ read_ids: function() { if (this.fop.options.read_function) { return this.fop.options.read_function.apply(null, arguments); @@ -2462,6 +2545,9 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ }; this.get_fields_values = view.get_fields_values; this.do_onchange = this.on_form_changed = this.on_nop; + this.dataset = this.view.dataset; + this.widgets_counter = 0; + this.view_id = 'reference_' + _.uniqueId(); this.widgets = {}; this.fields = {}; this.selection = new openerp.web.form.FieldSelection(this, { attrs: { @@ -2477,8 +2563,10 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ on_nop: function() { }, on_selection_changed: function() { - this.m2o.field.relation = this.selection.get_value(); + var sel = this.selection.get_value(); + this.m2o.field.relation = sel; this.m2o.set_value(null); + this.m2o.$element.toggle(sel !== false); }, start: function() { this._super(); @@ -2522,10 +2610,6 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ this.$element.find('button.oe-binary-file-save').click(this.on_save_as); this.$element.find('.oe-binary-file-clear').click(this.on_clear); }, - update_dom: function() { - this._super.apply(this, arguments); - this.$element.find('.oe-binary').toggle(!this.readonly); - }, human_filesize : function(size) { var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; var i = 0; @@ -2543,29 +2627,28 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ if ($(e.target).val() != '') { this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id); this.$element.find('form.oe-binary-form').submit(); - this.toggle_progress(); + this.$element.find('.oe-binary-progress').show(); + this.$element.find('.oe-binary').hide(); } }, - toggle_progress: function() { - this.$element.find('.oe-binary-progress, .oe-binary').toggle(); - }, on_file_uploaded: function(size, name, content_type, file_base64) { delete(window[this.iframe]); if (size === false) { - this.notification.warn("File Upload", "There was a problem while uploading your file"); + this.do_warn("File Upload", "There was a problem while uploading your file"); // TODO: use openerp web crashmanager console.warn("Error while uploading file : ", name); } else { this.on_file_uploaded_and_valid.apply(this, arguments); this.on_ui_change(); } - this.toggle_progress(); + this.$element.find('.oe-binary-progress').hide(); + this.$element.find('.oe-binary').show(); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { }, on_save_as: function() { if (!this.view.datarecord.id) { - this.notification.warn("Can't save file", "The record has not yet been saved"); + this.do_warn("Can't save file", "The record has not yet been saved"); } else { var url = '/web/binary/saveas?session_id=' + this.session.session_id + '&model=' + this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + @@ -2585,6 +2668,10 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ template: 'FieldBinaryFile', + update_dom: function() { + this._super.apply(this, arguments); + this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly); + }, set_value: function(value) { this._super.apply(this, arguments); var show_value = (value != null && value !== false) ? value : ''; @@ -2617,6 +2704,10 @@ openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ this._super.apply(this, arguments); this.$image = this.$element.find('img.oe-binary-image'); }, + update_dom: function() { + this._super.apply(this, arguments); + this.$element.find('.oe-binary').toggle(!this.readonly); + }, set_value: function(value) { this._super.apply(this, arguments); this.set_image_maxwidth(); @@ -2781,6 +2872,7 @@ openerp.web.form.widgets = new openerp.web.Registry({ 'frame' : 'openerp.web.form.WidgetFrame', 'group' : 'openerp.web.form.WidgetFrame', 'notebook' : 'openerp.web.form.WidgetNotebook', + 'notebookpage' : 'openerp.web.form.WidgetNotebookPage', 'separator' : 'openerp.web.form.WidgetSeparator', 'label' : 'openerp.web.form.WidgetLabel', 'button' : 'openerp.web.form.WidgetButton', diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 8a9deaab228..a97c249bfaf 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -391,9 +391,6 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# if (this.sidebar) { this.sidebar.$element.show(); } - if (!_(this.dataset.ids).isEmpty()) { - this.reload_content(); - } }, do_hide: function () { this.$element.hide(); @@ -443,40 +440,21 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# })); }, /** - * Event handler for a search, asks for the computation/folding of domains - * and contexts (and group-by), then reloads the view's content. - * - * @param {Array} domains a sequence of literal and non-literal domains - * @param {Array} contexts a sequence of literal and non-literal contexts - * @param {Array} groupbys a sequence of literal and non-literal group-by contexts - * @returns {$.Deferred} fold request evaluation promise - */ - do_search: function (domains, contexts, groupbys) { - return this.rpc('/web/session/eval_domain_and_context', { - domains: _([this.dataset.get_domain()].concat(domains)).compact(), - contexts: _([this.dataset.get_context()].concat(contexts)).compact(), - group_by_seq: groupbys - }, $.proxy(this, 'do_actual_search')); - }, - /** * Handler for the result of eval_domain_and_context, actually perform the * searching * * @param {Object} results results of evaluating domain and process for a search */ - do_actual_search: function (results) { + do_search: function (domain, context, group_by) { this.groups.datagroup = new openerp.web.DataGroup( - this, this.model, - results.domain, - results.context, - results.group_by); + this, this.model, domain, context, group_by); this.groups.datagroup.sort = this.dataset._sort; - if (_.isEmpty(results.group_by) && !results.context['group_by_no_leaf']) { - results.group_by = null; + if (_.isEmpty(group_by) && !context['group_by_no_leaf']) { + group_by = null; } - this.reload_view(!!results.group_by, results.context).then( + this.reload_view(!!group_by, context).then( $.proxy(this, 'reload_content')); }, /** @@ -493,6 +471,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# _(ids).each(function (id) { self.records.remove(self.records.get(id)); }); + self.configure_pager(self.dataset); self.compute_aggregates(); }); }, @@ -505,7 +484,13 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# do_select: function (ids, records) { this.$element.find('.oe-list-delete') .attr('disabled', !ids.length); - + if (this.sidebar) { + if (ids.length) { + this.sidebar.do_unfold(); + } else { + this.sidebar.do_fold(); + } + } if (!records.length) { this.compute_aggregates(); return; @@ -536,14 +521,8 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * @param {openerp.web.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups) */ do_activate_record: function (index, id, dataset) { - var self = this; - // TODO is it needed ? - this.dataset.read_slice([],{ - context: dataset.get_context(), - domain: dataset.get_domain() - }, function () { - self.select_record(index); - }); + this.dataset.ids = dataset.ids; + this.select_record(index); }, /** * Handles signal for the addition of a new record (can be a creation, @@ -642,7 +621,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# } $footer_cells.filter(_.sprintf('[data-field=%s]', column.id)) - .html(openerp.web.format_cell(aggregation, column)); + .html(openerp.web.format_cell(aggregation, column, undefined, false)); }); }, get_selected_ids: function() { @@ -806,6 +785,26 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis [this.records.at(this.dataset.index).get('id'), this.dataset]); }, + render_cell: function (record, column) { + if (column.type === 'many2one') { + var value = record.get(column.id); + // m2o values are usually name_get formatted, [Number, String] + // pairs, but in some cases only the id is provided. In these + // cases, we need to perform a name_get call to fetch the actual + // displayable value + if (typeof value === 'number' || value instanceof Number) { + // fetch the name, set it on the record (in the right field) + // and let the various registered events handle refreshing the + // row + new openerp.web.DataSet(this.view, column.relation) + .name_get([value], function (names) { + if (!names.length) { return; } + record.set(column.id, names[0]); + }); + } + } + return openerp.web.format_cell(record.toForm().data, column); + }, render: function () { if (this.$current) { this.$current.remove(); @@ -813,7 +812,7 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis this.$current = this.$_element.clone(true); this.$current.empty().append( QWeb.render('ListView.rows', _.extend({ - render_cell: openerp.web.format_cell}, this))); + render_cell: $.proxy(this, 'render_cell')}, this))); this.pad_table_to(5); }, pad_table_to: function (count) { @@ -837,7 +836,9 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis cells.push('</tr>'); var row = cells.join(''); - this.$current.append(new Array(count - this.records.length + 1).join(row)); + this.$current + .children('tr:not([data-id])').remove().end() + .append(new Array(count - this.records.length + 1).join(row)); this.refresh_zebra(this.records.length); }, /** @@ -918,7 +919,8 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis options: this.options, record: record, row_parity: (index % 2 === 0) ? 'even' : 'odd', - render_cell: openerp.web.format_cell + view: this.view, + render_cell: $.proxy(this, 'render_cell') }); }, /** @@ -1169,7 +1171,8 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name'); var options = { offset: page * limit, limit: limit }; - dataset.read_slice(fields, options , function (records) { + //TODO xmo: investigate why we need to put the setTimeout + setTimeout(function() {dataset.read_slice(fields, options , function (records) { if (!self.datagroup.openable) { view.configure_pager(dataset); } else { @@ -1188,7 +1191,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L self.records.add(records, {silent: true}); list.render(); d.resolve(list); - }); + });}, 0); return d.promise(); }, setup_resequence_rows: function (list, dataset) { diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index beb2c3ac0a9..4cc081a6266 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -22,6 +22,7 @@ openerp.web.list_editable = function (openerp) { if (self.groups.get_selection().length) { return; } + self.configure_pager(self.dataset); self.compute_aggregates(); } }) @@ -56,11 +57,11 @@ openerp.web.list_editable = function (openerp) { || this.defaults.editable); }, /** - * Replace do_actual_search to handle editability process + * Replace do_search to handle editability process */ - do_actual_search: function (results) { - this.set_editable(results.context['set_editable']); - this._super(results); + do_search: function(domain, context, group_by) { + this.set_editable(context['set_editable']); + this._super.apply(this, arguments); }, /** * Replace do_add_record to handle editability (and adding new record @@ -120,6 +121,7 @@ openerp.web.list_editable = function (openerp) { delete self.edition_id; delete self.edition; }); + this.pad_table_to(5); return cancelled.promise(); }, /** @@ -147,7 +149,7 @@ openerp.web.list_editable = function (openerp) { var $new_row = $('<tr>', { id: _.uniqueId('oe-editable-row-'), 'data-id': record_id, - 'class': $(row).attr('class') + ' oe_forms', + 'class': row ? $(row).attr('class') : '' + ' oe_forms', click: function (e) {e.stopPropagation();} }) .delegate('button.oe-edit-row-save', 'click', function () { @@ -173,14 +175,26 @@ openerp.web.list_editable = function (openerp) { }); if (row) { $new_row.replaceAll(row); - } else if (self.options.editable === 'top') { - self.$current.prepend($new_row); } else if (self.options.editable) { - self.$current.append($new_row); + var $last_child = self.$current.children('tr:last'); + if (self.records.length) { + if (self.options.editable === 'top') { + $new_row.insertBefore( + self.$current.children('[data-id]:first')); + } else { + $new_row.insertAfter( + self.$current.children('[data-id]:last')); + } + } else { + $new_row.prependTo(self.$current); + } + if ($last_child.is(':not([data-id])')) { + $last_child.remove(); + } } self.edition = true; self.edition_id = record_id; - self.edition_form = _.extend(new openerp.web.ListEditableFormView(self, self.dataset, false), { + self.edition_form = _.extend(new openerp.web.ListEditableFormView(self.view, self.dataset, false), { form_template: 'ListView.row.form', registry: openerp.web.list.form.widgets, $element: $new_row @@ -325,11 +339,13 @@ openerp.web.list_editable = function (openerp) { this.$element.children().css('visibility', ''); if (this.modifiers.tree_invisible) { var old_invisible = this.invisible; - this.invisible = !!this.modifiers.tree_invisible; + this.invisible = true; this._super(); this.invisible = old_invisible; } else if (this.invisible) { this.$element.children().css('visibility', 'hidden'); + } else { + this._super(); } } }); diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 7988d65805c..5143adb3838 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -32,6 +32,8 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# this.records = {}; this.options = _.extend({}, this.defaults, options || {}); + + _.bindAll(this, 'color_for'); }, start: function () { @@ -107,6 +109,43 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# $select.change(); } }); + + if (!this.fields_view.arch.attrs.colors) { + return; + } + this.colors = _(this.fields_view.arch.attrs.colors.split(';')).chain() + .compact() + .map(function(color_pair) { + var pair = color_pair.split(':'), + color = pair[0], + expr = pair[1]; + return [color, py.parse(py.tokenize(expr)), expr]; + }).value(); + }, + /** + * Returns the color for the provided record in the current view (from the + * ``@colors`` attribute) + * + * @param {Object} record record for the current row + * @returns {String} CSS color declaration + */ + color_for: function (record) { + if (!this.colors) { return ''; } + var context = _.extend({}, record, { + uid: this.session.uid, + current_date: new Date().toString('yyyy-MM-dd') + // TODO: time, datetime, relativedelta + }); + for(var i=0, len=this.colors.length; i<len; ++i) { + var pair = this.colors[i], + color = pair[0], + expression = pair[1]; + if (py.evaluate(expression, context)) { + return 'color: ' + color + ';'; + } + // TODO: handle evaluation errors + } + return ''; }, /** * Sets up opening a row @@ -159,7 +198,8 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# 'fields_view': self.fields_view.arch.children, 'fields': self.fields, 'level': $curr_node.data('level') || 0, - 'render': openerp.web.format_value + 'render': openerp.web.format_value, + 'color_for': self.color_for }); if ($curr_node.length) { diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index be47cfc3364..703961e6238 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -68,9 +68,11 @@ db.web.ActionManager = db.web.Widget.extend({ }, on_url_hashchange: function(url) { var self = this; - self.rpc("/web/action/load", { action_id: url.action_id }, function(result) { - self.do_action(result.result); - }); + if(url && url.action_id) { + self.rpc("/web/action/load", { action_id: url.action_id }, function(result) { + self.do_action(result.result); + }); + } }, do_action: function(action, on_close) { var type = action.type.replace(/\./g,'_'); @@ -116,6 +118,15 @@ db.web.ActionManager = db.web.Widget.extend({ */ }, ir_actions_act_window_close: function (action, on_closed) { + if (!this.dialog && on_closed) { + on_closed(); + } + if (this.dialog && action.context) { + var model = action.context.active_model; + if (model === 'base.module.upgrade' || model === 'base.setup.installer' || model === 'base.module.upgrade') { + db.webclient.do_reload(); + } + } this.dialog_stop(); }, ir_actions_server: function (action, on_closed) { @@ -166,6 +177,7 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{ this.model = dataset ? dataset.model : undefined; this.dataset = dataset; this.searchview = null; + this.last_search = false; this.active_view = null; this.views_src = _.map(views, function(x) {return x instanceof Array? {view_id: x[0], view_type: x[1]} : x;}); this.views = {}; @@ -224,35 +236,21 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{ controller.set_embedded_view(view.embedded_view); } controller.do_switch_view.add_last(this.on_mode_switch); - if (view_type === 'list' && this.flags.search_view === false && this.action && this.action['auto_search']) { - // In case the search view is not instantiated: manually call ListView#search - var domains = !_(self.action.domain).isEmpty() - ? [self.action.domain] : [], - contexts = !_(self.action.context).isEmpty() - ? [self.action.context] : []; - controller.on_loaded.add({ - callback: function () { - controller.do_search(domains, contexts, []); - }, - position: 'last', - unique: true - }); - } var container = $("#" + this.element_id + '_view_' + view_type); view_promise = controller.appendTo(container); + this.views[view_type].controller = controller; $.when(view_promise).then(function() { self.on_controller_inited(view_type, controller); + if (self.searchview && view.controller.searchable !== false) { + self.do_searchview_search(); + } }); - this.views[view_type].controller = controller; + } else if (this.searchview && view.controller.searchable !== false) { + self.do_searchview_search(); } - if (this.searchview) { - if (view.controller.searchable === false) { - this.searchview.hide(); - } else { - this.searchview.show(); - } + this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show'](); } this.$element @@ -273,14 +271,6 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{ return view_promise; }, /** - * Event launched when a controller has been inited. - * - * @param {String} view_type type of view - * @param {String} view the inited controller - */ - on_controller_inited: function(view_type, view) { - }, - /** * Sets up the current viewmanager's search view. * * @param {Number|false} view_id the view to use or false for a default one @@ -293,14 +283,37 @@ db.web.ViewManager = db.web.Widget.extend(/** @lends db.web.ViewManager# */{ } this.searchview = new db.web.SearchView( this, this.dataset, - view_id, search_defaults); + view_id, search_defaults, this.flags.search_view === false); - this.searchview.on_search.add(function(domains, contexts, groupbys) { - var controller = self.views[self.active_view].controller; - controller.do_search.call(controller, domains, contexts, groupbys); - }); + this.searchview.on_search.add(this.do_searchview_search); return this.searchview.appendTo($("#" + this.element_id + "_search")); }, + do_searchview_search: function(domains, contexts, groupbys) { + var self = this, + controller = this.views[this.active_view].controller; + if (domains || contexts) { + this.rpc('/web/session/eval_domain_and_context', { + domains: [this.action.domain || []].concat(domains || []), + contexts: [this.action.context || {}].concat(contexts || []), + group_by_seq: groupbys || [] + }, function (results) { + self.dataset.context = results.context; + self.dataset.domain = results.domain; + self.last_search = [results.domain, results.context, results.group_by]; + controller.do_search(results.domain, results.context, results.group_by); + }); + } else if (this.last_search) { + controller.do_search.apply(controller, this.last_search); + } + }, + /** + * Event launched when a controller has been inited. + * + * @param {String} view_type type of view + * @param {String} view the inited controller + */ + on_controller_inited: function(view_type, view) { + }, /** * Called when one of the view want to execute an action */ @@ -360,28 +373,24 @@ db.web.ViewManagerAction = db.web.ViewManager.extend(/** @lends oepnerp.web.View * launches an initial search after both views are done rendering. */ start: function() { - var self = this; + var self = this, + searchview_loaded, + search_defaults = {}; + _.each(this.action.context, function (value, key) { + var match = /^search_default_(.*)$/.exec(key); + if (match) { + search_defaults[match[1]] = value; + } + }); + // init search view + var searchview_id = this.action['search_view_id'] && this.action['search_view_id'][0]; - var searchview_loaded; - if (this.flags.search_view !== false) { - var search_defaults = {}; - _.each(this.action.context, function (value, key) { - var match = /^search_default_(.*)$/.exec(key); - if (match) { - search_defaults[match[1]] = value; - } - }); - // init search view - var searchview_id = this.action['search_view_id'] && this.action['search_view_id'][0]; - - searchview_loaded = this.setup_search_view( - searchview_id || false, search_defaults); - } + searchview_loaded = this.setup_search_view(searchview_id || false, search_defaults); var main_view_loaded = this._super(); var manager_ready = $.when(searchview_loaded, main_view_loaded); - if (searchview_loaded && this.action['auto_search']) { + if (searchview_loaded && this.action['auto_search'] !== false) { // schedule auto_search manager_ready.then(this.searchview.do_search); } @@ -511,9 +520,57 @@ db.web.Sidebar = db.web.Widget.extend({ self.do_toggle(); }); }, + + call_default_on_sidebar: function(item) { + var func_name = 'on_sidebar_' + _.underscored(item.label); + var fn = this.widget_parent[func_name]; + if(typeof fn === 'function') { + fn(item); + } + }, + + add_default_sections: function() { + this.add_section(_t('Customize'), 'customize'); + this.add_items('customize', [ + { + label: _t("Manage Views"), + callback: this.call_default_on_sidebar, + title: _t("Manage views of the current object"), + }, { + label: _t("Edit Workflow"), + callback: this.call_default_on_sidebar, + title: _t("Manage views of the current object"), + classname: 'oe_hide oe_sidebar_edit_workflow' + }, { + label: _t("Customize Object"), + callback: this.call_default_on_sidebar, + title: _t("Manage views of the current object"), + } + ]); + + this.add_section(_t('Other Options'), 'other'); + this.add_items('other', [ + { + label: _t("Import"), + callback: this.call_default_on_sidebar, + }, { + label: _t("Export"), + callback: this.call_default_on_sidebar, + }, { + label: _t("Translate"), + callback: this.call_default_on_sidebar, + classname: 'oe_sidebar_translate oe_hide' + }, { + label: _t("View Log"), + callback: this.call_default_on_sidebar, + classname: 'oe_hide oe_sidebar_view_log' + } + ]); + }, + add_toolbar: function(toolbar) { var self = this; - _.each([['print', "Reports"], ['action', "Actions"], ['relate', "Links"]], function(type) { + _.each([['print', _t("Reports")], ['action', _t("Actions")], ['relate', _t("Links")]], function(type) { var items = toolbar[type[0]]; if (items.length) { for (var i = 0; i < items.length; i++) { @@ -523,15 +580,30 @@ db.web.Sidebar = db.web.Widget.extend({ classname: 'oe_sidebar_' + type[0] } } - self.add_section(type[0], type[1], items); + self.add_section(type[1], type[0]); + self.add_items(type[0], items); } }); }, - add_section: function(code, name, items) { - // For each section, we pass a name/label and optionally an array of items. - // If no items are passed, then the section will be created as a custom section - // returning back an element_id to be used by a custom controller. - // Else, the section is a standard section with items displayed as links. + + add_section: function(name, code) { + if(!code) code = _.underscored(name); + var $section = this.sections[code]; + + if(!$section) { + section_id = _.uniqueId(this.element_id + '_section_' + code + '_'); + var $section = $(db.web.qweb.render("Sidebar.section", { + section_id: section_id, + name: name, + classname: 'oe_sidebar_' + code, + })); + $section.appendTo(this.$element.find('div.sidebar-actions')); + this.sections[code] = $section; + } + return $section; + }, + + add_items: function(section_code, items) { // An item is a dictonary : { // label: label to be displayed for the link, // action: action to be launch when the link is clicked, @@ -540,25 +612,24 @@ db.web.Sidebar = db.web.Widget.extend({ // title: optional title for the link // } // Note: The item should have one action or/and a callback + // + var self = this, - section_id = _.uniqueId(this.element_id + '_section_' + code + '_'); + $section = this.add_section(_.titleize(section_code.replace('_', ' ')), section_code), + section_id = $section.attr('id'); + if (items) { for (var i = 0; i < items.length; i++) { items[i].element_id = _.uniqueId(section_id + '_item_'); this.items[items[i].element_id] = items[i]; } - } - var $section = $(db.web.qweb.render("Sidebar.section", { - section_id: section_id, - name: name, - classname: 'oe_sidebar_' + code, - items: items - })); - if (items) { - $section.find('a.oe_sidebar_action_a').click(function() { + + var $items = $(db.web.qweb.render("Sidebar.section.items", {items: items})); + + $items.find('a.oe_sidebar_action_a').click(function() { var item = self.items[$(this).attr('id')]; if (item.callback) { - item.callback(); + item.callback.apply(self, [item]); } if (item.action) { var ids = self.widget_parent.get_selected_ids(); @@ -588,10 +659,13 @@ db.web.Sidebar = db.web.Widget.extend({ } return false; }); + + var $ul = $section.find('ul'); + if(!$ul.length) { + $ul = $('<ul/>').appendTo($section); + } + $items.appendTo($ul); } - $section.appendTo(this.$element.find('div.sidebar-actions')); - this.sections[code] = $section; - return section_id; }, do_fold: function() { this.$element.addClass('closed-sidebar').removeClass('open-sidebar'); @@ -770,7 +844,9 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{ var self = this; var result_handler = function () { if (on_closed) { on_closed.apply(null, arguments); } - return self.widget_parent.on_action_executed.apply(null, arguments); + if (self.widget_parent && self.widget_parent.on_action_executed) { + return self.widget_parent.on_action_executed.apply(null, arguments); + } }; var handler = function (r) { var action = r.result; @@ -783,7 +859,8 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{ }], domains: [] }).pipe(function (results) { - action.context = results.context + action.context = new db.web.CompoundContext( + results.context, action_data.context); return self.do_action(action, result_handler); }); } else { @@ -816,47 +893,18 @@ db.web.View = db.web.Widget.extend(/** @lends db.web.View# */{ }, do_switch_view: function(view) { }, + do_search: function(view) { + }, + set_common_sidebar_sections: function(sidebar) { - sidebar.add_section('customize', "Customize", [ - { - label: "Manage Views", - callback: this.on_sidebar_manage_view, - title: "Manage views of the current object" - }, { - label: "Edit Workflow", - callback: this.on_sidebar_edit_workflow, - title: "Manage views of the current object", - classname: 'oe_hide oe_sidebar_edit_workflow' - }, { - label: "Customize Object", - callback: this.on_sidebar_customize_object, - title: "Manage views of the current object" - } - ]); - sidebar.add_section('other', "Other Options", [ - { - label: "Import", - callback: this.on_sidebar_import - }, { - label: "Export", - callback: this.on_sidebar_export - }, { - label: "Translate", - callback: this.on_sidebar_translate, - classname: 'oe_sidebar_translate oe_hide' - }, { - label: "View Log", - callback: this.on_sidebar_view_log, - classname: 'oe_hide oe_sidebar_view_log' - } - ]); + sidebar.add_default_sections(); }, - on_sidebar_manage_view: function() { + on_sidebar_manage_views: function() { if (this.fields_view && this.fields_view.arch) { var view_editor = new db.web.ViewEditor(this, this.$element, this.dataset, this.fields_view.arch); view_editor.start(); } else { - this.notification.warn("Manage Views", "Could not find current view declaration"); + this.do_warn("Manage Views", "Could not find current view declaration"); } }, on_sidebar_edit_workflow: function() { diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index b8da6dcab78..cc3a0af8341 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -2,9 +2,8 @@ <!-- vim:fdl=1: --> <templates id="template" xml:space="preserve"> -<t t-name="Interface"> - <div id="oe_loading" class="loading"></div> - <div id="oe_notification" class="oe_notification"> +<t t-name="Notification"> + <div class="oe_notification"> <div id="oe_notification_default"> <a class="ui-notify-cross ui-notify-close" href="#">x</a> <h1>#{title}</h1> @@ -17,6 +16,9 @@ <p>#{text}</p> </div> </div> +</t> +<t t-name="Interface"> + <div id="oe_loading" class="loading"></div> <table border="0" cellpadding="0" cellspacing="0" width="100%" height="100%" class="main_table"> <tr> <td colspan="2" valign="top"> @@ -447,8 +449,8 @@ </blockquote> <a class="oe-shortcut-toggle" title="Add / Remove Shortcut..." href="javascript: void(0)"> </a> - <h2 class="oe_view_title"> - <t t-esc="self.action.name"/> + <h2 class="oe_view_title" t-if="self.session.debug || self.flags.display_title !==false"> + <t t-if="self.flags.display_title !==false" t-esc="self.action.name"/> <button t-if="self.session.debug" class="oe_get_xml_view"> View#<span></span> </button> @@ -467,17 +469,20 @@ </div> </t> <t t-name="Sidebar.section"> - <h2><t t-esc="name"/></h2> <div t-att-id="section_id" t-att-class="classname"> - <ul t-if="items"> + <h2><t t-esc="name"/></h2> + </div> +</t> + + +<t t-name="Sidebar.section.items"> <li t-foreach="items" t-as="item" t-att-class="item.classname"> <a class="oe_sidebar_action_a" t-att-id="item.element_id" t-att-title="item.title" href="#"> <t t-esc="item.label"/> </a> </li> - </ul> - </div> </t> + <t t-name="TranslateDialog"> <ul class="oe_translate_tabs"> <li><a t-attf-href="##{widget.element_id}_fields">Fields</a></li> @@ -539,7 +544,7 @@ <td t-foreach="fields_view" t-as="field" t-if="!field.attrs.modifiers.tree_invisible" t-att-data-id="record.id" - t-att-style="!field_index ? 'background-position: ' + 19*level + 'px; padding-left: ' + 19*level + 'px' : undefined" + t-att-style="color_for(record) + (!field_index ? 'background-position: ' + 19*level + 'px; padding-left: ' + 19*level + 'px;' : '')" t-att-class="!field_index and has_children ? 'treeview-tr' : 'treeview-td'"> <span t-if="!field.attrs.modifiers.invisible"> <t t-esc="render(record[field.attrs.name], fields[field.attrs.name])" /> @@ -640,7 +645,7 @@ <td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help" t-att-class="'oe-field-cell' + (align ? ' oe-number' : '')" t-att-data-field="column.id"> - <t t-raw="render_cell(record.toForm().data, column)"/> + <t t-raw="render_cell(record, column)"/> </td> </t> <td t-if="options.deletable" class='oe-record-delete' width="1"> @@ -776,7 +781,7 @@ <input type="text" size="12" t-att-name="widget.name" t-att-id="widget.element_id" - t-attf-class="field_#{widget.type} #{widget.element_class}" + t-attf-class="field_#{widget.type}" /> <!-- img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/--> @@ -784,7 +789,7 @@ <t t-name="FieldChar.readonly"> <div t-att-id="widget.element_id" - t-attf-class="field_#{widget.type} #{widget.element_class}" + t-attf-class="field_#{widget.type}" t-attf-style="width: #{widget.field.translate ? '99' : '100'}%"> </div> </t> @@ -823,7 +828,7 @@ <textarea rows="6" t-att-name="widget.name" t-att-id="widget.element_id" - t-attf-class="field_#{widget.type} #{widget.element_class}" + t-attf-class="field_#{widget.type}" t-attf-style="width: #{widget.field.translate ? '99' : '100'}%" ></textarea> <img class="oe_field_translate" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/> @@ -842,8 +847,8 @@ <t t-name="FieldSelection"> <select t-att-name="widget.name" - t-att-id="widget.element_id + '_field'" - t-attf-class="field_#{widget.type} #{widget.element_class}" + t-att-id="widget.element_id" + t-attf-class="field_#{widget.type}" style="width: 100%"> <t t-foreach="widget.values" t-as="option"> <option><t t-esc="option[1]"/></option> @@ -851,8 +856,9 @@ </select> </t> <t t-name="FieldMany2One"> - <div t-att-class="widget.element_class" class="oe-m2o"> - <input type="text" size="1" style="width: 100%;"/> + <div class="oe-m2o"> + <input type="text" size="1" style="width: 100%;" + t-att-id="widget.element_id"/> <span class="oe-m2o-drop-down-button"> <img src="/web/static/src/img/down-arrow.png" /></span> <span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'"> @@ -886,7 +892,7 @@ <td t-attf-class="oe_form_frame_cell oe_form_selection #{widget.selection.element_class}"> <t t-raw="widget.selection.render()"/> </td> - <td class="oe_form_frame_cell oe_form_many2one #{widget.selection.element_class}" nowrap="true"> + <td t-attf-class="oe_form_frame_cell oe_form_many2one #{widget.m2o.element_class}" nowrap="true" style="display: none"> <t t-raw="widget.m2o.render()"/> </td> </tr> @@ -895,8 +901,8 @@ <t t-name="FieldBoolean"> <input type="checkbox" t-att-name="widget.name" - t-att-id="widget.element_id + '_field'" - t-attf-class="field_#{widget.type} #{widget.element_class}"/> + t-att-id="widget.element_id" + t-attf-class="field_#{widget.type}"/> </t> <t t-name="FieldProgressBar"> <div t-opentag="true" class="oe-progressbar"> @@ -911,7 +917,7 @@ t-att-border="widget.readonly ? 0 : 1" t-att-id="widget.element_id + '_field'" t-att-name="widget.name" - t-attf-class="field_#{widget.type} #{widget.element_class}" + t-attf-class="field_#{widget.type}" t-att-width="widget.node.attrs.img_width || widget.node.attrs.width" t-att-height="widget.node.attrs.img_height || widget.node.attrs.height" /> @@ -959,7 +965,7 @@ <input type="text" size="1" t-att-name="widget.name" t-att-id="widget.element_id + '_field'" - t-attf-class="field_#{widget.type} #{widget.element_class}" style="width: 100%" + t-attf-class="field_#{widget.type}" style="width: 100%" /> </td> <td class="oe-binary" nowrap="true"> @@ -1004,7 +1010,6 @@ </t> <t t-name="WidgetButton"> <button type="button" - t-attf-class="#{widget.element_class}" t-att-title="widget.help" style="width: 100%" class="button"> <img t-if="widget.node.attrs.icon" t-att-src="'/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/> @@ -1101,14 +1106,14 @@ <div style="white-space: nowrap;"> <select t-att-name="attrs.name" t-att-id="element_id" t-att-autofocus="attrs.default_focus === '1' || undefined"> - <option/> + <option t-if="prepend_empty"/> <t t-foreach="attrs.selection" t-as="option"> <t t-set="selected" t-value="defaults[attrs.name] === option[0]"/> <option t-if="selected" - t-att-value="option[0]" selected="selected"> + t-att-value="option_index" selected="selected"> <t t-esc="option[1]"/> </option> - <option t-if="!selected" t-att-value="option[0]"> + <option t-if="!selected" t-att-value="option_index"> <t t-esc="option[1]"/> </option> </t> @@ -1302,20 +1307,24 @@ <td align="left" class="view_editor" width="15%"> <table cellspacing="0" cellpadding ="0" width="100%"> <tr> - <td> - <img id="side-add" src="/web/static/src/img/icons/gtk-add.png" style="cursor: pointer;"/> + <td width="20%"> + <img t-if="rec.att_list.length" + id="side-add" src="/web/static/src/img/icons/gtk-add.png" style="cursor: pointer;"/> </td> - <td> + <td width="20%"> <img id="side-remove" src="/web/static/src/img/icons/gtk-remove.png" style="cursor: pointer;"/> </td> - <td> - <img id="side-edit" src="/web/static/src/img/icons/gtk-edit.png" style="cursor: pointer;"/> + <td width="20%"> + <img t-if="rec.att_list.length" + id="side-edit" src="/web/static/src/img/icons/gtk-edit.png" style="cursor: pointer;"/> </td> - <td> - <img id="side-up" src="/web/static/src/img/icons/gtk-go-up.png" style="cursor: pointer;"/> + <td width="20%"> + <img t-if="rec.att_list.length" + id="side-up" src="/web/static/src/img/icons/gtk-go-up.png" style="cursor: pointer;"/> </td> - <td> - <img id="side-down" src="/web/static/src/img/icons/gtk-go-down.png" style="cursor: pointer;"/> + <td width="20%"> + <img t-if="rec.att_list.length" + id="side-down" src="/web/static/src/img/icons/gtk-go-down.png" style="cursor: pointer;"/> </td> </tr> </table> @@ -1570,4 +1579,4 @@ <t t-name="EmptyComponent"> <div></div> </t> -</templates> +</templates>
\ No newline at end of file |
