diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 4051e607e21..822d2fa3cbf 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -3,8 +3,10 @@ "category": "Hidden", "description": """ - OpenERP Web core module. - This module provides the core of the OpenERP Web Client. +OpenERP Web core module. +======================== + +This module provides the core of the OpenERP Web Client. """, "depends" : [], 'auto_install': True, @@ -51,7 +53,6 @@ "static/src/js/view_list.js", "static/src/js/view_list_editable.js", "static/src/js/view_tree.js", - "static/src/js/view_editor.js" ], 'css' : [ "static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css", diff --git a/addons/web/common/http.py b/addons/web/common/http.py index a70ef53a91c..f1426d76d27 100644 --- a/addons/web/common/http.py +++ b/addons/web/common/http.py @@ -426,10 +426,13 @@ class DisableCacheMiddleware(object): referer = environ.get('HTTP_REFERER', '') parsed = urlparse.urlparse(referer) debug = parsed.query.count('debug') >= 1 - filtered_headers = [(k,v) for k,v in headers if not (k=='Last-Modified' or (debug and (k=='Cache-Control' or k=='Expires')))] + nh = dict(headers) + if 'Last-Modified' in nh: del nh['Last-Modified'] if debug: - filtered_headers.append(('Cache-Control', 'no-cache')) - start_response(status, filtered_headers) + if 'Expires' in nh: del nh['Expires'] + if 'Etag' in nh: del nh['Etag'] + nh['Cache-Control'] = 'no-cache' + start_response(status, nh.items()) return self.app(environ, start_wrapped) class Root(object): diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index da034aa88b6..e4c576fecae 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -997,6 +997,16 @@ class DataSet(openerpweb.Controller): elif isinstance(kwargs[k], common.nonliterals.BaseDomain): kwargs[k] = req.session.eval_domain(kwargs[k]) + # Temporary implements future display_name special field for model#read() + if method == 'read' and kwargs.get('context') and kwargs['context'].get('future_display_name'): + if 'display_name' in args[1]: + names = req.session.model(model).name_get(args[0], **kwargs) + args[1].remove('display_name') + r = getattr(req.session.model(model), method)(*args, **kwargs) + for i in range(len(r)): + r[i]['display_name'] = names[i][1] or "%s#%d" % (model, names[i][0]) + return r + return getattr(req.session.model(model), method)(*args, **kwargs) @openerpweb.jsonrequest diff --git a/addons/web/i18n/pt_BR.po b/addons/web/i18n/pt_BR.po index 516ef990a6d..b76288d0387 100644 --- a/addons/web/i18n/pt_BR.po +++ b/addons/web/i18n/pt_BR.po @@ -14,7 +14,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-07-30 04:59+0000\n" +"X-Launchpad-Export-Date: 2012-07-31 05:10+0000\n" "X-Generator: Launchpad (build 15702)\n" #. openerp-web diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 1e59897cd86..ac04fb4d1e1 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -2474,27 +2474,6 @@ .openerp .oe_trad_field.touched { border: 1px solid green !important; } -.openerp .oe_view_editor { - width: 100%; - border-collapse: collapse; - margin-left: -12px; - width: 100%; - background-color: white; - border-spacing: 0; -} -.openerp .oe_view_editor td { - text-align: center; - white-space: nowrap; - border: 1px solid #d8d8d8; - cursor: pointer; - font-size: 90%; -} -.openerp .oe_view_editor_field td { - border: 0px !important; -} -.openerp .oe_view_editor tr:hover { - background-color: #ecebf2; -} .openerp .oe_layout_debugging .oe_form_group { outline: 2px dashed green; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 3bb52497454..2698787c5a3 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1929,25 +1929,6 @@ $sheet-max-width: 860px .oe_trad_field.touched border: 1px solid green !important // }}} - // View Editor {{{ - .oe_view_editor - width: 100% - border-collapse: collapse - margin-left: -12px - width: 100% - background-color: white - border-spacing: 0 - td - text-align: center - white-space: nowrap - border: 1px solid #D8D8D8 - cursor: pointer - font-size: 90% - .oe_view_editor_field td - border: 0px !important - .oe_view_editor tr:hover - background-color: #ecebf2 - // }}} // Debugging stuff {{{ .oe_layout_debugging .oe_form_group diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index c1b1b984abe..844dd88a0ac 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -48,7 +48,7 @@ * OpenERP Web web module split *---------------------------------------------------------*/ openerp.web = function(session) { - var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"]; + var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"]; for(var i=0; i 0) { - if (instance.connection.debug) { - this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); - } else { - this.$element.text(_t("Loading")); - } + if (instance.connection.debug) { + this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); + } else { + this.$element.text(_t("Loading")); + } this.$element.show(); this.getParent().$element.addClass('oe_wait'); } else { @@ -496,7 +502,11 @@ instance.web.Login = instance.web.Widget.extend({ this.has_local_storage = typeof(localStorage) != 'undefined'; this.selected_db = null; this.selected_login = null; - this.params = params; + this.params = params || {}; + + if (this.params.login_successful) { + this.on('login_successful', this, this.params.login_successful); + } if (this.has_local_storage && this.remember_credentials) { this.selected_db = localStorage.getItem('last_db_login_success'); @@ -513,7 +523,7 @@ instance.web.Login = instance.web.Widget.extend({ self.do_action("database_manager"); }); return self.load_db_list().then(self.on_db_list_loaded).then(function() { - if(self.params) { + if (self.params.db) { self.do_login(self.params.db, self.params.login, self.params.password); } }); @@ -589,6 +599,12 @@ instance.web.Login = instance.web.Widget.extend({ self.$(".oe_login_pane").fadeIn("fast"); self.$element.addClass("oe_login_invalid"); }); + }, + show: function () { + this.$element.show(); + }, + hide: function () { + this.$element.hide(); } }); instance.web.client_actions.add("login", "instance.web.Login"); @@ -880,12 +896,11 @@ instance.web.Client = instance.web.Widget.extend({ }, start: function() { var self = this; - return instance.connection.session_bind(this.origin).then(function() { + return instance.connection.session_bind(this.origin).pipe(function() { var $e = $(QWeb.render(self._template, {})); - self.$element.replaceWith($e); - self.$element = $e; + self.replaceElement($e); self.bind_events(); - self.show_common(); + return self.show_common(); }); }, bind_events: function() { @@ -896,7 +911,8 @@ instance.web.Client = instance.web.Widget.extend({ this.$element.on('click', '.oe_dropdown_toggle', function(ev) { ev.preventDefault(); var $toggle = $(this); - var $menu = $toggle.parent().find('.oe_dropdown_menu'); + var $menu = $toggle.siblings('.oe_dropdown_menu'); + $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu'); var state = $menu.is('.oe_opened'); setTimeout(function() { // Do not alter propagation @@ -913,8 +929,10 @@ instance.web.Client = instance.web.Widget.extend({ } }, 0); }); - instance.web.bus.on('click', this, function() { - self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened'); + instance.web.bus.on('click', this, function(ev) { + if (!$(ev.target).is('input[type=file]')) { + self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened'); + } }); }, show_common: function() { diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index a092b670ef1..a46d6314ebe 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -491,23 +491,19 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, { * * The semantics of this precisely replace closing over the method call. * - * @param {String} method_name name of the method to invoke + * @param {String|Function} method function or name of the method to invoke * @returns {Function} proxied method */ - proxy: function (method_name) { + proxy: function (method) { var self = this; return function () { - return self[method_name].apply(self, arguments); + var fn = (typeof method === 'string') ? self[method] : method; + return fn.apply(self, arguments); } } }); instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { - /** - * Tag name when creating a default $element. - * @type string - */ - tagName: 'div', /** * Constructs the widget and sets its parent if a parent is given. * @@ -517,14 +513,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. * When that widget is destroyed by calling destroy(), the current instance will be * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. */ init: function(parent) { instance.web.CallbackEnabledMixin.init.call(this); - this.$element = $(document.createElement(this.tagName)); this.setParent(parent); }, /** @@ -534,7 +525,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { _.each(this.getChildren(), function(el) { el.destroy(); }); - if(this.$element != null) { + if(this.$element) { this.$element.remove(); } instance.web.PropertiesMixin.destroy.call(this); @@ -613,6 +604,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { * @returns {jQuery.Deferred} */ start: function() { + return $.when(); } }); @@ -673,6 +665,12 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn * That will kill the widget in a clean way and erase its content from the dom. */ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { + // Backbone-ish API + tagName: 'div', + id: null, + className: null, + attributes: {}, + events: {}, /** * The name of the QWeb template that will be used for rendering. Must be * redefined in subclasses or the default render() method can not be used. @@ -689,13 +687,11 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. * When that widget is destroyed by calling destroy(), the current instance will be * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. */ init: function(parent) { instance.web.WidgetMixin.init.call(this,parent); + // FIXME: this should not be + this.setElement(this._make_descriptive()); this.session = instance.connection; }, /** @@ -704,20 +700,120 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * key that references `this`. */ renderElement: function() { - var rendered = null; - if (this.template) - rendered = instance.web.qweb.render(this.template, {widget: this}); - if (_.str.trim(rendered)) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; + var $el; + if (this.template) { + $el = $(_.str.trim(instance.web.qweb.render( + this.template, {widget: this}))); + } else { + $el = this._make_descriptive(); } + this.replaceElement($el); + }, + + /** + * Re-sets the widget's root element and replaces the old root element + * (if any) by the new one in the DOM. + * + * @param {HTMLElement | jQuery} $el + * @returns {*} this + */ + replaceElement: function ($el) { + var $oldel = this.$element; + this.setElement($el); + if ($oldel && !$oldel.is(this.$element)) { + $oldel.replaceWith(this.$element); + } + return this; }, /** - * Shortcut for $element.find() like backbone + * Re-sets the widget's root element (el/$el/$element). + * + * Includes: + * * re-delegating events + * * re-binding sub-elements + * * if the widget already had a root element, replacing the pre-existing + * element in the DOM + * + * @param {HTMLElement | jQuery} element new root element for the widget + * @return {*} this */ - "$": function() { - return this.$element.find.apply(this.$element,arguments); + setElement: function (element) { + // NB: completely useless, as WidgetMixin#init creates a $element + // always + if (this.$element) { + this.undelegateEvents(); + } + + this.$element = (element instanceof $) ? element : $(element); + this.el = this.$element[0]; + + this.delegateEvents(); + + return this; + }, + /** + * Utility function to build small DOM elements. + * + * @param {String} tagName name of the DOM element to create + * @param {Object} [attributes] map of DOM attributes to set on the element + * @param {String} [content] HTML content to set on the element + * @return {Element} + */ + make: function (tagName, attributes, content) { + var el = document.createElement(tagName); + if (!_.isEmpty(attributes)) { + $(el).attr(attributes); + } + if (content) { + $(el).html(content); + } + return el; + }, + /** + * Makes a potential root element from the declarative builder of the + * widget + * + * @return {jQuery} + * @private + */ + _make_descriptive: function () { + var attrs = _.extend({}, this.attributes || {}); + if (this.id) { attrs.id = this.id; } + if (this.className) { attrs['class'] = this.className; } + return $(this.make(this.tagName, attrs)); + }, + delegateEvents: function () { + var events = this.events; + if (_.isEmpty(events)) { return; } + + for(var key in events) { + if (!events.hasOwnProperty(key)) { continue; } + + var method = this.proxy(events[key]); + + var match = /^(\S+)(\s+(.*))?$/.exec(key); + var event = match[1]; + var selector = match[3]; + + event += '.widget_events'; + if (!selector) { + this.$element.on(event, method); + } else { + this.$element.on(event, selector, method); + } + } + }, + undelegateEvents: function () { + this.$element.off('.widget_events'); + }, + /** + * Shortcut for ``this.$element.find(selector)`` + * + * @param {String} selector CSS selector, rooted in $el + * @returns {jQuery} selector match + */ + $: function(selector) { + return this.$element.find(selector); }, /** * Informs the action manager to do an action. This supposes that @@ -1039,7 +1135,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ uid: new py.float(this.uid), datetime: datetime, time: time, - relativedelta: relativedelta + relativedelta: relativedelta, + current_date: date.today.__call__().strftime(['%Y-%m-%d']) }; }, /** @@ -1280,7 +1377,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ processData: false }, url); if (this.synch) - ajax.async = false; + ajax.async = false; return $.ajax(ajax); }, rpc_jsonp: function(url, payload) { @@ -1299,7 +1396,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ data: data }, url); if (this.synch) - ajax.async = false; + ajax.async = false; var payload_str = JSON.stringify(payload); var payload_url = $.param({r:payload_str}); if(payload_url.length < 2000) { diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 24441dc509f..2f2fb4f7832 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -19,15 +19,14 @@ instance.web.OldWidget = instance.web.Widget.extend({ this._super(parent); this.element_id = element_id; this.element_id = this.element_id || _.uniqueId('widget-'); + var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName)); + this.setElement(tmp || this._make_descriptive()); }, renderElement: function() { var rendered = this.render(); if (rendered) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; + this.replaceElement($(rendered)); } return this; }, @@ -394,13 +393,13 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess timer = setTimeout(waitLoop, CHECK_INTERVAL); }, synchronized_mode: function(to_execute) { - var synch = this.synch; - this.synch = true; - try { - return to_execute(); - } finally { - this.synch = synch; - } + var synch = this.synch; + this.synch = true; + try { + return to_execute(); + } finally { + this.synch = synch; + } } }); @@ -416,12 +415,12 @@ instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin, // check gtk bindings // http://unixpapa.com/js/key.html _.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) { - $('html').on(evtype, self, function(ev) { + $('html').on(evtype, function(ev) { self.trigger(evtype, ev); }); }); _.each('resize,scroll'.split(','), function(evtype) { - $(window).on(evtype, self, function(ev) { + $(window).on(evtype, function(ev) { self.trigger(evtype, ev); }); }); @@ -541,10 +540,10 @@ $.async_when = function() { // special tweak for the web client var old_async_when = $.async_when; $.async_when = function() { - if (instance.connection.synch) - return $.when.apply(this, arguments); - else - return old_async_when.apply(this, arguments); + if (instance.connection.synch) + return $.when.apply(this, arguments); + else + return old_async_when.apply(this, arguments); }; /** Setup blockui */ diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index fdb15d598af..2589afe5503 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -803,7 +803,7 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da return this.ids.length; }, alter_ids: function(n_ids) { - this.ids = n_ids; + this.ids = n_ids; }, }); instance.web.DataSetStatic = instance.web.DataSet.extend({ @@ -1042,7 +1042,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ self.cache.push({id: id, values: record}); } else { // I assume cache value is prioritary - cached.values = _.defaults(_.clone(cached.values), record); + cached.values = _.defaults(_.clone(cached.values), record); } }); return_records(); @@ -1067,7 +1067,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ return this._super(method, args, callback, error_callback); }, alter_ids: function(n_ids) { - this._super(n_ids); + this._super(n_ids); this.on_change(); }, }); diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js index 6059f292df9..620878569ce 100644 --- a/addons/web/static/src/js/data_import.js +++ b/addons/web/static/src/js/data_import.js @@ -156,7 +156,7 @@ instance.web.DataImport = instance.web.Dialog.extend({ }); }, toggle_import_button: function (newstate) { - instance.web.dialog(this.$element, 'widget') + instance.web.dialog(this.$element, 'widget') .find('.oe_import_dialog_button') .button('option', 'disabled', !newstate); }, diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index c3e7ad72dcb..53daa40ae31 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -661,7 +661,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea null, _(this.select_for_drawer()).invoke( 'appendTo', this.$element.find('.oe_searchview_drawer'))); - new instance.web.search.AddToDashboard(this).appendTo($('.oe_searchview_drawer', this.$element)); + new instance.web.search.AddToReporting(this).appendTo($('.oe_searchview_drawer', this.$element)); // load defaults var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( @@ -861,13 +861,13 @@ instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web ); } }); -instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{ +instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{ template: null, /** * Root class of all search widgets * * @constructs instance.web.search.Widget - * @extends instance.web.OldWidget + * @extends instance.web.Widget * * @param view the ancestor view of this widget */ @@ -1678,42 +1678,39 @@ instance.web.search.Filters = instance.web.search.Input.extend({ })); } }); -instance.web.search.AddToDashboard = instance.web.Widget.extend({ - template: 'SearchView.addtodashboard', +instance.web.search.AddToReporting = instance.web.Widget.extend({ + template: 'SearchView.addtoreporting', _in_drawer: true, start: function () { var self = this; - this.data_loaded = $.Deferred(); - this.dashboard_data =[]; this.$element .on('click', 'h4', this.proxy('show_option')) .on('submit', 'form', function (e) { e.preventDefault(); self.add_dashboard(); }); - return $.when(this.load_data(),this.data_loaded).pipe(this.proxy("render_data")); + return this.load_data().then(this.proxy("render_data")); }, load_data:function(){ if (!instance.webclient) { return $.Deferred().reject(); } - var self = this,dashboard_menu = instance.webclient.menu.data.data.children; - var ir_model_data = new instance.web.Model('ir.model.data',{},[['name','=','menu_reporting_dashboard']]).query(['res_id']); - var map_data = function(result){ - _.detect(dashboard_menu, function(dash){ - var id = _.pluck(dash.children, "id"),indexof = _.indexOf(id, result.res_id); - if(indexof !== -1){ - self.dashboard_data = dash.children[indexof].children - self.data_loaded.resolve(); - return; - } - }); - }; - return ir_model_data._execute().done(function(result){map_data(result[0])}); + var dashboard_menu = instance.webclient.menu.data.data.children; + return new instance.web.Model('ir.model.data') + .query(['res_id']) + .filter([['name','=','menu_reporting_dashboard']]) + .first().pipe(function (result) { + var menu = _(dashboard_menu).chain() + .pluck('children') + .flatten(true) + .find(function (child) { return child.id === result.res_id; }) + .value(); + return menu ? menu.children : []; + }); }, - - render_data: function(){ - var self = this; - var selection = instance.web.qweb.render("SearchView.addtodashboard.selection",{selections:this.dashboard_data}); - this.$element.find("input").before(selection) + render_data: function(dashboard_choices){ + var selection = instance.web.qweb.render( + "SearchView.addtoreporting.selection", { + selections: dashboard_choices}); + this.$("input").before(selection) }, add_dashboard:function(){ var self = this; @@ -1721,11 +1718,11 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({ var view_parent = this.getParent().getParent(); if (! view_parent.action || ! this.$element.find("select").val()) return this.do_warn("Can't find dashboard action"); - data = getParent.build_search_data(), - context = new instance.web.CompoundContext(getParent.dataset.get_context() || []), - domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []); - _.each(data.contexts, function(x) {context.add(x);}); - _.each(data.domains, function(x) {domain.add(x);}); + var data = getParent.build_search_data(); + var context = new instance.web.CompoundContext(getParent.dataset.get_context() || []); + var domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []); + _.each(data.contexts, context.add, context); + _.each(data.domains, domain.add, domain); this.rpc('/web/searchview/add_to_dashboard', { menu_id: this.$element.find("select").val(), action_id: view_parent.action.id, @@ -1746,7 +1743,7 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({ this.$element.toggleClass('oe_opened'); if (! this.$element.hasClass('oe_opened')) return; - this.$element.find("input").val(this.getParent().fields_view.name || "" ); + this.$("input").val(this.getParent().fields_view.name || "" ); } }); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 7f802be2100..77a9c64822e 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -79,6 +79,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM _.defaults(this.options, { "not_interactible_on_create": false, "initial_mode": "view", + "disable_autofocus": false, }); this.is_initialized = $.Deferred(); this.mutating_mutex = new $.Mutex(); @@ -101,7 +102,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM w.off('focused blurred'); w.destroy(); }); - this.$element.off('.formBlur'); + if (this.$element) { + this.$element.off('.formBlur'); + } this._super(); }, on_loaded: function(data) { @@ -267,7 +270,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM if (this.$pager) { this.$pager.show(); } - this.$element.show().css('visibility', 'hidden'); + this.$element.show().css({ + opacity: '0', + filter: 'alpha(opacity = 0)' + }); this.$element.add(this.$buttons).removeClass('oe_form_dirty'); var shown = this.has_been_loaded; @@ -277,8 +283,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM // null index means we should start a new record return self.on_button_new(); } - return self.dataset.read_index(_.keys(self.fields_view.fields), { - context: { 'bin_size': true } + var fields = _.keys(self.fields_view.fields); + fields.push('display_name'); + return self.dataset.read_index(fields, { + context: { 'bin_size': true, 'future_display_name' : true } }).pipe(self.on_record_loaded); }); } @@ -286,7 +294,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM if (options.editable) { self.to_edit_mode(); } - self.$element.css('visibility', 'visible'); + self.$element.css({ + opacity: '1', + filter: 'alpha(opacity = 100)' + }); }); }, do_hide: function () { @@ -310,7 +321,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } this.datarecord = record; this._actualize_mode(); - this.set({ 'title' : record.id ? record.name : "New record" }); + this.set({ 'title' : record.id ? record.display_name : "New record" }); if (this.qweb) { this.kill_current_form(); @@ -346,6 +357,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM self.do_push_state({id:record.id}); } self.$element.add(self.$buttons).removeClass('oe_form_dirty'); + self.autofocus(); }); }, /** @@ -645,15 +657,21 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM _.each(this.fields,function(field){ field.set({"force_readonly": false}); }); - var fields_order = self.fields_order.slice(0); - if (self.default_focus_field) { - fields_order.unshift(self.default_focus_field.name); + this.autofocus(); + } + }, + autofocus: function() { + if (this.get("actual_mode") !== "view" && !this.options.disable_autofocus) { + var fields_order = this.fields_order.slice(0); + if (this.default_focus_field) { + fields_order.unshift(this.default_focus_field.name); } for (var i = 0; i < fields_order.length; i += 1) { - var field = self.fields[fields_order[i]]; + var field = this.fields[fields_order[i]]; if (!field.get('effective_invisible') && !field.get('effective_readonly')) { - field.focus(); - break; + if (field.focus() !== false) { + break; + } } } } @@ -856,8 +874,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM if (self.dataset.index == null || self.dataset.index < 0) { return $.when(self.on_button_new()); } else { - return self.dataset.read_index(_.keys(self.fields_view.fields), { - context : { 'bin_size' : true } + var fields = _.keys(self.fields_view.fields); + fields.push('display_name'); + return self.dataset.read_index(fields, { + context : { 'bin_size' : true, 'future_display_name' : true } }).pipe(self.on_record_loaded); } }); @@ -1956,14 +1976,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w } }, focus: function() { - }, - /** - * Utility method to focus an element, but only after a small amount of time. - */ - delay_focus: function($elem) { - setTimeout(function() { - $elem[0].focus(); - }, 50); + return false; }, /** * Utility method to get the widget options defined in the field xml description. @@ -2062,7 +2075,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we return this.get('value') === '' || this._super(); }, focus: function() { - this.delay_focus(this.$element.find('input:first')); + this.$element.find('input:first').focus(); } }); @@ -2282,8 +2295,9 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc return this.get('value') === '' || this._super(); }, focus: function() { - if (this.datewidget && this.datewidget.$input) - this.delay_focus(this.datewidget.$input); + if (this.datewidget && this.datewidget.$input) { + this.datewidget.$input.focus(); + } } }); @@ -2338,7 +2352,7 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we return this.get('value') === '' || this._super(); }, focus: function($element) { - this.delay_focus(this.$textarea); + this.$textarea.focus(); }, do_resize: function(max_height) { max_height = parseInt(max_height, 10); @@ -2431,7 +2445,7 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({ this.$checkbox[0].checked = value_; }, focus: function() { - this.delay_focus(this.$checkbox); + this.$checkbox.focus(); } }); @@ -2455,9 +2469,6 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ } }); -instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({ -// to replace view editor -}); instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldSelection', @@ -2526,7 +2537,7 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan return !! value_; }, focus: function() { - this.delay_focus(this.$element.find('select:first')); + this.$element.find('select:first').focus(); } }); @@ -2813,6 +2824,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc } else if (item.action) { self.floating = true; item.action(); + // Cancel widget blurring, to avoid form blur event + self.trigger('focused'); return false; } }, @@ -2916,7 +2929,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc return ! this.get("value"); }, focus: function () { - this.delay_focus(this.$input); + this.$input.focus(); } }); @@ -3072,7 +3085,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.views = views; this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {}); - this.viewmanager.$element.addClass("oe_view_manager_one2many"); this.viewmanager.o2m = self; var once = $.Deferred().then(function() { self.init_form_last_update.resolve(); @@ -3486,10 +3498,23 @@ instance.web.form.One2ManyList = instance.web.ListView.List.extend({ colspan: columns, 'class': 'oe_form_field_one2many_list_row_add' }).text(_t("Add a row")) + .mousedown(function () { + // FIXME: needs to be an official API somehow + if (self.view.editor.is_editing()) { + self.view.__ignore_blur = true; + } + }) .click(function (e) { e.preventDefault(); e.stopPropagation(); - self.view.do_add_record(); + // FIXME: there should also be an API for that one + if (self.view.editor.form.__blur_timeout) { + clearTimeout(self.view.editor.form.__blur_timeout); + self.view.editor.form.__blur_timeout = false; + } + self.view.ensure_saved().then(function () { + self.view.do_add_record(); + }); }); this.$current.append( $('').append($cell)) @@ -4300,7 +4325,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan this.selection.view = this.view; this.selection.set({force_readonly: this.get('effective_readonly')}); this.selection.on("change:value", this, this.on_selection_changed); - this.selection.$element = $(".oe_form_view_reference_selection", this.$element); + this.selection.setElement(this.$(".oe_form_view_reference_selection")); this.selection.renderElement(); this.selection.start(); this.selection @@ -4313,7 +4338,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan this.m2o.view = this.view; this.m2o.set({force_readonly: this.get("effective_readonly")}); this.m2o.on("change:value", this, this.data_changed); - this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element); + this.m2o.setElement(this.$(".oe_form_view_reference_m2o")); this.m2o.renderElement(); this.m2o.start(); this.m2o @@ -4675,9 +4700,6 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ elem.css("color", color); } }, - focus: function() { - return false; - }, }); /** diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index ca889f68d8d..038593c54c3 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -96,9 +96,6 @@ openerp.web.list_editable = function (instance) { }, on_loaded: function (data, grouped) { var self = this; - if (this.editor) { - this.editor.destroy(); - } // tree/@editable takes priority on everything else if present. var result = this._super(data, grouped); if (this.editable()) { @@ -118,6 +115,7 @@ openerp.web.list_editable = function (instance) { self.start_edition(); } }); + this.editor.destroy(); // Editor is not restartable due to formview not being // restartable this.editor = this.make_editor(); @@ -604,6 +602,7 @@ openerp.web.list_editable = function (instance) { this.form = new (this.options.formView)( this, this.delegate.dataset, false, { initial_mode: 'edit', + disable_autofocus: true, $buttons: $(), $pager: $() }); @@ -683,9 +682,8 @@ openerp.web.list_editable = function (instance) { if (!field.$element.is(':visible')) { return false; } - field.focus(); // Stop as soon as a field got focused - return true; + return field.focus() !== false; }); }, edit: function (record, configureField, options) { diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 2e22b8b64c5..af4ae68737a 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -423,7 +423,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') .parent().addClass('active'); - $.when(view_promise).then(function () { + return $.when(view_promise).then(function () { _.each(_.keys(self.views), function(view_name) { var controller = self.views[view_name].controller; if (controller) { @@ -435,7 +435,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ container.hide(); controller.do_hide(); } - // put the