summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorChristophe Simonis <chs@openerp.com>2012-11-16 19:32:12 +0100
committerChristophe Simonis <chs@openerp.com>2012-11-16 19:32:12 +0100
commit4aa5e761746376e57d14115e194ea3bcb968836f (patch)
treeac404df8e9fcae331e265c42a89ea5ad280dfd9f /addons/web/static/src
parentc637a443165902f6024d5728f01484230f29960e (diff)
parent86842fe9c8129d3530c622875c2ad5ec8d06e8a0 (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.css169
-rw-r--r--addons/web/static/src/css/base.sass152
-rw-r--r--addons/web/static/src/js/boot.js10
-rw-r--r--addons/web/static/src/js/chrome.js29
-rw-r--r--addons/web/static/src/js/corelib.js19
-rw-r--r--addons/web/static/src/js/coresetup.js14
-rw-r--r--addons/web/static/src/js/data.js5
-rw-r--r--addons/web/static/src/js/testing.js393
-rw-r--r--addons/web/static/src/js/view_form.js123
-rw-r--r--addons/web/static/src/js/view_list.js2
-rw-r--r--addons/web/static/src/js/view_list_editable.js9
-rw-r--r--addons/web/static/src/js/views.js19
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();
}
};