diff options
| author | Xavier Morel <xmo@openerp.com> | 2012-11-29 10:10:38 +0100 |
|---|---|---|
| committer | Xavier Morel <xmo@openerp.com> | 2012-11-29 10:10:38 +0100 |
| commit | e00cb36f41ced6e3ccadd9d0bac0e1ab88e21fc3 (patch) | |
| tree | a2ce5fe45cdf3eee86b7e2ad561dd379b5b2dee9 /addons/web/static/src | |
| parent | 8431c69016ef24980ff5f01a0f6e388399514d8c (diff) | |
| parent | 4b464deeeebe61094a289cbd56cf3d780c295847 (diff) | |
[MERGE] from trunk
bzr revid: xmo@openerp.com-20121129091038-0fexjx8o12drjbga
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/base.css | 130 | ||||
| -rw-r--r-- | addons/web/static/src/css/base.sass | 104 | ||||
| -rw-r--r-- | addons/web/static/src/img/view_empty_arrow.png | bin | 4157 -> 2531 bytes | |||
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 217 | ||||
| -rw-r--r-- | addons/web/static/src/js/corelib.js | 146 | ||||
| -rw-r--r-- | addons/web/static/src/js/coresetup.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/data_export.js | 1 | ||||
| -rw-r--r-- | addons/web/static/src/js/search.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 41 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/views.js | 45 | ||||
| -rw-r--r-- | addons/web/static/src/xml/base.xml | 65 |
13 files changed, 474 insertions, 301 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index ab7b74f85fe..ee81492b8ad 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -652,7 +652,7 @@ cursor: pointer; } .openerp .oe_dropdown_toggle { - color: rgba(0, 0, 0, 0.3); + color: #4C4C4C; font-weight: normal; } .openerp .oe_dropdown_hover:hover .oe_dropdown_menu, .openerp .oe_dropdown_menu.oe_opened { @@ -807,8 +807,6 @@ padding: 0 0 4px 0; } .openerp .oe_login button { - float: right; - display: inline-block; cursor: pointer; padding: 6px 16px; border: 1px solid #222222; @@ -1387,60 +1385,6 @@ .openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_buttons { white-space: nowrap; } -.openerp .oe_view_manager .oe_view_manager_pager { - line-height: 26px; -} -.openerp .oe_view_manager .oe_view_manager_pager .oe_list_pager_single_page .oe_pager_group { - display: none; -} -.openerp .oe_view_manager .oe_pager_value { - float: left; - margin-right: 8px; -} -.openerp .oe_view_manager ul.oe_pager_group { - padding: 0; - margin: 0; -} -.openerp .oe_view_manager .oe_pager_group { - float: left; - height: 24px; - line-height: 24px; - display: inline-block; - border: 1px solid #ababab; - cursor: pointer; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; -} -.openerp .oe_view_manager .oe_pager_group li { - height: 24px; - line-height: 24px; - padding: 0; - margin: 0; - list-style-type: none; - float: left; - border-right: 1px solid #ababab; -} -.openerp .oe_view_manager .oe_pager_group li:last-child { - border: none; -} -.openerp .oe_view_manager .oe_pager_group a { - color: #4c4c4c; - padding: 0 8px; -} -.openerp .oe_view_manager .oe_pager_group a:hover { - text-decoration: none; -} -.openerp .oe_view_manager .oe_pager_group .active { - background: #999999; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; -} -.openerp .oe_view_manager .oe_pager_group .active a { - color: white; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); -} .openerp .oe_view_manager .oe_view_manager_switch { padding: 0; margin: 0 0 0 8px; @@ -1489,6 +1433,60 @@ .openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_diagram:after { content: "f"; } +.openerp .oe_list_pager { + line-height: 26px; +} +.openerp .oe_pager_value { + float: left; + margin-right: 8px; +} +.openerp ul.oe_pager_group { + padding: 0; + margin: 0; +} +.openerp .oe_pager_group { + float: left; + height: 24px; + line-height: 24px; + display: inline-block; + border: 1px solid #ababab; + cursor: pointer; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} +.openerp .oe_pager_group li { + height: 24px; + line-height: 24px; + padding: 0; + margin: 0; + list-style-type: none; + float: left; + border-right: 1px solid #ababab; +} +.openerp .oe_pager_group li:last-child { + border: none; +} +.openerp .oe_pager_group a { + color: #4c4c4c; + padding: 0 8px; +} +.openerp .oe_pager_group a:hover { + text-decoration: none; +} +.openerp .oe_pager_group .active { + background: #999999; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; +} +.openerp .oe_pager_group .active a { + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); +} +.openerp .oe_list_pager.oe_list_pager_single_page .oe_pager_group { + display: none; +} .openerp .oe_view_manager_current { height: 100%; } @@ -1520,6 +1518,9 @@ .openerp .oe_popup_form > .oe_formview > .oe_form_pager { display: none !important; } +.openerp .oe_popup_list_pager { + float: right; +} .openerp .oe_searchview { cursor: text; position: relative; @@ -1842,6 +1843,7 @@ padding: 0; } .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced li { + cursor: pointer; position: relative; list-style: none; margin: 0; @@ -1908,14 +1910,22 @@ max-width: 700px; } .openerp .oe_view_nocontent .oe_view_nocontent_create { - background: transparent url(/web/static/src/img/view_empty_arrow.png) no-repeat 7px 0; margin-top: 0; padding-top: 35px; - min-height: 28px; color: #4c4c4c; } +.openerp .oe_view_nocontent .oe_view_nocontent_create:before { + content: ""; + display: inline-block; + position: absolute; + width: 70px; + height: 80px; + margin-left: -70px; + margin-top: -50px; + background: transparent url(/web/static/src/img/view_empty_arrow.png) no-repeat 0px 0px; +} .openerp .oe_view_nocontent > p { - padding-left: 95px; + padding-left: 78px; } .openerp .oe_view_nocontent .oe_empty_custom_dashboard { background: transparent url(/web/static/src/img/graph_background.png) no-repeat 0 0; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c5fcb8008a5..3923fcaf2ff 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -564,7 +564,7 @@ $sheet-padding: 16px position: relative cursor: pointer .oe_dropdown_toggle - color: rgba(0,0,0,0.3) + color: #4C4C4C font-weight: normal .oe_dropdown_hover:hover .oe_dropdown_menu, .oe_dropdown_menu.oe_opened display: block @@ -686,8 +686,6 @@ $sheet-padding: 16px list-style-type: none padding: 0 0 4px 0 button - float: right - display: inline-block cursor: pointer padding: 6px 16px border: 1px solid #222 @@ -1121,47 +1119,6 @@ $sheet-padding: 16px .oe_view_manager_buttons white-space: nowrap // }}} - // ViewManager.pager {{{ - .oe_view_manager_pager - line-height: 26px - .oe_list_pager_single_page .oe_pager_group - display: none - .oe_pager_value - float: left - margin-right: 8px - ul.oe_pager_group - padding: 0 - margin: 0 - .oe_pager_group - float: left - height: 24px - line-height: 24px - display: inline-block - border: 1px solid #ababab - cursor: pointer - @include radius(5px) - li - height: 24px - line-height: 24px - padding: 0 - margin: 0 - list-style-type: none - float: left - border-right: 1px solid #ababab - &:last-child - border: none - a - color: #4c4c4c - padding: 0 8px - &:hover - text-decoration: none - .active - background: #999 - @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) - a - color: #fff - text-shadow: 0 1px 2px rgba(0,0,0,0.4) - // }}} // ViewManager.switches {{{ .oe_view_manager_switch padding: 0 @@ -1202,6 +1159,47 @@ $sheet-padding: 16px .oe_vm_switch_diagram:after content: "f" // }}} + // List pager {{{ + .oe_list_pager + line-height: 26px + .oe_pager_value + float: left + margin-right: 8px + ul.oe_pager_group + padding: 0 + margin: 0 + .oe_pager_group + float: left + height: 24px + line-height: 24px + display: inline-block + border: 1px solid #ababab + cursor: pointer + @include radius(5px) + li + height: 24px + line-height: 24px + padding: 0 + margin: 0 + list-style-type: none + float: left + border-right: 1px solid #ababab + &:last-child + border: none + a + color: #4c4c4c + padding: 0 8px + &:hover + text-decoration: none + .active + background: #999 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) + a + color: #fff + text-shadow: 0 1px 2px rgba(0,0,0,0.4) + .oe_list_pager.oe_list_pager_single_page .oe_pager_group + display: none + // }}} // ViewManager application {{{ .oe_view_manager_current height: 100% @@ -1222,9 +1220,11 @@ $sheet-padding: 16px > .oe_view_manager_header display: none // }}} - // Viewmanager popup {{{ + // FormPopup {{{ .oe_popup_form > .oe_formview > .oe_form_pager display: none !important + .oe_popup_list_pager + float: right // }}} // SearchView {{{ .oe_searchview @@ -1471,6 +1471,7 @@ $sheet-padding: 16px list-style: none padding: 0 li + cursor: pointer position: relative list-style: none margin: 0 @@ -1533,13 +1534,20 @@ $sheet-padding: 16px font-size: 125% max-width: 700px .oe_view_nocontent_create - background: transparent url(/web/static/src/img/view_empty_arrow.png) no-repeat 7px 0 margin-top: 0 padding-top: 35px - min-height: 28px color: #4c4c4c + &:before + content: "" + display: inline-block + position: absolute + width: 70px + height: 80px + margin-left: -70px + margin-top: -50px + background: transparent url(/web/static/src/img/view_empty_arrow.png) no-repeat 0px 0px > p - padding-left: 95px + padding-left: 78px .oe_empty_custom_dashboard background: transparent url(/web/static/src/img/graph_background.png) no-repeat 0 0 margin-top: -15px diff --git a/addons/web/static/src/img/view_empty_arrow.png b/addons/web/static/src/img/view_empty_arrow.png Binary files differindex ef8f3c820d7..99561e3abf3 100644 --- a/addons/web/static/src/img/view_empty_arrow.png +++ b/addons/web/static/src/img/view_empty_arrow.png diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index 77d23d8fa53..66c1f82013c 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -24,7 +24,7 @@ instance.web.Notification = instance.web.Widget.extend({ if (sticky) { opts.expires = false; } - this.$el.notify('create', { + return this.$el.notify('create', { title: title, text: text }, opts); @@ -35,7 +35,7 @@ instance.web.Notification = instance.web.Widget.extend({ if (sticky) { opts.expires = false; } - this.$el.notify('create', 'oe_notification_alert', { + return this.$el.notify('create', 'oe_notification_alert', { title: title, text: text }, opts); @@ -52,8 +52,29 @@ instance.web.dialog = function(element) { return result; }; +/** + A useful class to handle dialogs. + + Attributes: + - $buttons: A jQuery element targeting a dom part where buttons can be added. It always exists + during the lifecycle of the dialog. +*/ instance.web.Dialog = instance.web.Widget.extend({ dialog_title: "", + /** + Constructor. + + @param {Widget} parent + @param {dictionary} options A dictionary that will be forwarded to jQueryUI Dialog. Additionaly, that + dictionary can contain the following keys: + - buttons: Deprecated. The buttons key is not propagated to jQueryUI Dialog. It must be a dictionary (key = button + label, value = click handler) or a list of dictionaries (each element in the dictionary is send to the + corresponding method of a jQuery element targeting the <button> tag). It is deprecated because all dialogs + in OpenERP must be personalized in some way (button in red, link instead of button, ...) and this + feature does not allow that kind of personalization. + - destroy_on_close: Default true. If true and the dialog is closed, it is automatically destroyed. + @param {jQuery object} content Some content to replace this.$el . + */ init: function (parent, options, content) { var self = this; this._super(parent); @@ -66,50 +87,49 @@ instance.web.Dialog = instance.web.Widget.extend({ max_width: '95%', height: 'auto', min_height: 0, - max_height: this.get_height('100%') - 200, + max_height: $(window.top).height() - 200, autoOpen: false, position: [false, 40], - buttons: {}, + buttons: null, beforeClose: function () { self.trigger("closing"); }, - resizeStop: this.on_resized + resizeStop: function() { + self.trigger("resized"); + }, }; - for (var f in this) { - if (f.substr(0, 10) == 'on_button_') { - this.dialog_options.buttons[f.substr(10)] = this[f]; - } - } if (options) { _.extend(this.dialog_options, options); } this.on("closing", this, this._closing); + this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix"><span class="oe_dialog_custom_buttons"/></div>'); }, - get_options: function(options) { - var self = this, - o = _.extend({}, this.dialog_options, options || {}); - _.each(['width', 'height'], function(unit) { - o[unit] = self['get_' + unit](o[unit]); - o['min_' + unit] = self['get_' + unit](o['min_' + unit] || 0); - o['max_' + unit] = self['get_' + unit](o['max_' + unit] || 0); - if (o[unit] !== 'auto' && o['min_' + unit] && o[unit] < o['min_' + unit]) o[unit] = o['min_' + unit]; - if (o[unit] !== 'auto' && o['max_' + unit] && o[unit] > o['max_' + unit]) o[unit] = o['max_' + unit]; + _get_options: function() { + var self = this; + var o = _.extend({}, this.dialog_options); + var sizes = { + width: $(window.top).width(), + height: $(window.top).height(), + }; + _.each(sizes, function(available_size, unit) { + o[unit] = self._get_size(o[unit], available_size); + o['min_' + unit] = self._get_size(o['min_' + unit] || 0, available_size); + o['max_' + unit] = self._get_size(o['max_' + unit] || 0, available_size); + if (o[unit] !== 'auto' && o['min_' + unit] && o[unit] < o['min_' + unit]) { + o[unit] = o['min_' + unit]; + } + if (o[unit] !== 'auto' && o['max_' + unit] && o[unit] > o['max_' + unit]) { + o[unit] = o['max_' + unit]; + } }); - if (!o.title && this.dialog_title) { - o.title = this.dialog_title; - } + o.title = o.title || this.dialog_title; return o; }, - get_width: function(val) { - return this.get_size(val.toString(), $(window.top).width()); - }, - get_height: function(val) { - return this.get_size(val.toString(), $(window.top).height()); - }, - get_size: function(val, available_size) { + _get_size: function(val, available_size) { + val = val.toString(); if (val === 'auto') { return val; - } else if (val.slice(-1) == "%") { + } else if (val.slice(-1) === "%") { return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10)); } else { return parseInt(val, 10); @@ -122,41 +142,58 @@ instance.web.Dialog = instance.web.Widget.extend({ this._super(); } }, - open: function(options) { - if (! this.dialog_inited) + /** + Opens the popup. Inits the dialog if it is not already inited. + + @return this + */ + open: function() { + if (!this.dialog_inited) { this.init_dialog(); - var o = this.get_options(options); - this.add_buttons(o.buttons); - delete(o.buttons); - this.$buttons.appendTo($("body")); - instance.web.dialog(this.$el, o).dialog('open'); - this.$el.dialog("widget").find(".ui-dialog-buttonpane").remove(); - this.$buttons.appendTo(this.$el.dialog("widget")); - if (o.height === 'auto' && o.max_height) { - this.$el.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); } + this.$el.dialog('open'); + this.$el.dialog("widget").append(this.$buttons); return this; }, - add_buttons: function(buttons) { + _add_buttons: function(buttons) { var self = this; - _.each(buttons, function(fn, but) { - var $but = $(QWeb.render('WidgetButton', { widget : { string: but, node: { attrs: {} }}})); - self.$buttons.append($but); + var $customButons = this.$buttons.find('.oe_dialog_custom_buttons').empty(); + _.each(buttons, function(fn, text) { + // buttons can be object or array + if (!_.isFunction(fn)) { + text = fn.text; + fn = fn.click; + } + var $but = $(QWeb.render('WidgetButton', { widget : { string: text, node: { attrs: {} }}})); + $customButons.append($but); $but.on('click', function(ev) { fn.call(self.$el, ev); }); }); }, - init_dialog: function(options) { + /** + Initializes the popup. + + @return The result returned by start(). + */ + init_dialog: function() { + var options = this._get_options(); + if (options.buttons) { + this._add_buttons(options.buttons); + delete(options.buttons); + } this.renderElement(); - var o = this.get_options(options); - instance.web.dialog(this.$el, o); - this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" />'); - this.$el.dialog("widget").append(this.$buttons); + instance.web.dialog(this.$el, options); + if (options.height === 'auto' && options.max_height) { + this.$el.css({ 'max-height': options.max_height, 'overflow-y': 'auto' }); + } this.dialog_inited = true; var res = this.start(); return res; }, + /** + Closes the popup, if destroy_on_close was passed to the constructor, it is also destroyed. + */ close: function() { if (this.dialog_inited && this.$el.is(":data(dialog)")) { this.$el.dialog('close'); @@ -171,9 +208,11 @@ instance.web.Dialog = instance.web.Widget.extend({ this.__tmp_dialog_closing = undefined; } }, - on_resized: function() { - }, + /** + Destroys the popup, also closes it. + */ destroy: function () { + this.$buttons.remove(); _.each(this.getChildren(), function(el) { el.destroy(); }); @@ -182,7 +221,7 @@ instance.web.Dialog = instance.web.Widget.extend({ this.close(); this.__tmp_dialog_destroying = undefined; } - if (this.dialog_inited && !this.isDestroyed()) { + if (this.dialog_inited && !this.isDestroyed() && this.$el.is(":data(dialog)")) { this.$el.dialog('destroy'); } this._super(); @@ -726,20 +765,24 @@ instance.web.ChangePassword = instance.web.Widget.extend({ template: "ChangePassword", start: function() { var self = this; - self.$el.validate({ - submitHandler: function (form) { - self.rpc("/web/session/change_password",{ - 'fields': $(form).serializeArray() - }).done(function(result) { - if (result.error) { - self.display_error(result); - return; - } else { - instance.webclient.on_logout(); - } - }); - } - }); + this.getParent().dialog_title = "Change Password"; + var $button = self.$el.find('.oe_form_button'); + $button.appendTo(this.getParent().$buttons); + $button.eq(2).click(function(){ + self.getParent().close(); + }) + $button.eq(0).click(function(){ + self.rpc("/web/session/change_password",{ + 'fields': $("form[name=change_password_form]").serializeArray() + }).done(function(result) { + if (result.error) { + self.display_error(result); + return; + } else { + instance.webclient.on_logout(); + } + }); + }) }, display_error: function (error) { return instance.web.dialog($('<div>'), { @@ -946,6 +989,14 @@ instance.web.UserMenu = instance.web.Widget.extend({ if(res.company_id[0] > 1) topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]); self.$el.find('.oe_topbar_name').text(topbar_name); + if(!instance.session.debug) { + self.rpc("/web/database/get_list", {}).done( function(result) { + if (result.length > 1) { + topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.session.db); + } + self.$el.find('.oe_topbar_name').text(topbar_name); + }); + } var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid}); $avatar.attr('src', avatar_src); }); @@ -1055,9 +1106,7 @@ instance.web.WebClient = instance.web.Client.extend({ start: function() { var self = this; return $.when(this._super()).then(function() { - self.$el.on('click', '.oe_logo', function() { - self.action_manager.do_action('home'); - }); + self.$(".oe_logo").attr("href", $.param.fragment("" + window.location, "", 2).slice(0, -1)); if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) { $("body").addClass("kitten-mode-activated"); if ($.blockUI) { @@ -1114,6 +1163,30 @@ instance.web.WebClient = instance.web.Client.extend({ self.user_menu.do_update(); self.bind_hashchange(); self.set_title(); + self.check_timezone(); + }, + check_timezone: function() { + var self = this; + var user_offset = instance.session.user_context.tz_offset; + var offset = -(new Date().getTimezoneOffset()); + // _.str.sprintf()'s zero front padding is buggy with signed decimals, so doing it manually + var browser_offset = (offset < 0) ? "-" : "+"; + browser_offset += _.str.sprintf("%02d", Math.abs(offset / 60)); + browser_offset += _.str.sprintf("%02d", Math.abs(offset % 60)); + if (browser_offset !== user_offset) { + var notification = this.do_warn(_t("Timezone"), QWeb.render('WebClient.timezone_notification', { + user_timezone: instance.session.user_context.tz || 'UTC', + user_offset: user_offset, + browser_offset: browser_offset, + }), true); + notification.element.find('.oe_webclient_timezone_notification').on('click', function() { + notification.close(); + }).find('a').on('click', function() { + notification.close(); + self.user_menu.on_menu_settings(); + return false; + }); + } }, destroy_content: function() { _.each(_.clone(this.getChildren()), function(el) { @@ -1130,11 +1203,11 @@ instance.web.WebClient = instance.web.Client.extend({ }, do_notify: function() { var n = this.notification; - n.notify.apply(n, arguments); + return n.notify.apply(n, arguments); }, do_warn: function() { var n = this.notification; - n.warn.apply(n, arguments); + return n.warn.apply(n, arguments); }, on_logout: function() { var self = this; diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index d05a42061de..55984162a55 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -430,6 +430,85 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, { // Classes /** + A class containing common utility methods useful when working with OpenERP as well as the PropertiesMixin. +*/ +instance.web.Controller = instance.web.Class.extend(instance.web.PropertiesMixin, { + /** + * Constructs the object and sets its parent if a parent is given. + * + * @param {instance.web.Controller} parent Binds the current instance to the given Controller instance. + * When that controller is destroyed by calling destroy(), the current instance will be + * destroyed too. Can be null. + */ + init: function(parent) { + instance.web.PropertiesMixin.init.call(this); + this.setParent(parent); + }, + /** + * Proxies a method of the object, in order to keep the right ``this`` on + * method invocations. + * + * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and + * even more so to ``jQuery.proxy`` with a fundamental difference: its + * resolution of the method being called is lazy, meaning it will use the + * method as it is when the proxy is called, not when the proxy is created. + * + * Other methods will fix the bound method to what it is when creating the + * binding/proxy, which is fine in most javascript code but problematic in + * OpenERP Web where developers may want to replace existing callbacks with + * theirs. + * + * The semantics of this precisely replace closing over the method call. + * + * @param {String|Function} method function or name of the method to invoke + * @returns {Function} proxied method + */ + proxy: function (method) { + var self = this; + return function () { + var fn = (typeof method === 'string') ? self[method] : method; + return fn.apply(self, arguments); + } + }, + /** + * Informs the action manager to do an action. This supposes that + * the action manager can be found amongst the ancestors of the current widget. + * If that's not the case this method will simply return `false`. + */ + do_action: function() { + var parent = this.getParent(); + if (parent) { + return parent.do_action.apply(parent, arguments); + } + return false; + }, + do_notify: function() { + if (this.getParent()) { + return this.getParent().do_notify.apply(this,arguments); + } + return false; + }, + do_warn: function() { + if (this.getParent()) { + return this.getParent().do_warn.apply(this,arguments); + } + return false; + }, + rpc: function(url, data, options) { + var def = $.Deferred(); + var self = this; + instance.session.rpc(url, data, options).done(function() { + if (!self.isDestroyed()) + def.resolve.apply(def, arguments); + }).fail(function() { + if (!self.isDestroyed()) + def.reject.apply(def, arguments); + }); + return def.promise(); + } +}); + +/** * Base class for all visual components. Provides a lot of functionalities helpful * for the management of a part of the DOM. * @@ -477,7 +556,7 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, { * * That will kill the widget in a clean way and erase its content from the dom. */ -instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, { +instance.web.Widget = instance.web.Controller.extend({ // Backbone-ish API tagName: 'div', id: null, @@ -501,8 +580,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, { * destroyed too. Can be null. */ init: function(parent) { - instance.web.PropertiesMixin.init.call(this); - this.setParent(parent); + this._super(parent); // Bind on_/do_* methods to this // We might remove this automatic binding in the future for (var name in this) { @@ -605,32 +683,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, { return $.when(); }, /** - * Proxies a method of the object, in order to keep the right ``this`` on - * method invocations. - * - * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and - * even more so to ``jQuery.proxy`` with a fundamental difference: its - * resolution of the method being called is lazy, meaning it will use the - * method as it is when the proxy is called, not when the proxy is created. - * - * Other methods will fix the bound method to what it is when creating the - * binding/proxy, which is fine in most javascript code but problematic in - * OpenERP Web where developers may want to replace existing callbacks with - * theirs. - * - * The semantics of this precisely replace closing over the method call. - * - * @param {String|Function} method function or name of the method to invoke - * @returns {Function} proxied method - */ - proxy: function (method) { - var self = this; - return function () { - var fn = (typeof method === 'string') ? self[method] : method; - return fn.apply(self, arguments); - } - }, - /** * 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`. @@ -749,42 +801,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, { */ $: function(selector) { return this.$el.find(selector); - }, - /** - * Informs the action manager to do an action. This supposes that - * the action manager can be found amongst the ancestors of the current widget. - * If that's not the case this method will simply return `false`. - */ - do_action: function() { - var parent = this.getParent(); - if (parent) { - return parent.do_action.apply(parent, arguments); - } - return false; - }, - do_notify: function() { - if (this.getParent()) { - return this.getParent().do_notify.apply(this,arguments); - } - return false; - }, - do_warn: function() { - if (this.getParent()) { - return this.getParent().do_warn.apply(this,arguments); - } - return false; - }, - rpc: function(url, data, options) { - var def = $.Deferred(); - var self = this; - instance.session.rpc(url, data, options).done(function() { - if (!self.isDestroyed()) - def.resolve.apply(def, arguments); - }).fail(function() { - if (!self.isDestroyed()) - def.reject.apply(def, arguments); - }); - return def.promise(); } }); diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 7ddefa301f2..fa1348a5f8c 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -119,6 +119,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess }, session_logout: function() { this.set_cookie('session_id', ''); + var url = "#"; + $.bbq.pushState(url); + window.location.search = ""; return this.rpc("/web/session/destroy", {}); }, get_cookie: function (name) { @@ -543,12 +546,12 @@ instance.web._lt = function (s) { return {toString: function () { return instance.web._t(s); }} }; instance.web.qweb = new QWeb2.Engine(); -instance.web.qweb.default_dict['__debug__'] = instance.session.debug; // Which one ? instance.web.qweb.debug = instance.session.debug; instance.web.qweb.default_dict = { '_' : _, '_t' : instance.web._t, 'JSON': JSON, + '__debug__': instance.session.debug, }; instance.web.qweb.preprocess_node = function() { // Note that 'this' is the Qweb Node diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js index 477d89c99b3..e38d21d1cd2 100644 --- a/addons/web/static/src/js/data_export.js +++ b/addons/web/static/src/js/data_export.js @@ -22,7 +22,6 @@ instance.web.DataExport = instance.web.Dialog.extend({ start: function() { var self = this; this._super.apply(this, arguments); - this.open(); self.$el.removeClass('ui-dialog-content ui-widget-content'); self.$el.find('#add_field').click(function() { if ($('#field-tree-structure tr.ui-selected')) { diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index b7514e81c88..29fa71b5a97 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -1890,6 +1890,9 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext get_value: function() { return this.datewidget.get_value(); }, + toString: function () { + return instance.web.format_value(this.get_value(), { type:"datetime" }); + }, start: function() { var ready = this._super(); this.datewidget = new (this.widget())(this); @@ -1898,7 +1901,10 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext } }); instance.web.search.ExtendedSearchProposition.Date = instance.web.search.ExtendedSearchProposition.DateTime.extend({ - widget: function () { return instance.web.DateWidget; } + widget: function () { return instance.web.DateWidget; }, + toString: function () { + return instance.web.format_value(this.get_value(), { type:"date" }); + } }); instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.ExtendedSearchProposition.Field.extend({ template: 'SearchView.extended_search.proposition.integer', @@ -1915,7 +1921,8 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte }, get_value: function() { try { - return instance.web.parse_value(this.$el.val(), {'widget': 'integer'}); + var val =this.$el.val(); + return instance.web.parse_value(val == "" ? 0 : val, {'widget': 'integer'}); } catch (e) { return ""; } @@ -1939,7 +1946,8 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend }, get_value: function() { try { - return instance.web.parse_value(this.$el.val(), {'widget': 'float'}); + var val =this.$el.val(); + return instance.web.parse_value(val == "" ? 0.0 : val, {'widget': 'float'}); } catch (e) { return ""; } diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 5d94cdb5d89..49f4e30815f 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -195,7 +195,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM this.sidebar.add_items('other', _.compact([ self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete }, self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate }, - { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } } ])); } @@ -1003,8 +1002,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM } return true; }, - sidebar_context: function () { - return this.save().then(_.bind(function() {return this.get_fields_values();}, this)); + sidebar_eval_context: function () { + return $.when(this.build_eval_context()); }, open_defaults_dialog: function () { var self = this; @@ -1028,7 +1027,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM || field.get("readonly") || field.field.type === 'one2many' || field.field.type === 'many2many' - || field.field.type === 'binary') { + || field.field.type === 'binary' + || field.password) { return false; } @@ -1676,11 +1676,11 @@ instance.web.form.compute_domain = function(expr, fields) { switch (op.toLowerCase()) { case '=': case '==': - stack.push(field_value == val); + stack.push(_.isEqual(field_value, val)); break; case '!=': case '<>': - stack.push(field_value != val); + stack.push(!_.isEqual(field_value, val)); break; case '<': stack.push(field_value < val); @@ -2241,7 +2241,9 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we var self = this; var $input = this.$el.find('input'); $input.change(function() { - self.internal_set_value(self.parse_value($input.val())); + if(self.is_syntax_valid()){ + self.internal_set_value(self.parse_value($input.val())); + } }); this.setupFocus($input); }, @@ -4020,6 +4022,9 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in } this._super(value_); }, + is_false: function() { + return _(this.get("value")).isEmpty(); + }, get_value: function() { var tmp = [commands.replace_with(this.get("value"))]; return tmp; @@ -4230,16 +4235,14 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( var self = this; self.load_view(); - this.is_loaded.done(function() { - self.on("change:effective_readonly", self, function() { - self.is_loaded = self.is_loaded.then(function() { - self.kanban_view.destroy(); - return $.when(self.load_view()).done(function() { - self.render_value(); - }); + self.on("change:effective_readonly", self, function() { + self.is_loaded = self.is_loaded.then(function() { + self.kanban_view.destroy(); + return $.when(self.load_view()).done(function() { + self.render_value(); }); }); - }) + }); }, set_value: function(value_) { value_ = value_ || []; @@ -4622,6 +4625,8 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend 'selectable': !self.options.disable_multiple_selection, 'import_enabled': false, '$buttons': self.$buttonpane, + 'disable_editable_mode': true, + '$pager': self.$('.oe_popup_list_pager'), }, self.options.list_view_options || {})); self.view_list.on('edit:before', self, function (e) { e.cancel = true; @@ -4781,6 +4786,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance. this._super(field_manager, node); this.binary_value = false; this.useFileAPI = !!window.FileReader; + this.max_upload_size = 25 * 1024 * 1024; // 25Mo if (!this.useFileAPI) { this.fileupload_id = _.uniqueId('oe_fileupload'); $(window).on(this.fileupload_id, function() { @@ -4806,6 +4812,11 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance. if ((this.useFileAPI && file_node.files.length) || (!this.useFileAPI && $(file_node).val() !== '')) { if (this.useFileAPI) { var file = file_node.files[0]; + if (file.size > this.max_upload_size) { + var msg = _t("The selected file exceed the maximum file size of %s."); + instance.webclient.notification.warn(_t("File upload"), _.str.sprintf(msg, instance.web.human_size(this.max_upload_size))); + return false; + } var filereader = new FileReader(); filereader.readAsDataURL(file); filereader.onloadend = function(upload) { diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index 43d7ac6423e..13c41d48599 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -21,6 +21,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi // whether the view rows can be reordered (via vertical drag & drop) 'reorderable': true, 'action_buttons': true, + //whether the editable property of the view has to be disabled + 'disable_editable_mode': false, }, view_type: 'tree', events: { diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 281e49c7fa2..7fe6a8ec42b 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -94,9 +94,10 @@ openerp.web.list_editable = function (instance) { }); }, editable: function () { - return this.fields_view.arch.attrs.editable + return !this.options.disable_editable_mode + && (this.fields_view.arch.attrs.editable || this._context_editable - || this.options.editable; + || this.options.editable); }, /** * Replace do_search to handle editability process diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 7a854150129..def265db0bb 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -330,7 +330,6 @@ instance.web.ActionManager = instance.web.Widget.extend({ dialogClass: executor.klass, }); this.dialog.on("closing", null, options.on_close); - this.dialog.init_dialog(); this.dialog.dialog_title = executor.action.name; if (widget instanceof instance.web.ViewManager) { _.extend(widget.flags, { @@ -829,6 +828,9 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ case 'toggle_layout_outline': current_view.rendering_engine.toggle_layout_debugging(); break; + case 'set_defaults': + current_view.open_defaults_dialog(); + break; case 'translate': this.do_action({ name: "Technical Translation", @@ -999,11 +1001,7 @@ instance.web.Sidebar = instance.web.Widget.extend({ this.fileupload_id = _.uniqueId('oe_fileupload'); $(window).on(this.fileupload_id, function() { var args = [].slice.call(arguments).slice(1); - if (args[0] && args[0].error) { - alert(args[0].error); - } else { - self.do_attachement_update(self.dataset, self.model_id); - } + self.do_attachement_update(self.dataset, self.model_id,args); instance.web.unblockUI(); }); }, @@ -1084,24 +1082,27 @@ instance.web.Sidebar = instance.web.Widget.extend({ }, on_item_action_clicked: function(item) { var self = this; - self.getParent().sidebar_context().done(function (context) { + self.getParent().sidebar_eval_context().done(function (sidebar_eval_context) { var ids = self.getParent().get_selected_ids(); if (ids.length == 0) { instance.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true }); return false; } + var active_ids_context = { + active_id: ids[0], + active_ids: ids, + active_model: self.getParent().dataset.model + }; var c = instance.web.pyeval.eval('context', - new instance.web.CompoundContext({ - active_id: ids[0], - active_ids: ids, - active_model: self.getParent().dataset.model - }, context)); + new instance.web.CompoundContext( + sidebar_eval_context, active_ids_context)); self.rpc("/web/action/load", { action_id: item.action.id, context: c }).done(function(result) { result.context = new instance.web.CompoundContext( - c, result.context); + result.context || {}, active_ids_context) + .set_eval_context(c); result.flags = result.flags || {}; result.flags.new_window = true; self.do_action(result, { @@ -1113,9 +1114,21 @@ instance.web.Sidebar = instance.web.Widget.extend({ }); }); }, - do_attachement_update: function(dataset, model_id) { + do_attachement_update: function(dataset, model_id,args) { + var self = this; this.dataset = dataset; this.model_id = model_id; + if (args && args[0]["erorr"]) { + instance.web.dialog($('<div>'),{ + modal: true, + title: "OpenERP " + _.str.capitalize(args[0]["title"]), + buttons: [{ + text: _t("Ok"), + click: function(){ + $(this).dialog("close"); + }}] + }).html(args[0]["erorr"]); + } if (!model_id) { this.on_attachments_loaded([]); } else { @@ -1349,8 +1362,8 @@ instance.web.View = instance.web.Widget.extend({ on_sidebar_export: function() { new instance.web.DataExport(this, this.dataset).open(); }, - sidebar_context: function () { - return $.when(); + sidebar_eval_context: function () { + return $.when({}); }, /** * Asks the view to reload itself, if the reloading is asynchronous should diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 723f6707cce..62d8a872f25 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -71,9 +71,9 @@ </div> <ul> <li>Username</li> - <li><input type="text" name="login" t-att-value="widget.selected_login || ''" autofocus="autofocus"/></li> + <li><input name="login" type="text" t-att-value="widget.selected_login || ''" autofocus="autofocus"/></li> <li>Password</li> - <li><input type="password" name="password" t-att-value="widget.selected_password || ''"/></li> + <li><input name="password" type="password" t-att-value="widget.selected_password || ''"/></li> <li><button name="submit">Log in</button></li> </ul> </form> @@ -98,7 +98,6 @@ </select> </t> - <t t-name="DatabaseManager"> <div class="oe_view_manager_view_form"> <div class="oe_form"> @@ -315,26 +314,32 @@ <t t-name="ChangePassword"> <form name="change_password_form" method="POST"> + <div class="oe_form"> <table align="center"> <tr> - <td><label for="old_pwd">Old Password:</label></td> - <td><input type="password" name="old_pwd" + <td class="oe_form_group_cell oe_form_group_cell_label"><label for="old_pwd" class="oe_form_label">Old Password:</label></td> + <td class="oe_form_group_cell"><input type="password" name="old_pwd" minlength="1" autofocus="autofocus"/></td> </tr> <tr> - <td><label for="new_password">New Password:</label></td> - <td><input type="password" name="new_password" + <td class="oe_form_group_cell oe_form_group_cell_label"><label for="new_password" class="oe_form_label">New Password:</label></td> + <td class="oe_form_group_cell"><input type="password" name="new_password" minlength="1"/></td> </tr> <tr> - <td><label for="confirm_pwd">Confirm Password:</label></td> - <td><input type="password" name="confirm_pwd" + <td class="oe_form_group_cell oe_form_group_cell_label"><label for="confirm_pwd" class="oe_form_label">Confirm New Password:</label></td> + <td class="oe_form_group_cell"><input type="password" name="confirm_pwd" minlength="1"/></td> </tr> <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + <td colspan="2" align="right"> + <button class='oe_button oe_form_button'>Change Password</button> + <span class="oe_fade oe_form_button"> or </span> + <button type="button" class="oe_button oe_form_button_cancel oe_form_button oe_link" href="javascript:void(0)"><span>Cancel</span></button> + </td> </tr> </table> + </div> </form> </t> @@ -385,7 +390,7 @@ <t t-esc="menu.name"/> <t t-if="menu.needaction_enabled and menu.needaction_counter"> <div class="oe_tag oe_tag_dark oe_menu_counter"> - <t t-esc="menu.needaction_counter"/> + <t t-if="menu.needaction_counter > 99"> 99+ </t><t t-if="menu.needaction_counter <= 99"> <t t-esc="menu.needaction_counter"/> </t> </div> </t> </a> @@ -443,6 +448,20 @@ </table> </div> </t> +<t t-name="WebClient.timezone_notification"> + <div class="oe_webclient_timezone_notification"> + <p>Your user's preference timezone does not match your browser timezone:</p> + + <dl> + <dt>User's timezone</dt> + <dd><t t-esc="user_timezone"/> (<t t-esc="user_offset"/>)</dd> + <dt>Browser's timezone</dt> + <dd><t t-esc="browser_offset"/></dd> + </dl> + + <p><a href="#">Click here to change your user's timezone.</a></p> + </div> +</t> <t t-name="EmbedClient"> <div class="openerp"> @@ -501,8 +520,11 @@ </t> <t t-name="ViewManagerDebug"> <option value="">Debug View#<t t-esc="view.fields_view.view_id"/></option> - <option t-if="view_manager.active_view === 'form'" value="perm_read" data-views="form,page">View Log (perm_read)</option> - <option t-if="view_manager.active_view === 'form'" value="toggle_layout_outline">Toggle Form Layout Outline</option> + <t t-if="view_manager.active_view === 'form'"> + <option value="perm_read">View Log (perm_read)</option> + <option value="toggle_layout_outline">Toggle Form Layout Outline</option> + <option value="set_defaults">Set Defaults</option> + </t> <option value="tests">JS Tests</option> <option value="fields">View Fields</option> <option value="fvg">Fields View Get</option> @@ -673,13 +695,15 @@ </tr> </tfoot> </table> -<div t-name="ListView.buttons" class="oe_list_buttons"> +<t t-name="ListView.buttons"> + <div class="oe_list_buttons"> <t t-if="!widget.no_leaf and widget.options.action_buttons !== false and widget.options.addable and widget.is_action_enabled('create')"> <button type="button" class="oe_button oe_list_add oe_highlight"> <t t-esc="widget.options.addable"/> </button> </t> -</div> + </div> +</t> <t t-name="ListView.pager"> <div class="oe_list_pager" t-att-colspan="widget.columns_count"> <t t-if="!widget.no_leaf and widget.options.pager !== false" t-call="ViewPager"> @@ -1025,7 +1049,7 @@ t-att-autofocus="widget.node.attrs.autofocus" t-att-id="widget.id_for_label"> <t t-foreach="widget.values" t-as="option"> - <option><t t-esc="option[1]"/></option> + <option><t t-esc="widget.node.attrs.placeholder" t-if="option[0] == false and widget.node.attrs.placeholder"/><t t-esc="option[1]" t-if="option[0] != false"/></option> </t> </select> </span> @@ -1303,6 +1327,11 @@ </tr> <tr style="width:100%"> <td style="width:100%"> + <div class="oe_popup_list_pager"></div> + </td> + </tr> + <tr style="width:100%"> + <td style="width:100%"> <div class="oe_popup_list" style="width:100%"></div> </td> </tr> @@ -1589,10 +1618,10 @@ <span/> </t> <t t-name="SearchView.extended_search.proposition.integer"> - <input type="number" class="field_integer" step="1"/> + <input type="number" class="field_integer" value = "0" step="1"/> </t> <t t-name="SearchView.extended_search.proposition.float"> - <input type="number" class="field_float" step="0.01"/> + <input type="number" class="field_float" value = "0.0" step="0.01"/> </t> <t t-name="SearchView.extended_search.proposition.selection"> <select> |
