diff options
Diffstat (limited to 'addons/web/static/src/js/views')
| -rw-r--r-- | addons/web/static/src/js/views/form_common.js | 75 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form_relational_widgets.js | 426 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form_upgrade_widgets.js | 101 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form_view.js | 439 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/form_widgets.js | 848 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/graph_view.js | 26 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/graph_widget.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list_view.js | 543 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/list_view_editable.js | 41 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/pivot_view.js | 100 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/search_filters.js | 33 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/search_inputs.js | 3 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/search_menus.js | 68 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/search_view.js | 480 | ||||
| -rw-r--r-- | addons/web/static/src/js/views/tree_view.js | 22 |
15 files changed, 1339 insertions, 1867 deletions
diff --git a/addons/web/static/src/js/views/form_common.js b/addons/web/static/src/js/views/form_common.js index 1c86a86c1ec..b3d078dbc1a 100644 --- a/addons/web/static/src/js/views/form_common.js +++ b/addons/web/static/src/js/views/form_common.js @@ -215,7 +215,7 @@ var CompletionFieldMixin = { self._search_create_popup("search", data); }); }, - classname: 'oe_m2o_dropdown_option' + classname: 'o_m2o_dropdown_option' }); } // quick create @@ -228,7 +228,7 @@ var CompletionFieldMixin = { action: function() { self._quick_create(search_val); }, - classname: 'oe_m2o_dropdown_option' + classname: 'o_m2o_dropdown_option' }); } // create... @@ -238,14 +238,14 @@ var CompletionFieldMixin = { action: function() { self._search_create_popup("form", undefined, self._create_context(search_val)); }, - classname: 'oe_m2o_dropdown_option' + classname: 'o_m2o_dropdown_option' }); } else if (values.length === 0) { values.push({ label: _t("No results to show..."), action: function() {}, - classname: 'oe_m2o_dropdown_option' + classname: 'o_m2o_dropdown_option' }); } @@ -275,7 +275,7 @@ var CompletionFieldMixin = { // all search/create popup handling _search_create_popup: function(view, ids, context) { var self = this; - new SelectCreateDialog(this, { + new SelectCreateDialog(this, _.extend({}, (this.options || {}), { res_model: self.field.relation, domain: self.build_domain(), context: new data.CompoundContext(self.build_context(), context || {}), @@ -287,7 +287,7 @@ var CompletionFieldMixin = { self.add_id(element_ids[0]); self.focus(); } - }).open(); + })).open(); }, /** * To implement. @@ -474,7 +474,7 @@ var FormWidget = Widget.extend(InvisibilityChangerMixin, { debug: session.debug, widget: widget }); - }, + } }, options || {}); //only show tooltip if we are in debug or if we have a help to show, otherwise it will display //as empty @@ -666,16 +666,16 @@ var AbstractField = FormWidget.extend(FieldInterface, { this._check_css_flags(); }); - this.$translate = $(); // Enterprise compatibility + this.$translate = (_t.database.multi_lang && this.field.translate) ? $('<button/>', { + type: 'button', + }).addClass('o_field_translate fa fa-globe btn btn-link') : $(); }, + renderElement: function() { var self = this; this._super(); - if (this.field.translate && this.view) { - this.$el.addClass('oe_form_field_translatable'); - this.$el.find('.oe_field_translate').click(this.on_translate); - } - this.$label = this.view ? this.view.$el.find('label[for=' + this.id_for_label + ']') : $(); + this.$el.addClass('o_form_field'); + this.$label = this.view ? this.view.$('label[for=' + this.id_for_label + ']') : $(); this.do_attach_tooltip(this, this.$label[0] || this.$el); if (session.debug) { this.$label.off('dblclick').on('dblclick', function() { @@ -707,16 +707,23 @@ var AbstractField = FormWidget.extend(FieldInterface, { }); this.render_value(); this._toggle_label(); + + if (this.view) { + this.$translate + .insertAfter(this.$el) + .on('click', _.bind(this.on_translate, this)); + } }, _toggle_label: function() { var empty = this.get('effective_readonly') && this.is_false(); + this.$label.toggleClass('o_form_label_empty', empty).toggleClass('o_form_label_false', this.get('effective_readonly') && this.get('value') === false); this.$el.toggleClass('o_form_field_empty', empty); }, /** * Private. Do not use. */ _set_required: function() { - this.$el.toggleClass('oe_form_required', this.get("required")); + this.$el.toggleClass('o_form_required', this.get("required")); }, set_value: function(value_) { this.set({'value': value_}); @@ -752,10 +759,11 @@ var AbstractField = FormWidget.extend(FieldInterface, { return this.get('value') === false; }, _check_css_flags: function() { - if (this.field.translate) { - this.$el.find('.oe_field_translate').toggle(this.field_manager.get('actual_mode') !== "create"); - } - this.$el.toggleClass('oe_form_invalid', !this.disable_utility_classes && !!this.field_manager.get('display_invalid_fields') && !this.is_valid()); + var show_translate = (!this.get('effective_readonly') && this.field_manager.get('actual_mode') !== "create"); + this.$translate.toggleClass('o_translate_active', !!show_translate); + + this.$el.add(this.$label) + .toggleClass('o_form_invalid', !this.disable_utility_classes && !!this.field_manager.get('display_invalid_fields') && !this.is_valid()); }, focus: function() { return false; @@ -770,11 +778,10 @@ var AbstractField = FormWidget.extend(FieldInterface, { self.do_action(r); }); }, - - set_dimensions: function (height, width) { + set_dimensions: function(height, width) { this.$el.css({ width: width, - minHeight: height + height: height, }); }, commit_value: function() { @@ -802,7 +809,7 @@ var ViewDialog = Dialog.extend({ // FIXME should use ViewManager options.dialogClass = options.dialogClass || ''; options.dialogClass += ' o_act_window'; - this._super(parent, _.clone(options)); + this._super(parent, $.extend(true, {}, options)); this.res_model = options.res_model || null; this.res_id = options.res_id || null; @@ -864,7 +871,7 @@ var FormViewDialog = ViewDialog.extend({ ]; if(!readonly) { - options.buttons.splice(0, 0, {text: _t("Save") + ((multi_select)? " " + _t(" & Close") : ""), classes: "btn-primary o_formdialog_save", click: function() { // o_formdialog_save class for web_tests! + options.buttons.splice(0, 0, {text: _t("Save") + ((multi_select)? " " + _t(" & Close") : ""), classes: "btn-primary", click: function() { self.view_form.onchanges_mutex.def.then(function() { if (!self.view_form.warning_displayed) { $.when(self.view_form.save()).done(function() { @@ -923,6 +930,7 @@ var FormViewDialog = ViewDialog.extend({ var fragment = document.createDocumentFragment(); self.view_form.appendTo(fragment).then(function () { self.view_form.do_show().then(function() { + self.view_form.autofocus(); _super().$el.append(fragment); }); }); @@ -941,7 +949,7 @@ var SelectCreateListView = ListView.extend({ this.popup.close(); }, do_select: function(ids, records) { - this._super(ids, records); + this._super.apply(this, arguments); this.popup.on_click_element(ids); } }); @@ -1022,7 +1030,7 @@ var SelectCreateDialog = ViewDialog.extend({ _.extend({'deletable': false, 'selectable': !self.options.disable_multiple_selection, 'import_enabled': false, - '$buttons': self.$footer, + '$buttons': self.$buttons, 'disable_editable_mode': true, 'pager': true, }, self.options.list_view_options || {})); @@ -1030,6 +1038,9 @@ var SelectCreateDialog = ViewDialog.extend({ e.cancel = true; }); self.view_list.popup = self; + self.view_list.on('list_view_loaded', self, function() { + this.on_view_list_loaded(); + }); var buttons = [ {text: _t("Cancel"), classes: "btn-default o_form_button_cancel", close: true} @@ -1076,6 +1087,7 @@ var SelectCreateDialog = ViewDialog.extend({ this.close(); return new FormViewDialog(this.__parentedParent, this.options).open(); }, + on_view_list_loaded: function() {}, }); var DomainEditorDialog = SelectCreateDialog.extend({ @@ -1095,11 +1107,11 @@ var DomainEditorDialog = SelectCreateDialog.extend({ }, get_domain: function (selected_ids) { - var group_domain = [], - domain; - if (this.$('input.oe_list_record_selector').prop('checked')) { + var group_domain = []; + var domain; + if (this.$('.o_list_record_selector input').prop('checked')) { if (this.view_list.grouped) { - var group_domain = _.chain(_.values(this.view_list.groups.children)) + group_domain = _.chain(_.values(this.view_list.groups.children)) .filter(function (child) { return child.records.length; }) .map(function (c) { return c.datagroup.domain;}) .value(); @@ -1118,6 +1130,11 @@ var DomainEditorDialog = SelectCreateDialog.extend({ } return this.dataset.domain.concat(group_domain).concat(domain || []); }, + + on_view_list_loaded: function() { + this.$('.o_list_record_selector input').prop('checked', true); + this.$footer.find(".o_selectcreatepopup_search_select").prop('disabled', false); + }, }); return { diff --git a/addons/web/static/src/js/views/form_relational_widgets.js b/addons/web/static/src/js/views/form_relational_widgets.js index fc852108035..8e64599f470 100644 --- a/addons/web/static/src/js/views/form_relational_widgets.js +++ b/addons/web/static/src/js/views/form_relational_widgets.js @@ -6,7 +6,6 @@ var core = require('web.core'); var data = require('web.data'); var Dialog = require('web.Dialog'); var common = require('web.form_common'); -var FormView = require('web.FormView'); var ListView = require('web.ListView'); var Model = require('web.DataModel'); var session = require('web.session'); @@ -56,16 +55,15 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com events: { 'keydown input': function (e) { switch (e.which) { - case $.ui.keyCode.UP: - case $.ui.keyCode.DOWN: - e.stopPropagation(); + case $.ui.keyCode.UP: + case $.ui.keyCode.DOWN: + e.stopPropagation(); } }, }, init: function(field_manager, node) { this._super(field_manager, node); common.CompletionFieldMixin.init.call(this); - this.set({'value': false}); this.display_value = {}; this.display_value_backup = {}; this.last_search = []; @@ -91,8 +89,9 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com common.ReinitializeFieldMixin.initialize_field.call(this); }, initialize_content: function() { - if (!this.get("effective_readonly")) + if (!this.get("effective_readonly")) { this.render_editable(); + } }, destroy_content: function () { if (this.$dropdown) { @@ -125,16 +124,15 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com }, render_editable: function() { var self = this; - this.$input = this.$el.find("input"); + this.$input = this.$("input"); this.init_error_displayer(); - self.$input.on('focus', function() { self.hide_error_displayer(); }); - this.$dropdown = this.$el.find(".oe_m2o_drop_down_button"); - this.$follow_button = $(".oe_m2o_cm_button", this.$el); + this.$dropdown = this.$(".o_dropdown_button"); + this.$follow_button = this.$(".o_external_button"); this.$follow_button.click(function(ev) { ev.preventDefault(); @@ -177,8 +175,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com }; this.$input.keydown(input_changed); this.$input.change(input_changed); - this.$dropdown.click(function() { - self.$input.focus(); + this.$input.on('click', function() { if (self.$input.autocomplete("widget").is(":visible")) { self.$input.autocomplete("close"); } else { @@ -298,7 +295,6 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com }); // set position for list of suggestions box this.$input.autocomplete( "option", "position", { my : "left top", at: "left bottom" } ); - this.$input.autocomplete("widget").openerpClass(); // used to correct a bug when selecting an element by pushing 'enter' in an editable list this.$input.keyup(function(e) { if (e.which === 13) { // ENTER @@ -311,7 +307,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com }, render_value: function(no_recurse) { var self = this; - if (! this.get("value")) { + if (!this.get("value")) { this.display_string(""); return; } @@ -320,7 +316,7 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com this.display_string(display); return; } - if (! no_recurse) { + if (!no_recurse) { var dataset = new data.DataSetStatic(this, this.field.relation, self.build_context()); var def = this.alive(dataset.name_get([self.get("value")])).done(function(data) { if (!data[0]) { @@ -341,38 +337,33 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com } }, display_string: function(str) { - var self = this; if (!this.get("effective_readonly")) { this.$input.val(str.split("\n")[0]); this.current_display = this.$input.val(); - if (this.is_false()) { - this.$('.oe_m2o_cm_button').css({'display':'none'}); - } else { - this.$('.oe_m2o_cm_button').css({'display':'inline'}); - } + this.$follow_button.toggle(!this.is_false()); + this.$el.toggleClass('o_with_button', !!this.$follow_button && this.$follow_button.length > 0 && !this.is_false()); } else { - var lines = _.escape(str).split("\n"); - var link = ""; - var follow = ""; - link = lines[0]; - follow = _.rest(lines).join("<br />"); - if (follow) - link += "<br />"; - var $link = this.$el.find('.oe_form_uri') - .unbind('click') - .html(link); - if (! this.options.no_open) - $link.click(function () { - var context = self.build_context().eval(); - var model_obj = new Model(self.field.relation); - model_obj.call('get_formview_action', [self.get("value"), context]).then(function(action){ - self.do_action(action); - }); - return false; - }); - $(".oe_form_m2o_follow", this.$el).html(follow); + this.$el.html(_.escape(str).split("\n").join("<br/>")); + // Define callback to perform when clicking on the field + if (!this.options.no_open) { + // Remove potential previously added event handler + this.$el.off('click'); + // Ensure that the callback is performed only once even with multiple clicks + var execute_formview_action_once = _.once(this.execute_formview_action.bind(this)); + this.$el.click(function (ev) { + ev.preventDefault(); + execute_formview_action_once(); + }); + } } }, + execute_formview_action: function() { + var self = this; + var context = self.build_context().eval(); + (new Model(self.field.relation)).call('get_formview_action', [self.get("value"), context]).then(function(action) { + self.do_action(action); + }); + }, set_value: function(value_) { if (value_ instanceof Array) { this.display_value = {}; @@ -418,11 +409,6 @@ var FieldMany2One = common.AbstractField.extend(common.CompletionFieldMixin, com this.no_ed = false; return res; }, - set_dimensions: function (height, width) { - this._super(height, width); - if (!this.get("effective_readonly") && this.$input) - this.$input.css('height', height); - } }); /** @@ -586,40 +572,39 @@ var AbstractManyField = common.AbstractField.extend({ _.each(command_list, function(command) { self.mutex.exec(function() { var id = command[1]; - if (!id || _.isString(id) || _.isNumber(id)) { - switch (command[0]) { - case COMMANDS.CREATE: - var data = _.clone(command[2]); - delete data.id; - return dataset.create(data, internal_options).then(function (id) { + switch (command[0]) { + case COMMANDS.CREATE: + var data = _.clone(command[2]); + delete data.id; + return dataset.create(data, internal_options).then(function (id) { + dataset.ids.push(id); + res = id; + }); + case COMMANDS.UPDATE: + return dataset.write(id, command[2], internal_options).then(function () { + if (dataset.ids.indexOf(id) === -1) { dataset.ids.push(id); res = id; - }); - case COMMANDS.UPDATE: - return dataset.write(id, command[2], internal_options).then(function () { - if (dataset.ids.indexOf(id) === -1) { - dataset.ids.push(id); - res = id; - } - }); - case COMMANDS.FORGET: - return dataset.unlink([id]); - case COMMANDS.DELETE: - return dataset.unlink([id]); - case COMMANDS.LINK_TO: - if (dataset.ids.indexOf(id) === -1) { - return dataset.add_ids([id], internal_options); } - return; - case COMMANDS.DELETE_ALL: - return dataset.reset_ids([], {keep_read_data: true}); - case COMMANDS.REPLACE_WITH: - dataset.ids = []; - return dataset.alter_ids(command[2], internal_options); - } + }); + case COMMANDS.FORGET: + return dataset.unlink([id]); + case COMMANDS.DELETE: + return dataset.unlink([id]); + case COMMANDS.LINK_TO: + if (dataset.ids.indexOf(id) === -1) { + return dataset.add_ids([id], internal_options); + } + return; + case COMMANDS.DELETE_ALL: + return dataset.reset_ids([], {keep_read_data: true}); + case COMMANDS.REPLACE_WITH: + dataset.ids = []; + return dataset.alter_ids(command[2], internal_options); + default: + throw new Error("send_commands to '"+self.name+"' receive a non command value." + + "\n" + JSON.stringify(command_list)); } - throw new Error("send_commands to '"+self.name+"' receive a non command value." + - "\n" + JSON.stringify(command_list)); }); }); @@ -660,10 +645,6 @@ var AbstractManyField = common.AbstractField.extend({ if (record.to_create) { command_list.push(COMMANDS.create(values)); } else { - if (index === -1) { - // because the UPDATE below does not imply LINK_TO - command_list.push(COMMANDS.link_to(id)); - } command_list.push(COMMANDS.update(record.id, values)); } return; @@ -724,13 +705,12 @@ var FieldX2Many = AbstractManyField.extend({ }, start: function() { this._super.apply(this, arguments); - this.$el.addClass('oe_form_field'); - var self = this; this.load_views(); var destroy = function() { self.is_loaded = self.is_loaded.then(function() { + self.renderElement(); self.viewmanager.destroy(); return $.when(self.load_views()).done(function() { self.reload_current_view(); @@ -828,7 +808,8 @@ var FieldX2Many = AbstractManyField.extend({ }); }); $.async_when().done(function () { - self.alive( self.viewmanager.appendTo(self.$el)); + self.$el.addClass('o_view_manager_content'); + self.alive(self.viewmanager.attachTo(self.$el)); }); return def; }, @@ -881,6 +862,9 @@ var FieldX2Many = AbstractManyField.extend({ } return true; }, + is_false: function() { + return false; + }, }); var X2ManyDataSet = data.BufferedDataSet.extend({ @@ -891,10 +875,15 @@ var X2ManyDataSet = data.BufferedDataSet.extend({ self.context.add(context); }); return this.context; - } + }, }); var X2ManyViewManager = ViewManager.extend({ + custom_events: { + // Catch event scrollTo to prevent scrolling to the top when using the + // pager of List and Kanban views in One2Many fields + 'scrollTo': function() {}, + }, init: function(parent, dataset, views, flags, x2many_views) { // By default, render buttons and pager in X2M fields, but no sidebar flags = _.extend({}, flags, { @@ -984,6 +973,15 @@ var X2ManyListView = ListView.extend({ }); return valid; }, + render_pager: function($node, options) { + options = _.extend(options || {}, { + single_page_hidden: true, + }); + this._super($node, options); + }, + display_nocontent_helper: function () { + return false; + }, }); /** @@ -1008,7 +1006,7 @@ var X2ManyList = ListView.List.extend({ var $cell = $('<td>', { colspan: columns, - 'class': 'oe_form_field_x2many_list_row_add' + 'class': 'o_form_field_x2many_list_row_add' }).append( $('<a>', {href: '#'}).text(_t("Add an item")) .click(function (e) { @@ -1037,7 +1035,6 @@ var X2ManyList = ListView.List.extend({ }); var One2ManyListView = X2ManyListView.extend({ - _template: 'One2Many.listview', init: function () { this._super.apply(this, arguments); this.options = _.extend(this.options, { @@ -1177,7 +1174,7 @@ var One2ManyListView = X2ManyListView.extend({ var click_outside = ($target.closest('.ui-autocomplete,.btn,.modal-backdrop').length === 0); // Check if click inside the current list editable - var $o2m = $target.closest(".oe_list_editable"); + var $o2m = $target.closest(".o_list_editable"); if($o2m.length && $o2m[0] === this.el) { click_outside = false; } @@ -1251,39 +1248,19 @@ var One2ManyGroups = ListView.Groups.extend({ } }); -var One2ManyFormView = FormView.extend({ - form_template: 'One2Many.formview', - load_form: function(data) { - this._super(data); - var self = this; - this.$buttons.find('button.oe_form_button_create').click(function() { - self.save().done(self.on_button_new); - }); - }, - do_notify_change: function() { - if (this.dataset.parent_view) { - this.dataset.parent_view.do_notify_change(); - } else { - this._super.apply(this, arguments); - } - } -}); - var FieldOne2Many = FieldX2Many.extend({ init: function() { this._super.apply(this, arguments); this.x2many_views = { - form: One2ManyFormView, kanban: core.view_registry.get('one2many_kanban'), list: One2ManyListView, }; }, start: function() { - this.$el.addClass('oe_form_field_one2many'); + this.$el.addClass('o_form_field_one2many'); return this._super.apply(this, arguments); }, commit_value: function() { - var self = this; var view = this.viewmanager.active_view; if(view.type === "list" && view.controller.editable()) { return this.mutex.def.then(function () { @@ -1357,7 +1334,7 @@ var FieldMany2Many = FieldX2Many.extend({ }; }, start: function() { - this.$el.addClass('oe_form_field_many2many'); + this.$el.addClass('o_form_field_many2many'); return this._super.apply(this, arguments); } }); @@ -1373,7 +1350,7 @@ var FieldMany2ManyKanban = FieldMany2Many.extend({ }); var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, common.ReinitializeFieldMixin, { - className: "oe_form_field o_form_field_many2manytags", + className: "o_form_field_many2manytags", tag_template: "FieldMany2ManyTag", events: { @@ -1435,7 +1412,6 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c } }, get_render_data: function(ids){ - var self = this; this.dataset.cancel_read(); var fields = this.fields.color ? ['display_name', 'name', 'color'] : ['display_name', 'name']; // TODO master: remove useless 'name' return this.dataset.read_ids(ids, fields); @@ -1475,26 +1451,30 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c } return false; }, - set_dimensions: function (height, width) { - this._super(height, width); - var $input = this.$('input'); - if (!this.get("effective_readonly") && $input) { - $input.css('height', height); + set_dimensions: function(height, width) { + if(this.many2one) { + this.many2one.$el.css('height', 'auto'); + } + this.$el.css({ + width: width, + minHeight: height, + }); + if(this.many2one) { + this.many2one.$el.css('height', this.$el.height()); } }, open_color_picker: function(ev){ - this.mutex.exec(function(){ - if (this.fields.color) { - this.$color_picker = $(QWeb.render('FieldMany2ManyTag.colorpicker', { - 'widget': this, - 'tag_id': $(ev.currentTarget).data('id'), - })); - $(ev.currentTarget).append(this.$color_picker); - this.$color_picker.dropdown('toggle'); - this.$color_picker.attr("tabindex", 1).focus(); - } - }.bind(this)); + if (this.fields.color) { + this.$color_picker = $(QWeb.render('FieldMany2ManyTag.colorpicker', { + 'widget': this, + 'tag_id': $(ev.currentTarget).data('id'), + })); + + $(ev.currentTarget).append(this.$color_picker); + this.$color_picker.dropdown('toggle'); + this.$color_picker.attr("tabindex", 1).focus(); + } }, close_color_picker: function(){ this.$color_picker.remove(); @@ -1513,24 +1493,76 @@ var FieldMany2ManyTags = AbstractManyField.extend(common.CompletionFieldMixin, c var old_color = tag.data('color'); tag.removeClass('o_tag_color_' + old_color); tag.data('color', color); - tag.addClass('o_tag_color_' + color); + tag.addClass('o_tag_color_' + color); }); }, - }); /** * Widget for (many2many field) to upload one or more file in same time and display in list. * The user can delete his files. - * Options on attribute ; "blockui" {Boolean} block the UI or not - * during the file is uploading + * Options on attribute ; "blockui" {Boolean} block the UI or not during the file is uploading */ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.ReinitializeFieldMixin, { template: "FieldBinaryFileUploader", + events: { + 'click .o_attach': function(e) { + this.$('.o_form_input_file').click(); + }, + 'change .o_form_input_file': function(e) { + e.stopPropagation(); + + var $target = $(e.target); + var value = $target.val(); + + if(value !== '') { + if(this.data[0] && this.data[0].upload) { // don't upload more of one file in same time + return false; + } + + var filename = value.replace(/.*[\\\/]/, ''); + for(var id in this.get('value')) { + // if the files exits, delete the file before upload (if it's a new file) + if(this.data[id] && (this.data[id].filename || this.data[id].name) == filename && !this.data[id].no_unlink) { + this.ds_file.unlink([id]); + } + } + + if(this.node.attrs.blockui > 0) { // block UI or not + framework.blockUI(); + } + + // TODO : unactivate send on wizard and form + + // submit file + this.$('form.o_form_binary_form').submit(); + this.$(".oe_fileupload").hide(); + // add file on data result + this.data[0] = { + id: 0, + name: filename, + filename: filename, + url: '', + upload: true, + }; + } + }, + 'click .oe_delete': function(e) { + e.preventDefault(); + e.stopPropagation(); + + var file_id = $(e.currentTarget).data("id"); + if(file_id) { + var files = _.without(this.get('value'), file_id); + if(!this.data[file_id].no_unlink) { + this.ds_file.unlink([file_id]); + } + this.set({'value': files}); + } + }, + }, init: function(field_manager, node) { - this._super(field_manager, node); - this.field_manager = field_manager; - this.node = node; + this._super.apply(this, arguments); this.session = session; if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') { throw _.str.sprintf(_t("The type of the field '%s' must be a many2many field with a relation to 'ir.attachment' model."), this.field.string); @@ -1541,40 +1573,39 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ this.fileupload_id = _.uniqueId('oe_fileupload_temp'); $(window).on(this.fileupload_id, _.bind(this.on_file_loaded, this)); }, - initialize_content: function() { - this.$el.on('change', 'input.o_form_input_file', this.on_file_change ); - }, - get_file_url: function (attachment) { + get_file_url: function(attachment) { return '/web/content/' + attachment.id + '?download=true'; }, - read_name_values : function () { + read_name_values : function() { var self = this; // don't reset know values var ids = this.get('value'); - var _value = _.filter(ids, function (id) { return typeof self.data[id] == 'undefined'; } ); + var _value = _.filter(ids, function(id) { return self.data[id] === undefined; }); // send request for get_name - if (_value.length) { - return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname', 'mimetype']]).then(function (datas) { - _.each(datas, function (data) { - data.no_unlink = true; - data.url = self.get_file_url(data); - self.data[data.id] = data; - }); - return ids; - }); + if(_value.length) { + return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname', 'mimetype']]) + .then(process_data); } else { return $.when(ids); } + + function process_data(datas) { + _.each(datas, function(data) { + data.no_unlink = true; + data.url = self.get_file_url(data); + self.data[data.id] = data; + }); + return ids; + } }, - render_value: function () { + render_value: function() { var self = this; this.read_name_values().then(function (ids) { - var render = $(QWeb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids})); - render.on('click', '.oe_delete', _.bind(self.on_file_delete, self)); - self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render ); + self.$('.oe_placeholder_files, .oe_attachments') + .replaceWith($(QWeb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}))); // reinit input type file - var $input = self.$('input.o_form_input_file'); + var $input = self.$('.o_form_input_file'); $input.after($input.clone(true)).remove(); self.$(".oe_fileupload").show(); @@ -1587,77 +1618,26 @@ var FieldMany2ManyBinaryMultiFiles = AbstractManyField.extend(common.Reinitializ }); }); }, - on_file_change: function (event) { - event.stopPropagation(); - var self = this; - var $target = $(event.target); - if ($target.val() !== '') { - var filename = $target.val().replace(/.*[\\\/]/,''); - // don't uplode more of one file in same time - if (self.data[0] && self.data[0].upload ) { - return false; - } - for (var id in this.get('value')) { - // if the files exits, delete the file before upload (if it's a new file) - if (self.data[id] && (self.data[id].filename || self.data[id].name) == filename && !self.data[id].no_unlink ) { - self.ds_file.unlink([id]); - } - } - - // block UI or not - if(this.node.attrs.blockui>0) { - framework.blockUI(); - } - - // TODO : unactivate send on wizard and form - - // submit file - this.$('form.o_form_binary_form').submit(); - this.$(".oe_fileupload").hide(); - // add file on data result - this.data[0] = { - 'id': 0, - 'name': filename, - 'filename': filename, - 'url': '', - 'upload': true - }; - } - }, - on_file_loaded: function (event, result) { - var files = this.get('value'); - - // unblock UI - if(this.node.attrs.blockui>0) { + on_file_loaded: function(e, result) { + if(this.node.attrs.blockui > 0) { // unblock UI framework.unblockUI(); } - if (result.error || !result.id ) { - this.do_warn( _t('Uploading Error'), result.error); + if(result.error || !result.id) { + this.do_warn(_t('Uploading Error'), result.error); delete this.data[0]; } else { - if (this.data[0] && this.data[0].filename == result.filename && this.data[0].upload) { + if(this.data[0] && this.data[0].filename === result.filename && this.data[0].upload) { delete this.data[0]; } result.url = this.get_file_url(result); this.data[result.id] = result; var values = _.clone(this.get('value')); values.push(result.id); - this.set({'value': values}); + this.set({value: values}); } this.render_value(); }, - on_file_delete: function (event) { - event.stopPropagation(); - var file_id=$(event.target).closest('.oe_delete').data("id"); - if (file_id) { - var files = _.filter(this.get('value'), function (id) {return id != file_id;}); - if(!this.data[file_id].no_unlink) { - this.ds_file.unlink([file_id]); - } - this.set({'value': files}); - } - }, }); /* @@ -1672,7 +1652,7 @@ var FieldMany2ManyCheckBoxes = AbstractManyField.extend(common.ReinitializeField this.set("records", []); this.field_manager.on("view_content_has_changed", this, function() { var domain = new data.CompoundDomain(this.build_domain()).eval(); - if (! _.isEqual(domain, this.get("domain"))) { + if (!_.isEqual(domain, this.get("domain"))) { this.set("domain", domain); } }); @@ -1694,18 +1674,22 @@ var FieldMany2ManyCheckBoxes = AbstractManyField.extend(common.ReinitializeField }); }, render_value: function() { - this.$().html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this})); - var inputs = this.$("input"); - inputs.change(_.bind(this.from_dom, this)); - if (this.get("effective_readonly")) - inputs.attr("disabled", "true"); + this.$el.html(QWeb.render("FieldMany2ManyCheckBoxes", {widget: this})); + var $inputs = this.$("input"); + $inputs.change(_.bind(this.from_dom, this)); + if (this.get("effective_readonly")) { + $inputs.attr("disabled", "true"); + } }, from_dom: function() { var new_value = this.$("input:checked").map(function() { return +$(this).data("record-id"); }).get(); - if (! _.isEqual(new_value, this.get("value"))) { + if (!_.isEqual(new_value, this.get("value"))) { this.internal_set_value(new_value); } - } + }, + is_false: function() { + return false; + }, }); core.form_widget_registry @@ -1718,9 +1702,13 @@ core.form_widget_registry .add('many2many_binary', FieldMany2ManyBinaryMultiFiles) .add('many2many_checkboxes', FieldMany2ManyCheckBoxes); +core.one2many_view_registry + .add('list', One2ManyListView); + return { FieldMany2ManyTags: FieldMany2ManyTags, AbstractManyField: AbstractManyField, + FieldMany2One: FieldMany2One, }; }); diff --git a/addons/web/static/src/js/views/form_upgrade_widgets.js b/addons/web/static/src/js/views/form_upgrade_widgets.js new file mode 100644 index 00000000000..ce32ef0664b --- /dev/null +++ b/addons/web/static/src/js/views/form_upgrade_widgets.js @@ -0,0 +1,101 @@ +odoo.define('web.form_upgrade_widgets', function (require) { +"use strict"; + +var core = require('web.core'); +var Dialog = require('web.Dialog'); +var form_widgets = require('web.form_widgets'); +var framework = require('web.framework'); +var Model = require('web.DataModel'); + +var _t = core._t; +var QWeb = core.qweb; + +/** + * This widget is intended to be used in config settings. + * When checked, an upgrade popup is showed to the user. + */ +var AbstractFieldUpgrade = { + events: { + 'click input': 'on_click_input', + }, + + start: function() { + this._super.apply(this, arguments); + + this.get_enterprise_label().after($("<span>", { + text: "Enterprise", + 'class': "label label-primary oe_inline" + })); + }, + + open_dialog: function() { + var message = $(QWeb.render('EnterpriseUpgrade')); + + var buttons = [ + { + text: _t("Upgrade now"), + classes: 'btn-primary', + close: true, + click: this.confirm_upgrade, + }, + { + text: _t("Cancel"), + close: true, + }, + ]; + + return new Dialog(this, { + size: 'medium', + buttons: buttons, + $content: $('<div>', { + html: message, + }), + title: _t("Odoo Enterprise"), + }).open(); + }, + + confirm_upgrade: function() { + new Model("res.users").call("search_count", [[["share", "=", false]]]).then(function(data) { + framework.redirect("https://www.odoo.com/odoo-enterprise/upgrade?num_users=" + data); + }); + }, + + get_enterprise_label: function() {}, + on_click_input: function() {}, +}; + +var UpgradeBoolean = form_widgets.FieldBoolean.extend(AbstractFieldUpgrade, { + template: "FieldUpgradeBoolean", + + get_enterprise_label: function() { + return this.$label; + }, + + on_click_input: function() { + if(this.$checkbox.prop("checked")) { + this.open_dialog().on('closed', this, function() { + this.$checkbox.prop("checked", false); + }); + } + }, +}); + +var UpgradeRadio = form_widgets.FieldRadio.extend(AbstractFieldUpgrade, { + get_enterprise_label: function() { + return this.$('label').last(); + }, + + on_click_input: function(event) { + if($(event.target).val() === 1) { + this.open_dialog().on('closed', this, function() { + this.$('input').first().prop("checked", true); + }); + } + }, +}); + +core.form_widget_registry + .add('upgrade_boolean', UpgradeBoolean) + .add('upgrade_radio', UpgradeRadio); + +}); diff --git a/addons/web/static/src/js/views/form_view.js b/addons/web/static/src/js/views/form_view.js index 7a2cf428400..d2c7a21eb04 100644 --- a/addons/web/static/src/js/views/form_view.js +++ b/addons/web/static/src/js/views/form_view.js @@ -1,12 +1,14 @@ odoo.define('web.FormView', function (require) { "use strict"; +var common = require('web.form_common'); var core = require('web.core'); var crash_manager = require('web.crash_manager'); var data = require('web.data'); var Dialog = require('web.Dialog'); -var common = require('web.form_common'); +var FormRenderingEngine = require('web.FormRenderingEngine'); var Model = require('web.DataModel'); +var Pager = require('web.Pager'); var Sidebar = require('web.Sidebar'); var utils = require('web.utils'); var View = require('web.View'); @@ -14,7 +16,6 @@ var View = require('web.View'); var _t = core._t; var _lt = core._lt; var QWeb = core.qweb; -var Class = core.Class; /** * Properties: @@ -22,7 +23,7 @@ var Class = core.Class; * the mode used by the view. */ var FormView = View.extend(common.FieldManagerMixin, { - accesskey: "F", + className: "o_form_view", defaults: _.extend({}, View.prototype.defaults, { not_interactible_on_create: false, initial_mode: "view", @@ -34,8 +35,20 @@ var FormView = View.extend(common.FieldManagerMixin, { multi_record: false, // Indicates that this view is not searchable, and thus that no search view should be displayed. searchable: false, - template: "FormView", + /** + * Called each time the form view is attached into the DOM + */ + on_attach_callback: function() { + this.trigger('attached'); + this.autofocus(); + }, + /** + * Called each time the form view is detached from the DOM + */ + on_detach_callback: function() { + this.trigger('detached'); + }, init: function() { var self = this; this._super.apply(this, arguments); @@ -60,14 +73,11 @@ var FormView = View.extend(common.FieldManagerMixin, { this.__clicked_inside = false; this.__blur_timeout = null; this.rendering_engine = new FormRenderingEngine(this); - self.set({actual_mode: self.options.initial_mode}); + this.set({actual_mode: this.options.initial_mode}); this.has_been_loaded.done(function() { self._build_onchange_specs(); - self.check_actual_mode(); - self.on("change:actual_mode", self, self.check_actual_mode); self.on("change:actual_mode", self, self.toggle_buttons); self.on("change:actual_mode", self, self.toggle_sidebar); - self.on("change:actual_mode", self, self.do_update_pager); }); self.on("load_record", self, self.load_record); core.bus.on('clear_uncommitted_changes', this, function(chain_callbacks) { @@ -90,8 +100,7 @@ var FormView = View.extend(common.FieldManagerMixin, { this.rendering_engine.set_tags_registry(this.tags_registry); this.rendering_engine.set_widgets_registry(this.widgets_registry); this.rendering_engine.set_fields_view(this.fields_view); - var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); - this.rendering_engine.render_to($dest); + this.rendering_engine.render_to(this.$el); this.$el.on('mousedown.formBlur', function () { self.__clicked_inside = true; @@ -100,22 +109,14 @@ var FormView = View.extend(common.FieldManagerMixin, { this.has_been_loaded.resolve(); // Add bounce effect on button 'Edit' when click on readonly page view. - this.$el.find(".oe_form_group_row,.oe_form_field,label,h1,.oe_title,.oe_notebook_page, .oe_list_content").on('click', function (e) { + this.$(".oe_title,.o_group").on('mouseup', function (e) { // 'mouseup' event because some widget need bootstrap click event to go up to body if(self.get("actual_mode") === "view" && self.$buttons && !$(e.target).is('[data-toggle]')) { - var $button = self.$buttons.find(".oe_form_button_edit"); + var $button = self.$buttons.find(".o_form_button_edit"); $button.openerpBounce(); e.stopPropagation(); core.bus.trigger('click', e); } }); - //bounce effect on red button when click on statusbar. - this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) { - if((self.get("actual_mode") === "view")) { - var $button = self.$el.find(".oe_highlight:not(.o_form_invisible)").css({'float':'left','clear':'none'}); - $button.openerpBounce(); - e.stopPropagation(); - } - }); return this._super(); }, destroy: function() { @@ -133,10 +134,10 @@ var FormView = View.extend(common.FieldManagerMixin, { * Set this.$buttons with the produced jQuery element * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted * $node may be undefined, in which case the FormView inserts them into this.options.$buttons - * or into a div of its template + * if it exists */ render_buttons: function($node) { - this.$buttons = $('<div/>', {'class': 'oe_form_buttons'}); + this.$buttons = $('<div/>'); var $footer = this.$('footer'); if (this.options.action_buttons !== false || this.options.footer_to_buttons && $footer.children().length === 0) { @@ -148,23 +149,17 @@ var FormView = View.extend(common.FieldManagerMixin, { // Show or hide the buttons according to the view mode this.toggle_buttons(); - this.$buttons.on('click', '.oe_form_button_create', this.on_button_create); - this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit); - this.$buttons.on('click', '.oe_form_button_save', this.on_button_save); - this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel); + this.$buttons.on('click', '.o_form_button_create', this.on_button_create); + this.$buttons.on('click', '.o_form_button_edit', this.on_button_edit); + this.$buttons.on('click', '.o_form_button_save', this.on_button_save); + this.$buttons.on('click', '.o_form_button_cancel', this.on_button_cancel); - if ($node) { - this.$buttons.appendTo($node); - } else { - this.$('.oe_form_buttons').replaceWith(this.$buttons); - } + this.$buttons.appendTo($node); }, /** * Instantiate and render the sidebar if a sidebar is requested * Sets this.sidebar * @param {jQuery} [$node] a jQuery node where the sidebar should be inserted - * $node may be undefined, in which case the FormView inserts the sidebar in a - * div of its template **/ render_sidebar: function($node) { if (!this.sidebar && this.options.sidebar) { @@ -177,7 +172,6 @@ var FormView = View.extend(common.FieldManagerMixin, { this.is_action_enabled('create') && { label: _t('Duplicate'), callback: this.on_button_duplicate } ])); - $node = $node || this.$('.oe_form_sidebar'); this.sidebar.appendTo($node); // Show or hide the sidebar according to the view mode @@ -185,41 +179,33 @@ var FormView = View.extend(common.FieldManagerMixin, { } }, /** - * Render the pager according to the FormView.pager template and add listeners on it. - * Set this.$pager with the produced jQuery element - * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted + * Instantiate and render the pager and add listeners on it. + * Set this.pager + * @param {jQuery} [$node] a jQuery node where the pager should be inserted * $node may be undefined, in which case the FormView inserts the pager into this.options.$pager - * or into a div of its template */ render_pager: function($node) { if (this.options.pager) { var self = this; - if (this.$pager) { - this.$pager.remove(); - } - if (this.get("actual_mode") === "create") { - return; - } - this.$pager = $(QWeb.render("FormView.pager", {'widget': self})); - this.$pager.on('click','a[data-pager-action]',function() { - var $el = $(this); - if ($el.attr("disabled")) { - return; - } - var action = $el.data('pager-action'); - var def = $.when(self.execute_pager_action(action)); - $el.attr("disabled"); - def.always(function() { - $el.removeAttr("disabled"); + var options = { + validate: _.bind(this.can_be_discarded, this), + }; + + this.pager = new Pager(this, this.dataset.ids.length, this.dataset.index + 1, 1, options); + this.pager.on('pager_changed', this, function (new_state) { + this.pager.disable(); + this.dataset.index = new_state.current_min - 1; + this.trigger('pager_action_executed'); + $.when(this.reload()).then(function () { + self.pager.enable(); }); }); - this.do_update_pager(); - $node = $node || this.options.$pager; - if ($node) { - this.$pager.appendTo($node); - } else { - this.$('.oe_form_pager').replaceWith(this.$pager); + this.pager.appendTo($node = $node || this.options.$pager); + + // Hide the pager in create mode + if (this.get("actual_mode") === "create") { + this.pager.do_hide(); } } }, @@ -241,6 +227,16 @@ var FormView = View.extend(common.FieldManagerMixin, { this.sidebar.do_toggle(this.get("actual_mode") === "view"); } }, + update_pager: function() { + if (this.pager) { + // Hide the pager in create mode + if (this.get("actual_mode") === "create") { + this.pager.do_hide(); + } else { + this.pager.update_state({size: this.dataset.ids.length, current_min: this.dataset.index + 1}); + } + } + }, widgetFocused: function() { // Clear click flag if used to focus a widget this.__clicked_inside = false; @@ -281,11 +277,6 @@ var FormView = View.extend(common.FieldManagerMixin, { do_show: function (options) { var self = this; options = options || {}; - this._super(); - this.$el.css({ - opacity: '0', - filter: 'alpha(opacity = 0)' - }); this.$el.removeClass('oe_form_dirty'); var shown = this.has_been_loaded; @@ -307,10 +298,6 @@ var FormView = View.extend(common.FieldManagerMixin, { } return $.when(shown, this._super()).then(function() { self._actualize_mode(options.mode || self.options.initial_mode); - self.$el.css({ - opacity: '1', - filter: 'alpha(opacity = 100)' - }); core.bus.trigger('form_view_shown', self); }); }, @@ -324,6 +311,7 @@ var FormView = View.extend(common.FieldManagerMixin, { this.datarecord = record; this._actualize_mode(); this.set({ 'title' : record.id ? record.display_name : _t("New") }); + this.update_pager(); this.record_loaded = $.Deferred(); _(this.fields).each(function (field, f) { @@ -342,19 +330,17 @@ var FormView = View.extend(common.FieldManagerMixin, { self.rendering_engine.init_fields().then(function() { self.is_initialized.resolve(); self.record_loaded.resolve(); - self.do_update_pager(record.id === null || record.id === undefined); if (self.sidebar) { - self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); + self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); } if (record.id) { self.do_push_state({id:record.id}); } else { self.do_push_state({}); } - self.$el.removeClass('oe_form_dirty'); - self.autofocus(); + self.$el.removeClass('oe_form_dirty'); }); - }); + }); }, /** * Loads and sets up the default values for the model as the current @@ -370,7 +356,7 @@ var FormView = View.extend(common.FieldManagerMixin, { self.trigger('load_record', _.clone(r)); }); } - return self.trigger('load_record', {}); + return $.when().then(this.trigger.bind(this, 'load_record', {})); }, on_form_changed: function() { this.trigger("view_content_has_changed"); @@ -378,40 +364,6 @@ var FormView = View.extend(common.FieldManagerMixin, { do_notify_change: function() { this.$el.addClass('oe_form_dirty'); }, - execute_pager_action: function(action) { - var self = this; - return this.can_be_discarded().then(function() { - switch (action) { - case 'first': - self.dataset.index = 0; - break; - case 'previous': - self.dataset.previous(); - break; - case 'next': - self.dataset.next(); - break; - case 'last': - self.dataset.index = self.dataset.ids.length - 1; - break; - } - self.trigger('pager_action_executed'); - return self.reload(); - }); - }, - do_update_pager: function(hide_index) { - if (this.$pager) { - // Hide the pager in create mode or when there is only one record - var pager_visible = (this.get("actual_mode") !== "create") && (this.dataset.ids.length > 1); - this.$pager.toggle(pager_visible); - if (hide_index === true) { - this.$pager.find(".oe_form_pager_state").html(""); - } else { - this.$pager.find(".oe_form_pager_state").html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); - } - } - }, - _build_onchange_specs: function() { var self = this; var find = function(field_name, root) { @@ -664,15 +616,15 @@ var FormView = View.extend(common.FieldManagerMixin, { } else if (mode === "create") { mode = "edit"; } + + var viewMode = (mode === "view"); + this.$el.toggleClass('o_form_readonly', viewMode).toggleClass('o_form_editable', !viewMode); + this.render_value_defs = []; this.set({actual_mode: mode}); - }, - check_actual_mode: function(source, options) { - if(this.get("actual_mode") === "view") { - this.$el.removeClass('oe_form_editable').addClass('oe_form_readonly'); - } else { - this.$el.removeClass('oe_form_readonly').addClass('oe_form_editable'); - _.defer(_.bind(this.autofocus, this)); + + if(!viewMode) { + this.autofocus(); } }, autofocus: function() { @@ -699,7 +651,7 @@ var FormView = View.extend(common.FieldManagerMixin, { this.$('.oe_form_buttons').add(this.$buttons).find('button.o_disabled').removeClass('o_disabled').prop('disabled', false); this.is_disabled = false; }, - on_button_save: function(e) { + on_button_save: function() { var self = this; if (this.is_disabled) { return; @@ -716,7 +668,7 @@ var FormView = View.extend(common.FieldManagerMixin, { self.enable_button(); }); }, - on_button_cancel: function(event) { + on_button_cancel: function() { var self = this; this.can_be_discarded().then(function() { if (self.get('actual_mode') === 'create') { @@ -733,10 +685,11 @@ var FormView = View.extend(common.FieldManagerMixin, { }, on_button_new: function() { var self = this; - this.to_edit_mode(); return $.when(this.has_been_loaded).then(function() { return self.can_be_discarded().then(function() { - return self.load_defaults(); + return self.load_defaults().then(function() { + self.to_edit_mode(); + }); }); }); }, @@ -763,7 +716,8 @@ var FormView = View.extend(common.FieldManagerMixin, { if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) { self.dataset.unlink([self.datarecord.id]).done(function() { if (self.dataset.size()) { - self.execute_pager_action('next'); + self.reload(); + self.update_pager(); } else { self.do_action('history_back'); } @@ -790,7 +744,9 @@ var FormView = View.extend(common.FieldManagerMixin, { title: _t("Warning"), confirm_callback: function() { self.$el.removeClass('oe_form_dirty'); - def.resolve(); + this.on('closed', null, function() { // 'this' is the dialog widget + def.resolve(); + }); }, cancel_callback: function() { def.reject(); @@ -951,7 +907,7 @@ var FormView = View.extend(common.FieldManagerMixin, { this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids)); this.dataset.index = 0; } - this.do_update_pager(); + this.update_pager(); if (this.sidebar) { this.sidebar.do_attachement_update(this.dataset, this.datarecord.id); } @@ -1090,7 +1046,7 @@ var FormView = View.extend(common.FieldManagerMixin, { $box.remove(); }); // add content inside notification box on top of the sheet/form - var $target = this.$('.oe_form_sheet').length ? this.$('.oe_form_sheet') : this.$el; + var $target = this.$('.o_form_sheet_bg').length ? this.$('.o_form_sheet_bg') : this.$el; $target.prepend($box); }, open_defaults_dialog: function () { @@ -1153,7 +1109,7 @@ var FormView = View.extend(common.FieldManagerMixin, { var $defaults = d.$el.find('#formview_default_fields'); var field_to_set = $defaults.val(); if (!field_to_set) { - $defaults.parent().addClass('oe_form_invalid'); + $defaults.parent().addClass('o_form_invalid'); return; } var condition = d.$el.find('#formview_default_conditions').val(), @@ -1234,21 +1190,29 @@ var FormView = View.extend(common.FieldManagerMixin, { }, }); -/** - * Interface to be implemented by rendering engines for the form view. - */ -var FormRenderingEngineInterface = Class.extend({ - set_fields_view: function(fields_view) {}, - set_fields_registry: function(fields_registry) {}, - render_to: function($el) {}, +core.view_registry.add('form', FormView); + +return FormView; + }); + +odoo.define('web.FormRenderingEngine', function (require) { +"use strict"; + +var common = require('web.form_common'); +var core = require('web.core'); +var utils = require('web.utils'); + +var _t = core._t; +var QWeb = core.qweb; + /** * Default rendering engine for the form view. * * It is necessary to set the view using set_view() before usage. */ -var FormRenderingEngine = FormRenderingEngineInterface.extend({ +return core.Class.extend({ init: function(view) { this.view = view; }, @@ -1289,7 +1253,7 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ $('board', doc).each(function() { $(this).attr('layout', $(this).attr('style')); }); - return $('<div class="oe_form"/>').append(utils.xml_to_str(doc)); + return $(utils.xml_to_str(doc)); }, render_to: function($target) { var self = this; @@ -1305,7 +1269,8 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ this.labels = {}; this.process(this.$form); - this.$form.appendTo(this.$target); + this.$form.contents().appendTo(this.$target); + this.handle_common_properties(this.$target, this.$form); this.to_replace = []; @@ -1382,9 +1347,6 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ args[0] = $tag; return fn.apply(self, args); } else { - if( tagname === 'header') { - $tag.addClass('o_statusbar_buttons'); - } // generic tag handling, just process children $tag.children().each(function() { self.process($(this)); @@ -1408,14 +1370,14 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ process_sheet: function($sheet) { var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes()); this.handle_common_properties($new_sheet, $sheet); - var $dst = $new_sheet.find('.oe_form_sheet'); + var $dst = $new_sheet.find('.o_form_sheet'); $sheet.contents().appendTo($dst); $sheet.before($new_sheet).remove(); this.process($new_sheet); }, process_form: function($form) { if ($form.find('> sheet').length === 0) { - $form.addClass('oe_form_nosheet'); + $form.addClass('o_form_nosheet'); } var $new_form = this.render_element('FormRenderingForm', $form.getAttributes()); this.handle_common_properties($new_form, $form); @@ -1479,119 +1441,99 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ }, process_group: function($group) { var self = this; - $group.children('field').each(function() { - self.preprocess_field($(this)); - }); - var $new_group = this.render_element('FormRenderingGroup', $group.getAttributes()); - var $table; - if ($new_group.first().is('table.oe_form_group')) { - $table = $new_group; - } else if ($new_group.filter('table.oe_form_group').length) { - $table = $new_group.filter('table.oe_form_group').first(); - } else { - $table = $new_group.find('table.oe_form_group').first(); - } - var $tr, $td, - cols = parseInt($group.attr('col') || 2, 10), - row_cols = cols; + // Processes a group containing nested groups + // Renders a div for each nested group, into a div that is the outer group + // The nested groups's width is a percentage according to col and colspan attributes + var _process_outer_group = function(col) { + var $new_group = self.render_element('FormRenderingOuterGroup', $group.getAttributes()); + $group.children().each(function() { + var $child = $(this); + var tagName = $child[0].tagName.toLowerCase(); + var colspan = parseInt($child.attr('colspan') || 1, 10); - var children = []; - $group.children().each(function() { - var $child = $(this); - var colspan = parseInt($child.attr('colspan') || 1, 10); - var tagName = $child[0].tagName.toLowerCase(); - var $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan); - var newline = tagName === 'newline'; - - // Note FME: those classes are used in layout debug mode - if ($tr && row_cols > 0 && (newline || row_cols < colspan)) { - $tr.addClass('oe_form_group_row_incomplete'); - if (newline) { - $tr.addClass('oe_form_group_row_newline'); + if (tagName === 'newline') { + $new_group.append($('<br>')); // Skip to the next line + return; } - } - if (newline) { - $tr = null; - return; - } - if (!$tr || row_cols < colspan) { - $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table); - row_cols = cols; - } else if (tagName==='group') { - // When <group> <group/><group/> </group>, we need a spacing between the two groups - $td.addClass('oe_group_right'); - } - row_cols -= colspan; + // compute child's classname from col and colspan attributes + $child.addClass('o_group_col_' + Math.min(Math.floor(colspan/col*12), 12)); + $new_group.append($child); - // invisibility transfer - var field_modifiers = JSON.parse($child.attr('modifiers') || '{}'); - var invisible = field_modifiers.invisible; - self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible}))); + children.push($child[0]); + }); + return $new_group; + }; + // Processes a group containing no nested group + // Renders an HTML table + var _process_inner_group = function(col) { + var $new_group = self.render_element('FormRenderingInnerGroup', $group.getAttributes()); + $new_group.find('td').attr('colspan', col); // Group's title spans all column + var row_col = col; + var $row = null; - $tr.append($td.append($child)); - children.push($child[0]); - }); - if (row_cols && $td) { - $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols); - } - $group.before($new_group).remove(); + $group.children().each(function() { + var $child = $(this); + var tagName = $child[0].tagName.toLowerCase(); + var colspan = parseInt($child.attr('colspan') || 1, 10); - $table.find('> tbody > tr').each(function() { - var to_compute = [], - row_cols = cols, - total = 100; - $(this).children().each(function() { - var $td = $(this), - $child = $td.children(':first'); - if ($child.attr('cell-class')) { - $td.addClass($child.attr('cell-class')); + if (tagName === 'newline') { + $row = null; // Start with a new row + return; } - switch ($child[0].tagName.toLowerCase()) { - case 'separator': - break; - case 'label': - if ($child.attr('for')) { - $td.attr('width', '1%').addClass('oe_form_group_cell_label'); - row_cols-= $td.attr('colspan') || 1; - total--; - } - break; - default: - var width = _.str.trim($child.attr('width') || ''), - iwidth = parseInt(width, 10); - if (iwidth) { - if (width.substr(-1) === '%') { - total -= iwidth; - width = iwidth + '%'; - } else { - // Absolute width - $td.css('min-width', width + 'px'); - } - $td.attr('width', width); - $child.removeAttr('width'); - row_cols-= $td.attr('colspan') || 1; - } else { - to_compute.push($td); - } - + row_col -= colspan; + if (!$row || row_col < 0) { // Append a new row to the group + $row = $('<tr>').appendTo($new_group); + row_col = col-colspan; } + $('<td>').attr('colspan', colspan).append($child).appendTo($row); + + children.push($child[0]); }); - if (row_cols) { - var unit = Math.floor(total / row_cols); - if (!$(this).is('.oe_form_group_row_incomplete')) { - _.each(to_compute, function($td) { - var width = parseInt($td.attr('colspan'), 10) * unit; - $td.attr('width', width + '%'); - total -= width; - }); - } - } + + // Some kind of an hack + // Add width on table's cell to avoid automatic layout of table which + // makes first col to take full width available (typically, first col contains label) + $new_group.find('tr').each(function() { + var $tr = $(this); + var nonlabel_width = 100/(col - $tr.find('td > label:first-child').length); + $tr.find('td').each(function() { + var $td = $(this); + var $child = $td.children(':first'); + if ($child.is("label")) { + $td.addClass("o_td_label"); + } else { + $td.css("width", (nonlabel_width*parseInt($td.attr('colspan')))+"%"); + } + }); + + }); + return $new_group; + }; + + // Preprocess field children + $group.children('field').each(function() { + self.preprocess_field($(this)); }); + + // Process group + var nested_groups = $group.children('group').length; // Detect possible nested groups + var children = []; + var col = parseInt($group.attr('col') || 2, 10); + var $new_group; + if (nested_groups) { + $new_group = _process_outer_group(col); + } else { + $new_group = _process_inner_group(col); + } + this.handle_common_properties($new_group, $group); + + // Process group's children + $group.before($new_group).remove(); _.each(children, function(el) { self.process($(el)); }); - this.handle_common_properties($new_group, $group); + return $new_group; }, process_notebook: function($notebook) { @@ -1658,17 +1600,8 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ var dict = { string: $label.attr('string') || (field_orm || {}).string || '', help: $label.attr('help') || (field_orm || {}).help || '', - _for: name ? _.uniqueId('oe-field-input-') : undefined, + _for: name ? _.uniqueId('o_field_input_') : undefined, }; - var align = parseFloat(dict.align); - if (isNaN(align) || align === 1) { - align = 'right'; - } else if (align === 0) { - align = 'left'; - } else { - align = 'center'; - } - dict.align = align; var $new_label = this.render_element('FormRenderingLabel', dict); $label.before($new_label).remove(); this.handle_common_properties($new_label, $label); @@ -1698,8 +1631,4 @@ var FormRenderingEngine = FormRenderingEngineInterface.extend({ } }); -core.view_registry.add('form', FormView); - -return FormView; - }); diff --git a/addons/web/static/src/js/views/form_widgets.js b/addons/web/static/src/js/views/form_widgets.js index 2a2a36cdaba..2aeaaa17111 100644 --- a/addons/web/static/src/js/views/form_widgets.js +++ b/addons/web/static/src/js/views/form_widgets.js @@ -5,17 +5,17 @@ var core = require('web.core'); var crash_manager = require('web.crash_manager'); var data = require('web.data'); var datepicker = require('web.datepicker'); +var dom_utils = require('web.dom_utils'); +var Priority = require('web.Priority'); var ProgressBar = require('web.ProgressBar'); var Dialog = require('web.Dialog'); var common = require('web.form_common'); var formats = require('web.formats'); var framework = require('web.framework'); var Model = require('web.DataModel'); -var Priority = require('web.Priority'); var pyeval = require('web.pyeval'); var session = require('web.session'); var utils = require('web.utils'); -var dom_utils = require('web.dom_utils'); var _t = core._t; var QWeb = core.qweb; @@ -119,24 +119,25 @@ var WidgetButton = common.FormWidget.extend({ var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, { template: 'FieldChar', - widget_class: 'oe_form_field_char', events: { - 'change input': 'store_dom_value', + 'change': 'store_dom_value', }, init: function (field_manager, node) { this._super(field_manager, node); this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; }, initialize_content: function() { - this.setupFocus(this.$('input')); + if(!this.get('effective_readonly') && !this.$input) { + this.$input = this.$el; + } + this.setupFocus(this.$el); + }, + destroy_content: function() { + this.$input = undefined; }, store_dom_value: function () { - if (!this.get('effective_readonly') - && this.$('input').length - && this.is_syntax_valid()) { - this.internal_set_value( - this.parse_value( - this.$('input').val())); + if (this.$input && this.is_syntax_valid()) { + this.internal_set_value(this.parse_value(this.$input.val())); } }, commit_value: function () { @@ -145,20 +146,19 @@ var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, { }, render_value: function() { var show_value = this.format_value(this.get('value'), ''); - if (!this.get("effective_readonly")) { - this.$el.find('input').val(show_value); + if (this.$input) { + this.$input.val(show_value); } else { if (this.password) { show_value = new Array(show_value.length + 1).join('*'); } - this.$(".oe_form_char_content").text(show_value); + this.$el.text(show_value); } }, is_syntax_valid: function() { - if (!this.get("effective_readonly") && this.$("input").size() > 0) { + if (this.$input) { try { - this.parse_value(this.$('input').val(), ''); - return true; + this.parse_value(this.$input.val(), ''); } catch(e) { return false; } @@ -175,31 +175,30 @@ var FieldChar = common.AbstractField.extend(common.ReinitializeFieldMixin, { return this.get('value') === '' || this._super(); }, focus: function() { - var input = this.$('input:first')[0]; - return input ? input.focus() : false; - }, - set_dimensions: function (height, width) { - this._super(height, width); - this.$('input').css({ - height: height, - width: width - }); - } + if (this.$input) { + return this.$input.focus(); + } + return false; + }, }); -var KanbanSelection = FieldChar.extend({ +var KanbanSelection = common.AbstractField.extend({ template: "FormSelection", - init: function (field_manager, node) { - this._super(field_manager, node); + events: { + 'click a': function(e) { + e.preventDefault(); + }, + 'mouseup a': function(e) { + e.stopPropagation(); + }, + 'click li': 'set_kanban_selection' }, start: function () { - var self = this; - this.states = []; - this._super.apply(this, arguments); // hook on form view content changed: recompute the states, because it may be related to the current stage - this.getParent().on('view_content_has_changed', self, function () { - self.render_value(); + this.view.on('view_content_has_changed', this, function () { + this.render_value(); }); + return this._super(); }, prepare_dropdown_selection: function() { var self = this; @@ -233,6 +232,7 @@ var KanbanSelection = FieldChar.extend({ this._super(); this.states = this.prepare_dropdown_selection(); var self = this; + // Adapt "FormSelection" var current_state = _.find(this.states, function(state) { return state.name === self.get('value'); @@ -248,7 +248,6 @@ var KanbanSelection = FieldChar.extend({ var $dropdown = this.$el.find('.dropdown-menu'); $dropdown.children().remove(); // remove old items $items.appendTo($dropdown); - this.$el.find('a').on('click', this.set_kanban_selection.bind(this)); }, /* setting the value: in view mode, perform an asynchronous call and reload the form view; in edit mode, use set_value to save the new value that will @@ -259,7 +258,7 @@ var KanbanSelection = FieldChar.extend({ var li = $(ev.target).closest('li'); if (li.length) { var value = String(li.data('value')); - if (this.view.get('actual_mode') == 'view') { + if (this.view.get('actual_mode') === 'view') { var write_values = {}; write_values[self.name] = value; return this.view.dataset._model.call( @@ -339,66 +338,35 @@ var FieldID = FieldChar.extend({ var FieldEmail = FieldChar.extend({ template: 'FieldEmail', - initialize_content: function() { - this._super(); - var $button = this.$el.find('button'); - $button.click(this.on_button_clicked); - this.setupFocus($button); + prefix: 'mailto', + init: function() { + this._super.apply(this, arguments); + this.clickable = true; }, render_value: function() { - if (!this.get("effective_readonly")) { - this._super(); - } else { - this.$el.find('a') - .attr('href', 'mailto:' + this.get('value')) - .text(this.get('value') || ''); - } - }, - on_button_clicked: function() { - if (!this.get('value') || !this.is_syntax_valid()) { - this.do_warn(_t("E-mail Error"), _t("Can't send email to invalid e-mail address")); - } else { - location.href = 'mailto:' + this.get('value'); + this._super(); + if (this.get("effective_readonly") && this.clickable) { + this.$el.attr('href', this.prefix + ':' + this.get('value')); } } }); -var FieldUrl = FieldChar.extend({ - template: 'FieldUrl', - initialize_content: function() { - this._super(); - var $button = this.$el.find('button'); - $button.click(this.on_button_clicked); - this.setupFocus($button); - }, +var FieldUrl = FieldEmail.extend({ render_value: function() { - if (!this.get("effective_readonly")) { - this._super(); - } else { + this._super(); + if(this.get("effective_readonly")) { var tmp = this.get('value'); var s = /(\w+):(.+)|^\.{0,2}\//.exec(tmp); if (!s) { tmp = "http://" + this.get('value'); } var text = this.get('value') ? this.node.attrs.text || tmp : ''; - this.$el.find('a').attr('href', tmp).text(text); - } - }, - on_button_clicked: function() { - if (!this.get('value')) { - this.do_warn(_t("Resource Error"), _t("This resource is empty")); - } else { - var url = $.trim(this.get('value')); - if(/^www\./i.test(url)) - url = 'http://'+url; - window.open(url); + this.$el.attr('href', tmp).text(text); } } }); var FieldFloat = FieldChar.extend({ - is_field_number: true, - widget_class: 'oe_form_field_float', init: function (field_manager, node) { this._super(field_manager, node); this.internal_set_value(0); @@ -408,19 +376,18 @@ var FieldFloat = FieldChar.extend({ this.digits = this.field.digits; } }, + initialize_content: function() { + this._super(); + this.$el.addClass('o_form_field_number'); + }, set_value: function(value_) { if (value_ === false || value_ === undefined) { - // As in GTK client, floats default to 0 - value_ = 0; + value_ = 0; // As in GTK client, floats default to 0 } if (this.digits !== undefined && this.digits.length === 2) { value_ = utils.round_decimals(value_, this.digits[1]); } - this._super.apply(this, [value_]); - }, - focus: function () { - var $input = this.$('input:first'); - return $input.length ? $input.select() : false; + this._super(value_); } }); @@ -474,7 +441,7 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin, } else { this.$('.o_count').text(_t('No selected record')); var $arrow = this.$('button span').detach(); - this.$('button').text(_('Select records ')).append($("<span/>").addClass('fa fa-arrow-right')); + this.$('button').text(_t('Select records ')).append($("<span/>").addClass('fa fa-arrow-right')); } }, on_click: function(event) { @@ -497,60 +464,58 @@ var FieldCharDomain = common.AbstractField.extend(common.ReinitializeFieldMixin, }, }); -var FieldDatetime = common.AbstractField.extend(common.ReinitializeFieldMixin, { - template: "FieldDatetime", +var FieldDate = common.AbstractField.extend(common.ReinitializeFieldMixin, { + tagName: "span", + className: "o_form_field_date", build_widget: function() { - return new datepicker.DateTimeWidget(this); + return new datepicker.DateWidget(this); }, - destroy_content: function() { + initialize_content: function() { if (this.datewidget) { this.datewidget.destroy(); this.datewidget = undefined; } - }, - initialize_content: function() { + if (!this.get("effective_readonly")) { this.datewidget = this.build_widget(); - this.datewidget.on('datetime_changed', this, _.bind(function() { + this.datewidget.on('datetime_changed', this, function() { this.internal_set_value(this.datewidget.get_value()); - }, this)); - this.datewidget.appendTo(this.$el); - this.setupFocus(this.datewidget.$input); + }); + + var self = this; + this.datewidget.appendTo('<div>').done(function() { + self.datewidget.$el.addClass(self.$el.attr('class')); + self.replaceElement(self.datewidget.$el); + self.datewidget.$input.addClass('o_form_input'); + self.setupFocus(self.datewidget.$input); + }); } }, render_value: function() { - if (!this.get("effective_readonly")) { - this.datewidget.set_value(this.get('value')); - } else { + if (this.get("effective_readonly")) { this.$el.text(formats.format_value(this.get('value'), this, '')); + } else { + this.datewidget.set_value(this.get('value')); } }, is_syntax_valid: function() { - if (!this.get("effective_readonly") && this.datewidget) { - return this.datewidget.is_valid(); - } - return true; + return this.get("effective_readonly") || !this.datewidget || this.datewidget.is_valid(); }, is_false: function() { return this.get('value') === '' || this._super(); }, focus: function() { - var input = this.datewidget && this.datewidget.$input[0]; - return input ? input.focus() : false; - }, - set_dimensions: function (height, width) { - this._super(height, width); if (!this.get("effective_readonly")) { - this.datewidget.$input.css('height', height); + return this.datewidget.$input.focus(); } - } + return false; + }, }); -var FieldDate = FieldDatetime.extend({ - template: "FieldDate", +var FieldDatetime = FieldDate.extend({ build_widget: function() { - return new datepicker.DateWidget(this); - } + return new datepicker.DateTimeWidget(this); + }, }); var FieldText = common.AbstractField.extend(common.ReinitializeFieldMixin, { @@ -566,46 +531,37 @@ var FieldText = common.AbstractField.extend(common.ReinitializeFieldMixin, { e.stopPropagation(); } }, - 'change textarea': 'store_dom_value', + 'change': 'store_dom_value', }, initialize_content: function() { - if (! this.get("effective_readonly")) { - this.$textarea = this.$el.find('textarea'); + if (!this.get("effective_readonly")) { this.auto_sized = false; - this.default_height = this.$textarea.css('height'); - if (this.default_height === '0px') this.default_height = '90px'; - if (this.get("effective_readonly")) { - this.$textarea.attr('disabled', 'disabled'); - } - this.setupFocus(this.$textarea); - } else { - this.$textarea = undefined; + this.setupFocus(this.$el); } }, commit_value: function () { - if (! this.get("effective_readonly") && this.$textarea) { + if (!this.get("effective_readonly")) { this.store_dom_value(); } return this._super(); }, store_dom_value: function () { - this.internal_set_value(formats.parse_value(this.$textarea.val(), this)); + this.internal_set_value(formats.parse_value(this.$el.val(), this)); }, render_value: function() { - if (! this.get("effective_readonly")) { - var show_value = formats.format_value(this.get('value'), this, ''); - this.$textarea.val(show_value); - dom_utils.autoresize(this.$textarea, {parent: this, min_height: parseInt(this.default_height)}); - } else { + if (this.get("effective_readonly")) { var txt = this.get("value") || ''; - this.$(".oe_form_text_content").text(txt); + this.$el.text(txt); + } else { + var show_value = formats.format_value(this.get('value'), this, ''); + this.$el.val(show_value); + dom_utils.autoresize(this.$el, {parent: this}); } }, is_syntax_valid: function() { - if (!this.get("effective_readonly") && this.$textarea) { + if (!this.get("effective_readonly")) { try { - formats.parse_value(this.$textarea.val(), this, ''); - return true; + formats.parse_value(this.$el.val(), this, ''); } catch(e) { return false; } @@ -616,51 +572,48 @@ var FieldText = common.AbstractField.extend(common.ReinitializeFieldMixin, { return this.get('value') === '' || this._super(); }, focus: function($el) { - var input = !this.get("effective_readonly") && this.$textarea && this.$textarea[0]; - return input ? input.focus() : false; - }, - set_dimensions: function (height, width) { - this._super(height, width); - if (!this.get("effective_readonly") && this.$textarea) { - this.$textarea.css({ - width: width, - minHeight: height - }); + if(!this.get("effective_readonly")) { + return this.$el.focus(); } + return false; + }, + set_dimensions: function(height, width) { + this.$el.css({ + width: width, + minHeight: height, + }); }, }); var FieldBoolean = common.AbstractField.extend({ template: 'FieldBoolean', + events: { + 'click': function() { + this.internal_set_value(this.$checkbox.prop('checked')); + } + }, start: function() { - var self = this; - this.$checkbox = $("input", this.$el); + this.$checkbox = this.$('input'); + + this.$checkbox.prop('disabled', this.get("effective_readonly")); + this.on("change:effective_readonly", this, function() { + this.$checkbox.prop('disabled', this.get("effective_readonly")); + }); + this.setupFocus(this.$checkbox); - this.$el.click(_.bind(function() { - this.internal_set_value(this.$checkbox.is(':checked')); - }, this)); - var check_readonly = function() { - self.$checkbox.prop('disabled', self.get("effective_readonly")); - self.click_disabled_boolean(); - }; - this.on("change:effective_readonly", this, check_readonly); - check_readonly.call(this); - this._super.apply(this, arguments); + + return this._super(); }, render_value: function() { - this.$checkbox[0].checked = this.get('value'); + this.$checkbox.prop('checked', this.get('value')); }, focus: function() { - var input = this.$checkbox && this.$checkbox[0]; - return input ? input.focus() : false; - }, - click_disabled_boolean: function(){ - var $disabled = this.$el.find('input[type=checkbox]:disabled'); - $disabled.each(function (){ - $(this).next('div').remove(); - $(this).closest("span").append($('<div class="boolean"></div>')); - }); - } + return this.$checkbox.focus(); + }, + set_dimensions: function(height, width) {}, // Checkboxes have a fixed height and width (even in list editable) + is_false: function() { + return false; + }, }); /** @@ -706,9 +659,7 @@ var FieldBooleanButton = common.AbstractField.extend({ }, }); -/** - The progressbar field expect a float from 0 to 100. -*/ +// The progressbar field expects a float from 0 to 100. var FieldProgressBar = common.AbstractField.extend(common.ReinitializeFieldMixin, { initialize_content: function() { if(this.progressbar) { @@ -734,96 +685,41 @@ var FieldProgressBar = common.AbstractField.extend(common.ReinitializeFieldMixin render_value: function() { this.progressbar.set_value(this.get('value')); }, + is_false: function() { + return false; + }, }); -/** - The PercentPie field expect a float from 0 to 100. -*/ +// The PercentPie field expects a float from 0 to 100. var FieldPercentPie = common.AbstractField.extend({ template: 'FieldPercentPie', - + start: function() { + this.$left_mask = this.$('.o_mask').first(); + this.$right_mask = this.$('.o_mask').last(); + this.$pie_value = this.$('.o_pie_value'); + return this._super(); + }, render_value: function() { - var value = this.get('value'), - formatted_value = Math.round(value || 0) + '%', - svg = this.$('svg')[0]; - - svg.innerHTML = ""; - nv.addGraph(function() { - var width = 42, height = 42; - var chart = nv.models.pieChart() - .width(width) - .height(height) - .margin({top: 0, right: 0, bottom: 0, left: 0}) - .donut(true) - .showLegend(false) - .showLabels(false) - .color(['#7C7BAD','#DDD']) - .donutRatio(0.62); - - chart.tooltip.enabled(false); - - d3.select(svg) - .datum([{'x': 'value', 'y': value}, {'x': 'complement', 'y': 100 - value}]) - .transition() - .call(chart) - .attr('style', 'width: ' + width + 'px; height:' + height + 'px;'); - - d3.select(svg) - .append("text") - .attr({x: width/2, y: height/2 + 3, 'text-anchor': 'middle'}) - .style({"font-size": "10px", "font-weight": "bold"}) - .text(formatted_value); - - return chart; - }); - - } -}); + var value = this.get('value') || 0, degValue = 360*value/100; -/** - The FieldBarChart expectsa list of values (indeed) -*/ -var FieldBarChart = common.AbstractField.extend({ - template: 'FieldBarChart', + this.$right_mask.toggleClass('o_full', degValue >= 180); - render_value: function() { - var value = JSON.parse(this.get('value')); - var svg = this.$('svg')[0]; - svg.innerHTML = ""; - nv.addGraph(function() { - var width = 34, height = 34; - var chart = nv.models.discreteBarChart() - .x(function (d) { return d.tooltip; }) - .y(function (d) { return d.value; }) - .width(width) - .height(height) - .margin({top: 0, right: 0, bottom: 0, left: 0}) - .showValues(false) - .transition(350) - .showXAxis(false) - .showYAxis(false); - - chart.tooltip.enabled(false); - - d3.select(svg) - .datum([{key: 'values', values: value}]) - .transition() - .call(chart) - .attr('style', 'width: ' + (width + 4) + 'px; height: ' + (height + 8) + 'px;'); - - nv.utils.windowResize(chart.update); - - return chart; - }); - + var leftDeg = 'rotate(' + ((degValue < 180)? 180 : degValue) + 'deg)'; + var rightDeg = 'rotate(' + ((degValue < 180)? degValue : 0) + 'deg)'; + this.$left_mask.css({transform: leftDeg, msTransform: leftDeg, mozTransform: leftDeg, webkitTransform: leftDeg}); + this.$right_mask.css({transform: rightDeg, msTransform: rightDeg, mozTransform: rightDeg, webkitTransform: rightDeg}); + + this.$pie_value.html(Math.round(value) + '%'); + }, + is_false: function() { + return false; } }); - var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin, { template: 'FieldSelection', events: { - 'change select': 'store_dom_value', + 'change': 'store_dom_value', }, init: function(field_manager, node) { this._super(field_manager, node); @@ -832,7 +728,7 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin, this.records_orderer = new utils.DropMisordered(); this.field_manager.on("view_content_has_changed", this, function() { var domain = new data.CompoundDomain(this.build_domain()).eval(); - if (! _.isEqual(domain, this.get("domain"))) { + if (!_.isEqual(domain, this.get("domain"))) { this.set("domain", domain); } }); @@ -869,27 +765,27 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin, // current row or switching to the next one) // * If the user presses [RETURN] with a select closed (side-effect: // also if the user opened the select and pressed [RETURN] without - // changing the selected value), takes the action as validating the - // row - var ischanging = false; - var $select = this.$el.find('select') - .change(function () { ischanging = true; }) - .click(function () { ischanging = false; }) - .keyup(function (e) { - if (e.which !== 13 || !ischanging) { return; } - e.stopPropagation(); - ischanging = false; - }); - this.setupFocus($select); + // changing the selected value), takes the action as validating the row + if(!this.get('effective_readonly')) { + var ischanging = false; + this.$el + .change(function () { ischanging = true; }) + .click(function () { ischanging = false; }) + .keyup(function (e) { + if (e.which !== 13 || !ischanging) { return; } + e.stopPropagation(); + ischanging = false; + }); + this.setupFocus(this.$el); + } }, commit_value: function () { this.store_dom_value(); return this._super(); }, store_dom_value: function () { - if (!this.get('effective_readonly') && this.$('select').length) { - var val = JSON.parse(this.$('select').val()); - this.internal_set_value(val); + if (!this.get('effective_readonly')) { + this.internal_set_value(JSON.parse(this.$el.val())); } }, set_value: function(value_) { @@ -901,28 +797,29 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin, var values = this.get("values"); values = [[false, this.node.attrs.placeholder || '']].concat(values); var found = _.find(values, function(el) { return el[0] === this.get("value"); }, this); - if (! found) { + if (!found) { found = [this.get("value"), _t('Unknown')]; values = [found].concat(values); } - if (! this.get("effective_readonly")) { - this.$().html(QWeb.render("FieldSelectionSelect", {widget: this, values: values})); - this.$("select").val(JSON.stringify(found[0])); + if (!this.get("effective_readonly")) { + this.$el.empty(); + for(var i = 0 ; i < values.length ; i++) { + this.$el.append($('<option/>', { + value: JSON.stringify(values[i][0]), + html: values[i][1] + })) + } + this.$el.val(JSON.stringify(found[0])); } else { this.$el.text(found[1]); } }, focus: function() { - var input = this.$('select:first')[0]; - return input ? input.focus() : false; - }, - set_dimensions: function (height, width) { - this._super(height, width); - this.$('select').css({ - height: height, - width: width - }); - } + if (!this.get("effective_readonly")) { + return this.$el.focus(); + } + return false; + }, }); /** @@ -932,20 +829,20 @@ var FieldSelection = common.AbstractField.extend(common.ReinitializeFieldMixin, the time difference between UTC time and local time, in minutes. */ var TimezoneMismatch = FieldSelection.extend({ - initialize_content: function(){ + initialize_content: function() { this._super.apply(this, arguments); this.tz_offset_field = (this.options && this.options.tz_offset_field) || this.tz_offset_field || 'tz_offset'; this.set({"tz_offset": this.field_manager.get_field_value(this.tz_offset_field)}); this.on("change:tz_offset", this, this.render_value); }, - start: function(){ + start: function() { this._super.apply(this, arguments); // trigger a render_value when tz_offset field change this.field_manager.on("field_changed:" + this.tz_offset_field, this, function() { this.set({"tz_offset": this.field_manager.get_field_value(this.tz_offset_field)}); }); }, - check_timezone: function(){ + check_timezone: function() { var user_offset = this.get('tz_offset'); if (user_offset) { var offset = -(new Date().getTimezoneOffset()); @@ -956,17 +853,15 @@ var TimezoneMismatch = FieldSelection.extend({ } return false; }, - render_value: function(){ + render_value: function() { this._super.apply(this, arguments); + this.$label.next('.o_tz_warning').remove(); if(this.check_timezone()){ - this.$label.find('.oe_tz_warning').remove(); var options = _.extend({ delay: { show: 501, hide: 0 }, title: _t("Timezone Mismatch : The timezone of your browser doesn't match the selected one. The time in Odoo is displayed according to your field timezone."), }); - this.$label.css('white-space', 'normal'); - $(QWeb.render('WebClient.timezone_warning')).appendTo(this.$label); - this.$label.find('.oe_tz_warning').tooltip(options); + $('<span/>').addClass('fa fa-exclamation-triangle o_tz_warning').insertAfter(this.$label).tooltip(options); } } }); @@ -979,8 +874,8 @@ var LabelSelection = FieldSelection.extend({ render_value: function() { this._super.apply(this, arguments); if (this.get("effective_readonly")) { - var btn_class = this.classes[this.get('value')] || 'default'; - this.$el.wrapInner($('<span/>').addClass('label label-' + btn_class)); + var bt_class = this.classes[this.get('value')] || 'default'; + this.$el.html($('<span/>', {html: this.$el.html()}).addClass('label label-' + bt_class)); } }, }); @@ -1001,7 +896,6 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, { this.uniqueId = _.uniqueId("radio"); }, initialize_content: function () { - this.on("change:effective_readonly", this, this.render_value); this.field_manager.on("view_content_has_changed", this, this.get_selection); this.get_selection(); }, @@ -1043,7 +937,7 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, { def.resolve(); } return def.then(function () { - if (! _.isEqual(selection, self.selection)) { + if (!_.isEqual(selection, self.selection)) { self.selection = _.clone(selection); self.renderElement(); self.render_value(); @@ -1066,14 +960,16 @@ var FieldRadio = common.AbstractField.extend(common.ReinitializeFieldMixin, { }, render_value: function () { var self = this; - this.$el.toggleClass("oe_readonly", this.get('effective_readonly')); - this.$("input").filter(function () {return this.value == self.get_value();}).prop("checked", true); - this.$(".oe_radio_readonly").text(this.get('value') ? this.get('value')[1] : ""); + if(this.get('effective_readonly')) { + this.$el.html(this.get('value')? this.get('value')[1] : ""); + } else { + this.$("input").filter(function() {return this.value == self.get_value();}).prop("checked", true); + } } }); var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin, { - template: 'FieldReference', + className: 'o_row', init: function(field_manager, node) { this._super(field_manager, node); this.reference_ready = true; @@ -1086,9 +982,8 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin, }, initialize_content: function() { var self = this; - var fm = new common.DefaultFieldManager(this); - this.fm = fm; - fm.extend_field_desc({ + this.fm = new common.DefaultFieldManager(this); + this.fm.extend_field_desc({ "selection": { selection: this.field_manager.get_field_desc(this.name).selection, type: "selection", @@ -1098,23 +993,23 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin, type: "many2one", }, }); - this.selection = new FieldSelection(fm, { attrs: { + this.selection = new FieldSelection(this.fm, { attrs: { name: 'selection', modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); this.selection.on("change:value", this, this.on_selection_changed); - this.selection.appendTo(this.$(".oe_form_view_reference_selection")); + this.selection.appendTo(this.$el); this.selection .on('focused', null, function () {self.trigger('focused');}) .on('blurred', null, function () {self.trigger('blurred');}); var FieldMany2One = core.form_widget_registry.get('many2one'); - this.m2o = new FieldMany2One(fm, { attrs: { + this.m2o = new FieldMany2One(this.fm, { attrs: { name: 'Referenced Document', modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); this.m2o.on("change:value", this, this.data_changed); - this.m2o.appendTo(this.$(".oe_form_view_reference_m2o")); + this.m2o.appendTo(this.$el); this.m2o .on('focused', null, function () {self.trigger('focused');}) .on('blurred', null, function () {self.trigger('blurred');}); @@ -1151,6 +1046,9 @@ var FieldReference = common.AbstractField.extend(common.ReinitializeFieldMixin, this.m2o.do_toggle(!!this.get('value')[0]); this.reference_ready = true; }, + is_false: function() { + return !this.get_value(); + }, }); var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, { @@ -1161,7 +1059,7 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, { this.useFileAPI = !!window.FileReader; this.max_upload_size = 25 * 1024 * 1024; // 25Mo if (!this.useFileAPI) { - this.fileupload_id = _.uniqueId('oe_fileupload'); + this.fileupload_id = _.uniqueId('o_fileupload'); $(window).on(this.fileupload_id, function() { var args = [].slice.call(arguments).slice(1); self.on_file_uploaded.apply(self, args); @@ -1175,13 +1073,14 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, { this._super.apply(this, arguments); }, initialize_content: function() { - var self= this; - this.$('input.o_form_input_file').change(this.on_file_change); - this.$('button.oe_form_binary_file_save').click(this.on_save_as); - this.$('.oe_form_binary_file_clear').click(this.on_clear); - this.$('.oe_form_binary_file_edit').click(function() { - self.$('input.o_form_input_file').click(); + this.$inputFile = this.$('.o_form_input_file'); + this.$inputFile.change(this.on_file_change); + var self = this; + this.$('.o_select_file_button').click(function() { + self.$inputFile.click(); }); + this.$('.o_save_file_button').click(this.on_save_as); + this.$('.o_clear_file_button').click(this.on_clear); }, on_file_change: function(e) { var self = this; @@ -1202,26 +1101,28 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, { self.on_file_uploaded(file.size, file.name, file.type, data); }; } else { - this.$el.find('form.o_form_binary_form input[name=session_id]').val(this.session.session_id); - this.$el.find('form.o_form_binary_form').submit(); + this.$('form.o_form_binary_form input[name=session_id]').val(this.session.session_id); + this.$('form.o_form_binary_form').submit(); } - this.$el.find('.oe_form_binary_progress').show(); - this.$el.find('.oe_form_binary').hide(); + this.$('.o_form_binary_progress').show(); + this.$('button').hide(); } }, - on_file_uploaded: function(size, name, content_type, file_base64) { + on_file_uploaded: function(size, name) { if (size === false) { this.do_warn(_t("File Upload"), _t("There was a problem while uploading your file")); // TODO: use openerp web crashmanager console.warn("Error while uploading file : ", name); } else { - this.filename = name; this.on_file_uploaded_and_valid.apply(this, arguments); } - this.$el.find('.oe_form_binary_progress').hide(); - this.$el.find('.oe_form_binary').show(); + this.$('.o_form_binary_progress').hide(); + this.$('button').show(); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { + this.binary_value = true; + this.set_filename(name); + this.set_value(file_base64); }, on_save_as: function(ev) { var value = this.get('value'); @@ -1248,7 +1149,6 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, { 'error': c.rpc_error.bind(c) }); ev.stopPropagation(); - return false; } }, set_filename: function(value) { @@ -1262,129 +1162,95 @@ var FieldBinary = common.AbstractField.extend(common.ReinitializeFieldMixin, { } }, on_clear: function() { - if (this.get('value') !== false) { - this.binary_value = false; - this.internal_set_value(false); - } - return false; + this.binary_value = false; + this.set_filename(''); + this.set_value(false); // FIXME do not really remove the value } }); var FieldBinaryFile = FieldBinary.extend({ template: 'FieldBinaryFile', initialize_content: function() { + var self = this; this._super(); if (this.get("effective_readonly")) { - var self = this; - this.$el.find('a').click(function(ev) { + this.$el.click(function(ev) { if (self.get('value')) { self.on_save_as(ev); } return false; }); + } else { + this.$input = this.$('.o_form_input').eq(0); + this.$input.on('click', function() { + self.$inputFile.click(); + }); } }, render_value: function() { - var show_value; - if (!this.get("effective_readonly")) { - if (this.node.attrs.filename) { - show_value = this.view.datarecord[this.node.attrs.filename] || ''; - } else { - show_value = (this.get('value') !== null && this.get('value') !== undefined && this.get('value') !== false) ? this.get('value') : ''; + var filename = this.view.datarecord[this.node.attrs.filename]; + if (this.get("effective_readonly")) { + this.do_toggle(!!this.get('value')); + if (this.get('value')) { + this.$el.empty().append($("<span/>").addClass('fa fa-download')); + if (filename) { + this.$el.append(" " + filename); + } } - this.$el.find('input').eq(0).val(show_value); } else { - this.$el.find('a').toggle(!!this.get('value')); - if (this.get('value')) { - show_value = _t("Download"); - if (this.view) - show_value += " " + (this.view.datarecord[this.node.attrs.filename] || ''); - this.$el.find('a').text(show_value); + if(this.get('value')) { + this.$el.children().removeClass('o_hidden'); + this.$('.o_select_file_button').first().addClass('o_hidden'); + this.$input.val(filename || this.get('value')); + } else { + this.$el.children().addClass('o_hidden'); + this.$('.o_select_file_button').first().removeClass('o_hidden'); } } - }, - on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { - this.binary_value = true; - this.set_filename(name); - this.internal_set_value(file_base64); - var show_value = name + " (" + utils.human_size(size) + ")"; - this.$el.find('input').eq(0).val(show_value); - }, - on_clear: function() { - this._super.apply(this, arguments); - this.$el.find('input').eq(0).val(''); - this.set_filename(''); - }, - set_value: function(value_){ - var changed = value_ !== this.get_value(); - this._super.apply(this, arguments); - // Trigger value change if size is the same - if (!changed){ - this.trigger("change:value", this, { - oldValue: value_, - newValue: value_ - }); - } - } + } }); var FieldBinaryImage = FieldBinary.extend({ template: 'FieldBinaryImage', placeholder: "/web/static/src/img/placeholder.png", render_value: function() { - var self = this; - var url; - this.session = session; - if (this.get('value') && !utils.is_bin_size(this.get('value'))) { - url = 'data:image/png;base64,' + this.get('value'); - } else if (this.get('value')) { - var id = JSON.stringify(this.view.datarecord.id || null); - var field = this.name; - if (this.options.preview_image) - field = this.options.preview_image; - url = session.url('/web/image', { - model: this.view.dataset.model, - id: id, - field: field, - unique: (this.view.datarecord.__last_update || '').replace(/[^0-9]/g, ''), - }); - } else { - url = this.placeholder; + var url = this.placeholder; + if(this.get('value')) { + if(!utils.is_bin_size(this.get('value'))) { + url = 'data:image/png;base64,' + this.get('value'); + } else { + url = session.url('/web/image', { + model: this.view.dataset.model, + id: JSON.stringify(this.view.datarecord.id || null), + field: (this.options.preview_image)? this.options.preview_image : this.name, + unique: (this.view.datarecord.__last_update || '').replace(/[^0-9]/g, ''), + }); + } } - var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url })); - $($img).click(function(e) { + + var $img = $(QWeb.render("FieldBinaryImage-img", {widget: this, url: url})); + + var self = this; + $img.click(function(e) { if(self.view.get("actual_mode") == "view") { - var $button = $(".oe_form_button_edit"); + var $button = $(".o_form_button_edit"); $button.openerpBounce(); e.stopPropagation(); } }); - this.$el.find('> img').remove(); + this.$('> img').remove(); + if (self.options.size) { + $img.css("width", "" + self.options.size[0] + "px"); + $img.css("height", "" + self.options.size[1] + "px"); + } this.$el.prepend($img); - $img.load(function() { - if (! self.options.size) - return; - $img.css("max-width", "" + self.options.size[0] + "px"); - $img.css("max-height", "" + self.options.size[1] + "px"); - }); $img.on('error', function() { self.on_clear(); $img.attr('src', self.placeholder); self.do_warn(_t("Image"), _t("Could not display the selected image.")); }); }, - on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { - this.internal_set_value(file_base64); - this.binary_value = true; - this.render_value(); - this.set_filename(name); - }, - on_clear: function() { - this._super.apply(this, arguments); - this.render_value(); - this.set_filename(''); - }, - set_value: function(value_){ + set_value: function(value_) { var changed = value_ !== this.get_value(); this._super.apply(this, arguments); // By default, on binary images read, the server returns the binary size @@ -1397,7 +1263,16 @@ var FieldBinaryImage = FieldBinary.extend({ newValue: value_ }); } - } + }, + is_false: function() { + return false; + }, + set_dimensions: function(height, width) { + this.$el.css({ + maxWidth: width, + minHeight: height, + }); + }, }); var FieldStatus = common.AbstractField.extend({ @@ -1423,13 +1298,14 @@ var FieldStatus = common.AbstractField.extend({ }); this.get_selection(); if (this.options.clickable) { - this.$el.on('click','li[data-id]',this.on_click_stage); - } - if (this.$el.parent().is('header')) { - this.$el.after('<div class="oe_clear"/>'); + this.bind_stage_click(); } this._super(); }, + bind_stage_click: function () { + // This function is overriden in the enterprise webclient + this.$el.on('click','li[data-id]',this.on_click_stage); + }, set_value: function(value_) { if (value_ instanceof Array) { value_ = value_[0]; @@ -1468,15 +1344,15 @@ var FieldStatus = common.AbstractField.extend({ var fold_field = this.options.fold_field; var calculation = _.bind(function() { - if (this.field.type == "many2one") { - return self.get_distant_fields().then(function (fields) { + if (this.field.type === "many2one") { + return self.get_distant_fields().then(function () { return new data.DataSetSearch(self, self.field.relation, self.build_context(), self.get("evaluated_selection_domain")) .read_slice(_.union(_.keys(self.distant_fields), ['id']), {}).then(function (records) { var ids = _.pluck(records, 'id'); return self.dataset.name_get(ids).then(function (records_name) { _.each(records, function (record) { - var name = _.find(records_name, function (val) {return val[0] == record.id;})[1]; - if (fold_field && record[fold_field] && record.id != self.get('value')) { + var name = _.find(records_name, function (val) {return val[0] === record.id;})[1]; + if (fold_field && record[fold_field] && record.id !== self.get('value')) { selection_folded.push([record.id, name]); } else { selection_unfolded.push([record.id, name]); @@ -1492,7 +1368,7 @@ var FieldStatus = common.AbstractField.extend({ var select = this.field.selection; for(var i=0; i < select.length; i++) { var key = select[i][0]; - if(key == this.get('value') || !this.options.visible || this.options.visible.indexOf(key) != -1) { + if(key === this.get('value') || !this.options.visible || this.options.visible.indexOf(key) !== -1) { selection_unfolded.push(select[i]); } } @@ -1529,13 +1405,12 @@ var FieldStatus = common.AbstractField.extend({ return; } var val; - if (this.field.type == "many2one") { + if (this.field.type === "many2one") { val = parseInt($li.data("id"), 10); - } - else { + } else { val = $li.data("id"); } - if (val != self.get('value')) { + if (val !== self.get('value')) { this.view.recursive_save().done(function() { var change = {}; change[self.name] = val; @@ -1548,8 +1423,7 @@ var FieldStatus = common.AbstractField.extend({ }); var FieldMonetary = FieldFloat.extend({ - template: "FieldMonetary", - widget_class: 'oe_form_field_float oe_form_field_monetary', + template: 'FieldMonetary', init: function() { this._super.apply(this, arguments); this.set({"currency": false}); @@ -1567,6 +1441,23 @@ var FieldMonetary = FieldFloat.extend({ this.on("change:currency_info", this, this.update); return tmp; }, + initialize_content: function() { + if(!this.get('effective_readonly')) { + this.$input = this.$('input'); + this.add_symbol(); + } else { + this.$input = undefined; + } + + this._super(); + }, + add_symbol: function() { + var currency = this.get('currency_info'); + if(currency) { + var before = (currency.position === 'before'); + this.$el[(before)? 'prepend' : 'append']($('<span/>', {html: currency.symbol})); + } + }, get_currency_info: function() { var self = this; if (this.get("currency") === false) { @@ -1576,15 +1467,21 @@ var FieldMonetary = FieldFloat.extend({ return self.set({"currency_info": session.get_currency(self.get("currency"))}); }, update: function() { - if (this.view.options.is_list_editable){ + if (this.view.options.is_list_editable) { return; } else { return this.reinitialize(); } }, + render_value: function() { + this._super(); + if(this.get('effective_readonly')) { + this.add_symbol(); + } + }, get_digits_precision: function() { return this.node.attrs.digits || this.field.digits || (this.get('currency_info') && this.get('currency_info').digits); - }, + }, parse_value: function(val, def) { return formats.parse_value(val, {type: "float", digits: this.get_digits_precision()}, def); }, @@ -1625,7 +1522,6 @@ var StatInfo = common.AbstractField.extend({ this.$el.html(QWeb.render("StatInfo", options)); this.$el.addClass('o_stat_info'); }, - }); /** @@ -1633,14 +1529,18 @@ var StatInfo = common.AbstractField.extend({ switching between a green bullet / gray bullet. */ var FieldToggleBoolean = common.AbstractField.extend({ + template: "toggle_button", + events: { + 'click': 'set_toggle_button' + }, render_value: function () { - this.icon = this.get_value() ? 'gtk-yes.png' : 'gtk-normal.png'; - this.$el.html(QWeb.render("FieldToggleBoolean", {'widget': this})); - this.$('.oe_toggle_button').on('click', this.set_toggle_button.bind(this)); + var $img = this.$('img'); + var src = $img.attr('src'); + $img.attr('src', src.substr(0, src.lastIndexOf('/')+1) + (this.get_value() ? 'gtk-yes.png' : 'gtk-normal.png')); }, set_toggle_button: function () { var self = this; - var toggle_value = this.get_value() === false ? true: false; + var toggle_value = !this.get_value(); if (this.view.get('actual_mode') == 'view') { var rec_values = {}; rec_values[self.node.attrs.name] = toggle_value; @@ -1658,91 +1558,8 @@ var FieldToggleBoolean = common.AbstractField.extend({ reload_record: function () { this.view.reload(); }, -}); - -/** - This widget is intended to be used in config settings. - When checked, an upgrade popup is showed to the user. -*/ - -var AbstractFieldUpgrade = { - events: { - 'click input': 'on_click_input', - }, - - start: function() { - this._super.apply(this, arguments); - - this.get_enterprise_label().after($("<span>", { - text: "Enterprise", - 'class': "label label-primary oe_inline" - })); - }, - - open_dialog: function() { - var message = $(QWeb.render('EnterpriseUpgrade')); - - var buttons = [ - { - text: _t("Upgrade now"), - classes: 'btn-primary', - close: true, - click: this.confirm_upgrade, - }, - { - text: _t("Cancel"), - close: true, - }, - ]; - - return new Dialog(this, { - size: 'medium', - buttons: buttons, - $content: $('<div>', { - html: message, - }), - title: _t("Odoo Enterprise"), - }).open(); - }, - - confirm_upgrade: function() { - new Model("res.users").call("search_count", [[["share", "=", false]]]).then(function(data) { - framework.redirect("https://www.odoo.com/odoo-enterprise/upgrade?num_users=" + data); - }); - }, - - get_enterprise_label: function() {}, - on_click_input: function() {}, -}; - -var UpgradeBoolean = FieldBoolean.extend(AbstractFieldUpgrade, { - template: "FieldUpgradeBoolean", - - get_enterprise_label: function() { - return this.$label; - }, - - on_click_input: function(event) { - if(this.$checkbox.prop("checked")) { - this.open_dialog().on('closed', this, function() { - this.$checkbox.prop("checked", false); - }); - } - }, -}); - -var UpgradeRadio = FieldRadio.extend(AbstractFieldUpgrade, { - - get_enterprise_label: function() { - return this.$('label').last(); - }, - - on_click_input: function(event) { - if($(event.target).val() == 1) { - this.open_dialog().on('closed', this, function() { - this.$('input').first().prop("checked", true); - }); - } + is_false: function() { + return false; }, }); @@ -1770,7 +1587,6 @@ core.form_widget_registry .add('toggle_button', FieldToggleBoolean) .add('float', FieldFloat) .add('percentpie', FieldPercentPie) - .add('barchart', FieldBarChart) .add('integer', FieldFloat) .add('float_time', FieldFloat) .add('progressbar', FieldProgressBar) @@ -1782,9 +1598,7 @@ core.form_widget_registry .add('kanban_state_selection', KanbanSelection) .add('statinfo', StatInfo) .add('timezone_mismatch', TimezoneMismatch) - .add('label_selection', LabelSelection) - .add('upgrade_boolean', UpgradeBoolean) - .add('upgrade_radio', UpgradeRadio); + .add('label_selection', LabelSelection); /** @@ -1796,8 +1610,12 @@ core.form_widget_registry core.form_tag_registry.add('button', WidgetButton); return { + FieldBoolean: FieldBoolean, FieldChar: FieldChar, + FieldEmail: FieldEmail, FieldFloat: FieldFloat, + FieldRadio: FieldRadio, + FieldStatus: FieldStatus, FieldMonetary: FieldMonetary, WidgetButton: WidgetButton }; diff --git a/addons/web/static/src/js/views/graph_view.js b/addons/web/static/src/js/views/graph_view.js index 62ee1e62c23..c8ff16cd0f1 100644 --- a/addons/web/static/src/js/views/graph_view.js +++ b/addons/web/static/src/js/views/graph_view.js @@ -14,7 +14,7 @@ var _t = core._t; var QWeb = core.qweb; var GraphView = View.extend({ - className: 'oe_graph', + className: 'o_graph', display_name: _lt('Graph'), icon: 'fa-bar-chart', require_fields: true, @@ -54,12 +54,14 @@ var GraphView = View.extend({ if ($node) { var context = {measures: _.pairs(_.omit(this.measures, '__count__'))}; this.$buttons = $(QWeb.render('GraphView.buttons', context)); - this.$measure_list = this.$buttons.find('.oe-measure-list'); + this.$measure_list = this.$buttons.find('.o_graph_measures_list'); this.update_measure(); this.$buttons.find('button').tooltip(); this.$buttons.click(this.on_button_click.bind(this)); - this.$buttons.appendTo($node); + this.$buttons.find('.o_graph_button[data-mode="' + this.widget.mode + '"]').addClass('active'); + + this.$buttons.appendTo($node); } }, update_measure: function () { @@ -86,7 +88,7 @@ var GraphView = View.extend({ }, do_search: function (domain, context, group_by) { if (!this.widget) { - this.initial_groupbys = context.graph_groupbys || this.initial_groupbys; + this.initial_groupbys = context.graph_groupbys || (group_by.length ? group_by : this.initial_groupbys); this.widget = new GraphWidget(this, this.model, { measure: context.graph_measure || this.active_measure, mode: context.graph_mode || this.active_mode, @@ -112,14 +114,16 @@ var GraphView = View.extend({ }, on_button_click: function (event) { var $target = $(event.target); - if ($target.hasClass('oe-bar-mode')) {this.widget.set_mode('bar');} - if ($target.hasClass('oe-line-mode')) {this.widget.set_mode('line');} - if ($target.hasClass('oe-pie-mode')) {this.widget.set_mode('pie');} - if ($target.parents('.oe-measure-list').length) { - var parent = $target.parent(), - field = parent.data('field'); + if ($target.hasClass('o_graph_button')) { + this.widget.set_mode($target.data('mode')); + this.$buttons.find('.o_graph_button.active').removeClass('active'); + $target.addClass('active'); + } + else if ($target.parents('.o_graph_measures_list').length) { + var parent = $target.parent(); + var field = parent.data('field'); this.active_measure = field; - parent.toggleClass('selected'); + event.preventDefault(); event.stopPropagation(); this.update_measure(); this.widget.set_measure(this.active_measure); diff --git a/addons/web/static/src/js/views/graph_widget.js b/addons/web/static/src/js/views/graph_widget.js index a86ea35e449..e3786408125 100644 --- a/addons/web/static/src/js/views/graph_widget.js +++ b/addons/web/static/src/js/views/graph_widget.js @@ -14,6 +14,7 @@ var QWeb = core.qweb; var MAX_LEGEND_LENGTH = 25 * (1 + config.device.size_class); return Widget.extend({ + className: "o_graph_svg_container", init: function (parent, model, options) { this._super(parent); this.context = options.context; diff --git a/addons/web/static/src/js/views/list_view.js b/addons/web/static/src/js/views/list_view.js index d42d34a98d2..9214eb27152 100644 --- a/addons/web/static/src/js/views/list_view.js +++ b/addons/web/static/src/js/views/list_view.js @@ -8,6 +8,7 @@ var DataExport = require('web.DataExport'); var formats = require('web.formats'); var common = require('web.list_common'); var Model = require('web.DataModel'); +var Pager = require('web.Pager'); var pyeval = require('web.pyeval'); var session = require('web.session'); var Sidebar = require('web.Sidebar'); @@ -34,8 +35,7 @@ var row_decoration = [ var ListView = View.extend({ _template: 'ListView', - accesskey: 'L', - display_name: _lt('List'), + accesskey: "L", defaults: _.extend({}, View.prototype.defaults, { // records can be selected one by one selectable: true, @@ -54,8 +54,14 @@ var ListView = View.extend({ //whether the editable property of the view has to be disabled disable_editable_mode: false, }), + display_name: _lt('List'), events: { - 'click thead th.oe_sortable[data-id]': 'sort_by_column' + 'click thead th.o_column_sortable[data-id]': 'sort_by_column', + 'click .oe_view_nocontent': function() { + if (this.$buttons) { + this.$buttons.width(this.$buttons.width() + 1).openerpBounce(); + } + }, }, icon: 'fa-list-ul', @@ -92,8 +98,7 @@ var ListView = View.extend({ }); this.previous_colspan = null; - this.colors = null; - this.fonts = null; + this.decoration = null; this.columns = []; @@ -111,7 +116,6 @@ var ListView = View.extend({ this.groups.datagroup.sort = this.dataset._sort; } - this.page = 0; this.records.bind('change', function (event, record, key) { if (!_(self.aggregate_columns).chain() .pluck('name').contains(key).value()) { @@ -127,6 +131,7 @@ var ListView = View.extend({ this.options.$pager = false; } + this.options.deletable = this.options.deletable && this.is_action_enabled('delete'); this.name = "" + this.fields_view.arch.attrs.string; // the view's number of records per page (|| section) @@ -135,14 +140,26 @@ var ListView = View.extend({ (this.getParent().action || {}).limit || parseInt(this.fields_view.arch.attrs.limit, 10) || 80); + // the index of the first displayed record (starting from 1) + this.current_min = 1; - //Sort - var default_order = this.fields_view.arch.attrs.default_order, - unsorted = !this.dataset._sort.length; + // Sort + var default_order = this.fields_view.arch.attrs.default_order; + var unsorted = !this.dataset._sort.length; if (unsorted && default_order && !this.grouped) { this.dataset.set_sort(default_order.split(',')); } }, + willStart: function() { + // Retrieve the decoration defined on the model's list view + this.decoration = _.pick(this.fields_view.arch.attrs, function(value, key) { + return row_decoration.indexOf(key) >= 0; + }); + this.decoration = _.mapObject(this.decoration, function(value) { + return py.parse(py.tokenize(value)); + }); + return this._super(); + }, /** * Set a custom Group construct as the root of the List View. * @@ -171,43 +188,6 @@ var ListView = View.extend({ } }); }, - willStart: function() { - // Retrieve the decoration defined on the model's list view - this.decoration = _.pick(this.fields_view.arch.attrs, function(value, key) { - return row_decoration.indexOf(key) >= 0; - }); - this.decoration = _.mapObject(this.decoration, function(value) { - return py.parse(py.tokenize(value)); - }); - - this.setup_columns(this.fields_view.fields, this.grouped); - - return this._super(); - }, - start: function() { - var self = this; - this.$el.html(QWeb.render(this._template, this)); - this.$el.addClass(this.fields_view.arch.attrs['class']); - this.$el.addClass('oe_list o_list_view'); - - // Selecting records - this.$('.oe_list_record_selector').click(function() { - var checked = self.$('.oe_list_record_selector').prop('checked'); - self.$('.oe_list_record_selector input').prop('checked', checked || false); - var selection = self.groups.get_selection(); - $(self.groups).trigger('selected', [selection.ids, selection.records]); - }); - - if(this.dataset._sort.length){ - if(this.dataset._sort[0].indexOf('-') === -1){ - this.$el.find('th[data-id=' + this.dataset._sort[0] + ']').addClass("sortdown"); - }else { - this.$el.find('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("sortup"); - } - } - this.trigger('list_view_loaded', data, this.grouped); - return this._super(); - }, /** * Computes and returns the classnames for the provided record (from the * ``@decoration`` attribute) @@ -231,24 +211,58 @@ var ListView = View.extend({ return classnames; }, /** + * Renders the table itself and inserts its content + * Computes and displays the aggregates + * Hooks up the selection of records event + * Adds adequate classname on the sorted column + */ + load_list: function() { + var self = this; + + // Render the table and append its content + this.$el.html(QWeb.render(this._template, this)); + this.$el.addClass(this.fields_view.arch.attrs['class']); + if (this.grouped) { + this.$('.o_list_view').addClass('o_list_view_grouped'); + } + this.$('.o_list_view').append(this.groups.elements); + + // Compute the aggregates and display them in the list's footer + this.compute_aggregates(); + + // Head hook + // Selecting records + this.$('thead .o_list_record_selector input').click(function() { + self.$('tbody .o_list_record_selector input').prop('checked', $(this).prop('checked') || false); + var selection = self.groups.get_selection(); + $(self.groups).trigger('selected', [selection.ids, selection.records]); + }); + + // Sort + if (this.dataset._sort.length) { + if (this.dataset._sort[0].indexOf('-') === -1) { + this.$('th[data-id=' + this.dataset._sort[0] + ']').addClass("o-sort-down"); + } else { + this.$('th[data-id=' + this.dataset._sort[0].split('-')[1] + ']').addClass("o-sort-up"); + } + } + + this.trigger('list_view_loaded', data, this.grouped); + return $.when(); + }, + /** * Render the buttons according to the ListView.buttons template and * add listeners on it. * Set this.$buttons with the produced jQuery element * @param {jQuery} [$node] a jQuery node where the rendered buttons should be inserted * $node may be undefined, in which case the ListView inserts them into this.options.$buttons - * or into a div of its template + * if it exists */ render_buttons: function($node) { if (!this.$buttons) { this.$buttons = $(QWeb.render("ListView.buttons", {'widget': this})); - this.$buttons.find('.o_list_button_add').click(this.proxy('do_add_record')); - - if ($node) { - this.$buttons.appendTo($node); - } else { - this.$('.oe_list_buttons').replaceWith(this.$buttons); - } + this.$buttons.appendTo($node); } }, /** @@ -271,7 +285,7 @@ var ListView = View.extend({ this.is_action_enabled('delete') && { label: _t('Delete'), callback: this.do_delete_selected } ])); - $node = $node || this.options.$sidebar || this.$('.oe_list_sidebar'); + $node = $node || this.options.$sidebar; this.sidebar.appendTo($node); // Hide the sidebar by default (it will be shown as soon as a record is selected) @@ -279,72 +293,52 @@ var ListView = View.extend({ } }, /** - * Render the pager according to the ListView.pager template and add listeners on it. - * Set this.$pager with the produced jQuery element - * @param {jQuery} [$node] a jQuery node where the rendered pager should be inserted + * Instantiate and render the pager and add listeners on it. + * Set this.pager + * @param {jQuery} [$node] a jQuery node where the pager should be inserted * $node may be undefined, in which case the ListView inserts the pager into this.options.$pager - * or into a div of its template */ - render_pager: function($node) { - if (!this.$pager && this.options.pager) { - var self = this; - - this.$pager = $(QWeb.render("ListView.pager", {'widget': self})); - this.$pager - .on('click', 'a[data-pager-action]', function () { - var $this = $(this); - var max_page_index = Math.ceil(self.dataset.size() / self._limit) - 1; - switch ($this.data('pager-action')) { - case 'first': - self.page = 0; - break; - case 'last': - self.page = max_page_index; - break; - case 'next': - self.page += 1; - break; - case 'previous': - self.page -= 1; - break; + render_pager: function($node, options) { + if (!this.pager && this.options.pager) { + this.pager = new Pager(this, this.dataset.size(), 1, this._limit, options); + this.pager.appendTo($node || this.options.$pager); + + this.pager.on('pager_changed', this, function (new_state) { + var self = this; + var limit_changed = (this._limit !== new_state.limit); + + this._limit = new_state.limit; + this.current_min = new_state.current_min; + this.reload_content().then(function() { + // Reset the scroll position to the top on page changed only + if (!limit_changed) { + self.set_scrollTop(0); + self.trigger_up('scrollTo', {offset: 0}); } - if (self.page < 0) { - self.page = max_page_index; - } else if (self.page > max_page_index) { - self.page = 0; - } - self.reload_content(); - }).find('.oe_list_pager_state') - .click(function (e) { - e.stopPropagation(); - var $this = $(this); - - var $select = $('<select>') - .appendTo($this.empty()) - .click(function (e) {e.stopPropagation();}) - .append('<option value="80">80</option>' + - '<option value="200">200</option>' + - '<option value="500">500</option>' + - '<option value="2000">2000</option>' + - '<option value="NaN">' + _t("Unlimited") + '</option>') - .change(function () { - var val = parseInt($select.val(), 10); - self._limit = (isNaN(val) ? null : val); - self.page = 0; - self.reload_content(); - }).blur(function() { - $(this).trigger('change'); - }) - .val(self._limit || 'NaN'); - }); - this.configure_pager(this.dataset); - - $node = $node || this.options.$pager; - if ($node) { - this.$pager.appendTo($node); - } else { - this.$('.oe_list_pager').replaceWith(this.$pager); + }); + }); + } + }, + /** + * Updates the pager based on the provided dataset's information + * + * Horrifying side-effect: sets the dataset's data on this.dataset? + * + * @param {instance.web.DataSet} [dataset] + * @param {int} [current_min] the min pager value + */ + update_pager: function (dataset, current_min) { + this.dataset.ids = dataset.ids; + // Not exactly clean + if (dataset._length !== undefined) { + this.dataset._length = dataset._length; + } + if (this.pager && !this.grouped) { + var new_state = { size: this.dataset.size(), limit: this._limit }; + if (current_min) { + new_state.current_min = current_min; } + this.pager.update_state(new_state); } }, sort_by_column: function (e) { @@ -357,54 +351,14 @@ var ListView = View.extend({ return false; } this.dataset.sort(col_name); - if($column.hasClass("sortdown") || $column.hasClass("sortup")) { - $column.toggleClass("sortup sortdown"); + if($column.hasClass("o-sort-down") || $column.hasClass("o-sort-up")) { + $column.toggleClass("o-sort-up o-sort-down"); } else { - $column.addClass("sortdown"); - } - $column.siblings('.oe_sortable').removeClass("sortup sortdown"); - - this.reload_content(); - }, - /** - * Configures the ListView pager based on the provided dataset's information - * - * Horrifying side-effect: sets the dataset's data on this.dataset? - * - * @param {instance.web.DataSet} dataset - */ - configure_pager: function (dataset) { - this.dataset.ids = dataset.ids; - // Not exactly clean - if (dataset._length) { - this.dataset._length = dataset._length; + $column.addClass("o-sort-down"); } - if (this.$pager) { - if (this.grouped) { - // page count is irrelevant on grouped page, replace by limit - this.$pager.find('.oe-pager-buttons').hide(); - this.$pager.find('.oe_list_pager_state').text(this._limit || '∞'); - } else { - var total = dataset.size(); - var limit = this._limit || total; - this.$pager.find('.oe-pager-buttons').toggle(total > limit); - this.$pager.find('.oe_pager_value').toggle(total !== 0); - var spager = '-'; - if (total) { - var range_start = this.page * limit + 1; - var range_stop = range_start - 1 + limit; - if (this.records.length) { - range_stop = range_start - 1 + this.records.length; - } - if (range_stop > total) { - range_stop = total; - } - spager = _.str.sprintf(_t("%d-%d of %d"), range_start, range_stop, total); - } + $column.siblings('.o_column_sortable').removeClass("o-sort-up o-sort-down"); - this.$pager.find('.oe_list_pager_state').text(spager); - } - } + return this.reload_content(); }, /** * Sets up the listview's columns: merges view and fields data, move @@ -422,8 +376,7 @@ var ListView = View.extend({ return for_(id, fields[id], field); })); if (grouped) { - this.columns.unshift( - new ListView.MetaColumn('_group', _t("Group"))); + this.columns.unshift(new ListView.MetaColumn('_group')); } this.visible_columns = _.filter(this.columns, function (column) { @@ -468,27 +421,27 @@ var ListView = View.extend({ */ reload_content: synchronized(function () { var self = this; - self.$el.find('.oe_list_record_selector').prop('checked', false); + this.setup_columns(this.fields_view.fields, this.grouped); + this.$('tbody .o_list_record_selector input').prop('checked', false); this.records.reset(); var reloaded = $.Deferred(); - reloaded.then(function () { - self.configure_pager(self.dataset); - }); - this.$el.find('.oe_list_content').append( - this.groups.render(function () { - if (self.dataset.index === null) { - if (self.records.length) { - self.dataset.index = 0; - } - } else if (self.dataset.index >= self.records.length) { - self.dataset.index = self.records.length ? 0 : null; + this.groups.render(function () { + if (self.dataset.index === null) { + if (self.records.length) { + self.dataset.index = 0; + } + } else if (self.dataset.index >= self.records.length) { + self.dataset.index = self.records.length ? 0 : null; + } + self.load_list().then(function () { + if (!self.grouped && self.display_nocontent_helper()) { + self.no_result(); } - - self.compute_aggregates(); reloaded.resolve(); - })); + }); + }); this.do_push_state({ - page: this.page, + min: this.current_min, limit: this._limit }); return reloaded.promise(); @@ -531,8 +484,8 @@ var ListView = View.extend({ do_load_state: function(state, warm) { var reload = false; - if (state.page && this.page !== state.page) { - this.page = state.page; + if (state.min && this.current_min !== state.min) { + this.current_min = state.min; reload = true; } if (state.limit) { @@ -558,7 +511,7 @@ var ListView = View.extend({ * @param {Object} results results of evaluating domain and process for a search */ do_search: function (domain, context, group_by) { - this.page = 0; + this.current_min = 1; this.groups.datagroup = new DataGroup( this, this.model, domain, context, group_by); this.groups.datagroup.sort = this.dataset._sort; @@ -569,6 +522,10 @@ var ListView = View.extend({ this.no_leaf = !!context['group_by_no_leaf']; this.grouped = !!group_by; + // Hide the pager in grouped mode + if (this.pager && this.grouped) { + this.pager.do_hide(); + } return this.reload_content(); }, /** @@ -581,26 +538,25 @@ var ListView = View.extend({ return; } var self = this; + return $.when(this.dataset.unlink(ids)).done(function () { _(ids).each(function (id) { self.records.remove(self.records.get(id)); }); // Hide the table if there is no more record in the dataset - if (self.dataset.size() === 0) { + if (self.display_nocontent_helper()) { self.no_result(); } else { + // Load previous page if the current one is empty if (self.records.length === 0 && self.dataset.size() > 0) { - //Trigger previous manually to navigate to previous page, - //If all records are deleted on current page. - self.$pager.find('ul li:first a').trigger('click'); - } else if (self.dataset.size() === self._limit) { - //Reload listview to update current page with next page records - //because pager going to be hidden if dataset.size == limit + self.pager.previous(); + } + // Reload the list view if we are not on the last page + if (self.current_min + self._limit - 1 < self.dataset.size()) { self.reload(); - } else { - self.configure_pager(self.dataset); } } + self.update_pager(self.dataset); self.compute_aggregates(); }); }, @@ -613,7 +569,7 @@ var ListView = View.extend({ do_select: function (ids, records, deselected) { // uncheck header hook if at least one row has been deselected if (deselected) { - this.$('.oe_list_record_selector').prop('checked', false); + this.$('thead .o_list_record_selector input').prop('checked', false); } if (!ids.length) { @@ -810,13 +766,14 @@ var ListView = View.extend({ this.display_aggregates(aggregates); }, display_aggregates: function (aggregation) { - var $footer_cells = this.$el.find('.oe_list_footer'); + var $footer_cells = this.$('tfoot td'); _(this.aggregate_columns).each(function (column) { if (!column['function']) { return; } $footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id)) + .addClass('o_list_number') .html(column.format(aggregation, { process_modifiers: false })); }); }, @@ -831,7 +788,7 @@ var ListView = View.extend({ */ get_active_domain: function () { var self = this; - if (this.$('.oe_list_record_selector').prop('checked')) { + if (this.$('thead .o_list_record_selector input').prop('checked')) { var search_view = this.getParent().searchview; var search_data = search_view.build_search_data(); return pyeval.eval_domains_and_contexts({ @@ -859,7 +816,7 @@ var ListView = View.extend({ pad_columns: function (count, options) { options = options || {}; // padding for action/pager header - var $first_header = this.$el.find('thead tr:first th'); + var $first_header = this.$('thead tr:first th'); var colspan = $first_header.attr('colspan'); if (colspan) { if (!this.previous_colspan) { @@ -882,7 +839,7 @@ var ListView = View.extend({ * Removes all padding columns of the table */ unpad_columns: function () { - this.$el.find('.oe_list_padding').remove(); + this.$('.oe_list_padding').remove(); if (this.previous_colspan) { this.$el .find('thead tr:first th') @@ -890,23 +847,20 @@ var ListView = View.extend({ this.previous_colspan = null; } }, + display_nocontent_helper: function () { + return (this.dataset.size() === 0); + }, no_result: function () { - this.$el.find('.oe_view_nocontent').remove(); - if (this.groups.group_by - || !this.options.action - || !this.options.action.help) { + this.$('.oe_view_nocontent').remove(); + if (this.groups.group_by || + !this.options.action || + !this.options.action.help) { return; } - this.$el.find('table:first').hide(); + this.$('table:first').hide(); this.$el.prepend( $('<div class="oe_view_nocontent">').html(this.options.action.help) ); - if (this.$buttons) { - var $buttons = this.$buttons; - this.$el.find('.oe_view_nocontent').click(function() { - $buttons.width($buttons.width() + 1).openerpBounce(); - }); - } } }); core.view_registry.add('list', ListView); @@ -975,9 +929,8 @@ ListView.List = Class.extend({ $row = self.$current.children( '[data-id=' + record.get('id') + ']'); } - var $newRow = $(self.render_record(record)); - $newRow.find('.oe_list_record_selector input').prop('checked', !!$row.find('.oe_list_record_selector input').prop('checked')); + $newRow.find('.o_list_record_selector input').prop('checked', !!$row.find('.o_list_record_selector input').prop('checked')); $row.replaceWith($newRow); }, 'add': function (ev, records, record, index) { @@ -1012,14 +965,14 @@ ListView.List = Class.extend({ */ e.preventDefault(); }) - .delegate('th.oe_list_record_selector', 'click', function (e) { + .delegate('td.o_list_record_selector', 'click', function (e) { e.stopPropagation(); var selection = self.get_selection(); var checked = $(e.currentTarget).find('input').prop('checked'); $(self).trigger( 'selected', [selection.ids, selection.records, ! checked]); }) - .delegate('td.oe_list_record_delete', 'click', function (e) { + .delegate('td.o_list_record_delete', 'click', function (e) { e.stopPropagation(); var $row = $(e.target).closest('tr'); $(self).trigger('deleted', [[self.row_id($row)]]); @@ -1028,7 +981,7 @@ ListView.List = Class.extend({ $('<input />').appendTo('body').focus().remove(); } }) - .delegate('td.oe_list_field_cell button', 'click', function (e) { + .delegate('td button', 'click', function (e) { e.stopPropagation(); var $target = $(e.currentTarget), field = $target.closest('td').data('field'), @@ -1158,7 +1111,7 @@ ListView.List = Class.extend({ } var cells = []; if (this.options.selectable) { - cells.push('<th class="oe_list_record_selector"></td>'); + cells.push('<td class="o_list_record_selector"></td>'); } _(this.columns).each(function(column) { if (column.invisible === '1') { @@ -1167,7 +1120,7 @@ ListView.List = Class.extend({ cells.push('<td title="' + column.string + '"> </td>'); }); if (this.options.deletable) { - cells.push('<td class="oe_list_record_delete"></td>'); + cells.push('<td class="o_list_record_delete"></td>'); } cells.unshift('<tr>'); cells.push('</tr>'); @@ -1187,7 +1140,7 @@ ListView.List = Class.extend({ return result; } var records = this.records; - this.$current.find('th.oe_list_record_selector input:checked') + this.$current.find('td.o_list_record_selector input:checked') .closest('tr').each(function () { var record = records.get($(this).data('id')); result.ids.push(record.get('id')); @@ -1206,14 +1159,12 @@ ListView.List = Class.extend({ return $(row).data('id'); }, /** - * Death signal, cleans up list display + * Death signal, cleans up records's callbacks */ on_records_reset: function () { _(this.record_callbacks).each(function (callback, event) { this.records.unbind(event, callback); }, this); - if (!this.$current) { return; } - this.$current.remove(); }, get_records: function () { return this.records.map(function (record) { @@ -1279,7 +1230,7 @@ ListView.Groups = Class.extend({ this.$row = null; this.children = {}; - this.page = 0; + this.pager = null; // group pager var self = this; this.records.bind('reset', function () { @@ -1315,51 +1266,46 @@ ListView.Groups = Class.extend({ } return red_letter_tboday; }, - make_paginator: function () { + /** + * Renders the group pager and append it to the group's header + */ + render_group_pager: function () { var self = this; - var $prev = $('<button type="button" data-pager-action="previous"><</button>') - .click(function (e) { - e.stopPropagation(); - if (self.page > 0) { - self.page -= 1; - } else { - self.page = self.max_page_index; - } - - self.$row.closest('tbody').next() - .replaceWith(self.render()); + if (this.datagroup.length > this.view._limit) { + this.pager = new Pager(this, this.datagroup.length, 1, this.view._limit); + this.pager.on('pager_changed', this, function (state) { + self.view._limit = state.limit; + self.current_min = state.current_min; + self.render().then(function() { + self.$row.closest('tbody').next().replaceWith($(self.elements)); + }); }); - var $next = $('<button type="button" data-pager-action="next">></button>') - .click(function (e) { - e.stopPropagation(); - if (self.page < self.max_page_index) { - self.page += 1; - } else { - self.page = 0; - } - self.$row.closest('tbody').next() - .replaceWith(self.render()); + var $last_cell = this.$row.children().last(); + $last_cell.addClass('o_group_pager'); + // Prevent group to fold when clicking on the pager + $last_cell.click(function(e) { + e.stopPropagation(); }); - this.$row.children().last() - .addClass('oe_list_group_pagination') - .append($prev) - .append('<span class="oe_list_pager_state"></span>') - .append($next); + this.pager.appendTo($last_cell); + } }, open: function (point_insertion) { - this.render().insertAfter(point_insertion); + this.render(); + $(this.elements).insertAfter(point_insertion); var no_subgroups = _(this.datagroup.group_by).isEmpty(), records_terminated = !this.datagroup.context['group_by_no_leaf']; if (no_subgroups && records_terminated) { - this.make_paginator(); + this.render_group_pager(); } }, close: function () { - this.$row.children().last().find('button').remove(); - this.$row.children().last().find('span').remove(); + if (this.pager) { + this.pager.destroy(); + } this.records.reset(); + this.$to_be_removed.remove(); }, /** * Prefixes ``$node`` with floated spaces in order to indent it relative @@ -1391,20 +1337,20 @@ ListView.Groups = Class.extend({ self.bind_child_events(child); child.datagroup = group; - var $row = child.$row = $('<tr class="oe_group_header">'); + var $row = child.$row = $('<tr class="o_group_header">'); if (group.openable && group.length) { $row.click(function (e) { if (!$row.data('open')) { $row.data('open', true) - .find('span.ui-icon') - .removeClass('ui-icon-triangle-1-e') - .addClass('ui-icon-triangle-1-s'); + .find('span.fa') + .removeClass('fa-caret-right') + .addClass('fa-caret-down'); child.open(self.point_insertion(e.currentTarget)); } else { $row.removeData('open') - .find('span.ui-icon') - .removeClass('ui-icon-triangle-1-s') - .addClass('ui-icon-triangle-1-e'); + .find('span.fa') + .removeClass('fa-caret-down') + .addClass('fa-caret-right'); child.close(); // force recompute the selection as closing group reset properties var selection = self.get_selection(); @@ -1414,7 +1360,7 @@ ListView.Groups = Class.extend({ } placeholder.appendChild($row[0]); - var $group_column = $('<th class="oe_list_group_name">').appendTo($row); + var $group_column = $('<th class="o_group_name">').appendTo($row); // Don't fill this if group_by_no_leaf but no group_by if (group.grouped_on) { var row_data = {}; @@ -1449,12 +1395,9 @@ ListView.Groups = Class.extend({ if (group.length && group.openable) { // Make openable if not terminal group & group_by_no_leaf - $group_column.prepend('<span class="ui-icon ui-icon-triangle-1-e" style="float: left;">'); + $group_column.prepend('<span class="fa fa-caret-right" style="padding-right: 5px;">'); } else { - // Kinda-ugly hack: jquery-ui has no "empty" icon, so set - // wonky background position to ensure nothing is displayed - // there but the rest of the behavior is ui-icon's - $group_column.prepend('<span class="ui-icon" style="float: left; background-position: 150px 150px">'); + $group_column.prepend('<span class="fa">'); } } self.indent($group_column, group.level); @@ -1511,13 +1454,11 @@ ListView.Groups = Class.extend({ }); this.bind_child_events(list); - var view = this.view, - limit = view._limit, - page = this.datagroup.openable ? this.page : view.page; + var view = this.view; + var current_min = this.datagroup.openable ? this.current_min : view.current_min; var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name'); - var options = { offset: page * limit, limit: limit, context: {bin_size: true} }; - //TODO xmo: investigate why we need to put the setTimeout + var options = { offset: current_min - 1, limit: view._limit, context: {bin_size: true} }; return $.async_when().then(function() { return dataset.read_slice(fields, options).then(function (records) { // FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently @@ -1525,34 +1466,12 @@ ListView.Groups = Class.extend({ self.records.reset(null, {silent: true}); } if (!self.datagroup.openable) { - view.configure_pager(dataset); - } else { - if (dataset.size() == records.length) { - // only one page - self.$row.find('td.oe_list_group_pagination').find('button').remove(); - self.$row.find('td.oe_list_group_pagination').find('span').remove(); - } else { - self.max_page_index = Math.ceil(dataset.size() / limit) - 1; - self.$row - .find('.oe_list_pager_state') - .text(_.str.sprintf(_t("%(page)d/%(page_count)d"), { - page: page + 1, - page_count: self.max_page_index + 1 - })) - .end() - .find('button[data-pager-action=previous]') - .toggleClass('disabled', page === 0) - .end() - .find('button[data-pager-action=next]') - .toggleClass('disabled', page === self.max_page_index); - } + // Update the main list view pager + view.update_pager(dataset, current_min); } self.records.add(records, {silent: true}); list.render(); - if (_.isEmpty(records)) { - view.no_result(); - } return list; }); }); @@ -1580,7 +1499,7 @@ ListView.Groups = Class.extend({ helper: 'clone' }); if (sequence_field) { - list.$current.sortable('option', 'handle', '.oe_list_field_handle'); + list.$current.sortable('option', 'handle', '.o_row_handle'); } list.$current.sortable('option', { start: function (e, ui) { @@ -1631,7 +1550,7 @@ ListView.Groups = Class.extend({ var $el = $('<tbody>'); this.elements = [$el[0]]; - this.datagroup.list( + return this.datagroup.list( _(this.view.visible_columns).chain() .filter(function (column) { return column.tag === 'field';}) .pluck('name').value(), @@ -1640,7 +1559,7 @@ ListView.Groups = Class.extend({ self.render_groups(groups)); if (post_render) { post_render(); } }, function (dataset) { - self.render_dataset(dataset).then(function (list) { + return self.render_dataset(dataset).then(function (list) { self.children[null] = list; self.elements = [list.$current.replaceAll($el)[0]]; @@ -1650,7 +1569,6 @@ ListView.Groups = Class.extend({ self.view.trigger('view_list_rendered'); }); }); - return $el; }, /** * Returns the ids of all selected records for this group, and the records @@ -1669,8 +1587,11 @@ ListView.Groups = Class.extend({ return {ids: ids, records: records}; }, on_records_reset: function () { + this.$to_be_removed = $(this.elements); + _.each(this.children, function(child){ + this.$to_be_removed = this.$to_be_removed.add(child.$to_be_removed); + }.bind(this)); this.children = {}; - $(this.elements).remove(); }, get_records: function () { if (_(this.children).isEmpty()) { @@ -1725,13 +1646,12 @@ var DataGroup = Class.extend({ fields = _.unique((fields || []).concat(this.group_by)); } var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by); - $.when(query).done(function (querygroups) { + return $.when(query).then(function (querygroups) { // leaf node if (!querygroups) { var ds = new data.DataSetSearch(self, self.model.name, self.model.context(), self.model.domain()); ds._sort = self.sort; - ifRecords(ds); - return; + return ifRecords(ds); } // internal node var child_datagroups = _(querygroups).map(function (group) { @@ -1763,7 +1683,7 @@ var StaticDataGroup = DataGroup.extend({ this.dataset = dataset; }, list: function (fields, ifGroups, ifRecords) { - ifRecords(this.dataset); + return ifRecords(this.dataset); } }); @@ -1811,6 +1731,10 @@ var Column = Class.extend({ C.prototype = this; return new C(aggregation_func, this[aggregation_func]); }, + heading: function () { + return _.escape(this.string); + }, + width: function () {}, /** * * @param row_data record whose values should be displayed in the cell @@ -1834,7 +1758,8 @@ var Column = Class.extend({ ? '' : options.value_if_empty; } - return this._format(row_data, options); + var f = this._format(row_data, options); + return (f !== '')? f : ' '; }, /** * Method to override in order to provide alternative HTML content for the @@ -1890,7 +1815,7 @@ var ColumnBoolean = Column.extend({ * @private */ _format: function (row_data, options) { - return _.str.sprintf('<input type="checkbox" %s readonly="readonly"/>', + return _.str.sprintf('<div class="o_checkbox"><input type="checkbox" %s disabled="disabled"/><span/></div>', row_data[this.id].value ? 'checked="checked"' : ''); } }); @@ -1965,14 +1890,19 @@ var ColumnHandle = Column.extend({ this._super.apply(this, arguments); // Handle overrides the field to not be form-editable. this.modifiers.readonly = true; + this.string = ""; // Don't display the column header + }, + heading: function () { + return '<span class="o_row_handle fa fa-arrows invisible"></span>'; }, + width: function () { return 1; }, /** * Return styling hooks for a drag handle * * @private */ _format: function (row_data, options) { - return '<i class="oe_list_handle fa fa-circle"/>'; + return '<span class="o_row_handle fa fa-arrows"/>'; } }); @@ -2042,8 +1972,9 @@ var ColumnUrl = Column.extend({ var ColumnMonetary = Column.extend({ _format: function (row_data, options) { + var options = pyeval.py_eval(this.options || '{}'); //name of currency field is defined either by field attribute, in view options or we assume it is named currency_id - var currency_field = (this.options && this.options.currency_field) || this.currency_field || 'currency_id'; + var currency_field = (_.isEmpty(options) === false && options.currency_field) || this.currency_field || 'currency_id'; var currency_id = row_data[currency_field] && row_data[currency_field].value[0]; var currency = session.get_currency(currency_id); var digits_precision = this.digits || (currency && currency.digits); diff --git a/addons/web/static/src/js/views/list_view_editable.js b/addons/web/static/src/js/views/list_view_editable.js index 8937b8bb36f..f4a4c695334 100644 --- a/addons/web/static/src/js/views/list_view_editable.js +++ b/addons/web/static/src/js/views/list_view_editable.js @@ -69,9 +69,9 @@ var Editor = Widget.extend({ if (!(arch.attrs && arch.attrs.version === "7.0")) { throw new Error("Editor delegate's #edition_view must be a version 7 view"); } - if (!/\boe_form_container\b/.test(arch.attrs['class'])) { + if (!/\bo_list_editable_form\b/.test(arch.attrs['class'])) { throw new Error("Editor delegate's #edition_view must have the class " + - "'boe_form_container' on its root element"); + "'o_list_editable_form' on its root element"); } return edition_view; }, @@ -183,12 +183,12 @@ ListView.include(/** @lends instance.web.ListView# */{ } }); this.on('edit:after', this, function () { - self.$el.add(self.$buttons).addClass('oe_editing'); + self.$el.add(self.$buttons).addClass('o-editing'); self.$('.ui-sortable').sortable('disable'); }); this.on('save:after cancel:after', this, function () { self.$('.ui-sortable').sortable('enable'); - self.$el.add(self.$buttons).removeClass('oe_editing'); + self.$el.add(self.$buttons).removeClass('o-editing'); }); }, destroy: function () { @@ -260,7 +260,7 @@ ListView.include(/** @lends instance.web.ListView# */{ this._super.apply(this, arguments); } }, - start: function () { + load_list: function (data, grouped) { // tree/@editable takes priority on everything else if present. var result = this._super.apply(this, arguments); @@ -271,10 +271,10 @@ ListView.include(/** @lends instance.web.ListView# */{ this.editor = new Editor(this); // Editor is not restartable due to formview not being restartable if(this.editable()) { - this.$el.addClass('oe_list_editable'); + this.$el.addClass('o_list_editable'); return $.when(result, this.editor.prependTo(this.$el).done(this.proxy('setup_events'))); } else { - this.$el.removeClass('oe_list_editable'); + this.$el.removeClass('o_list_editable'); } return result; }, @@ -366,11 +366,11 @@ ListView.include(/** @lends instance.web.ListView# */{ fields[field_name] = field; self.fields_for_resize.push({field: field, cell: cell}); }, options).then(function () { - $recordRow.addClass('oe_edition'); + $recordRow.addClass('o_row_edition'); self.resize_fields(); // Local function that returns true if field is visible and editable var is_focusable = function(field) { - return field && field.$el.is(':visible:not(.oe_readonly)'); + return field && field.$el.is(':visible:not(.o_readonly)'); }; var focus_field = options && options.focus_field ? options.focus_field : undefined; if (!is_focusable(fields[focus_field])) { @@ -423,16 +423,21 @@ ListView.include(/** @lends instance.web.ListView# */{ resize_field: function (field, cell) { var $cell = $(cell); field.set_dimensions($cell.outerHeight(), $cell.outerWidth()); - field.$el.addClass('o_temp_visible').css({top: 0, left: 0}).position({ - my: 'left top', - at: 'left top', - of: $cell, - }).removeClass('o_temp_visible'); + position_element(field.$el, 'left top'); + position_element(field.$translate, 'right top'); if(field.get('effective_readonly')) { - field.$el.addClass('oe_readonly'); + field.$el.addClass('o_readonly'); } if(field.widget == "handle") { - field.$el.addClass('oe_list_field_handle'); + field.$el.addClass('o_row_handle'); + } + + function position_element($el, pos) { + $el.addClass('o_temp_visible').css({top: 0, left: 0}).position({ + my: pos, + at: pos, + of: $cell, + }).removeClass('o_temp_visible'); } }, /** @@ -541,7 +546,7 @@ ListView.include(/** @lends instance.web.ListView# */{ var view = $.extend(true, {}, this.fields_view); view.arch.tag = 'form'; _.extend(view.arch.attrs, { - 'class': 'oe_form_container', + 'class': 'o_list_editable_form', version: '7.0' }); _(view.arch.children).chain() @@ -817,7 +822,7 @@ ListView.List.include(/** @lends instance.web.ListView.List# */{ var record_id = $(event.currentTarget).data('id'); return this.view.start_edition( ((record_id)? this.records.get(record_id) : null), { - focus_field: $(event.target).not(".oe_readonly").data('field'), + focus_field: $(event.target).not(".o_readonly").data('field'), }).fail(function() { return _super.apply(self, args); // The record can't be edited so open it in a modal (use-case: readonly mode) }); diff --git a/addons/web/static/src/js/views/pivot_view.js b/addons/web/static/src/js/views/pivot_view.js index 9dc2c4c2cdd..c7710b86605 100644 --- a/addons/web/static/src/js/views/pivot_view.js +++ b/addons/web/static/src/js/views/pivot_view.js @@ -21,11 +21,11 @@ var QWeb = core.qweb; var PivotView = View.extend({ events: { - 'click .oe-opened': 'on_open_header_click', - 'click .oe-closed': 'on_closed_header_click', - 'click .o-field-menu': 'on_field_menu_selection', + 'click .o_pivot_header_cell_opened': 'on_open_header_click', + 'click .o_pivot_header_cell_closed': 'on_closed_header_click', + 'click .o_pivot_field_menu': 'on_field_menu_selection', 'click td': 'on_cell_click', - 'click .measure-row': 'on_measure_row_click', + 'click .o_pivot_measure_row': 'on_measure_row_click', }, display_name: _lt('Pivot'), icon: 'fa-table', @@ -100,6 +100,7 @@ var PivotView = View.extend({ if ((!this.active_measures.length) || this.fields_view.arch.attrs.display_quantity) { this.active_measures.push('__count__'); } + return $.when(fields_def, xlwt_def, this._super()).then(function (fields) { self.prepare_fields(fields); // add active measures to the measure list. This is very rarely necessary, but it @@ -116,14 +117,13 @@ var PivotView = View.extend({ }); }, start: function () { - this.$table_container = this.$('.o-pivot-table'); - this.$el.toggleClass('oe_enable_linking', this.enable_linking); + this.$el.toggleClass('o_enable_linking', this.enable_linking); var context = { fields: _.chain(this.groupable_fields).pairs().sortBy(function(f) { return f[1].string; }).value(), }; - this.$field_selection = this.$('.o-field-selection'); + this.$field_selection = this.$('.o_field_selection'); this.$field_selection.html(QWeb.render('PivotView.FieldSelection', context)); core.bus.on('click', this, function () { this.$field_selection.find('ul').first().hide(); @@ -161,11 +161,6 @@ var PivotView = View.extend({ render_sidebar: function($node) { if (this.xlwt_installed && $node && this.options.sidebar) { this.sidebar = new Sidebar(this, {editable: this.is_action_enabled('edit')}); - this.sidebar.add_items('other', [{ - label: _t("Download xls"), - callback: this.download_table.bind(this), - }]); - this.sidebar.appendTo($node); } }, @@ -207,11 +202,12 @@ var PivotView = View.extend({ }, do_show: function () { var self = this; + var _super = this._super.bind(this); this.do_push_state({}); - this.data_loaded.done(function () { + return this.data_loaded.done(function () { self.display_table(); + _super(); }); - return this._super(); }, get_context: function () { return !this.ready ? {} : { @@ -222,16 +218,17 @@ var PivotView = View.extend({ }, on_button_click: function (event) { var $target = $(event.target); - if ($target.hasClass('oe-pivot-flip')) { return this.flip();} - if ($target.hasClass('oe-pivot-expand-all')) {return this.expand_all();} - if ($target.parents('.oe-measure-list').length) { - var parent = $target.parent(), - field = parent.data('field'); + if ($target.hasClass('o_pivot_flip_button')) { return this.flip(); } + if ($target.hasClass('o_pivot_expand_button')) { return this.expand_all(); } + if ($target.parents('.o_pivot_measures_list').length) { + var parent = $target.parent(); + var field = parent.data('field'); parent.toggleClass('selected'); + event.preventDefault(); event.stopPropagation(); return this.toggle_measure(field); } - if ($target.hasClass('oe-pivot-download')) { + if ($target.hasClass('o_pivot_download')) { return this.download_table(); } }, @@ -252,21 +249,23 @@ var PivotView = View.extend({ this.expand_header(header, groupbys[header.path.length - 1]) .then(this.proxy('display_table')); } else { - var $test = $(event.target); - var pos = $test.position(); this.last_header_selected = id; + var $test = $(event.target); var $menu = this.$field_selection.find('ul').first(); - $menu.css('top', pos.top + $test.parent().height() - 2); - $menu.css('left', pos.left + event.offsetX); + var position = $test.position(); + $menu.css({ + top: position.top + $test.height(), + left: position.left + event.offsetX, + }); $menu.show(); event.stopPropagation(); } }, on_cell_click: function (event) { var $target = $(event.target); - if ($target.hasClass('oe-closed') - || $target.hasClass('oe-opened') - || $target.hasClass('oe-empty') + if ($target.hasClass('o_pivot_header_cell_closed') + || $target.hasClass('o_pivot_header_cell_opened') + || $target.hasClass('o_empty') || !this.enable_linking) { return; } @@ -297,11 +296,10 @@ var PivotView = View.extend({ col_id = $target.data('id'), measure = $target.data('measure'); - this.sort_rows(col_id, measure, $target.hasClass('o-sorted-asc')); + this.sort_rows(col_id, measure, $target.hasClass('o_pivot_measure_row_sorted_asc')); this.display_table(); }, sort_rows: function (col_id, measure, descending) { - console.log('order', descending); var self = this; traverse_tree(this.main_row.root, function (header) { header.children.sort(compare); @@ -321,6 +319,7 @@ var PivotView = View.extend({ } }, on_field_menu_selection: function (event) { + event.preventDefault(); var field = $(event.target).parent().data('field'), interval = $(event.target).data('interval'), header = this.headers[this.last_header_selected]; @@ -550,13 +549,22 @@ var PivotView = View.extend({ } return find_path_in_tree(root, path); }, + _clean_table: function () { + return this.$el + .find('table') + .remove() + .end() + .find('.oe_view_nocontent') + .remove() + .end(); + }, display_table: function () { if (!this.active_measures.length || !this.has_data) { - return this.$table_container.empty().append(QWeb.render('PivotView.nodata')); + return this._clean_table().append(QWeb.render('PivotView.nodata')); } var $fragment = $(document.createDocumentFragment()), $table = $('<table>') - .addClass('table table-hover table-condensed') + .addClass('table table-hover table-condensed table-bordered') .appendTo($fragment), $thead = $('<thead>').appendTo($table), $tbody = $('<tbody>').appendTo($table), @@ -572,8 +580,8 @@ var PivotView = View.extend({ $table.on('hover', 'td', function () { $table.find('col:eq(' + $(this).index()+')').toggleClass('hover'); }); - this.$table_container.empty().append($fragment); - this.$table_container.find('.oe-opened,.oe-closed').tooltip(); + this._clean_table().append($fragment); + this.$('.o_pivot_header_cell_opened,.o_pivot_header_cell_closed').tooltip(); }, draw_headers: function ($thead, headers) { var self = this, @@ -591,26 +599,27 @@ var PivotView = View.extend({ .text(cell.title) .attr('rowspan', cell.height) .attr('colspan', cell.width); - if (cell.total) { - $cell.addClass('oe-total'); - } if (i > 0) { $cell.attr('title', groupby_labels[i-1]); } if (cell.expanded !== undefined) { - $cell.addClass(cell.expanded ? 'oe-opened' : 'oe-closed'); + $cell.addClass(cell.expanded ? 'o_pivot_header_cell_opened' : 'o_pivot_header_cell_closed'); $cell.data('id', cell.id); } if (cell.measure) { - $cell.addClass('measure-row text-muted') - .text(this.measures[cell.measure].string) - .toggleClass('oe-total', cell.is_bold); + $cell.addClass('o_pivot_measure_row text-muted') + .text(this.measures[cell.measure].string); $cell.data('id', cell.id).data('measure', cell.measure); if (cell.id === this.sorted_column.id && cell.measure === this.sorted_column.measure) { - $cell.addClass('o-sorted o-sorted-' + this.sorted_column.order); + $cell.addClass('o_pivot_measure_row_sorted_' + this.sorted_column.order); } } $row.append($cell); + + $cell.toggleClass('hidden-xs', (cell.expanded !== undefined) || (cell.measure !== undefined && j < headers[i].length - this.active_measures.length)); + if (cell.height > 1) { + $cell.css('padding', 0); + } } $thead.append($row); } @@ -635,7 +644,7 @@ var PivotView = View.extend({ .text(rows[i].title) .data('id', rows[i].id) .css('padding-left', (5 + rows[i].indent * 30) + 'px') - .addClass(rows[i].expanded ? 'oe-opened' : 'oe-closed'); + .addClass(rows[i].expanded ? 'o_pivot_header_cell_opened' : 'o_pivot_header_cell_closed'); if (rows[i].indent > 0) $header.attr('title', groupby_labels[rows[i].indent - 1]); $header.appendTo($row); for (j = 0; j < length; j++) { @@ -643,12 +652,15 @@ var PivotView = View.extend({ $cell = $('<td>') .data('id', rows[i].id) .data('col_id', rows[i].col_ids[Math.floor(j / nbr_measures)]) - .toggleClass('oe-empty', !value) - .text(value); + .toggleClass('o_empty', !value) + .text(value) + .addClass('o_pivot_cell_value text-right'); if (((j >= length - this.active_measures.length) && display_total) || i === 0){ $cell.css('font-weight', 'bold'); } $row.append($cell); + + $cell.toggleClass('hidden-xs', j < length - this.active_measures.length); } $tbody.append($row); } diff --git a/addons/web/static/src/js/views/search_filters.js b/addons/web/static/src/js/views/search_filters.js index c6193488578..041cda35580 100644 --- a/addons/web/static/src/js/views/search_filters.js +++ b/addons/web/static/src/js/views/search_filters.js @@ -12,9 +12,9 @@ var _lt = core._lt; var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ template: 'SearchView.extended_search.proposition', events: { - 'change .searchview_extended_prop_field': 'changed', - 'change .searchview_extended_prop_op': 'operator_changed', - 'click .searchview_extended_delete_prop': function (e) { + 'change .o_searchview_extended_prop_field': 'changed', + 'change .o_searchview_extended_prop_op': 'operator_changed', + 'click .o_searchview_extended_delete_prop': function (e) { e.stopPropagation(); this.getParent().remove_proposition(this); }, @@ -40,13 +40,13 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext return this._super().done(this.proxy('changed')); }, changed: function() { - var nval = this.$(".searchview_extended_prop_field").val(); + var nval = this.$(".o_searchview_extended_prop_field").val(); if(this.attrs.selected === null || this.attrs.selected === undefined || nval != this.attrs.selected.name) { this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); } }, operator_changed: function (e) { - var $value = this.$('.searchview_extended_prop_value'); + var $value = this.$('.o_searchview_extended_prop_value'); switch ($(e.target).val()) { case '∃': case '∄': @@ -66,7 +66,7 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext if(this.attrs.selected !== null && this.attrs.selected !== undefined) { this.value.destroy(); this.value = null; - this.$('.searchview_extended_prop_op').html(''); + this.$('.o_searchview_extended_prop_op').html(''); } this.attrs.selected = field; if(field === null || field === undefined) { @@ -80,9 +80,9 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext _.each(this.value.operators, function(operator) { $('<option>', {value: operator.value}) .text(String(operator.text)) - .appendTo(self.$('.searchview_extended_prop_op')); + .appendTo(self.$('.o_searchview_extended_prop_op')); }); - var $value_loc = this.$('.searchview_extended_prop_value').show().empty(); + var $value_loc = this.$('.o_searchview_extended_prop_value').show().empty(); this.value.appendTo($value_loc); }, @@ -90,7 +90,7 @@ var ExtendedSearchProposition = Widget.extend(/** @lends instance.web.search.Ext if (this.attrs.selected === null || this.attrs.selected === undefined) return null; var field = this.attrs.selected, - op_select = this.$('.searchview_extended_prop_op')[0], + op_select = this.$('.o_searchview_extended_prop_op')[0], operator = op_select.options[op_select.selectedIndex]; return { @@ -155,7 +155,10 @@ ExtendedSearchProposition.Field = Widget.extend({ }); ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({ - template: 'SearchView.extended_search.proposition.char', + tagName: 'input', + attributes: { + type: 'text' + }, operators: [ {value: "ilike", text: _lt("contains")}, {value: "not ilike", text: _lt("doesn't contain")}, @@ -170,7 +173,7 @@ ExtendedSearchProposition.Char = ExtendedSearchProposition.Field.extend({ }); ExtendedSearchProposition.DateTime = ExtendedSearchProposition.Field.extend({ - template: 'SearchView.extended_search.proposition.empty', + tagName: 'span', operators: [ {value: "=", text: _lt("is equal to")}, {value: "!=", text: _lt("is not equal to")}, @@ -204,7 +207,11 @@ ExtendedSearchProposition.Date = ExtendedSearchProposition.DateTime.extend({ }); ExtendedSearchProposition.Integer = ExtendedSearchProposition.Field.extend({ - template: 'SearchView.extended_search.proposition.integer', + tagName: 'input', + attributes: { + type: 'number', + value: '0', + }, operators: [ {value: "=", text: _lt("is equal to")}, {value: "!=", text: _lt("is not equal to")}, @@ -280,7 +287,7 @@ ExtendedSearchProposition.Selection = ExtendedSearchProposition.Field.extend({ }); ExtendedSearchProposition.Boolean = ExtendedSearchProposition.Field.extend({ - template: 'SearchView.extended_search.proposition.empty', + tagName: 'span', operators: [ {value: "=", text: _lt("is true")}, {value: "!=", text: _lt("is false")} diff --git a/addons/web/static/src/js/views/search_inputs.js b/addons/web/static/src/js/views/search_inputs.js index 44b79999259..0263f270172 100644 --- a/addons/web/static/src/js/views/search_inputs.js +++ b/addons/web/static/src/js/views/search_inputs.js @@ -541,6 +541,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{ return { category: _t("Filter"), icon: this.icon, + separator: _t(" or "), values: values, field: this }; @@ -617,6 +618,7 @@ var FilterGroup = Input.extend(/** @lends instance.web.search.FilterGroup# */{ }); }, toggle_filter: function (e) { + e.preventDefault(); e.stopPropagation(); this.toggle(this.filters[Number($(e.target).parent().data('index'))]); }, @@ -686,6 +688,7 @@ var GroupbyGroup = FilterGroup.extend({ return { category: _t("Group By"), icon: this.icon, + separator: " > ", values: values, field: this.searchview._s_groupby }; diff --git a/addons/web/static/src/js/views/search_menus.js b/addons/web/static/src/js/views/search_menus.js index 9edb817c567..10ab44ef30c 100644 --- a/addons/web/static/src/js/views/search_menus.js +++ b/addons/web/static/src/js/views/search_menus.js @@ -15,12 +15,16 @@ return Widget.extend({ 'click li': function (event) { event.stopImmediatePropagation(); }, - 'click .oe-save-search a': function () { + 'click li a': function (event) { + event.preventDefault(); + }, + 'click .o_save_search a': function (event) { + event.preventDefault(); this.toggle_save_menu(); }, - 'click .oe-save-name button': 'save_favorite', + 'click .o_save_name button': 'save_favorite', 'hidden.bs.dropdown': 'close_menus', - 'keyup .oe-save-name input': function (ev) { + 'keyup .o_save_name input': function (ev) { if (ev.which === $.ui.keyCode.ENTER) { this.save_favorite(); } @@ -38,8 +42,8 @@ return Widget.extend({ start: function () { var self = this; this.$filters = {}; - this.$save_search = this.$('.oe-save-search'); - this.$save_name = this.$('.oe-save-name'); + this.$save_search = this.$('.o_save_search'); + this.$save_name = this.$('.o_save_name'); this.$inputs = this.$save_name.find('input'); this.$divider = this.$('.divider'); this.$inputs.eq(0).val(this.searchview.get_title()); @@ -116,7 +120,6 @@ return Widget.extend({ model_id: this.target_model, context: results.context, domain: results.domain, - sort: JSON.stringify(this.searchview.dataset._sort), is_default: default_filter, action_id: this.action_id, }; @@ -204,12 +207,11 @@ return Widget.extend({ this.$divider.show(); if (!(key in this.$filters)) { var $filter = $('<li>') - .addClass(filter.user_id ? 'oe_searchview_custom_private' - : 'oe_searchview_custom_public') - .toggleClass('oe_searchview_custom_default', filter.is_default) - .append($('<a>').text(filter.name)) + .addClass(filter.user_id ? 'o-searchview-custom-private' + : 'o-searchview-custom-public') + .append($('<a>', {'href': '#'}).text(filter.name)) .append($('<span>', { - class: 'fa fa-trash-o remove-filter', + class: 'fa fa-trash-o o-remove-filter', on: { click: function (event) { event.stopImmediatePropagation(); @@ -235,13 +237,6 @@ return Widget.extend({ } this.query.reset([this.facet_for(filter)], { preventSearch: preventSearch || false}); - - // Load sort settings on view - if (!_.isUndefined(filter.sort)){ - var sort_items = JSON.parse(filter.sort); - this.searchview.dataset.set_sort(sort_items); - } - this.$filters[this.key_for(filter)].addClass('selected'); }, remove_filter: function (filter, $filter, key) { @@ -277,16 +272,20 @@ var Widget = require('web.Widget'); return Widget.extend({ template: 'SearchView.FilterMenu', events: { - 'click .oe-add-filter': function () { + 'click .o_add_filter': function (event) { + event.preventDefault(); this.toggle_custom_filter_menu(); }, - 'click li': function (event) {event.stopImmediatePropagation();}, + 'click li': function (event) { + event.preventDefault(); + event.stopImmediatePropagation(); + }, 'hidden.bs.dropdown': function () { this.toggle_custom_filter_menu(false); }, - 'click .oe-add-condition': 'append_proposition', - 'click .oe-apply-filter': 'commit_search', - 'keyup .searchview_extended_prop_value': function (ev) { + 'click .o_add_condition': 'append_proposition', + 'click .o_apply_filter': 'commit_search', + 'keyup .o_searchview_extended_prop_value': function (ev) { if (ev.which === $.ui.keyCode.ENTER) { this.commit_search(); } @@ -301,10 +300,10 @@ return Widget.extend({ }, start: function () { var self = this; - this.$menu = this.$('.filters-menu'); - this.$add_filter = this.$('.oe-add-filter'); - this.$apply_filter = this.$('.oe-apply-filter'); - this.$add_filter_menu = this.$('.oe-add-filter-menu'); + this.$menu = this.$('.o_filters_menu'); + this.$add_filter = this.$('.o_add_filter'); + this.$apply_filter = this.$('.o_apply_filter'); + this.$add_filter_menu = this.$('.o_add_filter_menu'); _.each(this.filters, function (group) { if (group.is_visible()) { group.insertBefore(self.$add_filter); @@ -340,7 +339,7 @@ return Widget.extend({ .toggleClass('o_closed_menu', !self.custom_filters_open) .toggleClass('o_open_menu', self.custom_filters_open); self.$add_filter_menu.toggle(self.custom_filters_open); - self.$('.oe-filter-condition').toggle(self.custom_filters_open); + self.$('.o_filter_condition').toggle(self.custom_filters_open); }); }, append_proposition: function () { @@ -402,7 +401,8 @@ return Widget.extend({ 'hidden.bs.dropdown': function () { this.toggle_add_menu(false); }, - 'click .add-custom-group a': function () { + 'click .o_add_custom_group a': function (event) { + event.preventDefault(); this.toggle_add_menu(); }, }, @@ -413,13 +413,13 @@ return Widget.extend({ this.searchview = parent; }, start: function () { - this.$menu = this.$('.group-by-menu'); + this.$menu = this.$('.o_group_by_menu'); var divider = this.$menu.find('.divider'); _.invoke(this.groups, 'insertBefore', divider); if (this.groups.length) { divider.show(); } - this.$add_group = this.$menu.find('.add-custom-group'); + this.$add_group = this.$menu.find('.o_add_custom_group'); }, get_fields: function () { var self = this; @@ -435,9 +435,9 @@ return Widget.extend({ self.groupable_fields = _.sortBy(filter_group_field, 'string'); self.$menu.append(QWeb.render('GroupByMenuSelector', self)); - self.$add_group_menu = self.$('.oe-add-group'); - self.$group_selector = self.$('.oe-group-selector'); - self.$('.oe-select-group').click(function () { + self.$add_group_menu = self.$('.o_add_group'); + self.$group_selector = self.$('.o_group_selector'); + self.$('.o_select_group').click(function () { self.toggle_add_menu(false); var field = self.$group_selector.find(':selected').data('name'); self.add_groupby_to_menu(field); diff --git a/addons/web/static/src/js/views/search_view.js b/addons/web/static/src/js/views/search_view.js index 125b7c9c152..fa1794cdc7c 100644 --- a/addons/web/static/src/js/views/search_view.js +++ b/addons/web/static/src/js/views/search_view.js @@ -2,6 +2,7 @@ odoo.define('web.SearchView', function (require) { "use strict"; var AutoComplete = require('web.AutoComplete'); +var config = require('web.config'); var core = require('web.core'); var data_manager = require('web.data_manager'); var FavoriteMenu = require('web.FavoriteMenu'); @@ -9,9 +10,9 @@ var FilterMenu = require('web.FilterMenu'); var GroupByMenu = require('web.GroupByMenu'); var pyeval = require('web.pyeval'); var search_inputs = require('web.search_inputs'); -var utils = require('web.utils'); var View = require('web.View'); var Widget = require('web.Widget'); +var _t = core._t; var Backbone = window.Backbone; @@ -134,125 +135,32 @@ var InputView = Widget.extend({ template: 'SearchView.InputView', events: { focus: function () { this.trigger('focused', this); }, - blur: function () { this.$el.text(''); this.trigger('blurred', this); }, + blur: function () { this.$el.val(''); this.trigger('blurred', this); }, keydown: 'onKeydown', - paste: 'onPaste', - }, - getSelection: function () { - this.el.normalize(); - // get Text node - var root = this.el.childNodes[0]; - if (!root || !root.textContent) { - // if input does not have a child node, or the child node is an - // empty string, then the selection can only be (0, 0) - return {start: 0, end: 0}; - } - var range = window.getSelection().getRangeAt(0); - // In Firefox, depending on the way text is selected (drag, double- or - // triple-click) the range may start or end on the parent of the - // selected text node‽ Check for this condition and fixup the range - // note: apparently with C-a this can go even higher? - if (range.startContainer === this.el && range.startOffset === 0) { - range.setStart(root, 0); - } - if (range.endContainer === this.el && range.endOffset === 1) { - range.setEnd(root, root.length); - } - utils.assert(range.startContainer === root, - "selection should be in the input view"); - utils.assert(range.endContainer === root, - "selection should be in the input view"); - return { - start: range.startOffset, - end: range.endOffset - }; }, onKeydown: function (e) { - this.el.normalize(); - var sel; switch (e.which) { - // Do not insert newline, but let it bubble so searchview can use it - case $.ui.keyCode.ENTER: - e.preventDefault(); - break; + case $.ui.keyCode.BACKSPACE: + if(this.$el.val() === '') { + var preceding = this.getParent().siblingSubview(this, -1); + if (preceding && (preceding instanceof FacetView)) { + preceding.model.destroy(); + } + } + break; - // FIXME: may forget content if non-empty but caret at index 0, ok? - case $.ui.keyCode.BACKSPACE: - sel = this.getSelection(); - if (sel.start === 0 && sel.start === sel.end) { - e.preventDefault(); - var preceding = this.getParent().siblingSubview(this, -1); - if (preceding && (preceding instanceof FacetView)) { - preceding.model.destroy(); + case $.ui.keyCode.LEFT: // Stop propagation to parent if not at beginning of input value + if(this.el.selectionStart > 0) { + e.stopPropagation(); } - } - break; + break; - // let left/right events propagate to view if caret is at input border - // and not a selection - case $.ui.keyCode.LEFT: - sel = this.getSelection(); - if (sel.start !== 0 || sel.start !== sel.end) { - e.stopPropagation(); - } - break; - case $.ui.keyCode.RIGHT: - sel = this.getSelection(); - var len = this.$el.text().length; - if (sel.start !== len || sel.start !== sel.end) { - e.stopPropagation(); - } - break; + case $.ui.keyCode.RIGHT: // Stop propagation to parent if not at end of input value + if(this.el.selectionStart < this.$el.val().length) { + e.stopPropagation(); + } + break; } - }, - setCursorAtEnd: function () { - this.el.normalize(); - var sel = window.getSelection(); - sel.removeAllRanges(); - var range = document.createRange(); - // in theory, range.selectNodeContents should work here. In practice, - // MSIE9 has issues from time to time, instead of selecting the inner - // text node it would select the reference node instead (e.g. in demo - // data, company news, copy across the "Company News" link + the title, - // from about half the link to half the text, paste in search box then - // hit the left arrow key, getSelection would blow up). - // - // Explicitly selecting only the inner text node (only child node - // since we've normalized the parent) avoids the issue - range.selectNode(this.el.childNodes[0]); - range.collapse(false); - sel.addRange(range); - }, - onPaste: function () { - this.el.normalize(); - // In MSIE and Webkit, it is possible to get various representations of - // the clipboard data at this point e.g. - // window.clipboardData.getData('Text') and - // event.clipboardData.getData('text/plain') to ensure we have a plain - // text representation of the object (and probably ensure the object is - // pastable as well, so nobody puts an image in the search view) - // (nb: since it's not possible to alter the content of the clipboard - // — at least in Webkit — to ensure only textual content is available, - // using this would require 1. getting the text data; 2. manually - // inserting the text data into the content; and 3. cancelling the - // paste event) - // - // But Firefox doesn't support the clipboard API (as of FF18) - // although it correctly triggers the paste event (Opera does not even - // do that) => implement lowest-denominator system where onPaste - // triggers a followup "cleanup" pass after the data has been pasted - setTimeout(function () { - // Read text content (ignore pasted HTML) - var data = this.$el.text(); - if (!data) - return; - // paste raw text back in - this.$el.empty().text(data); - this.el.normalize(); - // Set the cursor at the end of the text, so the cursor is not lost - // in some kind of error-spawning limbo. - this.setCursorAtEnd(); - }.bind(this), 0); } }); @@ -262,7 +170,7 @@ var FacetView = Widget.extend({ 'focus': function () { this.trigger('focused', this); }, 'blur': function () { this.trigger('blurred', this); }, 'click': function (e) { - if ($(e.target).is('.oe_facet_remove')) { + if ($(e.target).hasClass('o_facet_remove')) { this.model.destroy(); return false; } @@ -272,10 +180,10 @@ var FacetView = Widget.extend({ 'keydown': function (e) { var keys = $.ui.keyCode; switch (e.which) { - case keys.BACKSPACE: - case keys.DELETE: - this.model.destroy(); - return false; + case keys.BACKSPACE: + case keys.DELETE: + this.model.destroy(); + return false; } } }, @@ -290,9 +198,12 @@ var FacetView = Widget.extend({ }, start: function () { var self = this; - var $e = this.$('> span:last-child'); + var $e = this.$('.o_facet_values').last(); return $.when(this._super()).then(function () { - return $.when.apply(null, self.model.values.map(function (value) { + return $.when.apply(null, self.model.values.map(function (value, index) { + if (index > 0) { + $('<span/>', {html: self.model.get('separator') || _t(" or ")}).addClass('o_facet_values_sep').appendTo($e); + } return new FacetValueView(self, value).appendTo($e); })); }); @@ -319,49 +230,34 @@ var FacetValueView = Widget.extend({ }); var SearchView = View.extend({ - defaults: _.extend({}, View.prototype.defaults, { - hidden: false, - disable_filters: false, - disable_groupby: false, - disable_favorites: false, - disable_custom_filters: false, - }), events: { - // focus last input if view itself is clicked - 'click': function (e) { - if (e.target === this.$('.oe_searchview_facets')[0]) { - this.$('.oe_searchview_input:last').focus(); - } - }, - // search button - 'click div.oe_searchview_search': function (e) { - e.stopImmediatePropagation(); - this.do_search(); - }, - 'click .oe_searchview_unfold_drawer': function (e) { - e.stopImmediatePropagation(); - $(e.target).toggleClass('fa-caret-down fa-caret-up'); + 'click .o_searchview_more': function (e) { + $(e.target).toggleClass('fa-search-plus fa-search-minus'); localStorage.visible_search_menu = (localStorage.visible_search_menu !== 'true'); this.toggle_buttons(); }, - 'keydown .oe_searchview_input, .oe_searchview_facet': function (e) { + 'keydown .o_searchview_input, .o_searchview_facet': function (e) { switch(e.which) { - case $.ui.keyCode.LEFT: - this.focusPreceding(e.target); - e.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - if (!this.autocomplete.is_expandable()) { - this.focusFollowing(e.target); - } - e.preventDefault(); - break; + case $.ui.keyCode.LEFT: + this.focusPreceding(e.target); + e.preventDefault(); + break; + case $.ui.keyCode.RIGHT: + if(!this.autocomplete.is_expandable()) { + this.focusFollowing(e.target); + } + e.preventDefault(); + break; } }, - 'autocompleteopen': function () { - this.$el.autocomplete('widget').css('z-index', 9999); - }, }, + defaults: _.extend({}, View.prototype.defaults, { + hidden: false, + disable_filters: false, + disable_groupby: false, + disable_favorites: false, + disable_custom_filters: false, + }), template: "SearchView", /** @@ -377,9 +273,9 @@ var SearchView = View.extend({ */ init: function() { this._super.apply(this, arguments); - this.title = this.options.action && this.options.action.name; - this.action_id = this.options.action && this.options.action.id; this.query = undefined; + this.title = this.options.action && this.options.action.name; + this.action_id = this.options.action && this.options.action.id; this.search_fields = []; this.filters = []; this.groupbys = []; @@ -408,15 +304,14 @@ var SearchView = View.extend({ this.do_hide(); } this.toggle_visibility(false); - this.$facets_container = this.$('div.oe_searchview_facets'); this.setup_global_completion(); this.query = new SearchQuery() .on('add change reset remove', this.proxy('do_search')) .on('change', this.proxy('renderChangedFacets')) .on('add reset remove', this.proxy('renderFacets')); - this.$('.oe_searchview_unfold_drawer') - .toggleClass('fa-caret-down', !this.visible_filters) - .toggleClass('fa-caret-up', this.visible_filters); + this.$('.o_searchview_more') + .toggleClass('fa-search-minus', this.visible_filters) + .toggleClass('fa-search-plus', !this.visible_filters); var menu_defs = []; this.prepare_search_inputs(); if (this.$buttons) { @@ -516,8 +411,8 @@ var SearchView = View.extend({ if (this.$buttons) { this.$buttons.toggle(!this.headless && is_visible && this.visible_filters); } - if (!this.headless && is_visible) { - this.$('div.oe_searchview_input').last().focus(); + if (!config.device.touch && config.device.size_class >= config.device.SIZES.SM) { + this.$('input').focus(); } }, toggle_buttons: function (is_visible) { @@ -535,7 +430,7 @@ var SearchView = View.extend({ source: this.proxy('complete_global_search'), select: this.proxy('select_completion'), get_search_string: function () { - return self.$('div.oe_searchview_input').text(); + return self.$('.o_searchview_input').val().trim(); }, }); this.autocomplete.appendTo(this.$el); @@ -569,10 +464,11 @@ var SearchView = View.extend({ */ select_completion: function (e, ui) { e.preventDefault(); - var input_index = _(this.input_subviews).indexOf( - this.subviewForRoot( - this.$('div.oe_searchview_input:focus')[0])); - this.query.add(ui.item.facet, {at: input_index / 2}); + if(ui.item.facet.values && ui.item.facet.values.length && String(ui.item.facet.values[0].value).trim() !== "") { + this.query.add(ui.item.facet); + } else { + this.query.trigger('add'); + } }, subviewForRoot: function (subview_root) { return _(this.input_subviews).detect(function (subview) { @@ -609,34 +505,21 @@ var SearchView = View.extend({ _.invoke(this.input_subviews, 'destroy'); this.input_subviews = []; - var i = new InputView(this); - started.push(i.appendTo(this.$facets_container)); - this.input_subviews.push(i); this.query.each(function (facet) { var f = new FacetView(this, facet); - started.push(f.appendTo(self.$facets_container)); + started.push(f.appendTo(self.$el)); self.input_subviews.push(f); - - var i = new InputView(this); - started.push(i.appendTo(self.$facets_container)); - self.input_subviews.push(i); }, this); + var i = new InputView(this); + started.push(i.appendTo(self.$el)); + self.input_subviews.push(i); _.each(this.input_subviews, function (childView) { childView.on('focused', self, self.proxy('childFocused')); childView.on('blurred', self, self.proxy('childBlurred')); }); $.when.apply(null, started).then(function () { - if (options && options.focus_input === false) return; - var input_to_focus; - // options.at: facet inserted at given index, focus next input - // otherwise just focus last input - if (!options || typeof options.at !== 'number') { - input_to_focus = _.last(self.input_subviews); - } else { - input_to_focus = self.input_subviews[(options.at + 1) * 2]; - } - input_to_focus.$el.focus(); + _.last(self.input_subviews).$el.focus(); }); }, childFocused: function () { @@ -726,230 +609,3 @@ _.extend(SearchView, { return SearchView; }); - -odoo.define('web.AutoComplete', function (require) { -"use strict"; - -var Widget = require('web.Widget'); - -return Widget.extend({ - template: "SearchView.autocomplete", - - // Parameters for autocomplete constructor: - // - // parent: this is used to detect keyboard events - // - // options.source: function ({term:query}, callback). This function will be called to - // obtain the search results corresponding to the query string. It is assumed that - // options.source will call callback with the results. - // options.select: function (ev, {item: {facet:facet}}). Autocomplete widget will call - // that function when a selection is made by the user - // options.get_search_string: function (). This function will be called by autocomplete - // to obtain the current search string. - init: function (parent, options) { - this._super(parent); - this.$input = parent.$el; - this.source = options.source; - this.select = options.select; - this.get_search_string = options.get_search_string; - - this.current_result = null; - - this.searching = true; - this.search_string = ''; - this.current_search = null; - }, - start: function () { - var self = this; - this.$input.on('keyup', function (ev) { - if (ev.which === $.ui.keyCode.RIGHT) { - self.searching = true; - ev.preventDefault(); - return; - } - if (ev.which === $.ui.keyCode.ENTER) { - if (self.search_string.length) { - self.select_item(ev); - } - return; - } - var search_string = self.get_search_string(); - if (self.search_string !== search_string) { - if (search_string.length) { - self.search_string = search_string; - self.initiate_search(search_string); - } else { - self.close(); - } - } - }); - this.$input.on('keypress', function (ev) { - self.search_string = self.search_string + String.fromCharCode(ev.which); - if (self.search_string.length) { - self.searching = true; - var search_string = self.search_string; - self.initiate_search(search_string); - } else { - self.close(); - } - }); - this.$input.on('keydown', function (ev) { - switch (ev.which) { - case $.ui.keyCode.ENTER: - - // TAB and direction keys are handled at KeyDown because KeyUp - // is not guaranteed to fire. - // See e.g. https://github.com/aef-/jquery.masterblaster/issues/13 - case $.ui.keyCode.TAB: - if (self.search_string.length) { - self.select_item(ev); - } - break; - case $.ui.keyCode.DOWN: - self.move('down'); - self.searching = false; - ev.preventDefault(); - break; - case $.ui.keyCode.UP: - self.move('up'); - self.searching = false; - ev.preventDefault(); - break; - case $.ui.keyCode.RIGHT: - self.searching = false; - var current = self.current_result; - if (current && current.expand && !current.expanded) { - self.expand(); - self.searching = true; - } - ev.preventDefault(); - break; - case $.ui.keyCode.ESCAPE: - self.close(); - self.searching = false; - break; - } - }); - }, - initiate_search: function (query) { - if (query === this.search_string && query !== this.current_search) { - this.search(query); - } - }, - search: function (query) { - var self = this; - this.current_search = query; - this.source({term:query}, function (results) { - if (results.length) { - self.render_search_results(results); - self.focus_element(self.$('li:first-child')); - } else { - self.close(); - } - }); - }, - render_search_results: function (results) { - var self = this; - var $list = this.$('ul'); - $list.empty(); - var render_separator = false; - results.forEach(function (result) { - if (result.is_separator) { - if (render_separator) - $list.append($('<li>').addClass('oe-separator')); - render_separator = false; - } else { - var $item = self.make_list_item(result).appendTo($list); - result.$el = $item; - render_separator = true; - } - }); - this.show(); - }, - make_list_item: function (result) { - var self = this; - var $li = $('<li>') - .hover(function () {self.focus_element($li);}) - .mousedown(function (ev) { - if (ev.button === 0) { // left button - self.select(ev, {item: {facet: result.facet}}); - self.close(); - } else { - ev.preventDefault(); - } - }) - .data('result', result); - if (result.expand) { - var $expand = $('<span class="oe-expand">').text('▶').appendTo($li); - $expand.mousedown(function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - if (result.expanded) - self.fold(); - else - self.expand(); - }); - result.expanded = false; - } - if (result.indent) $li.addClass('oe-indent'); - $li.append($('<span>').html(result.label)); - return $li; - }, - expand: function () { - var self = this; - var current_result = this.current_result; - current_result.expand(this.get_search_string()).then(function (results) { - (results || [{label: '(no result)'}]).reverse().forEach(function (result) { - result.indent = true; - var $li = self.make_list_item(result); - current_result.$el.after($li); - }); - current_result.expanded = true; - current_result.$el.find('span.oe-expand').html('▼'); - }); - }, - fold: function () { - var $next = this.current_result.$el.next(); - while ($next.hasClass('oe-indent')) { - $next.remove(); - $next = this.current_result.$el.next(); - } - this.current_result.expanded = false; - this.current_result.$el.find('span.oe-expand').html('▶'); - }, - focus_element: function ($li) { - this.$('li').removeClass('oe-selection-focus'); - $li.addClass('oe-selection-focus'); - this.current_result = $li.data('result'); - }, - select_item: function (ev) { - if (this.current_result.facet) { - this.select(ev, {item: {facet: this.current_result.facet}}); - this.close(); - } - }, - show: function () { - this.$el.show(); - }, - close: function () { - this.current_search = null; - this.search_string = ''; - this.searching = true; - this.$el.hide(); - }, - move: function (direction) { - var $next; - if (direction === 'down') { - $next = this.$('li.oe-selection-focus').nextAll(':not(.oe-separator)').first(); - if (!$next.length) $next = this.$('li:first-child'); - } else { - $next = this.$('li.oe-selection-focus').prevAll(':not(.oe-separator)').first(); - if (!$next.length) $next = this.$('li:not(.oe-separator)').last(); - } - this.focus_element($next); - }, - is_expandable: function () { - return !!this.$('.oe-selection-focus .oe-expand').length; - }, -}); -}); diff --git a/addons/web/static/src/js/views/tree_view.js b/addons/web/static/src/js/views/tree_view.js index 6246a1494f5..aeb206257fe 100644 --- a/addons/web/static/src/js/views/tree_view.js +++ b/addons/web/static/src/js/views/tree_view.js @@ -87,7 +87,7 @@ var TreeView = View.extend({ return; } - var $select = self.$el.find('select') + var $select = self.$('select') .change(function () { var $option = $(this).find(':selected'); self.getdata($option.val(), $option.data('children')); @@ -155,8 +155,8 @@ var TreeView = View.extend({ children_ids = record[self.children_field]; _(children_ids).each(function(childid) { - if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').length ) { - if (self.$el.find('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').is(':hidden')) { + if (self.$('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').length ) { + if (self.$('[id=treerow_' + childid + '][data-row-parent-id='+ record_id +']').is(':hidden')) { is_loaded = -1; } else { is_loaded++; @@ -164,7 +164,7 @@ var TreeView = View.extend({ } }); if (is_loaded === 0) { - if (!$this.parent().hasClass('oe_open')) { + if (!$this.parent().hasClass('o_open')) { self.getdata(record_id, children_ids); } } else { @@ -180,7 +180,7 @@ var TreeView = View.extend({ _(records).each(function (record) { self.records[record.id] = record; }); - var $curr_node = self.$el.find('#treerow_' + id); + var $curr_node = self.$('#treerow_' + id); var children_rows = QWeb.render('TreeView.rows', { 'records': records, 'children_field': self.children_field, @@ -192,10 +192,10 @@ var TreeView = View.extend({ 'row_parent_id': id }); if ($curr_node.length) { - $curr_node.addClass('oe_open'); + $curr_node.addClass('o_open'); $curr_node.after(children_rows); } else { - self.$el.find('tbody').html(children_rows); + self.$('tbody').html(children_rows); } }); }, @@ -229,11 +229,11 @@ var TreeView = View.extend({ // show & hide the contents showcontent: function (curnode,record_id, show) { - curnode.parent('tr').toggleClass('oe_open', show); + curnode.parent('tr').toggleClass('o_open', show); _(this.records[record_id][this.children_field]).each(function (child_id) { - var $child_row = this.$el.find('[id=treerow_' + child_id + '][data-row-parent-id='+ curnode.data('id') +']'); - if ($child_row.hasClass('oe_open')) { - $child_row.toggleClass('oe_open',show); + var $child_row = this.$('[id=treerow_' + child_id + '][data-row-parent-id='+ curnode.data('id') +']'); + if ($child_row.hasClass('o_open')) { + $child_row.toggleClass('o_open',show); this.showcontent($child_row, child_id, false); } $child_row.toggle(show); |
