diff --git a/addons/base/static/lib/qweb/qweb2.js b/addons/base/static/lib/qweb/qweb2.js index 89a77bf1b85..8987f1dc40e 100644 --- a/addons/base/static/lib/qweb/qweb2.js +++ b/addons/base/static/lib/qweb/qweb2.js @@ -169,7 +169,7 @@ var QWeb2 = { for (var i = 0; i < enu; i++) { _enu.push(i); } - this.foreach(context, enu, as, old_dict, callback); + this.foreach(context, _enu, as, old_dict, callback); } else { var index = 0; for (var k in enu) { diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 242167480f3..cd420ea8c0d 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -878,13 +878,20 @@ label.error { position: relative; vertical-align: top; } -.openerp img.ui-datepicker-trigger, .openerp img.ui-datepicker-trigger { +.openerp img.ui-datepicker-trigger { margin-left: -20px; vertical-align: middle; cursor: pointer; position: relative; top: -1px; } +.openerp img.oe_field_translate { + margin-left: -21px; + vertical-align: top; + cursor: pointer; + position: relative; + top: 4px; +} /* http://www.quirksmode.org/dom/inputfile.html * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 8927f6990fc..b1b9df73557 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -566,6 +566,11 @@ openerp.base.Login = openerp.base.Widget.extend({ openerp.base.Header = openerp.base.Widget.extend({ init: function(parent, element_id) { this._super(parent, element_id); + if (jQuery.deparam(jQuery.param.querystring()).debug !== undefined) { + this.qs = '?debug' + } else { + this.qs = '' + } }, start: function() { return this.do_update(); diff --git a/addons/base/static/src/js/core.js b/addons/base/static/src/js/core.js index 441230b3bac..fca993ffef9 100644 --- a/addons/base/static/src/js/core.js +++ b/addons/base/static/src/js/core.js @@ -10,6 +10,9 @@ openerp.base.core = function(openerp) { var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; // The base Class implementation (does nothing) + /** + * @class + */ openerp.base.Class = function(){}; // Create a new Class that inherits from this class diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index a62da5a7503..e1db5761e1c 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -38,6 +38,7 @@ openerp.base.FormView = openerp.base.View.extend( /** @lends openerp.base.FormVi this.registry = openerp.base.form.widgets; this.has_been_loaded = $.Deferred(); this.$form_header = null; + this.translatable_fields = []; _.defaults(this.options, {"always_show_new_button": true}); }, start: function() { @@ -461,7 +462,7 @@ openerp.base.form.SidebarAttachments = openerp.base.Widget.extend({ ['res_model', '=', this.view.dataset.model], ['res_id', '=', this.view.datarecord.id], ['type', 'in', ['binary', 'url']] - ])).read_slice(['name', 'url', 'type'], this.on_attachments_loaded); + ])).read_slice(['name', 'url', 'type'], {}, this.on_attachments_loaded); } }, on_attachments_loaded: function(attachments) { @@ -839,6 +840,13 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({ this.invalid = false; this.dirty = false; }, + start: function() { + this._super.apply(this, arguments); + if (this.field.translate) { + this.view.translatable_fields.push(this); + this.$element.find('.oe_field_translate').click(this.on_translate); + } + }, set_value: function(value) { this.value = value; this.invalid = false; @@ -850,6 +858,9 @@ openerp.base.form.Field = openerp.base.form.Widget.extend({ }, on_value_changed: function() { }, + on_translate: function() { + this.view.open_translate_dialog(this); + }, get_value: function() { return this.value; }, diff --git a/addons/base/static/src/js/formats.js b/addons/base/static/src/js/formats.js index 63ac2b1d82a..c141aad908c 100644 --- a/addons/base/static/src/js/formats.js +++ b/addons/base/static/src/js/formats.js @@ -149,6 +149,9 @@ openerp.base.format_cell = function (row_data, column, value_if_empty) { ].join('') } + if (!row_data[column.id]) { + return value_if_empty === undefined ? '' : value_if_empty; + } return openerp.base.format_value( row_data[column.id].value, column, value_if_empty); } diff --git a/addons/base/static/src/js/list-editable.js b/addons/base/static/src/js/list-editable.js index db10597aa8b..1cd43fd6ea8 100644 --- a/addons/base/static/src/js/list-editable.js +++ b/addons/base/static/src/js/list-editable.js @@ -92,7 +92,7 @@ openerp.base.list_editable = function (openerp) { if (!this.options.editable) { return this._super(event); } - this.edit_record(); + this.edit_record($(event.currentTarget).data('id')); }, /** * Checks if a record is being edited, and if so cancels it @@ -104,8 +104,8 @@ openerp.base.list_editable = function (openerp) { return cancelled.promise(); } - if (this.edition_index !== null) { - this.reload_record(this.edition_index, true).then(function () { + if (this.edition_id != null) { + this.reload_record(self.records.get(this.edition_id)).then(function () { cancelled.resolve(); }); } else { @@ -115,13 +115,14 @@ openerp.base.list_editable = function (openerp) { self.edition_form.stop(); self.edition_form.$element.remove(); delete self.edition_form; - delete self.edition_index; + delete self.edition_id; delete self.edition; }); return cancelled.promise(); }, /** - * Adapts this list's view description to be suitable to the inner form view of a row being edited. + * Adapts this list's view description to be suitable to the inner form + * view of a row being edited. * * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows. */ @@ -140,8 +141,10 @@ openerp.base.list_editable = function (openerp) { render_row_as_form: function (row) { var self = this; this.cancel_pending_edition().then(function () { + var record_id = $(row).data('id'); var $new_row = $('', { id: _.uniqueId('oe-editable-row-'), + 'data-id': record_id, 'class': $(row).attr('class') + ' oe_forms', click: function (e) {e.stopPropagation();} }) @@ -174,7 +177,7 @@ openerp.base.list_editable = function (openerp) { self.$current.append($new_row); } self.edition = true; - self.edition_index = self.dataset.index; + self.edition_id = record_id; self.edition_form = _.extend(new openerp.base.FormView( self, $new_row.attr('id'), self.dataset, false), { template: 'ListView.row.form', @@ -199,6 +202,25 @@ openerp.base.list_editable = function (openerp) { }); }); }, + handle_onwrite: function (source_record_id) { + var self = this; + var on_write_callback = self.view.fields_view.arch.attrs.on_write; + if (!on_write_callback) { return; } + this.dataset.call(on_write_callback, [source_record_id], function (ids) { + _(ids).each(function (id) { + var record = self.records.get(id); + if (!record) { + // insert after the source record + var index = self.records.indexOf( + self.records.get(source_record_id)) + 1; + record = new openerp.base.list.Record({id: id}); + self.records.add(record, {at: index}); + self.dataset.ids.splice(index, 0, id); + } + self.reload_record(record); + }); + }); + }, /** * Saves the current row, and triggers the edition of its following * sibling if asked. @@ -208,9 +230,16 @@ openerp.base.list_editable = function (openerp) { save_row: function (edit_next) { var self = this; this.edition_form.do_save(function (result) { - if (result.created && !self.edition_index) { - self.edition_index = self.dataset.index; + if (result.created && !self.edition_id) { + self.records.add({id: result.result}, + {at: self.options.editable === 'top' ? 0 : null}); + self.edition_id = result.result; } + var edited_record = self.records.get(self.edition_id), + next_record = self.records.at( + self.records.indexOf(edited_record) + 1); + + self.handle_onwrite(self.edition_id); self.cancel_pending_edition().then(function () { $(self).trigger('saved', [self.dataset]); if (!edit_next) { @@ -220,8 +249,16 @@ openerp.base.list_editable = function (openerp) { self.new_record(); return; } - self.dataset.next(); - self.edit_record(); + var next_record_id; + if (next_record) { + next_record_id = next_record.get('id'); + self.dataset.index = _(self.dataset.ids) + .indexOf(next_record_id); + } else { + self.dataset.index = 0; + next_record_id = self.records.at(0).get('id'); + } + self.edit_record(next_record_id); }); }, this.options.editable === 'top'); }, @@ -234,14 +271,12 @@ openerp.base.list_editable = function (openerp) { /** * Edits record currently selected via dataset */ - edit_record: function () { + edit_record: function (record_id) { this.render_row_as_form( - this.$current.children( - _.sprintf('[data-index=%d]', - this.dataset.index))); + this.$current.find('[data-id=' + record_id + ']')); $(this).trigger( 'edit', - [this.rows[this.dataset.index].data.id.value, this.dataset]); + [record_id, this.dataset]); }, new_record: function () { this.dataset.index = null; diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index d715c3600be..47c04537ddc 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -42,6 +42,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @borrows openerp.base.ActionExecutor#execute_action as #execute_action */ init: function(parent, element_id, dataset, view_id, options) { + var self = this; this._super(parent, element_id); this.set_default_options(_.extend({}, this.defaults, options || {})); this.dataset = dataset; @@ -50,6 +51,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.columns = []; + this.records = new Collection(); + this.set_groups(new openerp.base.ListView.Groups(this)); if (this.dataset instanceof openerp.base.DataSetStatic) { @@ -57,6 +60,13 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } this.page = 0; + this.records.bind('change', function (event, record, key) { + if (!_(self.aggregate_columns).chain() + .pluck('name').contains(key).value()) { + return; + } + self.compute_aggregates(); + }); }, /** * Retrieves the view's number of records per page (|| section) @@ -336,9 +346,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi this.sidebar.$element.show(); } if (this.hidden) { - this.$element.find('.oe-listview-content').append( - this.groups.apoptosis().render()); - this.hidden = false; + this.reload_content(); } }, do_hide: function () { @@ -355,27 +363,29 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Object} [context] context to send the server while loading the view */ reload_view: function (grouped, context) { - var self = this; - var callback = function (field_view_get) { + var self = this, d = $.Deferred(); + d.then(function (field_view_get) { self.on_loaded(field_view_get, grouped); - }; + }); if (this.embedded_view) { - return $.Deferred().then(callback).resolve({fields_view: this.embedded_view}); + d.resolve({fields_view: this.embedded_view}); } else { - return this.rpc('/base/listview/load', { + this.rpc('/base/listview/load', { model: this.model, view_id: this.view_id, context: this.dataset.get_context(context), toolbar: this.options.sidebar - }, callback); + }, function () { d.resolve.apply(this, arguments); }); } + return d.promise(); }, /** * re-renders the content of the list view */ reload_content: function () { + this.records.reset(); this.$element.find('.oe-listview-content').append( - this.groups.apoptosis().render( + this.groups.render( $.proxy(this, 'compute_aggregates'))); }, /** @@ -403,8 +413,8 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi do_actual_search: function (results) { this.groups.datagroup = new openerp.base.DataGroup( this, this.model, - this.dataset.get_domain(results.domain), - this.dataset.get_context(results.context), + results.domain, + results.context, results.group_by); this.groups.datagroup.sort = this.dataset._sort; @@ -426,7 +436,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } var self = this; return $.when(this.dataset.unlink(ids)).then(function () { - self.groups.drop_records(ids); + _(ids).each(function (id) { + self.records.remove(self.records.get(id)); + }); self.compute_aggregates(); }); }, @@ -456,16 +468,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Function} callback should be called after the action is executed, if non-null */ do_button_action: function (name, id, callback) { - var self = this, - action = _.detect(this.columns, function (field) { + var action = _.detect(this.columns, function (field) { return field.name === name; }); if (!action) { return; } - this.execute_action(action, this.dataset, id, function () { - $.when(callback.apply(this, arguments).then(function () { - self.compute_aggregates(); - })); - }); + this.execute_action(action, this.dataset, id, callback); }, /** * Handles the activation of a record (clicking on it) @@ -622,7 +629,41 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. this.options = opts.options; this.columns = opts.columns; this.dataset = opts.dataset; - this.rows = opts.rows; + this.records = opts.records; + + this.record_callbacks = { + 'remove': function (event, record) { + var $row = self.$current.find( + '[data-id=' + record.get('id') + ']'); + var index = $row.data('index'); + $row.remove(); + self.refresh_zebra(index); + }, + 'reset': $.proxy(this, 'on_records_reset'), + 'change': function (event, record) { + var $row = self.$current.find('[data-id=' + record.get('id') + ']'); + $row.replaceWith(self.render_record(record)); + }, + 'add': function (ev, records, record, index) { + var $new_row = $('').attr({ + 'data-id': record.get('id') + }); + + if (index === 0) { + $new_row.prependTo(self.$current); + } else { + var previous_record = records.at(index-1), + $previous_sibling = self.$current.find( + '[data-id=' + previous_record.get('id') + ']'); + $new_row.insertAfter($previous_sibling); + } + + self.refresh_zebra(index, 1); + } + }; + _(this.record_callbacks).each(function (callback, event) { + this.records.bind(event, callback); + }, this); this.$_element = $('') .appendTo(document.body) @@ -642,23 +683,24 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. var $target = $(e.currentTarget), field = $target.closest('td').data('field'), $row = $target.closest('tr'), - record_id = self.row_id($row), - index = self.row_position($row); + record_id = self.row_id($row); $(self).trigger('action', [field, record_id, function () { - return self.reload_record(index, true); + return self.reload_record(self.records.get(record_id)); }]); }) .delegate('tr', 'click', function (e) { e.stopPropagation(); - self.dataset.index = self.row_position(e.currentTarget); + self.dataset.index = self.records.indexOf( + self.records.get( + self.row_id(e.currentTarget))); self.row_clicked(e); }); }, row_clicked: function () { $(this).trigger( 'row_link', - [this.rows[this.dataset.index].data.id.value, + [this.records.at(this.dataset.index).get('id'), this.dataset]); }, render: function () { @@ -678,28 +720,16 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. if (!this.options.selectable) { return []; } - var rows = this.rows; + var records = this.records; var result = {ids: [], records: []}; this.$current.find('th.oe-record-selector input:checked') .closest('tr').each(function () { - var record = {}; - _(rows[$(this).data('index')].data).each(function (obj, key) { - record[key] = obj.value; - }); - result.ids.push(record.id); - result.records.push(record); + var record = records.get($(this).data('id')); + result.ids.push(record.get('id')); + result.records.push(record.attributes); }); return result; }, - /** - * Returns the index of the row in the list of rows. - * - * @param {Object} row the selected row - * @returns {Number} the position of the row in this.rows - */ - row_position: function (row) { - return $(row).data('index'); - }, /** * Returns the identifier of the object displayed in the provided table * row @@ -708,119 +738,80 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. * @returns {Number|String} the identifier of the row's object */ row_id: function (row) { - return this.rows[this.row_position(row)].data.id.value; + return $(row).data('id'); }, /** - * Death signal, cleans up list + * Death signal, cleans up list display */ - apoptosis: function () { + on_records_reset: function () { + _(this.record_callbacks).each(function (callback, event) { + this.records.unbind(event, callback); + }, this); if (!this.$current) { return; } this.$current.remove(); this.$current = null; this.$_element.remove(); }, get_records: function () { - return _(this.rows).map(function (row) { - var record = {}; - _(row.data).each(function (obj, key) { - record[key] = obj.value; - }); - return {count: 1, values: record}; + return this.records.map(function (record) { + return {count: 1, values: record.attributes}; }); }, /** - * Transforms a record from what is returned by a dataset read (a simple - * mapping of ``$fieldname: $value``) to the format expected by list rows - * and form views: + * Reloads the provided record by re-reading its content from the server. * - * data: { - * $fieldname: { - * value: $value - * } - * } - * - * This format allows for the insertion of a bunch of metadata (names, - * colors, etc...) - * - * @param {Object} record original record, in dataset format - * @returns {Object} record displayable in a form or list view + * @param {Record} record + * @returns {$.Deferred} promise to the finalization of the reloading */ - transform_record: function (record) { - // TODO: colors handling - var form_data = {}, - form_record = {data: form_data}; - - _(record).each(function (value, key) { - form_data[key] = {value: value}; - }); - - return form_record; - }, - /** - * Reloads the record at index ``row_index`` in the list's rows. - * - * By default, simply re-renders the record. If the ``fetch`` parameter is - * provided and ``true``, will first fetch the record anew. - * - * @param {Number} record_index index of the record to reload - * @param {Boolean} fetch fetches the record from remote before reloading it - */ - reload_record: function (record_index, fetch) { - var self = this; - var read_p = null; - if (fetch) { - // save index to restore it later, if already set - var old_index = this.dataset.index; - this.dataset.index = record_index; - read_p = this.dataset.read_index( - _.pluck(_(this.columns).filter(function (r) {return r.tag === 'field';}), 'name'), - function (record) { - var form_record = self.transform_record(record); - self.rows.splice(record_index, 1, form_record); - self.dataset.index = old_index; - } - ) - } - - return $.when(read_p).then(function () { - self.$current.children().eq(record_index) - .replaceWith(self.render_record(record_index)); }); + reload_record: function (record) { + return this.dataset.read_ids( + [record.get('id')], + _.pluck(_(this.columns).filter(function (r) { + return r.tag === 'field'; + }), 'name'), + function (records) { + _(records[0]).each(function (value, key) { + record.set(key, value, {silent: true}); + }); + record.trigger('change', record); + } + ); }, /** * Renders a list record to HTML * - * @param {Number} record_index index of the record to render in ``this.rows`` + * @param {Record} record index of the record to render in ``this.rows`` * @returns {String} QWeb rendering of the selected record */ - render_record: function (record_index) { + render_record: function (record) { + var index = this.records.indexOf(record); return QWeb.render('ListView.row', { columns: this.columns, options: this.options, - row: this.rows[record_index], - row_parity: (record_index % 2 === 0) ? 'even' : 'odd', - row_index: record_index, + record: record, + row_parity: (index % 2 === 0) ? 'even' : 'odd', render_cell: openerp.base.format_cell }); }, /** - * Stops displaying the records matching the provided ids. + * Fixes fixes the even/odd classes * - * @param {Array} ids identifiers of the records to remove + * @param {Number} [from_index] index from which to resequence + * @param {Number} [offset = 0] selection offset for DOM, in case there are rows to ignore in the table */ - drop_records: function (ids) { - var self = this; - _(this.rows).chain() - .map(function (record, index) { - return {index: index, id: record.data.id.value}; - }).filter(function (record) { - return _(ids).contains(record.id); - }).reverse() - .each(function (record) { - self.$current.find('tr:eq(' + record.index + ')').remove(); - self.rows.splice(record.index, 1); - }) + refresh_zebra: function (from_index, offset) { + offset = offset || 0; + from_index = from_index || 0; + var dom_offset = offset + from_index; + var sel = dom_offset ? ':gt(' + (dom_offset - 1) + ')' : null; + this.$current.children(sel).each(function (i, e) { + var index = from_index + i; + // reset record-index accelerators on rows and even/odd + var even = index%2 === 0; + $(e).toggleClass('even', even) + .toggleClass('odd', !even); + }); } - // drag and drop }); openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.base.ListView.Groups# */{ passtrough_events: 'action deleted row_link', @@ -830,17 +821,28 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas * * Provides events similar to those of * :js:class:`~openerp.base.ListView.List` + * + * @constructs + * @param {openerp.base.ListView} view + * @param {Object} [options] + * @param {Collection} [options.records] + * @param {Object} [options.options] + * @param {Array} [options.columns] */ - init: function (view) { + init: function (view, options) { + options = options || {}; this.view = view; - this.options = view.options; - this.columns = view.columns; + this.records = options.records || view.records; + this.options = options.options || view.options; + this.columns = options.columns || view.columns; this.datagroup = null; this.$row = null; this.children = {}; this.page = 0; + + this.records.bind('reset', $.proxy(this, 'on_records_reset')); }, make_fragment: function () { return document.createDocumentFragment(); @@ -901,7 +903,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas }, close: function () { this.$row.children().last().empty(); - this.apoptosis(); + this.records.reset(); }, /** * Prefixes ``$node`` with floated spaces in order to indent it relative @@ -922,10 +924,11 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas var placeholder = this.make_fragment(); _(datagroups).each(function (group) { if (self.children[group.value]) { - self.children[group.value].apoptosis(); + self.records.proxy(group.value).reset(); delete self.children[group.value]; } var child = self.children[group.value] = new openerp.base.ListView.Groups(self.view, { + records: self.records.proxy(group.value), options: self.options, columns: self.columns }); @@ -1020,13 +1023,12 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas }); }, render_dataset: function (dataset) { - var rows = [], - self = this, + var self = this, list = new openerp.base.ListView.List(this, { options: this.options, columns: this.columns, dataset: dataset, - rows: rows + records: this.records }); this.bind_child_events(list); @@ -1053,11 +1055,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas .attr('disabled', page === pages - 1); } - var form_records = _(records).map( - $.proxy(list, 'transform_record')); - - rows.splice(0, rows.length); - rows.push.apply(rows, form_records); + self.records.add(records, {silent: true}); list.render(); d.resolve(list); }); @@ -1074,33 +1072,27 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas // ondrop, move relevant record & fix sequences list.$current.sortable({ stop: function (event, ui) { - var from = ui.item.data('index'), - to = ui.item.prev().data('index') || 0; - if (from === to) { return; } - list.rows.splice(to, 0, list.rows.splice(from, 1)[0]); + var to_move = list.records.get(ui.item.data('id')), + target_id = ui.item.prev().data('id'); - ui.item.parent().children().each(function (i, e) { - // reset record-index accelerators on rows and even/odd - var even = i%2 === 0; - $(e).data('index', i) - .toggleClass('even', even) - .toggleClass('odd', !even); - }); + list.records.remove(to_move); + var to = target_id ? list.records.indexOf(list.records.get(target_id)) : 0; + list.records.add(to_move, { at: to }); // resequencing time! - var data, index = to, + var record, index = to, // if drag to 1st row (to = 0), start sequencing from 0 // (exclusive lower bound) - seq = to ? list.rows[to - 1].data.sequence.value : 0; - while (++seq, list.rows[index]) { - data = list.rows[index].data; - data.sequence.value = seq; + seq = to ? list.records.at(to - 1).get('sequence') : 0; + while (++seq, record = list.records.at(index++)) { // write are independent from one another, so we can just // launch them all at the same time and we don't really // give a fig about when they're done - dataset.write(data.id.value, {sequence: seq}); - list.reload_record(index++); + dataset.write(record.get('id'), {sequence: seq}); + record.set('sequence', seq); } + + list.refresh_zebra(); } }); }, @@ -1144,13 +1136,9 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas return {ids: ids, records: records}; }, - apoptosis: function () { - _(this.children).each(function (child) { - child.apoptosis(); - }); + on_records_reset: function () { this.children = {}; $(this.elements).remove(); - return this; }, get_records: function () { if (_(this.children).isEmpty()) { @@ -1163,22 +1151,306 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas .map(function (child) { return child.get_records(); }).flatten().value(); - }, - /** - * Stops displaying the records with the linked ids, assumes these records - * were deleted from the DB. - * - * This is the up-signal from the `deleted` event on groups and lists. - * - * @param {Array} ids list of identifier of the records to remove. - */ - drop_records: function (ids) { - _.each(this.children, function (child) { - child.drop_records(ids); - }); } }); + +/** + * @class + * @extends openerp.base.Class + */ +var Events = { + /** + * @param {String} event event to listen to on the current object, null for all events + * @param {Function} handler event handler to bind to the relevant event + * @returns this + */ + bind: function (event, handler) { + var calls = this['_callbacks'] || (this._callbacks = {}); + + if (event in calls) { + calls[event].push(handler); + } else { + calls[event] = [handler]; + } + return this; + }, + /** + * @param {String} event event to unbind on the current object + * @param {function} [handler] specific event handler to remove (otherwise unbind all handlers for the event) + * @returns this + */ + unbind: function (event, handler) { + var calls = this._callbacks || {}; + if (!(event in calls)) { return this; } + if (!handler) { + delete calls[event]; + } else { + var handlers = calls[event]; + handlers.splice( + _(handlers).indexOf(handler), + 1); + } + return this; + }, + /** + * @param {String} event + * @returns this + */ + trigger: function (event) { + var calls; + if (!(calls = this._callbacks)) { return this; } + var callbacks = (calls[event] || []).concat(calls[null] || []); + for(var i=0, length=callbacks.length; i} domain to include in the resulting search + */ + make_domain: function (name, operator, value) { + return [[name, operator, value]]; + }, get_domain: function () { var val = this.get_value(); if (val === null || val === '') { @@ -598,11 +612,10 @@ openerp.base.search.Field = openerp.base.search.Input.extend( /** @lends openerp var domain = this.attrs['filter_domain']; if (!domain) { - return [[ + return this.make_domain( this.attrs.name, this.attrs.operator || this.default_operator, - this.get_value() - ]]; + val); } return _.extend({}, domain, {own_values: {self: val}}); } @@ -723,7 +736,22 @@ openerp.base.search.DateField = openerp.base.search.Field.extend( /** @lends ope return this.$element.val(); } }); -openerp.base.search.DateTimeField = openerp.base.search.DateField.extend({ +/** + * Implementation of the ``datetime`` openerp field type: + * + * * Uses the same widget as the ``date`` field type (a simple date) + * + * * Builds a slighly more complex, it's a datetime range (includes time) + * spanning the whole day selected by the date widget + * + * @class + * @extends openerp.base.DateField + */ +openerp.base.search.DateTimeField = openerp.base.search.DateField.extend(/** @lends openerp.base.search.DateTimeField# */{ + make_domain: function (name, operator, value) { + return ['&', [name, '>=', value + ' 00:00:00'], + [name, '<=', value + ' 23:59:59']]; + } }); openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ init: function (view_section, field, view) { @@ -781,16 +809,16 @@ openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ } return this._super(defaults); }, - get_domain: function () { + make_domain: function (name, operator, value) { if (this.id && this.name) { - if (this.$element.val() === this.name) { - return [[this.attrs.name, '=', this.id]]; + if (value === this.name) { + return [[name, '=', this.id]]; } else { delete this.id; delete this.name; } } - return this._super(); + return this._super(name, operator, value); } }); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 1239ad4f559..2795cc8c8ff 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -4,6 +4,8 @@ openerp.base.views = function(openerp) { +var _t = openerp.base._t; + /** * Registry for all the client actions key: tag value: widget */ @@ -450,6 +452,106 @@ openerp.base.Sidebar = openerp.base.Widget.extend({ } }); +openerp.base.TranslateDialog = openerp.base.Dialog.extend({ + dialog_title: _t("Translations"), + init: function(view) { + this['on_button' + _t("Save")] = this.on_button_Save; + this['on_button' + _t("Close")] = this.on_button_Close; + this._super(view, { + width: '80%', + height: '80%' + }); + this.view = view; + this.view_type = view.fields_view.type || ''; + this.$fields_form = null; + this.$view_form = null; + this.$sidebar_form = null; + this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name }); + this.languages = null; + this.languages_loaded = $.Deferred(); + (new openerp.base.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(), + [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }, this.on_languages_loaded); + }, + start: function() { + var self = this; + this._super(); + $.when(this.languages_loaded).then(function() { + self.$element.html(QWeb.render('TranslateDialog', { widget: self })); + self.$element.tabs(); + if (!(self.view.translatable_fields && self.view.translatable_fields.length)) { + self.hide_tabs('fields'); + self.select_tab('view'); + } + self.$fields_form = self.$element.find('.oe_translation_form'); + }); + return this; + }, + on_languages_loaded: function(langs) { + this.languages = langs; + this.languages_loaded.resolve(); + }, + do_load_fields_values: function(callback) { + var self = this, + deffered = []; + this.$fields_form.find('.oe_trad_field').val('').removeClass('touched'); + _.each(self.languages, function(lg) { + var deff = $.Deferred(); + deffered.push(deff); + self.rpc('/base/dataset/get', { + model: self.view.dataset.model, + ids: [self.view.datarecord.id], + fields: self.translatable_fields_keys, + context: self.view.dataset.get_context({ + 'lang': lg.code + }) + }, function(values) { + _.each(self.translatable_fields_keys, function(f) { + self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || ''); + }); + deff.resolve(); + }); + }); + $.when.apply(null, deffered).then(callback); + }, + show_tabs: function() { + for (var i = 0; i < arguments.length; i++) { + this.$element.find('ul.oe_translate_tabs li a[href$="' + arguments[i] + '"]').parent().show(); + } + }, + hide_tabs: function() { + for (var i = 0; i < arguments.length; i++) { + this.$element.find('ul.oe_translate_tabs li a[href$="' + arguments[i] + '"]').parent().hide(); + } + }, + select_tab: function(name) { + this.show_tabs(name); + var index = this.$element.find('ul.oe_translate_tabs li a[href$="' + arguments[i] + '"]').parent().index() - 1; + this.$element.tabs('select', index); + }, + open: function(field) { + var self = this, + super = this._super; + $.when(this.languages_loaded).then(function() { + if (self.view.translatable_fields && self.view.translatable_fields.length) { + self.do_load_fields_values(function() { + super.call(self); + if (field) { + // TODO: focus and scroll to field + } + }); + } else { + super.call(self); + } + }); + }, + on_button_Save: function() { + this.close(); + }, + on_button_Close: function() { + this.close(); + } +}); + openerp.base.View = openerp.base.Widget.extend({ set_default_options: function(options) { this.options = options || {}; @@ -461,6 +563,12 @@ openerp.base.View = openerp.base.Widget.extend({ action_views_ids: {} }); }, + open_translate_dialog: function(field) { + if (!this.translate_dialog) { + this.translate_dialog = new openerp.base.TranslateDialog(this).start(); + } + this.translate_dialog.open(field); + }, /** * Fetches and executes the action identified by ``action_data``. * @@ -542,7 +650,7 @@ openerp.base.View = openerp.base.Widget.extend({ }, { label: "Translate", callback: this.on_sidebar_translate, - classname: 'oe_hide oe_sidebar_translate' + classname: 'oe_sidebar_translate' }, { label: "View Log", callback: this.on_sidebar_view_log, @@ -572,6 +680,7 @@ openerp.base.View = openerp.base.Widget.extend({ export_view.start(); }, on_sidebar_translate: function() { + this.open_translate_dialog(); }, on_sidebar_view_log: function() { } diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index e3deb48a14f..0660b97447d 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -326,7 +326,7 @@ - + + + +
+ + + + + + + + + +
+
Field
+
+
+
+ + + + +
+
+
+ Translate view +
+
+ Translate sidebar +
+

+
@@ -731,11 +770,13 @@
- + + diff --git a/openerpweb/openerpweb.py b/openerpweb/openerpweb.py index a48162138dc..b2364692203 100644 --- a/openerpweb/openerpweb.py +++ b/openerpweb/openerpweb.py @@ -1,5 +1,6 @@ #!/usr/bin/python import datetime +import urllib import dateutil.relativedelta import functools import optparse @@ -446,7 +447,11 @@ class Root(object): #for the mobile web client we are supposed to use a different url to just add '/mobile' raise cherrypy.HTTPRedirect('/web_mobile/static/src/web_mobile.html', 301) else: - raise cherrypy.HTTPRedirect('/base/webclient/home', 301) + if kw: + qs = '?' + urllib.urlencode(kw) + else: + qs = '' + raise cherrypy.HTTPRedirect('/base/webclient/home' + qs, 301) default.exposed = True def main(argv):