diff options
| author | Christophe Simonis <chs@openerp.com> | 2012-11-16 19:32:12 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@openerp.com> | 2012-11-16 19:32:12 +0100 |
| commit | 4aa5e761746376e57d14115e194ea3bcb968836f (patch) | |
| tree | ac404df8e9fcae331e265c42a89ea5ad280dfd9f /addons/web/static/src | |
| parent | c637a443165902f6024d5728f01484230f29960e (diff) | |
| parent | 86842fe9c8129d3530c622875c2ad5ec8d06e8a0 (diff) | |
merge upstream
bzr revid: chs@openerp.com-20121116183212-1ke59kde4ilicw6d
Diffstat (limited to 'addons/web/static/src')
| -rw-r--r-- | addons/web/static/src/css/base.css | 169 | ||||
| -rw-r--r-- | addons/web/static/src/css/base.sass | 152 | ||||
| -rw-r--r-- | addons/web/static/src/js/boot.js | 10 | ||||
| -rw-r--r-- | addons/web/static/src/js/chrome.js | 29 | ||||
| -rw-r--r-- | addons/web/static/src/js/corelib.js | 19 | ||||
| -rw-r--r-- | addons/web/static/src/js/coresetup.js | 14 | ||||
| -rw-r--r-- | addons/web/static/src/js/data.js | 5 | ||||
| -rw-r--r-- | addons/web/static/src/js/testing.js | 393 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_form.js | 123 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list.js | 2 | ||||
| -rw-r--r-- | addons/web/static/src/js/view_list_editable.js | 9 | ||||
| -rw-r--r-- | addons/web/static/src/js/views.js | 19 |
12 files changed, 713 insertions, 231 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index a421336c43f..9effff12226 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -20,20 +20,6 @@ font-style: normal; } -@media print { - .oe_topbar, .oe_leftbar, .oe_loading { - display: none !important; - } -} -.openerp.openerp_webclient_container { - height: 100%; -} - -.text-tag .text-button { - height: auto !important; - min-height: 16px; -} - .openerp { padding: 0; margin: 0; @@ -46,6 +32,9 @@ * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image */ } +.openerp.openerp_webclient_container { + height: 100%; +} .openerp :-moz-placeholder { color: #afafb6 !important; font-style: italic !important; @@ -197,6 +186,10 @@ .openerp .oe_bounce_container { display: inline-block; } +.openerp .text-tag .text-button { + height: auto !important; + min-height: 16px; +} .openerp .ui-tabs { position: static; } @@ -1146,6 +1139,7 @@ height: 40px; width: 157px; margin: 14px 0; + border: 0; } .openerp .oe_footer { position: fixed; @@ -2371,6 +2365,77 @@ .openerp .oe_form .oe_form_field_image:hover .oe_form_field_image_controls { display: block; } +.openerp .oe_fileupload { + display: inline-block; + clear: both; + width: 100%; +} +.openerp .oe_fileupload .oe_add { + float: left; + position: relative; + width: 100%; + left: 2px; + top: 7px; +} +.openerp .oe_fileupload .oe_add button { + display: inline; + height: 24px; + font-size: 12px; + line-height: 12px; + vertical-align: middle; +} +.openerp .oe_fileupload .oe_add button.oe_attach { + width: 24px; + overflow: hidden; + width: 24px; + overflow: hidden; + background: transparent; + color: #7c7bad; + box-shadow: none; + border: none; + text-shadow: none; +} +.openerp .oe_fileupload .oe_add button.oe_attach .oe_e { + position: relative; + top: -1px; + left: -9px; +} +.openerp .oe_fileupload .oe_add input.oe_form_binary_file { + display: inline-block; + margin-left: -5px; + height: 28px; + width: 52px; + margin-top: -26px; +} +.openerp .oe_fileupload .oe_add .oe_attach_label { + color: #7c7bad; + margin-left: -3px; +} +.openerp .oe_fileupload .oe_attachments { + margin-bottom: 4px; + margin-right: 0px; + font-size: 12px; + border-radius: 2px; + border: solid 1px rgba(124, 123, 173, 0.14); +} +.openerp .oe_fileupload .oe_attachments .oe_attachment { + padding: 2px; + padding-left: 4px; + padding-right: 4px; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e { + font-size: 23px; + margin-top: -5px; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover { + text-decoration: none; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) { + background: white; +} +.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) { + background: #f4f5fa; +} .openerp .oe_form_field_many2one td:first-child { position: relative; } @@ -2903,78 +2968,6 @@ color: #333333; } -.openerp .oe_fileupload { - display: inline-block; - clear: both; - width: 100%; -} -.openerp .oe_fileupload .oe_add { - float: left; - position: relative; - width: 100%; - left: 2px; - top: 7px; -} -.openerp .oe_fileupload .oe_add button { - display: inline; - height: 24px; - font-size: 12px; - line-height: 12px; - vertical-align: middle; -} -.openerp .oe_fileupload .oe_add button.oe_attach { - width: 24px; - overflow: hidden; - width: 24px; - overflow: hidden; - background: transparent; - color: #7c7bad; - box-shadow: none; - border: none; - text-shadow: none; -} -.openerp .oe_fileupload .oe_add button.oe_attach .oe_e { - position: relative; - top: -1px; - left: -9px; -} -.openerp .oe_fileupload .oe_add input.oe_form_binary_file { - display: inline-block; - margin-left: -5px; - height: 28px; - width: 52px; - margin-top: -26px; -} -.openerp .oe_fileupload .oe_add .oe_attach_label { - color: #7c7bad; - margin-left: -3px; -} -.openerp .oe_fileupload .oe_attachments { - margin-bottom: 4px; - margin-right: 0px; - font-size: 12px; - border-radius: 2px; - border: solid 1px rgba(124, 123, 173, 0.14); -} -.openerp .oe_fileupload .oe_attachments .oe_attachment { - padding: 2px; - padding-left: 4px; - padding-right: 4px; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e { - font-size: 23px; - margin-top: -5px; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment .oe_e:hover { - text-decoration: none; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(odd) { - background: white; -} -.openerp .oe_fileupload .oe_attachments .oe_attachment:nth-child(even) { - background: #f4f5fa; -} - .kitten-mode-activated { background-image: url(http://placekitten.com/g/1365/769); background-size: cover; @@ -3033,8 +3026,8 @@ div.ui-widget-overlay { .openerp { text-shadow: none; } - .openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default { - display: none; + .openerp .oe_header_row, .openerp ul.oe_header, .openerp div.oe_mail_thread_action, .openerp .oe_mail_recthread_actions, .openerp .oe_button_box, .openerp .oe_form button, .openerp button.oe_invite, .openerp .oe_form header, .openerp .openerp .oe_notebook > li.ui-state-default, .openerp .oe_topbar, .openerp .oe_leftbar, .openerp .oe_loading { + display: none !important; } .openerp .oe_list_content button, .openerp .oe_list_content input[type=checkbox] { visibility: hidden; diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index 596426917b5..9211aec7b0c 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -140,18 +140,6 @@ $sheet-padding: 16px // }}} -@media print - .oe_topbar, .oe_leftbar, .oe_loading - display: none !important - -.openerp.openerp_webclient_container - height: 100% - -// jQueryUI css bug fixing -.text-tag .text-button - height: auto !important - min-height: 16px - .openerp // Global style {{{ padding: 0 @@ -161,6 +149,8 @@ $sheet-padding: 16px font-size: 13px background: white text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5) + &.openerp_webclient_container + height: 100% // }}} //Placeholder style{{{ \:-moz-placeholder @@ -254,6 +244,11 @@ $sheet-padding: 16px .oe_bounce_container display: inline-block + // Bug lp:1051746 + .text-tag .text-button + height: auto !important + min-height: 16px + // bug noted in jquery ui CSS doesn't seem to occur in IE9, // so remove position:relative .ui-tabs @@ -937,6 +932,7 @@ $sheet-padding: 16px height: 40px width: 157px margin: 14px 0 + border: 0 .oe_footer position: fixed bottom: 0 @@ -1895,6 +1891,64 @@ $sheet-padding: 16px @include box-sizing(border) &:hover .oe_form_field_image_controls display: block + .oe_fileupload + display: inline-block + clear: both + width: 100% + .oe_add + float: left + position: relative + width: 100% + left: +2px + top: +7px + button + display: inline + height: 24px + font-size: 12px + line-height: 12px + vertical-align: middle + button.oe_attach + width: 24px + overflow: hidden + width: 24px + overflow: hidden + background: transparent + color: #7C7BAD + box-shadow: none + border: none + text-shadow: none + .oe_e + position: relative + top: -1px + left: -9px + input.oe_form_binary_file + display: inline-block + margin-left: -5px + height: 28px + width: 52px + margin-top: -26px + .oe_attach_label + color: #7C7BAD + margin-left: -3px + .oe_attachments + margin-bottom: 4px + margin-right: 0px + font-size: 12px + border-radius: 2px + border: solid 1px rgba(124,123,173,0.14) + .oe_attachment + padding: 2px + padding-left: 4px + padding-right: 4px + .oe_e + font-size: 23px + margin-top: -5px + .oe_e:hover + text-decoration: none + .oe_attachment:nth-child(odd) + background: white + .oe_attachment:nth-child(even) + background: #F4F5FA // }}} // FormView.many2one {{{ .oe_form_field_many2one @@ -2297,67 +2351,6 @@ $sheet-padding: 16px float: right color: #333 // }}} - -.openerp - .oe_fileupload - display: inline-block - clear: both - width: 100% - .oe_add - float: left - position: relative - width: 100% - left: +2px - top: +7px - button - display: inline - height: 24px - font-size: 12px - line-height: 12px - vertical-align: middle - button.oe_attach - width: 24px - overflow: hidden - width: 24px - overflow: hidden - background: transparent - color: #7C7BAD - box-shadow: none - border: none - text-shadow: none - .oe_e - position: relative - top: -1px - left: -9px - input.oe_form_binary_file - display: inline-block - margin-left: -5px - height: 28px - width: 52px - margin-top: -26px - .oe_attach_label - color: #7C7BAD - margin-left: -3px - .oe_attachments - margin-bottom: 4px - margin-right: 0px - font-size: 12px - border-radius: 2px - border: solid 1px rgba(124,123,173,0.14) - .oe_attachment - padding: 2px - padding-left: 4px - padding-right: 4px - .oe_e - font-size: 23px - margin-top: -5px - .oe_e:hover - text-decoration: none - .oe_attachment:nth-child(odd) - background: white - .oe_attachment:nth-child(even) - background: #F4F5FA - // Kitten Mode {{{ .kitten-mode-activated background-image: url(http://placekitten.com/g/1365/769) @@ -2367,11 +2360,13 @@ $sheet-padding: 16px opacity: 0.70 // }}} +// jQueryUI top level {{{ // The jQuery-ui overlay and Autocomplete are outside the .openerp div, please don't add indentation !!! div.ui-widget-overlay background: black @include opacity(0.3) - +// TODO: I think only the overlay is problematic, the other top level widgets should use $.fn.openerpClass() +// eg: $el.autocomplete().openerpClass(); .ui-widget font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif color: #4c4c4c @@ -2398,11 +2393,14 @@ div.ui-widget-overlay .ui-corner-all @include radius(3px) +// }}} +// @media print {{{ @media print .openerp - .oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default - display: none + .oe_header_row, ul.oe_header, div.oe_mail_thread_action, .oe_mail_recthread_actions, .oe_button_box, .oe_form button, button.oe_invite, .oe_form header, .openerp .oe_notebook > li.ui-state-default, .oe_topbar, .oe_leftbar, .oe_loading + // We use !important here because jQuery adds @style = display: block on elements when using $.fn.show() + display: none !important .oe_list_content button, input[type=checkbox] visibility: hidden @@ -2428,5 +2426,7 @@ div.ui-widget-overlay background: none .openerp div.oe_mail_wall overflow: hidden !important +// }}} + // au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css" // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index 05ce709e80a..631a7f8c6f5 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -19,12 +19,14 @@ /** * OpenERP instance constructor * - * @param {Array} modules list of modules to initialize + * @param {Array|String} modules list of modules to initialize */ init: function(modules) { - // By default only web will be loaded, the rest will be by loaded - // by openerp.web.Session on the first session_authenticate - modules = _.union(['web'], modules || []); + if (modules === "fuck your shit, don't load anything you cunt") { + modules = []; + } else { + modules = _.union(['web'], modules || []); + } var new_instance = { // links to the global openerp _openerp: openerp, diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index c58451ba812..4197880182d 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -48,7 +48,7 @@ instance.web.Notification = instance.web.Widget.extend({ */ instance.web.dialog = function(element) { var result = element.dialog.apply(element, _.rest(_.toArray(arguments))); - result.dialog("widget").addClass("openerp"); + result.dialog("widget").openerpClass(); return result; }; @@ -190,7 +190,14 @@ instance.web.Dialog = instance.web.Widget.extend({ }); instance.web.CrashManager = instance.web.Class.extend({ + init: function() { + this.active = true; + }, + rpc_error: function(error) { + if (!this.active) { + return; + } if (error.data.fault_code) { var split = ("" + error.data.fault_code).split('\n')[0].split(' -- '); if (split.length > 1) { @@ -205,6 +212,9 @@ instance.web.CrashManager = instance.web.Class.extend({ } }, show_warning: function(error) { + if (!this.active) { + return; + } instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), { title: "OpenERP " + _.str.capitalize(error.type), buttons: [ @@ -213,7 +223,9 @@ instance.web.CrashManager = instance.web.Class.extend({ }); }, show_error: function(error) { - var self = this; + if (!this.active) { + return; + } var buttons = {}; buttons[_t("Ok")] = function() { $(this).dialog("close"); @@ -310,7 +322,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ self.db_list = null; }); var fetch_langs = this.rpc("/web/session/get_lang_list", {}).done(function(result) { - self.lang_list = result.lang_list; + self.lang_list = result; }); return $.when(fetch_db, fetch_langs).done(self.do_render); }, @@ -643,7 +655,7 @@ instance.web.client_actions.add("login", "instance.web.Login"); instance.web.redirect = function(url, wait) { // Dont display a dialog if some xmlhttprequest are in progress if (instance.client && instance.client.crashmanager) { - instance.client.crashmanager.destroy(); + instance.client.crashmanager.active = false; } var wait_server = function() { @@ -659,7 +671,7 @@ instance.web.redirect = function(url, wait) { } else { window.location = url; } -} +}; /** * Client action to reload the whole interface. @@ -941,7 +953,7 @@ instance.web.UserMenu = instance.web.Widget.extend({ on_menu_settings: function() { var self = this; if (!this.getParent().has_uncommitted_changes()) { - self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) { + self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) { result.res_id = instance.session.uid; self.getParent().action_manager.do_action(result); }); @@ -972,6 +984,7 @@ instance.web.Client = instance.web.Widget.extend({ return instance.session.session_bind(this.origin).then(function() { var $e = $(QWeb.render(self._template, {})); self.replaceElement($e); + $e.openerpClass(); self.bind_events(); return self.show_common(); }); @@ -1148,10 +1161,10 @@ instance.web.WebClient = instance.web.Client.extend({ var self = this; var state = event.getState(true); if (!_.isEqual(this._current_state, state)) { - if(state.action === undefined && state.menu_id) { + if(!state.action && state.menu_id) { self.menu.has_been_loaded.done(function() { self.menu.do_reload().done(function() { - self.menu.menu_click(state.menu_id) + self.menu.menu_click(state.menu_id); }); }); } else { diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 6ea0fd53372..06b3940f1e1 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -774,10 +774,10 @@ instance.web.Widget = instance.web.Class.extend(instance.web.PropertiesMixin, { } return false; }, - rpc: function(url, data, success, error) { - var def = $.Deferred().done(success).fail(error); + rpc: function(url, data, options) { + var def = $.Deferred(); var self = this; - instance.session.rpc(url, data).done(function() { + instance.session.rpc(url, data, options).done(function() { if (!self.isDestroyed()) def.resolve.apply(def, arguments); }).fail(function() { @@ -1231,12 +1231,14 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, { * * @param {String} url RPC endpoint * @param {Object} params call parameters + * @param {Object} options additional options for rpc call * @param {Function} success_callback function to execute on RPC call success * @param {Function} error_callback function to execute on RPC call failure * @returns {jQuery.Deferred} jquery-provided ajax deferred */ - rpc: function(url, params) { + rpc: function(url, params, options) { var self = this; + options = options || {}; // url can be an $.ajax option object if (_.isString(url)) { url = { url: url }; @@ -1251,10 +1253,12 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, { id: _.uniqueId('r') }; var deferred = $.Deferred(); - this.trigger('request', url, payload); + if (! options.shadow) + this.trigger('request', url, payload); var request = this.rpc_function(url, payload).done( function (response, textStatus, jqXHR) { - self.trigger('response', response); + if (! options.shadow) + self.trigger('response', response); if (!response.error) { if (url.url === '/web/session/eval_domain_and_context') { self.test_eval(params, response.result); @@ -1268,7 +1272,8 @@ instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, { } ).fail( function(jqXHR, textStatus, errorThrown) { - self.trigger('response_failed', jqXHR); + if (! options.shadow) + self.trigger('response_failed', jqXHR); var error = { code: -32098, message: "XmlHttpRequestError " + errorThrown, diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 65b1874e093..a740827c1b5 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -22,9 +22,9 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess this.name = instance._session_id; this.qweb_mutex = new $.Mutex(); }, - rpc: function(url, params) { + rpc: function(url, params, options) { params.session_id = this.session_id; - return this._super(url, params); + return this._super(url, params, options); }, /** * Setup a sessionm @@ -466,6 +466,16 @@ $.fn.getAttributes = function() { } return o; } +$.fn.openerpClass = function(additionalClass) { + // This plugin should be applied on top level elements + additionalClass = additionalClass || ''; + if (!!$.browser.msie) { + additionalClass += ' openerp_ie'; + } + return this.each(function() { + $(this).addClass('openerp ' + additionalClass); + }); +}; /** Jquery extentions */ $.Mutex = (function() { diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index 9162d0066c2..524a88bdc48 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -278,9 +278,10 @@ instance.web.Model = instance.web.Class.extend({ * @param {String} method name of the method to call * @param {Array} [args] positional arguments * @param {Object} [kwargs] keyword arguments + * @param {Object} [options] additional options for the rpc() method * @returns {jQuery.Deferred<>} call result */ - call: function (method, args, kwargs) { + call: function (method, args, kwargs, options) { args = args || []; kwargs = kwargs || {}; if (!_.isArray(args)) { @@ -294,7 +295,7 @@ instance.web.Model = instance.web.Class.extend({ method: method, args: args, kwargs: kwargs - }); + }, options); }, /** * Fetches a Query instance bound to this model, for searching diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js new file mode 100644 index 00000000000..bf39f543734 --- /dev/null +++ b/addons/web/static/src/js/testing.js @@ -0,0 +1,393 @@ +// Test support structures and methods for OpenERP +openerp.testing = {}; +(function (testing) { + var dependencies = { + corelib: [], + coresetup: ['corelib'], + data: ['corelib', 'coresetup'], + dates: [], + formats: ['coresetup', 'dates'], + chrome: ['corelib', 'coresetup'], + views: ['corelib', 'coresetup', 'data', 'chrome'], + search: ['data', 'coresetup', 'formats'], + list: ['views', 'data'], + form: ['data', 'views', 'list', 'formats'], + list_editable: ['list', 'form', 'data'], + }; + + testing.dependencies = window['oe_all_dependencies'] || []; + testing.current_module = null; + testing.templates = { }; + testing.add_template = function (name) { + var xhr = QWeb2.Engine.prototype.get_xhr(); + xhr.open('GET', name, false); + xhr.send(null); + (testing.templates[testing.current_module] = + testing.templates[testing.current_module] || []) + .push(xhr.responseXML); + }; + /** + * Function which does not do anything + */ + testing.noop = function () { }; + /** + * Alter provided instance's ``session`` attribute to make response + * mockable: + * + * * The ``responses`` parameter can be used to provide a map of (RPC) + * paths (e.g. ``/web/view/load``) to a function returning a response + * to the query. + * * ``instance.session`` grows a ``responses`` attribute which is + * a map of the same (and is in fact initialized to the ``responses`` + * parameter if one is provided) + * + * Note that RPC requests to un-mocked URLs will be rejected with an + * error message: only explicitly specified urls will get a response. + * + * Mocked sessions will *never* perform an actual RPC connection. + * + * @param instance openerp instance being initialized + * @param {Object} [responses] + */ + testing.mockifyRPC = function (instance, responses) { + var session = instance.session; + session.responses = responses || {}; + session.rpc_function = function (url, payload) { + var fn, params; + var needle = payload.params.model + ':' + payload.params.method; + if (url.url === '/web/dataset/call_kw' + && needle in this.responses) { + fn = this.responses[needle]; + params = [ + payload.params.args || [], + payload.params.kwargs || {} + ]; + } else { + fn = this.responses[url.url]; + params = [payload]; + } + + if (!fn) { + return $.Deferred().reject({}, 'failed', + _.str.sprintf("Url %s not found in mock responses, with arguments %s", + url.url, JSON.stringify(payload.params)) + ).promise(); + } + try { + return $.when(fn.apply(null, params)).then(function (result) { + // Wrap for RPC layer unwrapper thingy + return {result: result}; + }); + } catch (e) { + // not sure why this looks like that + return $.Deferred().reject({}, 'failed', String(e)); + } + }; + }; + + var StackProto = { + execute: function (fn) { + var args = [].slice.call(arguments, 1); + // Warning: here be dragons + var i = 0, setups = this.setups, teardowns = this.teardowns; + var d = $.Deferred(); + + var succeeded, failed; + var success = function () { + succeeded = _.toArray(arguments); + return teardown(); + }; + var failure = function () { + // save first failure + if (!failed) { + failed = _.toArray(arguments); + } + // chain onto next teardown + return teardown(); + }; + + var setup = function () { + // if setup to execute + if (i < setups.length) { + var f = setups[i] || testing.noop; + $.when(f.apply(null, args)).then(function () { + ++i; + setup(); + }, failure); + } else { + $.when(fn.apply(null, args)).then(success, failure); + } + }; + var teardown = function () { + // if teardown to execute + if (i > 0) { + var f = teardowns[--i] || testing.noop; + $.when(f.apply(null, args)).then(teardown, failure); + } else { + if (failed) { + d.reject.apply(d, failed); + } else if (succeeded) { + d.resolve.apply(d, succeeded); + } else { + throw new Error("Didn't succeed or fail?"); + } + } + }; + setup(); + + return d; + }, + push: function (setup, teardown) { + return _.extend(Object.create(StackProto), { + setups: this.setups.concat([setup]), + teardowns: this.teardowns.concat([teardown]) + }); + }, + unshift: function (setup, teardown) { + return _.extend(Object.create(StackProto), { + setups: [setup].concat(this.setups), + teardowns: [teardown].concat(this.teardowns) + }); + } + }; + /** + * + * @param {Function} [setup] + * @param {Function} [teardown] + * @return {*} + */ + testing.Stack = function (setup, teardown) { + return _.extend(Object.create(StackProto), { + setups: setup ? [setup] : [], + teardowns: teardown ? [teardown] : [] + }); + }; + + var db = window['oe_db_info']; + testing.section = function (name, options, body) { + if (_.isFunction(options)) { + body = options; + options = {}; + } + _.defaults(options, { + setup: testing.noop, + teardown: testing.noop + }); + + QUnit.module(testing.current_module + '.' + name, {_oe: options}); + body(testing.case); + }; + testing.case = function (name, options, callback) { + if (_.isFunction(options)) { + callback = options; + options = {}; + } + + var module = testing.current_module; + var module_index = _.indexOf(testing.dependencies, module); + var module_deps = testing.dependencies.slice( + // If module not in deps (because only tests, no JS) -> indexOf + // returns -1 -> index becomes 0 -> replace with ``undefined`` so + // Array#slice returns a full copy + 0, module_index + 1 || undefined); + + // Serialize options for this precise test case + // WARNING: typo is from jquery, do not fix! + var env = QUnit.config.currentModuleTestEnviroment; + // section setup + // case setup + // test + // case teardown + // section teardown + var case_stack = testing.Stack() + .push(env._oe.setup, env._oe.teardown) + .push(options.setup, options.teardown); + var opts = _.defaults({}, options, env._oe); + // FIXME: if this test is ignored, will still query + if (opts.rpc === 'rpc' && !db) { + QUnit.config.autostart = false; + db = { + source: null, + supadmin: null, + password: null + }; + var $msg = $('<form style="margin: 0 1em 1em;">') + .append('<h3>A test needs to clone a database</h3>') + .append('<h4>Please provide the source clone information</h4>') + .append(' Source DB: ').append('<input name="source">').append('<br>') + .append(' DB Password: ').append('<input name="supadmin">').append('<br>') + .append('Admin Password: ').append('<input name="password">').append('<br>') + .append('<input type="submit" value="OK"/>') + .submit(function (e) { + e.preventDefault(); + e.stopPropagation(); + db.source = $msg.find('input[name=source]').val(); + db.supadmin = $msg.find('input[name=supadmin]').val(); + db.password = $msg.find('input[name=password]').val(); + QUnit.start(); + $.unblockUI(); + }); + $.blockUI({ + message: $msg, + css: { + fontFamily: 'monospace', + textAlign: 'left', + whiteSpace: 'pre-wrap', + cursor: 'default' + } + }); + } + + QUnit.test(name, function () { + var instance; + if (!opts.dependencies) { + instance = openerp.init(module_deps); + } else { + // empty-but-specified dependencies actually allow running + // without loading any module into the instance + + // TODO: clean up this mess + var d = opts.dependencies.slice(); + // dependencies list should be in deps order, reverse to make + // loading order from last + d.reverse(); + var di = 0; + while (di < d.length) { + var m = /^web\.(\w+)$/.exec(d[di]); + if (m) { + d[di] = m[1]; + } + d.splice.apply(d, [di+1, 0].concat( + _(dependencies[d[di]]).reverse())); + ++di; + } + + instance = openerp.init("fuck your shit, don't load anything you cunt"); + _(d).chain() + .reverse() + .uniq() + .each(function (module) { + openerp.web[module](instance); + }); + } + if (_.isNumber(opts.asserts)) { + expect(opts.asserts); + } + + if (opts.templates) { + for(var i=0; i<module_deps.length; ++i) { + var dep = module_deps[i]; + var templates = testing.templates[dep]; + if (_.isEmpty(templates)) { continue; } + + for (var j=0; j < templates.length; ++j) { + instance.web.qweb.add_template(templates[j]); + } + } + } + + var $fixture = $('#qunit-fixture'); + + var mock, async = false; + switch (opts.rpc) { + case 'mock': + async = true; + testing.mockifyRPC(instance); + mock = function (spec, handler) { + instance.session.responses[spec] = handler; + }; + break; + case 'rpc': + async = true; + (function () { + // Bunch of random base36 characters + var dbname = 'test_' + Math.random().toString(36).slice(2); + // Add db setup/teardown at the start of the stack + case_stack = case_stack.unshift(function (instance) { + // FIXME hack: don't want the session to go through shitty loading process of everything + instance.session.session_init = testing.noop; + instance.session.load_modules = testing.noop; + instance.session.session_bind(); + return instance.session.rpc('/web/database/duplicate', { + fields: [ + {name: 'super_admin_pwd', value: db.supadmin}, + {name: 'db_original_name', value: db.source}, + {name: 'db_name', value: dbname} + ] + }).then(function (result) { + if (result.error) { + return $.Deferred().reject(result.error).promise(); + } + return instance.session.session_authenticate( + dbname, 'admin', db.password, true); + }); + }, function (instance) { + return instance.session.rpc('/web/database/drop', { + fields: [ + {name: 'drop_pwd', value: db.supadmin}, + {name: 'drop_db', value: dbname} + ] + }).then(function (result) { + if (result.error) { + return $.Deferred().reject(result.error).promise(); + } + return result; + }); + }); + })(); + } + + // Always execute tests asynchronously + stop(); + var timeout; + case_stack.execute(function () { + var result = callback.apply(null, arguments); + if (!(result && _.isFunction(result.then))) { + if (async) { + ok(false, "asynchronous test cases must return a promise"); + } + } else { + if (!_.isNumber(opts.asserts)) { + ok(false, "asynchronous test cases must specify the " + + "number of assertions they expect"); + } + } + + return $.Deferred(function (d) { + $.when(result).then(function () { + d.resolve.apply(d, arguments) + }, function () { + d.reject.apply(d, arguments); + }); + if (async || (result && result.then)) { + // async test can be either implicit async (rpc) or + // promise-returning + timeout = setTimeout(function () { + QUnit.config.semaphore = 1; + d.reject({message: "Test timed out"}); + }, 2000); + } + }); + }, instance, $fixture, mock).always(function () { + if (timeout) { clearTimeout(timeout); } + start(); + }).fail(function (error) { + if (options.fail_on_rejection === false) { + return; + } + var message; + if (typeof error !== 'object' + || typeof error.message !== 'string') { + message = JSON.stringify([].slice.apply(arguments)); + } else { + message = error.message; + if (error.data && error.data.debug) { + message += '\n\n' + error.data.debug; + } + } + + ok(false, message); + }); + }); + }; +})(openerp.testing); diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 867569e2ea5..dad622fb7f9 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -622,6 +622,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM return self._process_save(save_obj).then(function() { save_obj.ret = _.toArray(arguments); return iterate(); + }, function() { + save_obj.error = true; }); } return $.when(); @@ -805,6 +807,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM var save_obj = {prepend_on_create: prepend_on_create, ret: null}; this.save_list.push(save_obj); return this._process_operations().then(function() { + if (save_obj.error) + return $.Deferred().reject(); return $.when.apply($, save_obj.ret); }); }, @@ -3122,7 +3126,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc minLength: 0, delay: 0 }); - this.$input.autocomplete("widget").addClass("openerp"); + this.$input.autocomplete("widget").openerpClass(); // used to correct a bug when selecting an element by pushing 'enter' in an editable list this.$input.keyup(function(e) { if (e.which === 13) { // ENTER @@ -3833,26 +3837,13 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ this._super.apply(this, arguments); }, do_delete: function (ids) { - var self = this; - var next = $.when(); - var _super = this._super; - // handle deletion of an item which does not exist - // TODO: better handle that in the editable list? - var false_id_index = _(ids).indexOf(false); - if (false_id_index !== -1) { - ids.splice(false_id_index, 1); - next = this.cancel_edition(true); - } - return next.then(function () { - // wheeee - var confirm = window.confirm; - window.confirm = function () { return true; }; - try { - return _super.call(self, ids); - } finally { - window.confirm = confirm; - } - }); + var confirm = window.confirm; + window.confirm = function () { return true; }; + try { + return this._super(ids); + } finally { + window.confirm = confirm; + } } }); instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({ @@ -4952,7 +4943,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) { url = 'data:image/png;base64,' + this.get('value'); } else if (this.get('value')) { - var id = escape(JSON.stringify(this.view.datarecord.id || null)); + var id = JSON.stringify(this.view.datarecord.id || null); var field = this.name; if (this.options.preview_image) field = this.options.preview_image; @@ -5000,14 +4991,14 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ * Options on attribute ; "blockui" {Boolean} block the UI or not * during the file is uploading */ -instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({ +instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractField.extend({ template: "FieldBinaryFileUploader", init: function(field_manager, node) { this._super(field_manager, node); this.field_manager = field_manager; this.node = node; - if(this.field.type != "one2many" || this.field.relation != 'ir.attachment') { - throw "The type of the field '"+this.field.string+"' must be a one2many field with a relation to 'ir.attachment' model."; + if(this.field.type != "many2many" || this.field.relation != 'ir.attachment') { + throw "The type of the field '"+this.field.string+"' must be a many2many field with a relation to 'ir.attachment' model."; } this.ds_file = new instance.web.DataSetSearch(this, 'ir.attachment'); this.fileupload_id = _.uniqueId('oe_fileupload_temp'); @@ -5017,21 +5008,85 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel this._super(this); this.$el.on('change', 'input.oe_form_binary_file', this.on_file_change ); }, + set_value: function(value_) { + var value_ = value_ || []; + var self = this; + var ids = []; + _.each(value_, function(command) { + if (isNaN(command) && command.id == undefined) { + switch (command[0]) { + case commands.CREATE: + ids = ids.concat(command[2]); + return; + case commands.REPLACE_WITH: + ids = ids.concat(command[2]); + return; + case commands.UPDATE: + ids = ids.concat(command[2]); + return; + case commands.LINK_TO: + ids = ids.concat(command[1]); + return; + case commands.DELETE: + ids = _.filter(ids, function (id) { return id != command[1];}); + return; + case commands.DELETE_ALL: + ids = []; + return; + } + } else { + ids.push(command); + } + }); + this._super( ids ); + }, get_value: function() { return _.map(this.get('value'), function (value) { return commands.link_to( value.id ); }); }, get_file_url: function (attachment) { return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']}); }, + read_name_values : function () { + var self = this; + // select the list of id for a get_name + var values = []; + _.each(this.get('value'), function (val) { + if (typeof val != 'object') { + values.push(val); + } + }); + // send request for get_name + if (values.length) { + return this.ds_file.call('read', [values, ['id', 'name', 'datas_fname']]).done(function (datas) { + _.each(datas, function (data) { + data.no_unlink = true; + data.url = self.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: data.id}); + + _.each(self.get('value'), function (val, key) { + if(val == data.id) { + self.get('value')[key] = data; + } + }); + }); + }); + } else { + return $.when(this.get('value')); + } + }, render_value: function () { - var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': this})); - render.on('click', '.oe_delete', _.bind(this.on_file_delete, this)); - this.$('.oe_placeholder_files, .oe_attachments').replaceWith( render ); + var self = this; + this.read_name_values().then(function (datas) { + + var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self})); + render.on('click', '.oe_delete', _.bind(self.on_file_delete, self)); + self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render ); - // reinit input type file - var $input = this.$('input.oe_form_binary_file'); - $input.after($input.clone(true)).remove(); - this.$(".oe_fileupload").show(); + // reinit input type file + var $input = self.$('input.oe_form_binary_file'); + $input.after($input.clone(true)).remove(); + self.$(".oe_fileupload").show(); + + }); }, on_file_change: function (event) { event.stopPropagation(); @@ -5111,7 +5166,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel if(file_id != this.get('value')[i].id){ files.push(this.get('value')[i]); } - else { + else if(!this.get('value')[i].no_unlink) { this.ds_file.unlink([file_id]); } } @@ -5273,7 +5328,7 @@ instance.web.form.widgets = new instance.web.Registry({ 'progressbar': 'instance.web.form.FieldProgressBar', 'image': 'instance.web.form.FieldBinaryImage', 'binary': 'instance.web.form.FieldBinaryFile', - 'one2many_binary': 'instance.web.form.FieldOne2ManyBinaryMultiFiles', + 'many2many_binary': 'instance.web.form.FieldMany2ManyBinaryMultiFiles', 'statusbar': 'instance.web.form.FieldStatus', 'monetary': 'instance.web.form.FieldMonetary', }); diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index db8c962efc7..df74674a343 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -1,6 +1,6 @@ openerp.web.list = function (instance) { var _t = instance.web._t, - _lt = instance.web._lt; + _lt = instance.web._lt; var QWeb = instance.web.qweb; instance.web.views.add('list', 'instance.web.ListView'); instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ { diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index 8bcf8b5d036..1abe3caab39 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -77,6 +77,15 @@ openerp.web.list_editable = function (instance) { do_edit: function (index, id, dataset) { _.extend(this.dataset, dataset); }, + do_delete: function (ids) { + var _super = this._super.bind(this); + var next = this.editor.is_editing() + ? this.cancel_edition(true) + : $.when(); + return next.then(function () { + return _super(ids); + }); + }, editable: function () { return this.fields_view.arch.attrs.editable || this._context_editable diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index c9c1f918114..641821152b2 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -310,15 +310,15 @@ instance.web.ActionManager = instance.web.Widget.extend({ } var widget = executor.widget(); if (executor.action.target === 'new') { - if (this.dialog_widget && ! this.dialog_widget.isDestroyed()) + if (this.dialog_widget && !this.dialog_widget.isDestroyed()) { this.dialog_widget.destroy(); - if (this.dialog === null || this.dialog.isDestroyed()) { - this.dialog = new instance.web.Dialog(this, { - dialogClass: executor.klass, - }); - this.dialog.on("closing", null, options.on_close); - this.dialog.init_dialog(); } + this.dialog_stop(); + this.dialog = new instance.web.Dialog(this, { + 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, { @@ -789,8 +789,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ name: "JS Tests", target: 'new', type : 'ir.actions.act_url', - url: '/web/static/test/test.html' - }) + url: '/web/tests?mod=*' + }); break; case 'perm_read': var ids = current_view.get_selected_ids(); @@ -1239,6 +1239,7 @@ instance.web.View = instance.web.Widget.extend({ }); }, null); } else { + self.do_action({"type":"ir.actions.act_window_close"}); return result_handler(); } }; |
