diff options
| author | Xavier Morel <xmo@openerp.com> | 2012-03-22 15:33:31 +0100 |
|---|---|---|
| committer | Xavier Morel <xmo@openerp.com> | 2012-03-22 15:33:31 +0100 |
| commit | 7353e02e2ef4a403d4b79c1b157f2ca00b77d2fc (patch) | |
| tree | a319552e3664ea272fdfb8abf31c961bcd5e1e6e /addons/web/static/src/js | |
| parent | 514c63288c7d5d88d764b44696c5e3a5fc148dfb (diff) | |
| parent | 1da49a44a57aa413e5eb96afe9c6d294e09848fd (diff) | |
[MERGE] from trunk
bzr revid: xmo@openerp.com-20120322143331-iajlifomw1hohbht
Diffstat (limited to 'addons/web/static/src/js')
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 640 | ||||
| -rw-r--r-- | addons/web/static/src/js/core.js | 624 | ||||
| -rw-r--r-- | addons/web/static/src/js/data.js | 31 | ||||
| -rw-r--r-- | addons/web/static/src/js/data_import.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/formats.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 80 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_editor.js | 4 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 282 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 35 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 33 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_page.js | 30 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_tree.js | 16 | ||||
| -rw-r--r-- | addons/web/static/src/js/views.js | 56 |
13 files changed, 978 insertions, 871 deletions
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index ba31ec81846..f8af815d187 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -45,6 +45,12 @@ openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web. }); +openerp.web.dialog = function(element) { + var result = element.dialog.apply(element, _.rest(_.toArray(arguments))); + result.dialog("widget").addClass("openerp"); + return result; +} + openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{ dialog_title: "", /** @@ -86,7 +92,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# if (this.dialog_options.autoOpen) { this.open(); } else { - this.$element.dialog(this.get_options()); + openerp.web.dialog(this.$element, this.get_options()); } }, get_options: function(options) { @@ -125,7 +131,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# this.$element.html(this.render()); } var o = this.get_options(options); - this.$element.dialog(o).dialog('open'); + openerp.web.dialog(this.$element, o).dialog('open'); if (o.height === 'auto' && o.max_height) { this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); } @@ -142,7 +148,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# on_resized: function() { //openerp.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height()); }, - stop: function () { + destroy: function () { // Destroy widget this.close(); this.$element.dialog('destroy'); @@ -166,7 +172,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({ } }, on_managed_error: function(error) { - $('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>').dialog({ + openerp.web.dialog($('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>'), { title: "OpenERP " + _.str.capitalize(error.type), buttons: [ {text: _t("Ok"), click: function() { $(this).dialog("close"); }} @@ -234,7 +240,7 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin this.session.on_rpc_request.add_first(this.request_call); this.session.on_rpc_response.add_last(this.response_call); }, - stop: function() { + destroy: function() { this.session.on_rpc_request.remove(this.request_call); this.session.on_rpc_response.remove(this.response_call); this.on_rpc_event(-this.count); @@ -255,7 +261,7 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin $(".loading",this.$element).text(_.str.sprintf( _t("Loading (%d)"), this.count)); $(".loading",this.$element).show(); - this.widget_parent.$element.addClass('loading'); + this.getParent().$element.addClass('loading'); } else { this.count = 0; clearTimeout(this.long_running_timer); @@ -265,7 +271,7 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin $.unblockUI(); } $(".loading",this.$element).fadeOut(); - this.widget_parent.$element.removeClass('loading'); + this.getParent().$element.removeClass('loading'); } } }); @@ -312,7 +318,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab self.hide(); }); }, - stop: function () { + destroy: function () { this.hide(); this.$option_id.empty(); @@ -345,44 +351,6 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab return result; }, /** - * Waits until the new database is done creating, then unblocks the UI and - * logs the user in as admin - * - * @param {Number} db_creation_id identifier for the db-creation operation, used to fetch the current installation progress - * @param {Object} info info fields for this database creation - * @param {String} info.db name of the database being created - * @param {String} info.password super-admin password for the database - */ - wait_for_newdb: function (db_creation_id, info) { - var self = this; - self.rpc('/web/database/progress', { - id: db_creation_id, - password: info.password - }, function (result) { - var progress = result[0]; - // I'd display a progress bar, but turns out the progress status - // the server report kind-of blows goats: it's at 0 for ~75% of - // the installation, then jumps to 75%, then jumps down to either - // 0 or ~40%, then back up to 75%, then terminates. Let's keep that - // mess hidden behind a not-very-useful but not overly weird - // message instead. - if (progress < 1) { - setTimeout(function () { - self.wait_for_newdb(db_creation_id, info); - }, 500); - return; - } - - var admin = result[1][0]; - setTimeout(function () { - self.widget_parent.do_login( - info.db, admin.login, admin.password); - self.stop(); - self.unblockUI(); - }); - }); - }, - /** * Blocks UI and replaces $.unblockUI by a noop to prevent third parties * from unblocking the UI */ @@ -407,7 +375,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab * @param {String} error.error message of the error dialog */ display_error: function (error) { - return $('<div>').dialog({ + return openerp.web.dialog($('<div>'), { modal: true, title: error.title, buttons: [ @@ -421,23 +389,19 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab self.$option_id.find("form[name=create_db_form]").validate({ submitHandler: function (form) { var fields = $(form).serializeArray(); - self.blockUI(); self.rpc("/web/database/create", {'fields': fields}, function(result) { - if (result.error) { - self.unblockUI(); - self.display_error(result); - return; - } if (self.db_list) { self.db_list.push(self.to_object(fields)['db_name']); self.db_list.sort(); self.widget_parent.set_db_list(self.db_list); } + var form_obj = self.to_object(fields); - self.wait_for_newdb(result, { - password: form_obj['super_admin_pwd'], - db: form_obj['db_name'] - }); + self.getParent().do_login( + form_obj['db_name'], + 'admin', + form_obj['create_admin_pwd']); + self.destroy(); }); } }); @@ -449,7 +413,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab submitHandler: function (form) { var $form = $(form), fields = $form.serializeArray(), - $db_list = $form.find('select[name=drop_db]'), + $db_list = $form.find('[name=drop_db]'), db = $db_list.val(); if (!confirm("Do you really want to delete the database: " + db + " ?")) { @@ -463,7 +427,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab $db_list.find(':selected').remove(); if (self.db_list) { self.db_list.splice(_.indexOf(self.db_list, db, true), 1); - self.widget_parent.set_db_list(self.db_list); + self.getParent().set_db_list(self.db_list); } self.do_notify("Dropping database", "The database '" + db + "' has been dropped"); }); @@ -660,58 +624,212 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login# } }); -openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header# */{ - template: "Header", +openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{ /** - * @constructs openerp.web.Header - * @extends openerp.web.OldWidget + * @constructs openerp.web.Menu + * @extends openerp.web.Widget + * + * @param parent + */ + template: 'Menu', + init: function() { + this._super.apply(this, arguments); + this.has_been_loaded = $.Deferred(); + }, + start: function() { + this._super.apply(this, arguments); + this.$secondary_menus = this.getParent().$element.find('.oe_secondary_menus_container'); + this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click); + }, + do_reload: function() { + var self = this; + return this.rpc("/web/menu/load", {}, this.on_loaded).then(function () { + if (self.current_menu) { + self.open_menu(self.current_menu); + } + }); + }, + on_loaded: function(data) { + this.data = data; + this.renderElement(); + this.$element.on('click', 'a[data-menu]', this.on_menu_click); + this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this })); + // Hide second level submenus + this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide(); + this.has_been_loaded.resolve(); + }, + /** + * Opens a given menu by id, as if a user had browsed to that menu by hand + * except does not trigger any event on the way + * + * @param {Number} id database id of the terminal menu to select + */ + open_menu: function (id) { + var $clicked_menu, $sub_menu, $main_menu; + $clicked_menu = this.$element.add(this.$secondary_menus).find('a[data-menu=' + id + ']'); + + if (this.$secondary_menus.has($clicked_menu).length) { + $sub_menu = $clicked_menu.parents('.oe_secondary_menu'); + $main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']'); + } else { + $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']'); + $main_menu = $clicked_menu; + } + + // Activate current main menu + this.$element.find('.oe_active').removeClass('oe_active'); + $main_menu.addClass('oe_active'); + + // Show current sub menu + this.$secondary_menus.find('.oe_secondary_menu').hide(); + $sub_menu.show(); + + // Activate current menu item and show parents + this.$secondary_menus.find('.oe_active').removeClass('oe_active'); + if ($main_menu !== $clicked_menu) { + $clicked_menu.parents().show(); + if ($clicked_menu.is('.oe_menu_toggler')) { + $clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggle(); + } else { + $clicked_menu.parent().addClass('oe_active'); + } + } + }, + open_action: function (id) { + var menu_id, $menu = this.$element.add(this.$secondary_menus).find('a[data-action-id=' + id + ']'); + if (menu_id = $menu.data('menu')) { + this.open_menu(menu_id); + } + }, + on_menu_click: function(ev, id) { + id = id || 0; + var $clicked_menu, manual = false; + + if (id) { + // We can manually activate a menu with it's id (for hash url mapping) + manual = true; + $clicked_menu = this.$element.find('a[data-menu=' + id + ']'); + if (!$clicked_menu.length) { + $clicked_menu = this.$secondary_menus.find('a[data-menu=' + id + ']'); + } + } else { + $clicked_menu = $(ev.currentTarget); + id = $clicked_menu.data('menu'); + } + + this.trigger('menuClicked', id, $clicked_menu); + + if (id) { + this.open_menu(id); + this.current_menu = id; + this.session.active_id = id; + var action_id = $clicked_menu.data('action-id'); + if (action_id) { + this.on_action(action_id); + } + } + if (ev) { + ev.stopPropagation(); + } + return false; + }, + do_show_secondary: function($sub_menu, $main_menu) { + var self = this; + this.$secondary_menus.show(); + if (!arguments.length) { + return; + } + $sub_menu.show(); + }, + on_action: function(action) { + } +}); + +openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMenu# */{ + template: "UserMenu", + /** + * @constructs openerp.web.UserMenu + * @extends openerp.web.Widget * * @param parent */ init: function(parent) { this._super(parent); - this.qs = "?" + jQuery.param.querystring(); - this.$content = $(); this.update_promise = $.Deferred().resolve(); }, start: function() { - this._super(); + var self = this; + this._super.apply(this, arguments); + $('html').bind('click', function() { + self.$element.find('.oe_dropdown_options').hide(); + }); + this.$element.find('.oe_dropdown_toggle').click(function() { + self.$element.find('.oe_dropdown_options').toggle(); + return false; + }); + this.$element.on('click', '.oe_dropdown_options li a[data-menu]', function() { + var f = self['on_menu_' + $(this).data('menu')]; + f && f($(this)); + self.$element.find('.oe_dropdown_options').hide(); + return false; + }); + }, + change_password :function() { + var self = this; + this.dialog = new openerp.web.Dialog(this, { + title: _t("Change Password"), + width : 'auto' + }).open(); + this.dialog.$element.html(QWeb.render("Change_Pwd", self)); + this.dialog.$element.find("form[name=change_password_form]").validate({ + submitHandler: function (form) { + self.rpc("/web/session/change_password",{ + 'fields': $(form).serializeArray() + }, function(result) { + if (result.error) { + self.display_error(result); + return; + } else { + openerp.webclient.on_logout(); + } + }); + } + }); + }, + display_error: function (error) { + return openerp.web.dialog($('<div>'), { + modal: true, + title: error.title, + buttons: [ + {text: _("Ok"), click: function() { $(this).dialog("close"); }} + ] + }).html(error.error); }, do_update: function () { var self = this; var fct = function() { - self.$content.remove(); + var $avatar = self.$element.find('.oe_topbar_avatar'); + $avatar.attr('src', $avatar.data('default-src')); if (!self.session.uid) return; var func = new openerp.web.Model("res.users").get_func("read"); return func(self.session.uid, ["name", "company_id"]).pipe(function(res) { - self.$content = $(QWeb.render("Header-content", {widget: self, user: res})); - self.$content.appendTo(self.$element); - self.$element.find(".logout").click(self.on_logout); - self.$element.find("a.preferences").click(self.on_preferences); - self.$element.find(".about").click(self.on_about); + // TODO: Only show company if multicompany in use + self.$element.find('.oe_topbar_name').text(res.name + '/' + res.company_id[1]); return self.shortcut_load(); }); }; this.update_promise = this.update_promise.pipe(fct, fct); }, - on_about: function() { - var self = this; - self.rpc("/web/webclient/version_info", {}).then(function(res) { - var $help = $(QWeb.render("About-Page", {version_info: res})); - $help.find('a.oe_activate_debug_mode').click(function (e) { - e.preventDefault(); - window.location = $.param.querystring( - window.location.href, 'debug'); - }); - $help.dialog({autoOpen: true, - modal: true, width: 960, title: _t("About")}); - }); + on_action: function() { }, shortcut_load :function(){ var self = this, sc = self.session.shortcuts, shortcuts_ds = new openerp.web.DataSet(this, 'ir.ui.view_sc'); + self.$element.find('.oe_dropdown_options a[data-menu=shortcut]').each(function() { + $(this).parent().remove(); + }); // TODO: better way to communicate between sections. // sc.bindings, because jquery does not bind/trigger on arrays... if (!sc.binding) { @@ -719,52 +837,41 @@ openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header $(sc.binding).bind({ 'add': function (e, attrs) { shortcuts_ds.create(attrs, function (out) { - $('<li>', { - 'data-shortcut-id':out.result, - 'data-id': attrs.res_id - }).text(attrs.name) - .appendTo(self.$element.find('.oe-shortcuts ul')); + var shortcut = QWeb.render('UserMenu.shortcut', { + shortcuts : [{ + name : attrs.name, + id : out.result, + res_id : attrs.res_id + }] + }); + $(shortcut).appendTo(self.$element.find('.oe_dropdown_options')); attrs.id = out.result; sc.push(attrs); }); }, 'remove-current': function () { var menu_id = self.session.active_id; - var $shortcut = self.$element - .find('.oe-shortcuts li[data-id=' + menu_id + ']'); + var $shortcut = self.$element.find('.oe_dropdown_options li a[data-id=' + menu_id + ']'); var shortcut_id = $shortcut.data('shortcut-id'); $shortcut.remove(); shortcuts_ds.unlink([shortcut_id]); var sc_new = _.reject(sc, function(shortcut){ return shortcut_id === shortcut.id}); sc.splice(0, sc.length); sc.push.apply(sc, sc_new); - } + } }); } return this.rpc('/web/session/sc_list', {}, function(shortcuts) { sc.splice(0, sc.length); sc.push.apply(sc, shortcuts); - self.$element.find('.oe-shortcuts') - .html(QWeb.render('Shortcuts', {'shortcuts': shortcuts})) - .undelegate('li', 'click') - - .delegate('li', 'click', function(e) { - e.stopPropagation(); - var id = $(this).data('id'); - self.session.active_id = id; - self.rpc('/web/menu/action', {'menu_id':id}, function(ir_menu_data) { - if (ir_menu_data.action.length){ - self.on_action(ir_menu_data.action[0][2]); - } - }); - }); + $(QWeb.render('UserMenu.shortcut', {'shortcuts': shortcuts})) + .appendTo(self.$element.find('.oe_dropdown_options')); }); }, - - on_action: function(action) { + on_menu_logout: function() { }, - on_preferences: function(){ + on_menu_settings: function() { var self = this; var action_manager = new openerp.web.ActionManager(this); var dataset = new openerp.web.DataSet (this,'res.users',this.context); @@ -793,7 +900,7 @@ openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header var inner_viewmanager = action_manager.inner_viewmanager; inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save() .then(function() { - self.dialog.stop(); + self.dialog.destroy(); // needs to refresh interface in case language changed window.location.reload(); }); @@ -804,298 +911,66 @@ openerp.web.Header = openerp.web.OldWidget.extend(/** @lends openerp.web.Header action_manager.appendTo(this.dialog); action_manager.render(this.dialog); }, - - change_password :function() { + on_menu_about: function() { var self = this; - this.dialog = new openerp.web.Dialog(this, { - title: _t("Change Password"), - width : 'auto' - }).open(); - this.dialog.$element.html(QWeb.render("Change_Pwd", self)); - this.dialog.$element.find("form[name=change_password_form]").validate({ - submitHandler: function (form) { - self.rpc("/web/session/change_password",{ - 'fields': $(form).serializeArray() - }, function(result) { - if (result.error) { - self.display_error(result); - return; - } else { - openerp.webclient.on_logout(); - } - }); - } + self.rpc("/web/webclient/version_info", {}).then(function(res) { + var $help = $(QWeb.render("About-Page", {version_info: res})); + $help.find('a.oe_activate_debug_mode').click(function (e) { + e.preventDefault(); + window.location = $.param.querystring( + window.location.href, 'debug'); + }); + openerp.web.dialog($help, {autoOpen: true, + modal: true, width: 960, title: _t("About")}); }); }, - display_error: function (error) { - return $('<div>').dialog({ - modal: true, - title: error.title, - buttons: [ - {text: _("Ok"), click: function() { $(this).dialog("close"); }} - ] - }).html(error.error); - }, - on_logout: function() { - } -}); - -openerp.web.Menu = openerp.web.OldWidget.extend(/** @lends openerp.web.Menu# */{ - /** - * @constructs openerp.web.Menu - * @extends openerp.web.OldWidget - * - * @param parent - * @param element_id - * @param secondary_menu_id - */ - init: function(parent, element_id, secondary_menu_id) { - this._super(parent, element_id); - this.secondary_menu_id = secondary_menu_id; - this.$secondary_menu = $("#" + secondary_menu_id); - this.menu = false; - this.folded = false; - if (window.localStorage) { - this.folded = localStorage.getItem('oe_menu_folded') === 'true'; - } - this.float_timeout = 700; - }, - start: function() { - this.$secondary_menu.addClass(this.folded ? 'oe_folded' : 'oe_unfolded'); - }, - do_reload: function() { - var self = this; - return this.rpc("/web/menu/load", {}, this.on_loaded).then(function () { - if (self.current_menu) { - self.open_menu(self.current_menu); + on_menu_shortcut: function($link) { + var self = this, + id = $link.data('id'); + self.session.active_id = id; + self.rpc('/web/menu/action', {'menu_id': id}, function(ir_menu_data) { + if (ir_menu_data.action.length){ + self.on_action(ir_menu_data.action[0][2]); } }); - }, - on_loaded: function(data) { - this.data = data; - this.$element.html(QWeb.render("Menu", { widget : this })); - this.$secondary_menu.html(QWeb.render("Menu.secondary", { widget : this })); - this.$element.add(this.$secondary_menu).find("a").click(this.on_menu_click); - this.$secondary_menu.find('.oe_toggle_secondary_menu').click(this.on_toggle_fold); - }, - on_toggle_fold: function() { - this.$secondary_menu.toggleClass('oe_folded').toggleClass('oe_unfolded'); - if (this.folded) { - this.$secondary_menu.find('.oe_secondary_menu.active').show(); - } else { - this.$secondary_menu.find('.oe_secondary_menu').hide(); - } - this.folded = !this.folded; - if (window.localStorage) { - localStorage.setItem('oe_menu_folded', this.folded.toString()); - } - }, - /** - * Opens a given menu by id, as if a user had browsed to that menu by hand - * except does not trigger any event on the way - * - * @param {Number} menu_id database id of the terminal menu to select - */ - open_menu: function (menu_id) { - this.$element.add(this.$secondary_menu).find('.active') - .removeClass('active'); - this.$secondary_menu.find('> .oe_secondary_menu').hide(); - - var $primary_menu; - var $secondary_submenu = this.$secondary_menu.find( - 'a[data-menu=' + menu_id +']'); - if ($secondary_submenu.length) { - for(;;) { - if ($secondary_submenu.hasClass('leaf')) { - $secondary_submenu.addClass('active'); - } else if ($secondary_submenu.hasClass('submenu')) { - $secondary_submenu.addClass('opened') - } - var $parent = $secondary_submenu.parent().show(); - if ($parent.hasClass('oe_secondary_menu')) { - var primary_id = $parent.data('menu-parent'); - $primary_menu = this.$element.find( - 'a[data-menu=' + primary_id + ']'); - break; - } - $secondary_submenu = $parent.prev(); - } - } else { - $primary_menu = this.$element.find('a[data-menu=' + menu_id + ']'); - } - if (!$primary_menu.length) { - return; - } - $primary_menu.addClass('active'); - this.$secondary_menu.find( - 'div[data-menu-parent=' + $primary_menu.data('menu') + ']').addClass('active').toggle(!this.folded); - }, - on_menu_click: function(ev, id) { - id = id || 0; - var $clicked_menu, manual = false; - - if (id) { - // We can manually activate a menu with it's id (for hash url mapping) - manual = true; - $clicked_menu = this.$element.find('a[data-menu=' + id + ']'); - if (!$clicked_menu.length) { - $clicked_menu = this.$secondary_menu.find('a[data-menu=' + id + ']'); - } - } else { - $clicked_menu = $(ev.currentTarget); - id = $clicked_menu.data('menu'); - } - - if (this.do_menu_click($clicked_menu, manual) && id) { - this.current_menu = id; - this.session.active_id = id; - this.rpc('/web/menu/action', {'menu_id': id}, this.on_menu_action_loaded); - } - if (ev) { - ev.stopPropagation(); - } - return false; - }, - do_menu_click: function($clicked_menu, manual) { - var $sub_menu, $main_menu, - active = $clicked_menu.is('.active'), - sub_menu_visible = false, - has_submenu_items = false; - - if (this.$secondary_menu.has($clicked_menu).length) { - $sub_menu = $clicked_menu.parents('.oe_secondary_menu'); - $main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']'); - } else { - $sub_menu = this.$secondary_menu.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']'); - $main_menu = $clicked_menu; - } - - sub_menu_visible = $sub_menu.is(':visible'); - has_submenu_items = !!$sub_menu.children().length; - this.$secondary_menu.find('.oe_secondary_menu').hide(); - - $('.active', this.$element.add(this.$secondary_menu)).removeClass('active'); - $main_menu.add($clicked_menu).add($sub_menu).addClass('active'); - - if (has_submenu_items) { - if (!(this.folded && manual)) { - this.do_show_secondary($sub_menu, $main_menu); - } else { - this.do_show_secondary(); - } - } - - if ($main_menu != $clicked_menu) { - if ($clicked_menu.is('.submenu')) { - $sub_menu.find('.submenu.opened').each(function() { - if (!$(this).next().has($clicked_menu).length && !$(this).is($clicked_menu)) { - $(this).removeClass('opened').next().hide(); - } - }); - $clicked_menu.toggleClass('opened').next().toggle(); - } else if ($clicked_menu.is('.leaf')) { - $sub_menu.toggle(!this.folded); - return true; - } - } else if (this.folded) { - if ((active && sub_menu_visible) || !has_submenu_items) { - $sub_menu.hide(); - return true; - } - return manual; - } else { - return true; - } - return false; - }, - do_hide_secondary: function() { - this.$secondary_menu.hide(); - }, - do_show_secondary: function($sub_menu, $main_menu) { - var self = this; - this.$secondary_menu.show(); - if (!arguments.length) { - return; - } - if (this.folded) { - var css = $main_menu.position(), - fold_width = this.$secondary_menu.width() + 2, - window_width = $(window).width(); - css.top += 33; - css.left -= Math.round(($sub_menu.width() - $main_menu.width()) / 2); - css.left = css.left < fold_width ? fold_width : css.left; - if ((css.left + $sub_menu.width()) > window_width) { - delete(css.left); - css.right = 1; - } - $sub_menu.css(css); - $sub_menu.mouseenter(function() { - clearTimeout($sub_menu.data('timeoutId')); - $sub_menu.data('timeoutId', null); - return false; - }).mouseleave(function(evt) { - var timeoutId = setTimeout(function() { - if (self.folded && $sub_menu.data('timeoutId')) { - $sub_menu.hide().unbind('mouseenter').unbind('mouseleave'); - } - }, self.float_timeout); - $sub_menu.data('timeoutId', timeoutId); - return false; - }); - } - $sub_menu.show(); - }, - on_menu_action_loaded: function(data) { - var self = this; - if (data.action.length) { - var action = data.action[0][2]; - action.from_menu = true; - self.on_action(action); - } else { - self.on_action({type: 'null_action'}); - } - }, - on_action: function(action) { } }); -openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebClient */{ +openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClient */{ /** * @constructs openerp.web.WebClient - * @extends openerp.web.OldWidget - * - * @param element_id + * @extends openerp.web.Widget */ init: function(parent) { var self = this; this._super(parent); openerp.webclient = this; - + this.querystring = '?' + jQuery.param.querystring(); this._current_state = null; }, start: function() { var self = this; - this.$element = $(document.body); + this.$element.addClass("openerp openerp2"); if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { this.$element.addClass("kitten-mode-activated"); this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) { self.$element.toggleClass('clark-gable'); }); } - this.session.bind().then(function() { + this.session.bind_session().then(function() { if (!self.session.session_is_valid()) { self.show_login(); } }); this.session.on_session_valid.add(function() { self.show_application(); - - self.header.do_update(); + + self.user_menu.do_update(); self.menu.do_reload(); if(self.action_manager) - self.action_manager.stop(); + self.action_manager.destroy(); self.action_manager = new openerp.web.ActionManager(self); - self.action_manager.appendTo($("#oe_app")); + self.action_manager.appendTo(self.$element.find('.oe_application')); self.bind_hashchange(); var version_label = _t("OpenERP - Unsupported/Community Version"); if (!self.session.openerp_entreprise) { @@ -1103,6 +978,9 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC document.title = version_label; } }); + this.$element.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() { + $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter'); + }); }, show_login: function() { var self = this; @@ -1115,15 +993,15 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC var self = this; this.destroy_content(); this.show_common(); - self.$table = $(QWeb.render("Interface", {})); + self.$table = $(QWeb.render("WebClient", {})); self.$element.append(self.$table); - self.header = new openerp.web.Header(self); - self.header.on_logout.add(this.proxy('on_logout')); - self.header.on_action.add(this.proxy('on_menu_action')); - self.header.appendTo($("#oe_header")); - self.menu = new openerp.web.Menu(self, "oe_menu", "oe_secondary_menu"); + self.menu = new openerp.web.Menu(self); + self.menu.replace(this.$element.find('.oe_menu_placeholder')); self.menu.on_action.add(this.proxy('on_menu_action')); - self.menu.start(); + self.user_menu = new openerp.web.UserMenu(self); + self.user_menu.replace(this.$element.find('.oe_user_menu_placeholder')); + self.user_menu.on_menu_logout.add(this.proxy('on_logout')); + self.user_menu.on_action.add(this.proxy('on_menu_action')); }, show_common: function() { var self = this; @@ -1144,8 +1022,8 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC this.loading.appendTo(this.$element); }, destroy_content: function() { - _.each(_.clone(this.widget_children), function(el) { - el.stop(); + _.each(_.clone(this.getChildren()), function(el) { + el.destroy(); }); this.$element.children().remove(); }, @@ -1207,6 +1085,12 @@ openerp.web.WebClient = openerp.web.OldWidget.extend(/** @lends openerp.web.WebC self.menu.on_menu_click(null, action.menu_id); }); } + }, + set_content_full_screen: function(fullscreen) { + if (fullscreen) + $(".oe_webclient", this.$element).addClass("oe_content_full_screen"); + else + $(".oe_webclient", this.$element).removeClass("oe_content_full_screen"); } }); @@ -1250,7 +1134,7 @@ openerp.web.embed = function (origin, dbname, login, key, action, options) { var sc = document.getElementsByTagName('script'); currentScript = sc[sc.length-1]; } - openerp.connection.bind(origin).then(function () { + openerp.connection.bind_session(origin).then(function () { openerp.connection.session_authenticate(dbname, login, key, true).then(function () { var client = new openerp.web.EmbeddedClient(action, options); client.insertAfter(currentScript); diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index a1d2d04f697..9bcf037fefc 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -10,110 +10,8 @@ if (!console.debug) { } openerp.web.core = function(openerp) { -/** - * John Resig Class with factory improvement - */ -(function() { - var initializing = false, - fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; - // The web Class implementation (does nothing) - /** - * Extended version of John Resig's Class pattern - * - * @class - */ - openerp.web.Class = function(){}; - - /** - * Subclass an existing class - * - * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class - */ - openerp.web.Class.extend = function(prop) { - var _super = this.prototype; - - // Instantiate a web class (but only create the instance, - // don't run the init constructor) - initializing = true; - var prototype = new this(); - initializing = false; - - // Copy the properties over onto the new prototype - for (var name in prop) { - // Check if we're overwriting an existing function - prototype[name] = typeof prop[name] == "function" && - typeof _super[name] == "function" && - fnTest.test(prop[name]) ? - (function(name, fn) { - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same - // method but on the super-class - this._super = _super[name]; - - // The method only need to be bound temporarily, so - // we remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]) : - prop[name]; - } - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if (!initializing && this.init) { - var ret = this.init.apply(this, arguments); - if (ret) { return ret; } - } - return this; - } - Class.include = function (properties) { - for (var name in properties) { - if (typeof properties[name] !== 'function' - || !fnTest.test(properties[name])) { - prototype[name] = properties[name]; - } else if (typeof prototype[name] === 'function' - && prototype.hasOwnProperty(name)) { - prototype[name] = (function (name, fn, previous) { - return function () { - var tmp = this._super; - this._super = previous; - var ret = fn.apply(this, arguments); - this._super = tmp; - return ret; - } - })(name, properties[name], prototype[name]); - } else if (typeof _super[name] === 'function') { - prototype[name] = (function (name, fn) { - return function () { - var tmp = this._super; - this._super = _super[name]; - var ret = fn.apply(this, arguments); - this._super = tmp; - return ret; - } - })(name, properties[name]); - } - } - }; - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.constructor = Class; - // And make this class extendable - Class.extend = arguments.callee; - - return Class; - }; -})(); +openerp.web.Class = nova.Class; openerp.web.callback = function(obj, method) { var callback = function() { @@ -175,31 +73,6 @@ openerp.web.callback = function(obj, method) { }; /** - * Generates an inherited class that replaces all the methods by null methods (methods - * that does nothing and always return undefined). - * - * @param {Class} claz - * @param {Object} add Additional functions to override. - * @return {Class} - */ -openerp.web.generate_null_object_class = function(claz, add) { - var newer = {}; - var copy_proto = function(prototype) { - for (var name in prototype) { - if(typeof prototype[name] == "function") { - newer[name] = function() {}; - } - } - if (prototype.prototype) - copy_proto(prototype.prototype); - }; - copy_proto(claz.prototype); - newer.init = openerp.web.Widget.prototype.init; - var tmpclass = claz.extend(newer); - return tmpclass.extend(add || {}); -}; - -/** * web error for lookup failure * * @class @@ -362,11 +235,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry } }); -openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.CallbackEnabled# */{ - /** - * @constructs openerp.web.CallbackEnabled - * @extends openerp.web.Class - */ +openerp.web.CallbackEnabledMixin = { init: function() { // Transform on_* method into openerp.web.callbacks for (var name in this) { @@ -404,7 +273,14 @@ openerp.web.CallbackEnabled = openerp.web.Class.extend(/** @lends openerp.web.Ca return self[method_name].apply(self, arguments); } } -}); +}; + +openerp.web.CallbackEnabled = openerp.web.Class.extend(_.extend({}, nova.GetterSetterMixin, openerp.web.CallbackEnabledMixin, { + init: function() { + nova.GetterSetterMixin.init.call(this); + openerp.web.CallbackEnabledMixin.init.call(this); + } +})); openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Connection# */{ /** @@ -422,7 +298,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.name = openerp._session_id; this.qweb_mutex = new $.Mutex(); }, - bind: function(origin) { + bind_session: function(origin) { var window_origin = location.protocol+"//"+location.host, self=this; this.origin = origin ? _.str.rtrim(origin,'/') : window_origin; this.prefix = this.origin; @@ -445,6 +321,269 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.active_id = null; return this.session_init(); }, + test_eval_get_context: function () { + var asJS = function (arg) { + if (arg instanceof py.object) { + return arg.toJSON(); + } + return arg; + }; + + var datetime = new py.object(); + datetime.datetime = new py.type(function datetime() { + throw new Error('datetime.datetime not implemented'); + }); + var date = datetime.date = new py.type(function date(y, m, d) { + if (y instanceof Array) { + d = y[2]; + m = y[1]; + y = y[0]; + } + this.year = asJS(y); + this.month = asJS(m); + this.day = asJS(d); + }, py.object, { + strftime: function (args) { + var f = asJS(args[0]), self = this; + return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) { + switch (c) { + case 'Y': return self.year; + case 'm': return _.str.sprintf('%02d', self.month); + case 'd': return _.str.sprintf('%02d', self.day); + } + throw new Error('ValueError: No known conversion for ' + m); + })); + } + }); + date.__getattribute__ = function (name) { + if (name === 'today') { + return date.today; + } + throw new Error("AttributeError: object 'date' has no attribute '" + name +"'"); + }; + date.today = new py.def(function () { + var d = new Date(); + return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()); + }); + datetime.time = new py.type(function time() { + throw new Error('datetime.time not implemented'); + }); + + var time = new py.object(); + time.strftime = new py.def(function (args) { + return date.today.__call__().strftime(args); + }); + + var relativedelta = new py.type(function relativedelta(args, kwargs) { + if (!_.isEmpty(args)) { + throw new Error('Extraction of relative deltas from existing datetimes not supported'); + } + this.ops = kwargs; + }, py.object, { + __add__: function (other) { + if (!(other instanceof datetime.date)) { + return py.NotImplemented; + } + // TODO: test this whole mess + var year = asJS(this.ops.year) || asJS(other.year); + if (asJS(this.ops.years)) { + year += asJS(this.ops.years); + } + + var month = asJS(this.ops.month) || asJS(other.month); + if (asJS(this.ops.months)) { + month += asJS(this.ops.months); + // FIXME: no divmod in JS? + while (month < 1) { + year -= 1; + month += 12; + } + while (month > 12) { + year += 1; + month -= 12; + } + } + + var lastMonthDay = new Date(year, month, 0).getDate(); + var day = asJS(this.ops.day) || asJS(other.day); + if (day > lastMonthDay) { day = lastMonthDay; } + var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0); + if (days_offset) { + day = new Date(year, month-1, day + days_offset).getDate(); + } + // TODO: leapdays? + // TODO: hours, minutes, seconds? Not used in XML domains + // TODO: weekday? + return new datetime.date(year, month, day); + }, + __radd__: function (other) { + return this.__add__(other); + }, + + __sub__: function (other) { + if (!(other instanceof datetime.date)) { + return py.NotImplemented; + } + // TODO: test this whole mess + var year = asJS(this.ops.year) || asJS(other.year); + if (asJS(this.ops.years)) { + year -= asJS(this.ops.years); + } + + var month = asJS(this.ops.month) || asJS(other.month); + if (asJS(this.ops.months)) { + month -= asJS(this.ops.months); + // FIXME: no divmod in JS? + while (month < 1) { + year -= 1; + month += 12; + } + while (month > 12) { + year += 1; + month -= 12; + } + } + + var lastMonthDay = new Date(year, month, 0).getDate(); + var day = asJS(this.ops.day) || asJS(other.day); + if (day > lastMonthDay) { day = lastMonthDay; } + var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0); + if (days_offset) { + day = new Date(year, month-1, day - days_offset).getDate(); + } + // TODO: leapdays? + // TODO: hours, minutes, seconds? Not used in XML domains + // TODO: weekday? + return new datetime.date(year, month, day); + }, + __rsub__: function (other) { + return this.__sub__(other); + } + }); + + return { + uid: new py.float(this.uid), + datetime: datetime, + time: time, + relativedelta: relativedelta + }; + }, + /** + * FIXME: Huge testing hack, especially the evaluation context, rewrite + test for real before switching + */ + test_eval: function (source, expected) { + try { + var ctx = this.test_eval_contexts(source.contexts); + if (!_.isEqual(ctx, expected.context)) { + console.group('Local context does not match remote, nothing is broken but please report to R&D (xmo)'); + console.warn('source', source.contexts); + console.warn('local', ctx); + console.warn('remote', expected.context); + console.groupEnd(); + } + } catch (e) { + console.group('Failed to evaluate contexts, nothing is broken but please report to R&D (xmo)'); + console.error(e); + console.log('source', source.contexts); + console.groupEnd(); + } + + try { + var dom = this.test_eval_domains(source.domains, this.test_eval_get_context()); + if (!_.isEqual(dom, expected.domain)) { + console.group('Local domain does not match remote, nothing is broken but please report to R&D (xmo)'); + console.warn('source', source.domains); + console.warn('local', dom); + console.warn('remote', expected.domain); + console.groupEnd(); + } + } catch (e) { + console.group('Failed to evaluate domains, nothing is broken but please report to R&D (xmo)'); + console.error(e); + console.log('source', source.domains); + console.groupEnd(); + } + + try { + var groups = this.test_eval_groupby(source.group_by_seq); + if (!_.isEqual(groups, expected.group_by)) { + console.group('Local groupby does not match remote, nothing is broken but please report to R&D (xmo)'); + console.warn('source', source.group_by_seq); + console.warn('local', groups); + console.warn('remote', expected.group_by); + console.groupEnd(); + } + } catch (e) { + console.group('Failed to evaluate groupby, nothing is broken but please report to R&D (xmo)'); + console.error(e); + console.log('source', source.group_by_seq); + console.groupEnd(); + } + }, + test_eval_contexts: function (contexts) { + var result_context = _.extend({}, this.user_context), + self = this; + _(contexts).each(function (ctx) { + switch(ctx.__ref) { + case 'context': + _.extend(result_context, py.eval(ctx.__debug)); + break; + case 'compound_context': + _.extend( + result_context, self.test_eval_contexts(ctx.__contexts)); + break; + default: + _.extend(result_context, ctx); + } + }); + return result_context; + }, + test_eval_domains: function (domains, eval_context) { + var result_domain = [], self = this; + _(domains).each(function (dom) { + switch(dom.__ref) { + case 'domain': + result_domain.push.apply( + result_domain, py.eval(dom.__debug, eval_context)); + break; + case 'compound_domain': + result_domain.push.apply( + result_domain, self.test_eval_domains( + dom.__domains, eval_context)); + break; + default: + result_domain.push.apply( + result_domain, dom); + } + }); + return result_domain; + }, + test_eval_groupby: function (contexts) { + var result_group = [], self = this; + _(contexts).each(function (ctx) { + var group; + switch(ctx.__ref) { + case 'context': + group = py.eval(ctx.__debug).group_by; + break; + case 'compound_context': + group = self.test_eval_contexts(ctx.__contexts).group_by; + break; + default: + group = ctx.group_by + } + if (!group) { return; } + if (typeof group === 'string') { + result_group.push(group); + } else if (group instanceof Array) { + result_group.push.apply(result_group, group); + } else { + throw new Error('Got invalid groupby {{' + + JSON.stringify(group) + '}}'); + } + }); + return result_group; + }, /** * Executes an RPC call, registering the provided callbacks. * @@ -480,6 +619,9 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. function (response, textStatus, jqXHR) { self.on_rpc_response(); if (!response.error) { + if (url.url === '/web/session/eval_domain_and_context') { + self.test_eval(params, response.result); + } deferred.resolve(response["result"], textStatus, jqXHR); } else if (response.error.data.type === "session_invalid") { self.uid = false; @@ -733,6 +875,14 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; return self.rpc('/web/webclient/jslist', {mods: to_load}).pipe(function(files) { return self.do_load_js(file_list.concat(files)); + }).then(function () { + if (!Date.CultureInfo.pmDesignator) { + // If no am/pm designator is specified but the openerp + // datetime format uses %i, date.js won't be able to + // correctly format a date. See bug#938497. + Date.CultureInfo.amDesignator = 'AM'; + Date.CultureInfo.pmDesignator = 'PM'; + } }); })) } @@ -873,9 +1023,12 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. _(_.extend({}, options.data || {}, {session_id: this.session_id, token: token})) .each(function (value, key) { - $('<input type="hidden" name="' + key + '">') - .val(value) - .appendTo($form_data); + var $input = $form.find('[name=' + key +']'); + if (!$input.length) { + $input = $('<input type="hidden" name="' + key + '">') + .appendTo($form_data); + } + $input.val(value) }); $form @@ -958,11 +1111,11 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. * * And of course, when you don't need that widget anymore, just do: * - * my_widget.stop(); + * my_widget.destroy(); * * That will kill the widget in a clean way and erase its content from the dom. */ -openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.Widget# */{ +openerp.web.Widget = nova.Widget.extend(_.extend({}, openerp.web.CallbackEnabledMixin, { /** * 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. @@ -971,18 +1124,13 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W */ template: null, /** - * Tag name when creating a default $element. - * @type string - */ - tag_name: 'div', - /** * Constructs the widget and sets its parent if a parent is given. * * @constructs openerp.web.Widget * @extends openerp.web.CallbackEnabled * * @param {openerp.web.Widget} parent Binds the current instance to the given Widget instance. - * When that widget is destroyed by calling stop(), the current instance will be + * 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 @@ -990,132 +1138,24 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W * for new components this argument should not be provided any more. */ init: function(parent) { - this._super(); + this._super(parent); + openerp.web.CallbackEnabledMixin.init.call(this); this.session = openerp.connection; - - this.$element = $(document.createElement(this.tag_name)); - - this.widget_parent = parent; - this.widget_children = []; - if(parent && parent.widget_children) { - parent.widget_children.push(this); - } - // useful to know if the widget was destroyed and should not be used anymore - this.widget_is_stopped = false; - }, - /** - * Renders the current widget and appends it to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - appendTo: function(target) { - var self = this; - return this._render_and_insert(function(t) { - self.$element.appendTo(t); - }, target); - }, - /** - * Renders the current widget and prepends it to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - prependTo: function(target) { - var self = this; - return this._render_and_insert(function(t) { - self.$element.prependTo(t); - }, target); }, /** - * Renders the current widget and inserts it after to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. + * Renders the element. The default implementation renders the widget using QWeb, + * `this.template` must be defined. The context given to QWeb contains the "widget" + * key that references `this`. */ - insertAfter: function(target) { - var self = this; - return this._render_and_insert(function(t) { - self.$element.insertAfter(t); - }, target); - }, - /** - * Renders the current widget and inserts it before to the given jQuery object or Widget. - * - * @param target A jQuery object or a Widget instance. - */ - insertBefore: function(target) { - var self = this; - return this._render_and_insert(function(t) { - self.$element.insertBefore(t); - }, target); - }, - /** - * Renders the current widget and replaces the given jQuery object. - * - * @param target A jQuery object or a Widget instance. - */ - replace: function(target) { - return this._render_and_insert(_.bind(function(t) { - this.$element.replaceAll(t); - }, this), target); - }, - _render_and_insert: function(insertion, target) { - this.render_element(); - if (target instanceof openerp.web.Widget) - target = target.$element; - insertion(target); - this.on_inserted(this.$element, this); - return this.start(); - }, - on_inserted: function(element, widget) {}, - /** - * Renders the element and insert the result of the render() method in this.$element. - */ - render_element: function() { - var rendered = this.render(); - if (rendered) { + renderElement: function() { + var rendered = null; + if (this.template) + rendered = openerp.web.qweb.render(this.template, {widget: this}); + if (_.str.trim(rendered)) { var elem = $(rendered); this.$element.replaceWith(elem); this.$element = elem; } - return this; - }, - /** - * Renders the widget using QWeb, `this.template` must be defined. - * The context given to QWeb contains the "widget" key that references `this`. - * - * @param {Object} additional Additional context arguments to pass to the template. - */ - render: function (additional) { - if (this.template) - return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {})); - return null; - }, - /** - * Method called after rendering. Mostly used to bind actions, perform asynchronous - * calls, etc... - * - * By convention, the method should return a promise to inform the caller when - * this widget has been initialized. - * - * @returns {jQuery.Deferred} - */ - start: function() { - return $.Deferred().done().promise(); - }, - /** - * Destroys the current widget, also destroys all its children before destroying itself. - */ - stop: function() { - _.each(_.clone(this.widget_children), function(el) { - el.stop(); - }); - if(this.$element != null) { - this.$element.remove(); - } - if (this.widget_parent && this.widget_parent.widget_children) { - this.widget_parent.widget_children = _.without(this.widget_parent.widget_children, this); - } - this.widget_parent = null; - this.widget_is_stopped = true; }, /** * Informs the action manager to do an action. This supposes that @@ -1123,37 +1163,36 @@ openerp.web.Widget = openerp.web.CallbackEnabled.extend(/** @lends openerp.web.W * If that's not the case this method will simply return `false`. */ do_action: function(action, on_finished) { - if (this.widget_parent) { - return this.widget_parent.do_action(action, on_finished); + if (this.getParent()) { + return this.getParent().do_action(action, on_finished); } return false; }, do_notify: function() { - if (this.widget_parent) { - return this.widget_parent.do_notify.apply(this,arguments); + if (this.getParent()) { + return this.getParent().do_notify.apply(this,arguments); } return false; }, do_warn: function() { - if (this.widget_parent) { - return this.widget_parent.do_warn.apply(this,arguments); + if (this.getParent()) { + return this.getParent().do_warn.apply(this,arguments); } return false; }, - rpc: function(url, data, success, error) { var def = $.Deferred().then(success, error); var self = this; openerp.connection.rpc(url, data). then(function() { - if (!self.widget_is_stopped) + if (!self.isDestroyed()) def.resolve.apply(def, arguments); }, function() { - if (!self.widget_is_stopped) + if (!self.isDestroyed()) def.reject.apply(def, arguments); }); return def.promise(); } -}); +})); /** * @deprecated use :class:`openerp.web.Widget` @@ -1164,7 +1203,21 @@ openerp.web.OldWidget = openerp.web.Widget.extend({ 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.tag_name)); + this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName)); + }, + renderElement: function() { + var rendered = this.render(); + if (rendered) { + var elem = $(rendered); + this.$element.replaceWith(elem); + this.$element = elem; + } + return this; + }, + render: function (additional) { + if (this.template) + return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {})); + return null; } }); @@ -1196,7 +1249,7 @@ openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.we if (translation_bundle.lang_parameters) { this.parameters = translation_bundle.lang_parameters; this.parameters.grouping = py.eval( - this.parameters.grouping).toJSON(); + this.parameters.grouping); } }, add_module_translation: function(mod) { @@ -1244,7 +1297,7 @@ openerp.web._lt = function (s) { return {toString: function () { return openerp.web._t(s); }} }; openerp.web.qweb = new QWeb2.Engine(); -openerp.web.qweb.debug = (window.location.search.indexOf('?debug') !== -1); +openerp.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined); openerp.web.qweb.default_dict = { '_' : _, '_t' : openerp.web._t @@ -1279,6 +1332,23 @@ openerp.web.qweb.preprocess_node = function() { } }; +/** + * A small utility function to check if a class implements correctly an interface, assuming that + * interface is simply specified using a dictionary containing methods and attributes with the + * correct type. It only performs the check when in debug mode and the only effect of an invalid + * check is messages in the console. + */ +openerp.web.check_interface = function(_class, _interface) { + if (! openerp.web.check_interface.debug) + return; + for (var member in _interface) { + if ( (typeof _class.prototype[member] != typeof _interface[member]) ) { + console.error("class failed to implement interface member '" + member + "'"); + } + } +} +openerp.web.check_interface.debug = ($.deparam($.param.querystring()).debug != undefined); + /** Jquery extentions */ $.Mutex = (function() { function Mutex() { diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index b3ed5b08280..a29a9c5ad36 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -1012,18 +1012,25 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({ : (v1 > v2) ? 1 : 0; }; - records.sort(function (a, b) { - return _.reduce(sort_fields, function (acc, field) { - if (acc) { return acc; } - - var sign = 1; - if (field[0] === '-') { - sign = -1; - field = field.slice(1); - } - return sign * compare(a[field], b[field]); - }, 0); - }); + // Array.sort is not necessarily stable. We must be careful with this because + // sorting an array where all items are considered equal is a worst-case that + // will randomize the array with an unstable sort! Therefore we must avoid + // sorting if there are no sort_fields (i.e. all items are considered equal) + // See also: http://ecma262-5.com/ELS5_Section_15.htm#Section_15.4.4.11 + // http://code.google.com/p/v8/issues/detail?id=90 + if (sort_fields.length) { + records.sort(function (a, b) { + return _.reduce(sort_fields, function (acc, field) { + if (acc) { return acc; } + var sign = 1; + if (field[0] === '-') { + sign = -1; + field = field.slice(1); + } + return sign * compare(a[field], b[field]); + }, 0); + }); + } completion.resolve(records); }; if(to_get.length > 0) { diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js index 83eea2cccbb..3054a7784c4 100644 --- a/addons/web/static/src/js/data_import.js +++ b/addons/web/static/src/js/data_import.js @@ -66,11 +66,11 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ this._super(); this.open({ buttons: [ - {text: _t("Close"), click: function() { self.stop(); }}, + {text: _t("Close"), click: function() { self.destroy(); }}, {text: _t("Import File"), click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'} ], close: function(event, ui) { - self.stop(); + self.destroy(); } }); this.toggle_import_button(false); @@ -151,7 +151,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ }); }, toggle_import_button: function (newstate) { - this.$element.dialog('widget') + openerp.web.dialog(this.$element, 'widget') .find('.oe-dialog-import-button') .button('option', 'disabled', !newstate); }, @@ -201,10 +201,10 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ return; } if (results['success']) { - if (this.widget_parent.widget_parent.active_view == "list") { - this.widget_parent.reload_content(); + if (this.getParent().getParent().active_view == "list") { + this.getParent().reload_content(); } - this.stop(); + this.destroy(); return; } @@ -358,7 +358,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({ } return true; }, - stop: function() { + destroy: function() { this.$element.remove(); this._super(); } diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 4fb576e4a90..53863e63e97 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -148,7 +148,7 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) { if (typeof(value) == "string") value = openerp.web.auto_str_to_date(value); return value.toString(normalize_format(l10n.time_format)); - case 'selection': + case 'selection': case 'statusbar': // Each choice is [value, label] if(_.isArray(value)) { value = value[0] @@ -336,7 +336,7 @@ openerp.web.format_cell = function (row_data, column, options) { case 'progressbar': return _.template( '<progress value="<%-value%>" max="100"><%-value%>%</progress>', { - value: row_data[column.id].value + value: _.str.sprintf("%.0f", row_data[column.id].value || 0) }); } diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index b1ee33c4125..7263aa948e2 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -32,6 +32,8 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea this.hidden = !!hidden; this.headless = this.hidden && !this.has_defaults; + this.filter_data = {}; + this.ready = $.Deferred(); }, start: function() { @@ -232,7 +234,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea context.add({"group_by": groupbys}); var dial_html = QWeb.render("SearchView.managed-filters.add"); var $dial = $(dial_html); - $dial.dialog({ + openerp.web.dialog($dial, { modal: true, title: _t("Filter Entry"), buttons: [ @@ -263,10 +265,20 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea var filter = this.managed_filters[val]; this.do_clear(false).then(_.bind(function() { select.val('get:' + val); - var groupbys = _.map(filter.context.group_by.split(","), function(el) { - return {"group_by": el}; - }); - this.on_search([filter.domain], [filter.context], groupbys); + + var groupbys = []; + var group_by = filter.context.group_by; + if (group_by) { + groupbys = _.map( + group_by instanceof Array ? group_by : group_by.split(','), + function (el) { return { group_by: el }; }); + } + this.filter_data = { + domains: [filter.domain], + contexts: [filter.context], + groupbys: groupbys + }; + this.do_search(); }, this)); } else { select.val(''); @@ -281,7 +293,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea selected_menu_id : menu.$element.find('a.active').data('menu') })); $dialog.find('input').val(this.fields_view.name); - $dialog.dialog({ + openerp.web.dialog($dialog, { modal: true, title: _t("Add to Dashboard"), buttons: [ @@ -303,10 +315,10 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea }); self.rpc('/web/searchview/add_to_dashboard', { menu_id: menu_id, - action_id: self.widget_parent.action.id, + action_id: self.getParent().action.id, context_to_save: context, domain: domain, - view_mode: self.widget_parent.active_view, + view_mode: self.getParent().active_view, name: title }, function(r) { if (r === false) { @@ -334,9 +346,6 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea if (this.headless && !this.has_defaults) { return this.on_search([], [], []); } - // reset filters management - var select = this.$element.find(".oe_search-view-filters-management"); - select.val("_filters"); if (e && e.preventDefault) { e.preventDefault(); } @@ -380,6 +389,16 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea .map(function (filter) { return filter.get_context();}) .compact() .value(); + + if (this.filter_data.contexts) { + contexts = this.filter_data.contexts.concat(contexts) + } + if (this.filter_data.domains) { + domains = this.filter_data.domains.concat(domains); + } + if (this.filter_data.groupbys) { + groupbys = this.filter_data.groupbys.concat(groupbys); + } return {domains: domains, contexts: contexts, errors: errors, groupbys: groupbys}; }, /** @@ -418,6 +437,9 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea * @param {Boolean} [reload_view=true] */ do_clear: function (reload_view) { + this.filter_data = {}; + this.$element.find(".oe_search-view-filters-management").val(''); + this.$element.find('.filter_label, .filter_icon').removeClass('enabled'); this.enabled_filters.splice(0); var string = $('a.searchview_group_string'); @@ -552,7 +574,7 @@ openerp.web.search.Widget = openerp.web.OldWidget.extend( /** @lends openerp.web * "Stops" the widgets. Called when the view destroys itself, this * lets the widgets clean up after themselves. */ - stop: function () { + destroy: function () { delete this.view; this._super(); }, @@ -1114,7 +1136,7 @@ openerp.web.search.ExtendedSearch = openerp.web.search.Input.extend({ if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") { return null; } - return _.reduce(this.widget_children, + return _.reduce(this.getChildren(), function(mem, x) { return mem.concat(x.get_domain());}, []); }, on_activate: function() { @@ -1133,9 +1155,9 @@ openerp.web.search.ExtendedSearch = openerp.web.search.Input.extend({ } }, check_last_element: function() { - _.each(this.widget_children, function(x) {x.set_last_group(false);}); - if (this.widget_children.length >= 1) { - this.widget_children[this.widget_children.length - 1].set_last_group(true); + _.each(this.getChildren(), function(x) {x.set_last_group(false);}); + if (this.getChildren().length >= 1) { + this.getChildren()[this.getChildren().length - 1].set_last_group(true); } } }); @@ -1148,7 +1170,7 @@ openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({ }, add_prop: function() { var prop = new openerp.web.search.ExtendedSearchProposition(this, this.fields); - var render = prop.render({'index': this.widget_children.length - 1}); + var render = prop.render({'index': this.getChildren().length - 1}); this.$element.find('.searchview_extended_propositions_list').append(render); prop.start(); }, @@ -1159,11 +1181,11 @@ openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({ _this.add_prop(); }); this.$element.find('.searchview_extended_delete_group').click(function () { - _this.stop(); + _this.destroy(); }); }, get_domain: function() { - var props = _(this.widget_children).chain().map(function(x) { + var props = _(this.getChildren()).chain().map(function(x) { return x.get_proposition(); }).compact().value(); var choice = this.$element.find(".searchview_extended_group_choice").val(); @@ -1172,10 +1194,10 @@ openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({ _.map(_.range(_.max([0,props.length - 1])), function() { return op; }), props); }, - stop: function() { - var parent = this.widget_parent; - if (this.widget_parent.widget_children.length == 1) - this.widget_parent.hide(); + destroy: function() { + var parent = this.getParent(); + if (this.getParent().getChildren().length == 1) + this.getParent().hide(); this._super(); parent.check_last_element(); }, @@ -1210,16 +1232,16 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** _this.changed(); }); this.$element.find('.searchview_extended_delete_prop').click(function () { - _this.stop(); + _this.destroy(); }); }, - stop: function() { + destroy: function() { var parent; - if (this.widget_parent.widget_children.length == 1) - parent = this.widget_parent; + if (this.getParent().getChildren().length == 1) + parent = this.getParent(); this._super(); if (parent) - parent.stop(); + parent.destroy(); }, changed: function() { var nval = this.$element.find(".searchview_extended_prop_field").val(); @@ -1235,7 +1257,7 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** select_field: function(field) { var self = this; if(this.attrs.selected != null) { - this.value.stop(); + this.value.destroy(); this.value = null; this.$element.find('.searchview_extended_prop_op').html(''); } diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js index 0258509f172..2c0dc3d70b4 100644 --- a/addons/web/static/src/js/view_editor.js +++ b/addons/web/static/src/js/view_editor.js @@ -1002,10 +1002,10 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({ $.when(action_manager.do_action(action)).then(function() { var controller = action_manager.dialog_viewmanager.views['form'].controller; controller.on_button_cancel.add_last(function(){ - action_manager.stop() + action_manager.destroy() }); controller.do_save.add_last(function(){ - action_manager.stop(); + action_manager.destroy(); var value =controller.fields.name.value; self.add_node_dialog.$element.find('select[id=field_value]').append($("<option selected></option>").attr("value",value).text(value)); _.detect(self.add_widget,function(widget){ diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 2a678bee393..7808c646a43 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -74,13 +74,13 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }, this.on_loaded); } }, - stop: function() { + destroy: function() { if (this.sidebar) { - this.sidebar.attachments.stop(); - this.sidebar.stop(); + this.sidebar.attachments.destroy(); + this.sidebar.destroy(); } _.each(this.widgets, function(w) { - w.stop(); + w.destroy(); }); this._super(); }, @@ -90,17 +90,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# }, on_loaded: function(data) { var self = this; - if (data) { - this.fields_order = []; - this.fields_view = data; - var frame = new (this.registry.get_object('frame'))(this, this.fields_view.arch); - - this.rendered = QWeb.render(this.form_template, { 'frame': frame, 'widget': this }); + if (!data) { + throw "No data provided."; + } + if (this.root_frame) { + throw "Form view does not support multiple calls to on_loaded"; } + this.fields_order = []; + this.fields_view = data; + + this.rendered = QWeb.render(this.form_template, {'widget': this}); this.$element.html(this.rendered); - _.each(this.widgets, function(w) { - w.start(); - }); + + this.root_frame = instanciate_widget(this.registry.get_object('frame'), this, this.fields_view.arch); + var to_append = $(".oe_form_header", this.$element); + this.root_frame.appendTo(to_append.length > 0 ? to_append : this.$element); + this.root_frame.$element.children().unwrap(); + this.$form_header = this.$element.find('.oe_form_header:first'); this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() { var action = $(this).data('pager-action'); @@ -205,7 +211,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# if (self.sidebar) { self.sidebar.attachments.do_update(); } - if (self.default_focus_field && !self.embedded_view) { + if (self.default_focus_field) { self.default_focus_field.focus(); } if (record.id) { @@ -418,7 +424,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# this.on_form_changed(); } if (!_.isEmpty(result.warning)) { - $(QWeb.render("CrashManagerWarning", result.warning)).dialog({ + openerp.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), { modal: true, buttons: [ {text: _t("Ok"), click: function() { $(this).dialog("close"); }} @@ -879,7 +885,7 @@ openerp.web.form.compute_domain = function(expr, fields) { }; openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{ - template: 'Widget', + form_template: 'Widget', /** * @constructs openerp.web.form.Widget * @extends openerp.web.OldWidget @@ -894,13 +900,9 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true); this.type = this.type || node.tag; this.element_name = this.element_name || this.type; - this.element_class = [ - 'formview', this.view.view_id, this.element_name, - this.view.widgets_counter++].join("_"); this._super(view); - this.view.widgets[this.element_class] = this; this.children = node.children; this.colspan = parseInt(node.attrs.colspan || 1, 10); this.decrease_max_width = 0; @@ -919,14 +921,9 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo this.align = 'center'; } - this.width = this.node.attrs.width; }, - start: function() { - this.$element = this.view.$element.find( - '.' + this.element_class.replace(/[^\r\n\f0-9A-Za-z_-]/g, "\\$&")); - }, - stop: function() { + destroy: function() { this._super.apply(this, arguments); $.fn.tipsy.clear(); }, @@ -939,9 +936,8 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo update_dom: function() { this.$element.toggle(!this.invisible); }, - render: function() { - var template = this.template; - return QWeb.render(template, { "widget": this }); + renderElement: function() { + this.$element.html(QWeb.render(this.form_template, { "widget": this })); }, do_attach_tooltip: function(widget, trigger, options) { widget = widget || this; @@ -951,7 +947,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo delayOut: 0, fade: true, title: function() { - var template = widget.template + '.tooltip'; + var template = widget.form_template + '.tooltip'; if (!QWeb.has_template(template)) { template = 'WidgetLabel.tooltip'; } @@ -1016,7 +1012,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo }); openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ - template: 'WidgetFrame', + form_template: 'WidgetFrame', init: function(view, node) { this._super(view, node); this.columns = parseInt(node.attrs.col || 4, 10); @@ -1034,6 +1030,23 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ } this.set_row_cells_with(this.table[this.table.length - 1]); }, + renderElement: function() { + this._super(); + var self = this; + _.each(this.table, function(row) { + _.each(row, function(td) { + td.$element = self.$element.find('.' + td.element_class); + td.renderElement(); + }); + }); + }, + start: function() { + _.each(this.table, function(row) { + _.each(row, function(td) { + td.start(); + }); + }); + }, add_row: function(){ if (this.table.length) { this.set_row_cells_with(this.table[this.table.length - 1]); @@ -1081,14 +1094,14 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ node.attrs.nolabel = '1'; } } - var widget = new (this.view.registry.get_any( - [node.attrs.widget, type.type, node.tag])) (this.view, node); + var widget = instanciate_widget(this.view.registry.get_any( + [node.attrs.widget, type.type, node.tag]), this.view, node); if (node.tag == 'field') { if (!this.view.default_focus_field || node.attrs.default_focus == '1') { this.view.default_focus_field = widget; } if (node.attrs.nolabel != '1') { - var label = new (this.view.registry.get_object('label')) (this.view, node); + var label = instanciate_widget(this.view.registry.get_object('label'), this.view, node); label["for"] = widget; this.add_widget(label, widget.colspan + 1); } @@ -1110,23 +1123,31 @@ openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({ }); openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({ - template: 'WidgetGroup' + form_template: 'WidgetGroup' }), openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ - template: 'WidgetNotebook', + form_template: 'WidgetNotebook', init: function(view, node) { this._super(view, node); this.pages = []; for (var i = 0; i < node.children.length; i++) { var n = node.children[i]; if (n.tag == "page") { - var page = new (this.view.registry.get_object('notebookpage'))( + var page = instanciate_widget(this.view.registry.get_object('notebookpage'), this.view, n, this, this.pages.length); this.pages.push(page); } } }, + renderElement: function() { + this._super(); + var self = this; + _.each(this.pages, function(page) { + page.$element = self.$element.find('.' + page.element_class); + page.renderElement(); + }); + }, start: function() { var self = this; this._super.apply(this, arguments); @@ -1146,6 +1167,9 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ gravity: 's' }); } + _.each(this.pages, function(page) { + page.start(); + }); }, do_select_first_visible_tab: function() { for (var i = 0; i < this.pages.length; i++) { @@ -1159,7 +1183,7 @@ openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({ }); openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({ - template: 'WidgetNotebookPage', + form_template: 'WidgetNotebookPage', init: function(view, node, notebook, index) { this.notebook = notebook; this.index = index; @@ -1181,7 +1205,7 @@ openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({ }); openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({ - template: 'WidgetSeparator', + form_template: 'WidgetSeparator', init: function(view, node) { this._super(view, node); this.orientation = node.attrs.orientation || 'horizontal'; @@ -1193,7 +1217,7 @@ openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({ }); openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ - template: 'WidgetButton', + form_template: 'WidgetButton', init: function(view, node) { this._super(view, node); this.force_disabled = false; @@ -1227,7 +1251,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ var exec_action = function() { if (self.node.attrs.confirm) { var def = $.Deferred(); - var dialog = $('<div>' + self.node.attrs.confirm + '</div>').dialog({ + var dialog = openerp.web.dialog($('<div>' + self.node.attrs.confirm + '</div>'), { title: _t('Confirm'), modal: true, buttons: [ @@ -1283,7 +1307,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({ }); openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ - template: 'WidgetLabel', + form_template: 'WidgetLabel', init: function(view, node) { this.element_name = 'label_' + node.attrs.name; @@ -1295,7 +1319,7 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ } if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) { - this.template = "WidgetParagraph"; + this.form_template = "WidgetParagraph"; this.colspan = parseInt(this.node.attrs.colspan || 1, 10); // Widgets default to right-aligned, but paragraph defaults to // left-aligned @@ -1311,12 +1335,15 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ this.nowrap = true; } }, - render: function () { + renderElement: function() { + var rendered; if (this['for'] && this.type !== 'label') { - return QWeb.render(this.template, {widget: this['for']}); + rendered = QWeb.render(this.form_template, {widget: this['for']}); + } else { + // Actual label widgets should not have a false and have type label + rendered = QWeb.render(this.form_template, {widget: this}); } - // Actual label widgets should not have a false and have type label - return QWeb.render(this.template, {widget: this}); + this.$element.html(rendered); }, start: function() { this._super(); @@ -1332,6 +1359,58 @@ openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({ } }); +/** + * Interface to be implemented by fields. + * + * Novajs Attributes: + * - ... + * + * Novajs Events: + * - ... + * + */ +openerp.web.form.FieldInterface = { + /** + * Called by the form view to indicate the value of the field. + * + * set_value() may return an object that can be passed to $.when() that represents the moment when + * the field has finished all operations necessary before the user can effectively use the widget. + * + * Multiple calls to set_value() can occur at any time and must be handled correctly by the implementation, + * regardless of any asynchronous operation currently running and the status of any promise that a + * previous call to set_value() could have returned. + * + * set_value() must be able, at any moment, to handle the syntax returned by the "read" method of the + * osv class in the OpenERP server as well as the syntax used by the set_value() (see below). It must + * also be able to handle any other format commonly used in the _defaults key on the models in the addons + * as well as any format commonly returned in a on_change. It must be able to autodetect those formats as + * no information is ever given to know which format is used. + */ + set_value: function(value) {}, + /** + * Get the current value of the widget. + * + * Must always return a syntaxically correct value to be passed to the "write" method of the osv class in + * the OpenERP server, although it is not assumed to respect the constraints applied to the field. + * For example if the field is marqued as "required", a call to get_value() can return false. + * + * get_value() can also be called *before* a call to set_value() and, in that case, is supposed to + * return a defaut value according to the type of field. + * + * This method is always assumed to perform synchronously, it can not return a promise. + * + * If there was no user interaction to modify the value of the field, it is always assumed that + * get_value() return the same semantic value than the one passed in the last call to set_value(), + * altough the syntax can be different. This can be the case for type of fields that have a different + * syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0). + */ + get_value: function() {}, +}; + +/** + * Abstract class for classes implementing FieldInterface. Should be renamed to AbstractField some + * day. + */ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{ /** * @constructs openerp.web.form.Field @@ -1448,7 +1527,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f }); openerp.web.form.FieldChar = openerp.web.form.Field.extend({ - template: 'FieldChar', + form_template: 'FieldChar', init: function (view, node) { this._super(view, node); this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; @@ -1493,7 +1572,7 @@ openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({ }); openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ - template: 'FieldEmail', + form_template: 'FieldEmail', start: function() { this._super.apply(this, arguments); this.$element.find('button').click(this.on_button_clicked); @@ -1508,7 +1587,7 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({ }); openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({ - template: 'FieldUrl', + form_template: 'FieldUrl', start: function() { this._super.apply(this, arguments); this.$element.find('button').click(this.on_button_clicked); @@ -1526,16 +1605,11 @@ openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({ init: function (view, node) { this._super(view, node); if (node.attrs.digits) { - this.parse_digits(node.attrs.digits); + this.digits = py.eval(node.attrs.digits).toJSON(); } else { this.digits = view.fields_view.fields[node.attrs.name].digits; } }, - parse_digits: function (digits_attr) { - // could use a Python parser instead. - var match = /^\s*[\(\[](\d+),\s*(\d+)/.exec(digits_attr); - return [parseInt(match[1], 10), parseInt(match[2], 10)]; - }, set_value: function(value) { if (value === false || value === undefined) { // As in GTK client, floats default to 0 @@ -1631,7 +1705,7 @@ openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({ }); openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({ - template: "EmptyComponent", + form_template: "EmptyComponent", build_widget: function() { return new openerp.web.DateTimeWidget(this); }, @@ -1668,7 +1742,7 @@ openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({ }); openerp.web.form.FieldText = openerp.web.form.Field.extend({ - template: 'FieldText', + form_template: 'FieldText', start: function() { this._super.apply(this, arguments); this.$element.find('textarea').change(this.on_ui_change); @@ -1729,7 +1803,7 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({ }); openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({ - template: 'FieldBoolean', + form_template: 'FieldBoolean', start: function() { var self = this; this._super.apply(this, arguments); @@ -1753,7 +1827,7 @@ openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({ }); openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({ - template: 'FieldProgressBar', + form_template: 'FieldProgressBar', start: function() { this._super.apply(this, arguments); this.$element.find('div').progressbar({ @@ -1777,7 +1851,7 @@ openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({ }); openerp.web.form.FieldSelection = openerp.web.form.Field.extend({ - template: 'FieldSelection', + form_template: 'FieldSelection', init: function(view, node) { var self = this; this._super(view, node); @@ -1883,7 +1957,7 @@ openerp.web.form.dialog = function(content, options) { }; openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ - template: 'FieldMany2One', + form_template: 'FieldMany2One', init: function(view, node) { this._super(view, node); this.limit = 7; @@ -1912,9 +1986,13 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self})); var bindings = {}; bindings[self.cm_id + "_search"] = function() { + if (self.readonly) + return; self._search_create_popup("search"); }; bindings[self.cm_id + "_create"] = function() { + if (self.readonly) + return; self._search_create_popup("form"); }; bindings[self.cm_id + "_open"] = function() { @@ -2019,6 +2097,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ minLength: 0, delay: 0 }); + this.$input.autocomplete("widget").addClass("openerp"); // 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) { @@ -2201,6 +2280,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({ this.$input.prop('readonly', this.readonly); } }); +openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface); /* # Values: (0, 0, { fields }) create @@ -2248,7 +2328,7 @@ var commands = { } }; openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ - template: 'FieldOne2Many', + form_template: 'FieldOne2Many', multi_selection: false, init: function(view, node) { this._super(view, node); @@ -2522,7 +2602,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({ this.previous_readonly = this.readonly; if (this.viewmanager) { this.is_loaded = this.is_loaded.pipe(function() { - self.viewmanager.stop(); + self.viewmanager.destroy(); return $.when(self.load_views()).then(function() { self.reload_current_view(); }); @@ -2622,7 +2702,7 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({ }); openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ - template: 'FieldMany2Many', + form_template: 'FieldMany2Many', multi_selection: false, init: function(view, node) { this._super(view, node); @@ -2702,7 +2782,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({ this.previous_readonly = this.readonly; if (this.list_view) { this.is_loaded = this.is_loaded.pipe(function() { - self.list_view.stop(); + self.list_view.destroy(); return $.when(self.load_view()).then(function() { self.reload_content(); }); @@ -2750,7 +2830,7 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open var pop = new openerp.web.form.FormOpenPopup(this); pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), { title: _t("Open: ") + this.name, - readonly: this.widget_parent.is_readonly() + readonly: this.getParent().is_readonly() }); pop.on_write_completed.add_last(function() { self.reload_content(); @@ -2787,7 +2867,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope }, read_function: null}); this.initial_ids = this.options.initial_ids; this.created_elements = []; - this.render_element(); + this.renderElement(); openerp.web.form.dialog(this.$element, { close: function() { self.check_exit(); @@ -2831,10 +2911,14 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope this.new_object(); } }, + stop: function () { + this.$element.dialog('close'); + this._super(); + }, setup_search_view: function(search_defaults) { var self = this; if (this.searchview) { - this.searchview.stop(); + this.searchview.destroy(); } this.searchview = new openerp.web.SearchView(this, this.dataset, false, search_defaults); @@ -2864,7 +2948,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope $buttons.prepend(QWeb.render("SelectCreatePopup.search.buttons")); var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close"); $cbutton.click(function() { - self.stop(); + self.destroy(); }); var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select"); if(self.options.disable_multiple_selection) { @@ -2872,7 +2956,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope } $sbutton.click(function() { self.on_select_elements(self.selected_ids); - self.stop(); + self.destroy(); }); }); }); @@ -2956,7 +3040,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope if (this.created_elements.length > 0) { this.on_select_elements(this.created_elements); } - this.stop(); + this.destroy(); }, on_default_get: function(res) {} }); @@ -2967,7 +3051,7 @@ openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({ }, select_record: function(index) { this.popup.on_select_elements([this.dataset.ids[index]]); - this.popup.stop(); + this.popup.destroy(); }, do_select: function(ids, records) { this._super(ids, records); @@ -2996,8 +3080,8 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp this.row_id = row_id; this.context = context || {}; this.options = _.defaults(options || {}, {"auto_write": true}); - this.render_element(); - this.$element.dialog({ + this.renderElement(); + openerp.web.dialog(this.$element, { title: options.title || '', modal: true, width: 960, @@ -3043,12 +3127,12 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp var $nbutton = $buttons.find(".oe_formopenpopup-form-save"); $nbutton.click(function() { self.view_form.do_save().then(function() { - self.stop(); + self.destroy(); }); }); var $cbutton = $buttons.find(".oe_formopenpopup-form-close"); $cbutton.click(function() { - self.stop(); + self.destroy(); }); if (self.options.readonly) { $nbutton.hide(); @@ -3071,7 +3155,7 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({ }); openerp.web.form.FieldReference = openerp.web.form.Field.extend({ - template: 'FieldReference', + form_template: 'FieldReference', init: function(view, node) { this._super(view, node); this.fields_view = { @@ -3086,7 +3170,7 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ }; this.get_fields_values = view.get_fields_values; this.get_selected_ids = view.get_selected_ids; - this.do_onchange = this.on_form_changed = this.on_nop; + this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop; this.dataset = this.view.dataset; this.widgets_counter = 0; this.view_id = 'reference_' + _.uniqueId(); @@ -3117,7 +3201,11 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({ }, start: function() { this._super(); + this.selection.$element = $(".oe_form_view_reference_selection", this.$element); + this.selection.renderElement(); this.selection.start(); + this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element); + this.m2o.renderElement(); this.m2o.start(); }, is_valid: function() { @@ -3226,7 +3314,7 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({ }); openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ - template: 'FieldBinaryFile', + form_template: 'FieldBinaryFile', update_dom: function() { this._super.apply(this, arguments); this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly); @@ -3264,7 +3352,7 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({ }); openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ - template: 'FieldBinaryImage', + form_template: 'FieldBinaryImage', start: function() { this._super.apply(this, arguments); this.$image = this.$element.find('img.oe-binary-image'); @@ -3276,8 +3364,14 @@ openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ set_value: function(value) { this._super.apply(this, arguments); this.set_image_maxwidth(); - var url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + - this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime()); + + var url; + if (value && value.substr(0, 10).indexOf(' ') == -1) { + url = 'data:image/png;base64,' + this.value; + } else { + url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + + this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime()); + } this.$image.attr('src', url); }, set_image_maxwidth: function() { @@ -3299,7 +3393,7 @@ openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({ }); openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ - template: "EmptyComponent", + form_template: "EmptyComponent", start: function() { this._super(); this.selected_value = null; @@ -3364,10 +3458,11 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({ }); openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({ - render: function () { + renderElement: function () { var $root = $('<div class="oe_form_html_view">'); this.render_children(this, $root); - return $root.html(); + var rendered = $root.html(); + this.$element.html(rendered); }, render_children: function (object, $into) { var self = this, @@ -3376,10 +3471,9 @@ openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({ if (typeof child === 'string') { $into.text(child); } else if (child.tag === 'field') { - $into.append( - new (self.view.registry.get_object('frame'))( - self.view, {tag: 'ueule', attrs: {}, children: [child] }) - .render()); + var widget = instanciate_widget(self.view.registry.get_object('frame'), + self.view, {tag: 'ueule', attrs: {}, children: [child] }); + widget.appendTo($into); } else { var $child = $(document.createElement(child.tag)) .attr(child.attrs) @@ -3426,6 +3520,14 @@ openerp.web.form.widgets = new openerp.web.Registry({ 'html': 'openerp.web.form.WidgetHtml' }); +var instanciate_widget = function(claz, view, node, o1, o2) { + var widget = new (claz)(view, node, o1, o2); + widget.element_class = (['formview', view.view_id, widget.element_name, + view.widgets_counter++].join("_")).replace(/[^\r\n\f0-9A-Za-z_-]/g, "_"); + view.widgets[widget.element_class] = widget; + return widget; +} + }; diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 8d8e342370e..e8fff450c6e 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -96,7 +96,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# if (this._limit === undefined) { this._limit = (this.options.limit || this.defaults.limit - || (this.widget_parent.action || {}).limit + || (this.getParent().action || {}).limit || 80); } return this._limit; @@ -158,7 +158,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# var pair = this.colors[i], color = pair[0], expression = pair[1]; - if (py.evaluate(expression, _.extend({bool: py.bool}, context))) { + if (py.evaluate(expression, context).toJSON()) { return 'color: ' + color + ';'; } // TODO: handle evaluation errors @@ -291,7 +291,10 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# */ configure_pager: function (dataset) { this.dataset.ids = dataset.ids; - this.dataset._length = dataset._length; + // Not exactly clean + if (dataset._length) { + this.dataset._length = dataset._length; + } var limit = this.limit(), total = dataset.size(), @@ -360,7 +363,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# _(this.fields_view.arch.children).map(field_to_column)); if (grouped) { this.columns.unshift({ - id: '_group', tag: '', string: "Group", meta: true, + id: '_group', tag: '', string: _t("Group"), meta: true, modifiers_for: function () { return {}; } }, { id: '_count', tag: '', string: '#', meta: true, @@ -541,19 +544,17 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# * @param {Array} records selected record values */ do_select: function (ids, records) { - this.$element.find('.oe-list-delete') - .attr('disabled', !ids.length); - if (this.sidebar) { - if (ids.length) { - this.sidebar.do_unfold(); - } else { - this.sidebar.do_fold(); - } - } - if (!records.length) { + this.$element.find('.oe-list-delete').attr('disabled', !ids.length); + if (!ids.length) { + this.dataset.index = 0; + if (this.sidebar) { this.sidebar.do_fold(); } this.compute_aggregates(); return; } + + this.dataset.index = _(this.dataset.ids).indexOf(ids[0]); + if (this.sidebar) { this.sidebar.do_unfold(); } + this.compute_aggregates(_(records).map(function (record) { return {count: 1, values: record}; })); @@ -1260,9 +1261,11 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L if (column.meta) { // do not do anything } else if (column.id in group.aggregates) { - var value = group.aggregates[column.id]; + var r = {}; + r[column.id] = {value: group.aggregates[column.id]}; $('<td class="oe-number">') - .html(openerp.web.format_value(value, column)) + .html(openerp.web.format_cell( + r, column, {process_modifiers: false})) .appendTo($row); } else { $row.append('<td>'); diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index c7222455889..b8699bcc006 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -139,7 +139,7 @@ openerp.web.list_editable = function (openerp) { } cancelled.then(function () { self.view.unpad_columns(); - self.edition_form.stop(); + self.edition_form.destroy(); self.edition_form.$element.remove(); delete self.edition_form; delete self.edition_id; @@ -235,26 +235,16 @@ openerp.web.list_editable = function (openerp) { } self.edition = true; self.edition_id = record_id; - self.edition_form = _.extend(new openerp.web.ListEditableFormView(self.view, self.dataset, false), { - form_template: 'ListView.row.form', - registry: openerp.web.list.form.widgets, - $element: $new_row - }); - // HA HA - self.edition_form.appendTo(); + self.edition_form = new openerp.web.ListEditableFormView(self.view, self.dataset, false); + self.edition_form.$element = $new_row; + self.edition_form.editable_list = self; + // HO HO + // empty $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () { // put in $.when just in case FormView.on_loaded becomes asynchronous $new_row.find('> td') - .addClass('oe-field-cell') - .removeAttr('width') .end() .find('td:last').removeClass('oe-field-cell').end(); - if (self.options.selectable) { - $new_row.prepend('<th>'); - } - if (self.options.isClarkGable) { - $new_row.prepend('<th>'); - } // pad in case of groupby _(self.columns).each(function (column) { if (column.meta) { @@ -383,7 +373,7 @@ openerp.web.list_editable = function (openerp) { openerp.web.list.form = {}; } openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({ - template: 'ListView.row.frame' + form_template: 'ListView.row.frame' }); var form_widgets = openerp.web.form.widgets; openerp.web.list.form.widgets = form_widgets.extend({ @@ -418,9 +408,10 @@ openerp.web.list_editable = function (openerp) { }); openerp.web.ListEditableFormView = openerp.web.FormView.extend({ - init_view: function() {}, - _render_and_insert: function () { - return this.start(); - } + form_template: 'ListView.row.form', + init: function() { + this._super.apply(this, arguments); + this.registry = openerp.web.list.form.widgets; + }, }); }; diff --git a/addons/web/static/src/js/view_page.js b/addons/web/static/src/js/view_page.js index 33eabc63b3c..e3367f56b4a 100644 --- a/addons/web/static/src/js/view_page.js +++ b/addons/web/static/src/js/view_page.js @@ -68,13 +68,13 @@ openerp.web.page = function (openerp) { openerp.web.page = {}; openerp.web.page.WidgetFrameReadonly = openerp.web.form.WidgetFrame.extend({ - template: 'WidgetFrame.readonly' + form_template: 'WidgetFrame.readonly' }); openerp.web.page.FieldReadonly = openerp.web.form.Field.extend({ }); openerp.web.page.FieldCharReadonly = openerp.web.page.FieldReadonly.extend({ - template: 'FieldChar.readonly', + form_template: 'FieldChar.readonly', init: function(view, node) { this._super(view, node); this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1'; @@ -89,13 +89,27 @@ openerp.web.page = function (openerp) { return show_value; } }); + openerp.web.page.FieldFloatReadonly = openerp.web.page.FieldCharReadonly.extend({ + init: function (view, node) { + this._super(view, node); + if (node.attrs.digits) { + this.digits = py.eval(node.attrs.digits).toJSON(); + } else { + this.digits = view.fields_view.fields[node.attrs.name].digits; + } + } + }); openerp.web.page.FieldURIReadonly = openerp.web.page.FieldCharReadonly.extend({ - template: 'FieldURI.readonly', + form_template: 'FieldURI.readonly', scheme: null, format_value: function (value) { return value; }, set_value: function (value) { + if (!value) { + this.$element.find('a').text('').attr('href', '#'); + return; + } this.$element.find('a') .attr('href', this.scheme + ':' + value) .text(this.format_value(value)); @@ -106,6 +120,10 @@ openerp.web.page = function (openerp) { }); openerp.web.page.FieldUrlReadonly = openerp.web.page.FieldURIReadonly.extend({ set_value: function (value) { + if (!value) { + this.$element.find('a').text('').attr('href', '#'); + return; + } var s = /(\w+):(.+)/.exec(value); if (!s) { value = "http://" + value; @@ -120,7 +138,7 @@ openerp.web.page = function (openerp) { } }); openerp.web.page.FieldSelectionReadonly = openerp.web.page.FieldReadonly.extend({ - template: 'FieldChar.readonly', + form_template: 'FieldChar.readonly', init: function(view, node) { // lifted straight from r/w version var self = this; @@ -225,7 +243,7 @@ openerp.web.page = function (openerp) { } }); openerp.web.page.FieldBinaryFileReadonly = openerp.web.form.FieldBinary.extend({ - template: 'FieldURI.readonly', + form_template: 'FieldURI.readonly', start: function() { this._super.apply(this, arguments); var self = this; @@ -261,7 +279,7 @@ openerp.web.page = function (openerp) { 'one2many_list' : 'openerp.web.page.FieldOne2ManyReadonly', 'reference': 'openerp.web.page.FieldReferenceReadonly', 'boolean': 'openerp.web.page.FieldBooleanReadonly', - 'float': 'openerp.web.page.FieldCharReadonly', + 'float': 'openerp.web.page.FieldFloatReadonly', 'integer': 'openerp.web.page.FieldCharReadonly', 'float_time': 'openerp.web.page.FieldCharReadonly', 'binary': 'openerp.web.page.FieldBinaryFileReadonly', diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index dea53793ffb..6039e4a0822 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -146,7 +146,7 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# var pair = this.colors[i], color = pair[0], expression = pair[1]; - if (py.evaluate(expression, _.extend({bool: py.bool}, context))) { + if (py.evaluate(expression, context).toJSON()) { return 'color: ' + color + ';'; } // TODO: handle evaluation errors @@ -224,21 +224,25 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# active_model: self.dataset.model, active_id: id, active_ids: [id]}; - this.rpc('/web/treeview/action', { + return this.rpc('/web/treeview/action', { id: id, model: this.dataset.model, context: new openerp.web.CompoundContext( this.dataset.get_context(), local_context) - }, function (actions) { + }).pipe(function (actions) { if (!actions.length) { return; } var action = actions[0][2]; var c = new openerp.web.CompoundContext(local_context); if (action.context) { c.add(action.context); } - action.context = c; - self.do_action(action); - }); + return self.rpc('/web/session/eval_domain_and_context', { + contexts: [c], domains: [] + }).pipe(function (res) { + action.context = res.context; + return self.do_action(action); + }, null); + }, null); }, // show & hide the contents diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 56997190bc1..5be0fad74f0 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -30,31 +30,31 @@ session.web.ActionManager = session.web.OldWidget.extend({ }, dialog_stop: function () { if (this.dialog) { - this.dialog_viewmanager.stop(); + this.dialog_viewmanager.destroy(); this.dialog_viewmanager = null; - this.dialog.stop(); + this.dialog.destroy(); this.dialog = null; } }, content_stop: function () { if (this.inner_viewmanager) { - this.inner_viewmanager.stop(); + this.inner_viewmanager.destroy(); this.inner_viewmanager = null; } if (this.client_widget) { - this.client_widget.stop(); + this.client_widget.destroy(); this.client_widget = null; } }, do_push_state: function(state) { - if (this.widget_parent && this.widget_parent.do_push_state) { + if (this.getParent() && this.getParent().do_push_state) { if (this.inner_action) { state['model'] = this.inner_action.res_model; if (this.inner_action.id) { state['action_id'] = this.inner_action.id; } } - this.widget_parent.do_push_state(state); + this.getParent().do_push_state(state); } }, do_load_state: function(state, warm) { @@ -65,6 +65,9 @@ session.web.ActionManager = session.web.OldWidget.extend({ if (run_action) { this.null_action(); action_loaded = this.do_action(state.action_id); + session.webclient.menu.has_been_loaded.then(function() { + session.webclient.menu.open_action(state.action_id); + }); } } else if (state.model && state.id) { // TODO handle context & domain ? @@ -142,7 +145,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ if(on_close) this.dialog.on_close.add(on_close); } else { - this.dialog_viewmanager.stop(); + this.dialog_viewmanager.destroy(); } this.dialog.dialog_title = action.name; this.dialog_viewmanager = new session.web.ViewManagerAction(this, action); @@ -150,7 +153,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ this.dialog.open(); } else { if(action.menu_id) { - return this.widget_parent.do_action(action, function () { + return this.getParent().do_action(action, function () { session.webclient.menu.open_menu(action.menu_id); }); } @@ -180,7 +183,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ this.content_stop(); this.dialog_stop(); var ClientWidget = session.web.client_actions.get_object(action.tag); - (this.client_widget = new ClientWidget(this, action.params)).appendTo(this); + (this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element); }, ir_actions_report_xml: function(action, on_closed) { var self = this; @@ -209,7 +212,7 @@ session.web.ActionManager = session.web.OldWidget.extend({ window.open(action.url, action.target === 'self' ? '_self' : '_blank'); }, ir_ui_menu: function (action) { - this.widget_parent.do_action(action); + this.getParent().do_action(action); } }); @@ -385,7 +388,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V setup_search_view: function(view_id, search_defaults) { var self = this; if (this.searchview) { - this.searchview.stop(); + this.searchview.destroy(); } this.searchview = new session.web.SearchView( this, this.dataset, @@ -408,6 +411,9 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V var groupby = results.group_by.length ? results.group_by : action_context.group_by; + if (_.isString(groupby)) { + groupby = [groupby]; + } controller.do_search(results.domain, results.context, groupby || []); }); }, @@ -680,9 +686,9 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner }); }, do_push_state: function(state) { - if (this.widget_parent && this.widget_parent.do_push_state) { + if (this.getParent() && this.getParent().do_push_state) { state["view_type"] = this.active_view; - this.widget_parent.do_push_state(state); + this.getParent().do_push_state(state); } }, do_load_state: function(state, warm) { @@ -702,7 +708,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner }, shortcut_check : function(view) { var self = this; - var grandparent = this.widget_parent && this.widget_parent.widget_parent; + var grandparent = this.getParent() && this.getParent().getParent(); // display shortcuts if on the first view for the action var $shortcut_toggle = this.$element.find('.oe-shortcut-toggle'); if (!this.action.name || @@ -796,8 +802,8 @@ session.web.Sidebar = session.web.OldWidget.extend({ }, add_default_sections: function() { var self = this, - view = this.widget_parent, - view_manager = view.widget_parent, + view = this.getParent(), + view_manager = view.getParent(), action = view_manager.action; if (this.session.uid === 1) { this.add_section(_t('Customize'), 'customize'); @@ -912,11 +918,11 @@ session.web.Sidebar = session.web.OldWidget.extend({ }, on_item_action_clicked: function(item) { var self = this; - self.widget_parent.sidebar_context().then(function (context) { - var ids = self.widget_parent.get_selected_ids(); + self.getParent().sidebar_context().then(function (context) { + var ids = self.getParent().get_selected_ids(); if (ids.length == 0) { //TODO: make prettier warning? - $("<div />").text(_t("You must choose at least one record.")).dialog({ + openerp.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true }); @@ -925,7 +931,7 @@ session.web.Sidebar = session.web.OldWidget.extend({ var additional_context = _.extend({ active_id: ids[0], active_ids: ids, - active_model: self.widget_parent.dataset.model + active_model: self.getParent().dataset.model }, context); self.rpc("/web/action/load", { action_id: item.action.id, @@ -937,7 +943,7 @@ session.web.Sidebar = session.web.OldWidget.extend({ result.result.flags.new_window = true; self.do_action(result.result, function () { // reload view - self.widget_parent.reload(); + self.getParent().reload(); }); }); }); @@ -1111,8 +1117,8 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ var self = this; var result_handler = function () { if (on_closed) { on_closed.apply(null, arguments); } - if (self.widget_parent && self.widget_parent.on_action_executed) { - return self.widget_parent.on_action_executed.apply(null, arguments); + if (self.getParent() && self.getParent().on_action_executed) { + return self.getParent().on_action_executed.apply(null, arguments); } }; var context = new session.web.CompoundContext(dataset.get_context(), action_data.context || {}); @@ -1184,8 +1190,8 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{ this.$element.hide(); }, do_push_state: function(state) { - if (this.widget_parent && this.widget_parent.do_push_state) { - this.widget_parent.do_push_state(state); + if (this.getParent() && this.getParent().do_push_state) { + this.getParent().do_push_state(state); } }, do_load_state: function(state, warm) { |
