summaryrefslogtreecommitdiff
path: root/addons/web/static/src
diff options
context:
space:
mode:
authorChristophe Simonis <chs@openerp.com>2012-11-14 23:35:38 +0100
committerChristophe Simonis <chs@openerp.com>2012-11-14 23:35:38 +0100
commita161d749f8bf3fe86f9553d249a34fe05acd44e0 (patch)
tree63d06db3751b3571a1c644a13303b890594d627d /addons/web/static/src
parent28f7f46a5ced9d6a0ac7e68a64f1f9849422c465 (diff)
parenteb5a5bf838fdeac8564802f3b08fbfad6066e8e7 (diff)
merge upstream
bzr revid: chs@openerp.com-20121114223538-qp233873r33uh3e3
Diffstat (limited to 'addons/web/static/src')
-rw-r--r--addons/web/static/src/css/base.css39
-rw-r--r--addons/web/static/src/css/base.sass28
-rw-r--r--addons/web/static/src/js/chrome.js26
-rw-r--r--addons/web/static/src/js/corelib.js273
-rw-r--r--addons/web/static/src/js/coresetup.js51
-rw-r--r--addons/web/static/src/js/data.js13
-rw-r--r--addons/web/static/src/js/formats.js36
-rw-r--r--addons/web/static/src/js/search.js136
-rw-r--r--addons/web/static/src/js/view_form.js125
-rw-r--r--addons/web/static/src/js/view_list.js36
-rw-r--r--addons/web/static/src/js/view_list_editable.js91
-rw-r--r--addons/web/static/src/js/views.js49
-rw-r--r--addons/web/static/src/xml/base.xml89
13 files changed, 609 insertions, 383 deletions
diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css
index b34e699b541..a421336c43f 100644
--- a/addons/web/static/src/css/base.css
+++ b/addons/web/static/src/css/base.css
@@ -2316,16 +2316,16 @@
text-align: justify;
}
.openerp .oe_form_editable .oe_form .oe_form_field_integer input {
- width: 6em !important;
+ width: 6em;
}
.openerp .oe_form_editable .oe_form .oe_form_field_float input {
- width: 7em !important;
+ width: 7em;
}
.openerp .oe_form_editable .oe_form .oe_form_field_date input {
- width: 7.5em !important;
+ width: 7.5em;
}
.openerp .oe_form_editable .oe_form .oe_form_field_datetime input {
- width: 11.5em !important;
+ width: 11.5em;
}
.openerp .oe_hidden_input_file {
position: relative;
@@ -2384,6 +2384,9 @@
float: right;
padding-left: 2px;
}
+.openerp .oe_form_field_many2one input {
+ padding-right: 13px;
+}
.openerp.ui-autocomplete li.oe_m2o_dropdown_option a {
font-style: italic;
padding-left: 2em;
@@ -2588,32 +2591,33 @@
top: 5px;
}
.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_cm_button {
- display: none;
+ line-height: 19px;
}
-.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
- height: 27px;
+.openerp .oe_list.oe_list_editable.oe_editing .oe_input_icon {
+ margin-top: 5px;
+}
+.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field {
+ min-width: 0;
+ max-width: none;
}
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea {
+ height: 27px;
-moz-border-radius: 0;
-webkit-border-radius: 0;
border-radius: 0;
border: 1px solid #aaaaff;
margin: 0;
}
+.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field select {
+ min-width: 0;
+}
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input {
text-align: right;
width: 100% !important;
}
-.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime > span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date > span {
- width: 100% !important;
-}
.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master {
width: 100% !important;
}
-.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_field_float, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_view_integer, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date {
- min-width: 0 !important;
- max-width: none !important;
-}
.openerp .oe_list_group_name {
white-space: nowrap;
}
@@ -2678,6 +2682,9 @@
text-align: right !important;
max-width: 100px;
}
+.openerp .oe_list_content td.oe_list_field_date, .openerp .oe_list_content th.oe_list_header_date {
+ min-width: 6em;
+}
.openerp .oe_list_content > thead {
border-bottom: 2px solid #cacaca;
background: #eeeeee;
@@ -2745,6 +2752,10 @@
.openerp .oe_list_content > tbody > tr > td.oe_list_checkbox:first-child:after, .openerp .oe_list_content > tbody > tr th.oe_list_checkbox:first-child:after {
border-width: 0;
}
+.openerp .oe_list_content > tbody > tr > td.oe_list_field_boolean input {
+ filter: alpha(opacity=50);
+ opacity: 0.5;
+}
.openerp .oe_list_content > tbody > tr:nth-child(odd) {
background-color: #f0f0fa;
background-color: #f0f0fa;
diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass
index 368df7f047c..596426917b5 100644
--- a/addons/web/static/src/css/base.sass
+++ b/addons/web/static/src/css/base.sass
@@ -1846,13 +1846,13 @@ $sheet-padding: 16px
.oe_form_editable
.oe_form
.oe_form_field_integer input
- width: 6em !important
+ width: 6em
.oe_form_field_float input
- width: 7em !important
+ width: 7em
.oe_form_field_date input
- width: 7.5em !important
+ width: 7.5em
.oe_form_field_datetime input
- width: 11.5em !important
+ width: 11.5em
// }}}
// FormView.fields_binary {{{
/* http://www.quirksmode.org/dom/inputfile.html
@@ -1908,6 +1908,8 @@ $sheet-padding: 16px
line-height: 14px
float: right
padding-left: 2px
+ input
+ padding-right: 13px
&.ui-autocomplete
li.oe_m2o_dropdown_option a
font-style: italic
@@ -2050,26 +2052,26 @@ $sheet-padding: 16px
.oe_m2o_drop_down_button
top: 5px
.oe_m2o_cm_button
- display: none
+ line-height: 19px
+ .oe_input_icon
+ margin-top: 5px
.oe_form_field
+ min-width: 0
+ max-width: none
input, textarea
height: $row-height
- input, textarea
@include radius(0)
border: 1px solid #aaf
margin: 0
+ input, textarea, select
+ min-width: 0
&.oe_form_field_float,&.oe_form_view_integer
input
text-align: right
width: 100% !important
&.oe_form_field_datetime,&.oe_form_field_date
- > span
- width: 100% !important
input.oe_datepicker_master
width: 100% !important
- .oe_form_field_float,.oe_form_view_integer,&.oe_form_field_datetime,&.oe_form_field_date
- min-width: 0 !important
- max-width: none !important
.oe_list_group_name
white-space: nowrap
// }}}
@@ -2128,6 +2130,8 @@ $sheet-padding: 16px
td.oe_number
text-align: right !important
max-width: 100px
+ td.oe_list_field_date, th.oe_list_header_date
+ min-width: 6em
> thead
border-bottom: 2px solid #cacaca
background: #eee
@@ -2177,6 +2181,8 @@ $sheet-padding: 16px
width: 17px
&:after
border-width: 0
+ > td.oe_list_field_boolean input
+ @include opacity()
> tr:nth-child(odd)
background-color: #f0f0fa
@include vertical-gradient(#f0f0fa, #eeeef6)
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index f103828251a..c48c231bb99 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -189,7 +189,7 @@ instance.web.Dialog = instance.web.Widget.extend({
}
});
-instance.web.CrashManager = instance.web.CallbackEnabled.extend({
+instance.web.CrashManager = instance.web.Class.extend({
rpc_error: function(error) {
if (error.data.fault_code) {
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
@@ -303,7 +303,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
$('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty();
var fetch_db = this.rpc("/web/database/get_list", {}).then(
function(result) {
- self.db_list = result.db_list;
+ self.db_list = result;
},
function (_, ev) {
ev.preventDefault();
@@ -333,6 +333,7 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label");
self.$el.find("label").addClass("oe_form_label");
self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create });
+ self.$el.find("form[name=duplicate_db_form]").validate({ submitHandler: self.do_duplicate });
self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop });
self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup });
self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore });
@@ -418,6 +419,18 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.do_action(client_action);
});
},
+ do_duplicate: function(form) {
+ var self = this;
+ var fields = $(form).serializeArray();
+ self.rpc("/web/database/duplicate", {'fields': fields}).then(function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ self.do_notify("Duplicating database", "The database has been duplicated.");
+ self.start();
+ });
+ },
do_drop: function(form) {
var self = this;
var $form = $(form),
@@ -518,6 +531,9 @@ instance.web.Login = instance.web.Widget.extend({
this.selected_db = null;
this.selected_login = null;
this.params = action.params || {};
+ if (_.isEmpty(this.params)) {
+ this.params = $.bbq.getState(true);
+ }
if (this.params.login_successful) {
this.on('login_successful', this, this.params.login_successful);
@@ -548,7 +564,7 @@ instance.web.Login = instance.web.Widget.extend({
return d;
},
on_db_loaded: function (result) {
- this.db_list = result.db_list;
+ this.db_list = result;
this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db}));
if(this.db_list.length === 0) {
this.do_action("database_manager");
@@ -911,7 +927,7 @@ 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);
- var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=image_small&id=%s', self.session.prefix, self.session.session_id, self.session.uid);
+ var avatar_src = self.session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: self.session.uid});
$avatar.attr('src', avatar_src);
});
};
@@ -1130,7 +1146,7 @@ instance.web.WebClient = instance.web.Client.extend({
var self = this;
var state = event.getState(true);
if (!_.isEqual(this._current_state, state)) {
- if(state.action_id === undefined && state.menu_id) {
+ if(state.action === undefined && state.menu_id) {
self.menu.has_been_loaded.done(function() {
self.menu.do_reload().done(function() {
self.menu.menu_click(state.menu_id)
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index d2e94d37939..6ea0fd53372 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -243,15 +243,17 @@ instance.web.ParentedMixin = {
/**
* Backbone's events. Do not ever use it directly, use EventDispatcherMixin instead.
+ *
+ * This class just handle the dispatching of events, it is not meant to be extended,
+ * nor used directly. All integration with parenting and automatic unregistration of
+ * events is done in EventDispatcherMixin.
+ *
+ * Copyright notice for the following Class:
*
* (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
* Backbone may be freely distributed under the MIT license.
* For all details and documentation:
* http://backbonejs.org
- *
- * This class just handle the dispatching of events, it is not meant to be extended,
- * nor used directly. All integration with parenting and automatic unregistration of
- * events is done in EventDispatcherMixin.
*
*/
var Events = instance.web.Class.extend({
@@ -334,7 +336,6 @@ var Events = instance.web.Class.extend({
return this;
}
});
-// end of Jeremy Ashkenas' code
instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
__eventDispatcherMixin: true,
@@ -426,66 +427,94 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
}
});
-instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
- init: function() {
- instance.web.PropertiesMixin.init.call(this);
- var self = this;
- // Transform on_/do_* methods into callbacks
- var callback_maker = function(fn) {
- return function() {
- return fn.apply(self, arguments);
- }
- };
- for (var name in this) {
- if(typeof(this[name]) == "function") {
- if((/^on_|^do_/).test(name)) {
- this[name] = callback_maker(this[name]);
- }
- }
- }
- },
+// Classes
+
+/**
+ * Base class for all visual components. Provides a lot of functionalities helpful
+ * for the management of a part of the DOM.
+ *
+ * Widget handles:
+ * - Rendering with QWeb.
+ * - Life-cycle management and parenting (when a parent is destroyed, all its children are
+ * destroyed too).
+ * - Insertion in DOM.
+ *
+ * Guide to create implementations of the Widget class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = instance.base.Widget.extend({
+ * // the name of the QWeb template to use for rendering
+ * template: "MyQWebTemplate",
+ *
+ * init: function(parent) {
+ * this._super(parent);
+ * // stuff that you want to init before the rendering
+ * },
+ * start: function() {
+ * // stuff you want to make after the rendering, `this.$el` holds a correct value
+ * this.$el.find(".my_button").click(/* an example of event binding * /);
+ *
+ * // if you have some asynchronous operations, it's a good idea to return
+ * // a promise in start()
+ * var promise = this.rpc(...);
+ * return promise;
+ * }
+ * });
+ *
+ * Now this class can simply be used with the following syntax:
+ *
+ * var my_widget = new MyWidget(this);
+ * my_widget.appendTo($(".some-div"));
+ *
+ * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
+ * DOM inside the ".some-div" div and its events were binded.
+ *
+ * And of course, when you don't need that widget anymore, just do:
+ *
+ * my_widget.destroy();
+ *
+ * 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, {
+ // Backbone-ish API
+ tagName: 'div',
+ id: null,
+ className: null,
+ attributes: {},
+ events: {},
/**
- * 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.
+ * The name of the QWeb template that will be used for rendering. Must be
+ * redefined in subclasses or the default render() method can not be used.
*
- * @param {String|Function} method function or name of the method to invoke
- * @returns {Function} proxied method
+ * @type string
*/
- proxy: function (method) {
- var self = this;
- return function () {
- var fn = (typeof method === 'string') ? self[method] : method;
- return fn.apply(self, arguments);
- }
- }
-});
-
-instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
+ template: null,
/**
* Constructs the widget and sets its parent if a parent is given.
*
* @constructs instance.web.Widget
- * @extends instance.web.CallbackEnabled
*
* @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
* When that widget is destroyed by calling destroy(), the current instance will be
* destroyed too. Can be null.
*/
init: function(parent) {
- instance.web.CallbackEnabledMixin.init.call(this);
+ instance.web.PropertiesMixin.init.call(this);
this.setParent(parent);
+ // Bind on_/do_* methods to this
+ // We might remove this automatic binding in the future
+ for (var name in this) {
+ if(typeof(this[name]) == "function") {
+ if((/^on_|^do_/).test(name)) {
+ this[name] = this[name].bind(this);
+ }
+ }
+ }
+ // FIXME: this should not be
+ this.setElement(this._make_descriptive());
+ this.session = instance.session;
},
/**
* Destroys the current widget, also destroys all its children before destroying itself.
@@ -574,94 +603,32 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
*/
start: function() {
return $.when();
- }
-});
-
-// Classes
-
-instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEnabledMixin, {
- init: function() {
- instance.web.CallbackEnabledMixin.init.call(this);
- }
-});
-
-/**
- * Base class for all visual components. Provides a lot of functionalities helpful
- * for the management of a part of the DOM.
- *
- * Widget handles:
- * - Rendering with QWeb.
- * - Life-cycle management and parenting (when a parent is destroyed, all its children are
- * destroyed too).
- * - Insertion in DOM.
- *
- * Guide to create implementations of the Widget class:
- * ==============================================
- *
- * Here is a sample child class:
- *
- * MyWidget = instance.base.Widget.extend({
- * // the name of the QWeb template to use for rendering
- * template: "MyQWebTemplate",
- *
- * init: function(parent) {
- * this._super(parent);
- * // stuff that you want to init before the rendering
- * },
- * start: function() {
- * // stuff you want to make after the rendering, `this.$el` holds a correct value
- * this.$el.find(".my_button").click(/* an example of event binding * /);
- *
- * // if you have some asynchronous operations, it's a good idea to return
- * // a promise in start()
- * var promise = this.rpc(...);
- * return promise;
- * }
- * });
- *
- * Now this class can simply be used with the following syntax:
- *
- * var my_widget = new MyWidget(this);
- * my_widget.appendTo($(".some-div"));
- *
- * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
- * DOM inside the ".some-div" div and its events were binded.
- *
- * And of course, when you don't need that widget anymore, just do:
- *
- * my_widget.destroy();
- *
- * That will kill the widget in a clean way and erase its content from the dom.
- */
-instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
- // Backbone-ish API
- tagName: 'div',
- id: null,
- className: null,
- attributes: {},
- events: {},
+ },
/**
- * The name of the QWeb template that will be used for rendering. Must be
- * redefined in subclasses or the default render() method can not be used.
+ * Proxies a method of the object, in order to keep the right ``this`` on
+ * method invocations.
*
- * @type string
- */
- template: null,
- /**
- * Constructs the widget and sets its parent if a parent is given.
+ * 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.
*
- * @constructs instance.web.Widget
- * @extends instance.web.CallbackEnabled
+ * 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.
*
- * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
- * When that widget is destroyed by calling destroy(), the current instance will be
- * destroyed too. Can be null.
+ * 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
*/
- init: function(parent) {
- instance.web.WidgetMixin.init.call(this,parent);
- // FIXME: this should not be
- this.setElement(this._make_descriptive());
- this.session = instance.session;
+ 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,
@@ -678,7 +645,6 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
}
this.replaceElement($el);
},
-
/**
* Re-sets the widget's root element and replaces the old root element
* (if any) by the new one in the DOM.
@@ -941,7 +907,7 @@ instance.web.Registry = instance.web.Class.extend({
}
});
-instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
+instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
triggers: {
'request': 'Request sent',
'response': 'Response received',
@@ -950,13 +916,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
},
/**
* @constructs instance.web.JsonRPC
- * @extends instance.web.CallbackEnabled
*
* @param {String} [server] JSON-RPC endpoint hostname
* @param {String} [port] JSON-RPC endpoint port
*/
init: function() {
- this._super();
+ instance.web.PropertiesMixin.init.call(this);
this.server = null;
this.debug = ($.deparam($.param.querystring()).debug != undefined);
},
@@ -1311,7 +1276,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
};
deferred.reject(error, $.Event());
});
- // Allow deferred user to disable on_rpc_error in fail
+ // Allow deferred user to disable rpc_error call in fail
deferred.fail(function() {
deferred.fail(function(error, event) {
if (!event.isDefaultPrevented()) {
@@ -1344,9 +1309,18 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
// extracted from payload to set on the url
var data = {
session_id: this.session_id,
- id: payload.id
+ id: payload.id,
+ sid: this.httpsessionid,
};
- url.url = this.get_url(url.url);
+
+ var set_sid = function (response, textStatus, jqXHR) {
+ // If response give us the http session id, we store it for next requests...
+ if (response.httpsessionid) {
+ self.httpsessionid = response.httpsessionid;
+ }
+ };
+
+ url.url = this.url(url.url, null);
var ajax = _.extend({
type: "GET",
dataType: 'jsonp',
@@ -1361,7 +1335,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
if(payload_url.length < 2000) {
// Direct jsonp request
ajax.data.r = payload_str;
- return $.ajax(ajax);
+ return $.ajax(ajax).done(set_sid);
} else {
// Indirect jsonp request
var ifid = _.uniqueId('oe_rpc_iframe');
@@ -1399,11 +1373,20 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
});
// append the iframe to the DOM (will trigger the first load)
$form.after($iframe);
- return deferred;
+ return deferred.done(set_sid);
}
},
- get_url: function (file) {
- return this.prefix + file;
+
+ url: function(path, params) {
+ var qs = '';
+ if (!_.isNull(params)) {
+ params = _.extend(params || {}, {session_id: this.session_id});
+ if (this.httpsessionid) {
+ params.sid = this.httpsessionid;
+ }
+ qs = '?' + $.param(params);
+ }
+ return this.prefix + path + qs;
},
});
diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js
index 76e2bf8002c..65b1874e093 100644
--- a/addons/web/static/src/js/coresetup.js
+++ b/addons/web/static/src/js/coresetup.js
@@ -3,10 +3,13 @@
*--------------------------------------------------------*/
var console;
if (!console) {
- console = {log: function () {}};
-}
-if (!console.debug) {
- console.debug = console.log;
+ // Even IE9 only exposes console object if debug window opened
+ console = {};
+ ('log error debug info warn assert clear dir dirxml trace group'
+ + ' groupCollapsed groupEnd time timeEnd profile profileEnd count'
+ + ' exception').split(/\s+/).forEach(function(property) {
+ console[property] = _.identity;
+ });
}
openerp.web.coresetup = function(instance) {
@@ -59,7 +62,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
this.session_id = this.get_cookie('session_id');
return this.session_reload().then(function(result) {
var modules = instance._modules.join(',');
- var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.do_load_qweb);
+ var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).then(self.load_qweb.bind(self));
if(self.session_is_valid()) {
return deferred.then(function() { return self.load_modules(); });
}
@@ -161,28 +164,26 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
load_modules: function() {
var self = this;
return this.rpc('/web/session/modules', {}).then(function(result) {
- var lang = self.user_context.lang,
- all_modules = _.uniq(self.module_list.concat(result));
- var params = { mods: all_modules, lang: lang};
+ var all_modules = _.uniq(self.module_list.concat(result));
var to_load = _.difference(result, self.module_list).join(',');
self.module_list = all_modules;
- var loaded = self.rpc('/web/webclient/translations', params).done(function(trans) {
- instance.web._t.database.set_bundle(trans);
- });
- var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"];
+ var loaded = self.load_translations();
+ var datejs_locale = "/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js";
+
+ var file_list = [ datejs_locale ];
if(to_load.length) {
loaded = $.when(
loaded,
- self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.do_load_css),
- self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.do_load_qweb),
+ self.rpc('/web/webclient/csslist', {mods: to_load}).done(self.load_css.bind(self)),
+ self.rpc('/web/webclient/qweblist', {mods: to_load}).then(self.load_qweb.bind(self)),
self.rpc('/web/webclient/jslist', {mods: to_load}).done(function(files) {
file_list = file_list.concat(files);
})
);
}
return loaded.then(function () {
- return self.do_load_js(file_list);
+ return self.load_js(file_list);
}).done(function() {
self.on_modules_loaded();
self.trigger('module_loaded');
@@ -196,29 +197,35 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
});
});
},
- do_load_css: function (files) {
+ load_translations: function() {
+ var params = { mods: this.module_list, lang: this.user_context.lang };
+ return this.rpc('/web/webclient/translations', params).done(function(trans) {
+ instance.web._t.database.set_bundle(trans);
+ });
+ },
+ load_css: function (files) {
var self = this;
_.each(files, function (file) {
$('head').append($('<link>', {
- 'href': self.get_url(file),
+ 'href': self.url(file, null),
'rel': 'stylesheet',
'type': 'text/css'
}));
});
},
- do_load_js: function(files) {
+ load_js: function(files) {
var self = this;
var d = $.Deferred();
- if(files.length != 0) {
+ if(files.length !== 0) {
var file = files.shift();
var tag = document.createElement('script');
tag.type = 'text/javascript';
- tag.src = self.get_url(file);
+ tag.src = self.url(file, null);
tag.onload = tag.onreadystatechange = function() {
if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
return;
tag.onload_done = true;
- self.do_load_js(files).done(function () {
+ self.load_js(files).done(function () {
d.resolve();
});
};
@@ -229,7 +236,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
}
return d;
},
- do_load_qweb: function(files) {
+ load_qweb: function(files) {
var self = this;
_.each(files, function(file) {
self.qweb_mutex.exec(function() {
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index 2f8679794b0..9162d0066c2 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -358,17 +358,16 @@ instance.web.Model = instance.web.Class.extend({
},
});
-instance.web.DataSet = instance.web.CallbackEnabled.extend({
+instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin, {
/**
* Collection of OpenERP records, used to share records and the current selection between views.
*
* @constructs instance.web.DataSet
- * @extends instance.web.CallbackEnabled
*
* @param {String} model the OpenERP model this dataset will manage
*/
init: function(parent, model, context) {
- this._super(parent);
+ instance.web.PropertiesMixin.init.call(this);
this.model = model;
this.context = context || {};
this.index = null;
@@ -722,10 +721,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
this.last_default_get = {};
},
default_get: function(fields, options) {
- return this._super(fields, options).done(this.on_default_get);
- },
- on_default_get: function(res) {
- this.last_default_get = res;
+ var self = this;
+ return this._super(fields, options).done(function(res) {
+ self.last_default_get = res;
+ });
},
create: function(data) {
var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data,
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 03b2c5b775d..e586b94db6b 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -83,6 +83,42 @@ instance.web.strip_raw_chars = function (value) {
var normalize_format = function (format) {
return Date.normalizeFormat(instance.web.strip_raw_chars(format));
};
+
+/**
+ * Check with a scary heuristic if the value is a bin_size or not.
+ * If not, compute an approximate size out of the base64 encoded string.
+ *
+ * @param {String} value original format
+ */
+instance.web.binary_to_binsize = function (value) {
+ if (!value) {
+ return instance.web.human_size(0);
+ }
+ if (value.substr(0, 10).indexOf(' ') == -1) {
+ // Computing approximate size out of base64 encoded string
+ // http://en.wikipedia.org/wiki/Base64#MIME
+ return instance.web.human_size(value.length / 1.37);
+ } else {
+ // already bin_size
+ return value;
+ }
+};
+
+/**
+ * Returns a human readable size
+ *
+ * @param {Number} numner of bytes
+ */
+instance.web.human_size = function(size) {
+ var units = _t("Bytes,Kb,Mb,Gb,Tb,Pb,Eb,Zb,Yb").split(',');
+ var i = 0;
+ while (size >= 1024) {
+ size /= 1024;
+ ++i;
+ }
+ return size.toFixed(2) + ' ' + units[i];
+};
+
/**
* Formats a single atomic value based on a field descriptor
*
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index e84ea66ff9a..0f25b2484c3 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -349,7 +349,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
$.when(load_view)
.then(function(r) {
self.search_view_loaded(r)
- }).fail(function () {
+ }, function () {
self.ready.reject.apply(null, arguments);
});
}
@@ -459,7 +459,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
complete_global_search: function (req, resp) {
$.when.apply(null, _(this.inputs).chain()
.invoke('complete', req.term)
- .value()).done(function () {
+ .value()).then(function () {
resp(_(_(arguments).compact()).flatten(true));
});
},
@@ -527,7 +527,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
childView.on('blurred', self, self.proxy('childBlurred'));
});
- $.when.apply(null, started).done(function () {
+ $.when.apply(null, started).then(function () {
var input_to_focus;
// options.at: facet inserted at given index, focus next input
// otherwise just focus last input
@@ -608,7 +608,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
// add Filters to this.inputs, need view.controls filled
(new instance.web.search.Filters(this));
// add custom filters to this.inputs
- (new instance.web.search.CustomFilters(this));
+ this.custom_filters = new instance.web.search.CustomFilters(this);
// add Advanced to this.inputs
(new instance.web.search.Advanced(this));
},
@@ -635,16 +635,41 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
// load defaults
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
- 'facet_for_defaults', this.defaults)).done(function () {
- self.query.reset(_(arguments).compact(), {preventSearch: true});
- });
-
+ 'facet_for_defaults', this.defaults))
+ .then(this.proxy('setup_default_query'));
+
return $.when(drawer_started, defaults_fetched)
.done(function () {
self.trigger("search_view_loaded", data);
self.ready.resolve();
});
},
+ setup_default_query: function () {
+ // Hacky implementation of CustomFilters#facet_for_defaults ensure
+ // CustomFilters will be ready (and CustomFilters#filters will be
+ // correctly filled) by the time this method executes.
+ var custom_filters = this.custom_filters.filters;
+ if (!_(custom_filters).isEmpty()) {
+ // Check for any is_default custom filter
+ var personal_filter = _(custom_filters).find(function (filter) {
+ return filter.user_id && filter.is_default;
+ });
+ if (personal_filter) {
+ this.custom_filters.enable_filter(personal_filter, true);
+ return;
+ }
+
+ var global_filter = _(custom_filters).find(function (filter) {
+ return !filter.user_id && filter.is_default;
+ });
+ if (global_filter) {
+ this.custom_filters.enable_filter(global_filter, true);
+ return;
+ }
+ }
+ // No custom filter, or no is_default custom filter, apply view defaults
+ this.query.reset(_(arguments).compact(), {preventSearch: true});
+ },
/**
* Extract search data from the view's facets.
*
@@ -1460,10 +1485,15 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
instance.web.search.CustomFilters = instance.web.search.Input.extend({
template: 'SearchView.CustomFilters',
_in_drawer: true,
+ init: function () {
+ this.is_ready = $.Deferred();
+ this._super.apply(this, arguments);
+ },
start: function () {
var self = this;
this.model = new instance.web.Model('ir.filters');
this.filters = {};
+ this.$filters = {};
this.view.query
.on('remove', function (facet) {
if (!facet.get('is_custom_filter')) {
@@ -1479,24 +1509,78 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
// FIXME: local eval of domain and context to get rid of special endpoint
return this.rpc('/web/searchview/get_filters', {
model: this.view.model
- }).then(this.proxy('set_filters'));
+ })
+ .then(this.proxy('set_filters'))
+ .then(function () {
+ self.is_ready.resolve(null);
+ }, function () {
+ self.is_ready.reject();
+ });
+ },
+ /**
+ * Special implementation delaying defaults until CustomFilters is loaded
+ */
+ facet_for_defaults: function () {
+ return this.is_ready;
+ },
+ /**
+ * Generates a mapping key (in the filters and $filter mappings) for the
+ * filter descriptor object provided (as returned by ``get_filters``).
+ *
+ * The mapping key is guaranteed to be unique for a given (user_id, name)
+ * pair.
+ *
+ * @param {Object} filter
+ * @param {String} filter.name
+ * @param {Number|Pair<Number, String>} [filter.user_id]
+ * @return {String} mapping key corresponding to the filter
+ */
+ key_for: function (filter) {
+ var user_id = filter.user_id;
+ var uid = (user_id instanceof Array) ? user_id[0] : user_id;
+ return _.str.sprintf('(%s)%s', uid, filter.name);
+ },
+ /**
+ * Generates a :js:class:`~instance.web.search.Facet` descriptor from a
+ * filter descriptor
+ *
+ * @param {Object} filter
+ * @param {String} filter.name
+ * @param {Object} [filter.context]
+ * @param {Array} [filter.domain]
+ * @return {Object}
+ */
+ facet_for: function (filter) {
+ return {
+ category: _t("Custom Filter"),
+ icon: 'M',
+ field: {
+ get_context: function () { return filter.context; },
+ get_groupby: function () { return [filter.context]; },
+ get_domain: function () { return filter.domain; }
+ },
+ is_custom_filter: true,
+ values: [{label: filter.name, value: null}]
+ };
},
clear_selection: function () {
this.$('li.oe_selected').removeClass('oe_selected');
},
append_filter: function (filter) {
var self = this;
- var key = _.str.sprintf('(%s)%s', filter.user_id, filter.name);
+ var key = this.key_for(filter);
var $filter;
- if (key in this.filters) {
- $filter = this.filters[key];
+ if (key in this.$filters) {
+ $filter = this.$filters[key];
} else {
var id = filter.id;
- $filter = this.filters[key] = $('<li></li>')
+ this.filters[key] = filter;
+ $filter = this.$filters[key] = $('<li></li>')
.appendTo(this.$('.oe_searchview_custom_list'))
.addClass(filter.user_id ? 'oe_searchview_custom_private'
: 'oe_searchview_custom_public')
+ .toggleClass('oe_searchview_custom_default', filter.is_default)
.text(filter.name);
$('<a class="oe_searchview_custom_delete">x</a>')
@@ -1504,33 +1588,30 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
e.stopPropagation();
self.model.call('unlink', [id]).done(function () {
$filter.remove();
+ delete self.$filters[key];
+ delete self.filters[key];
});
})
.appendTo($filter);
}
$filter.unbind('click').click(function () {
- self.view.query.reset([{
- category: _t("Custom Filter"),
- icon: 'M',
- field: {
- get_context: function () { return filter.context; },
- get_groupby: function () { return [filter.context]; },
- get_domain: function () { return filter.domain; }
- },
- is_custom_filter: true,
- values: [{label: filter.name, value: null}]
- }]);
- $filter.addClass('oe_selected');
+ self.enable_filter(filter);
});
},
+ enable_filter: function (filter, preventSearch) {
+ this.view.query.reset([this.facet_for(filter)], {
+ preventSearch: preventSearch || false});
+ this.$filters[this.key_for(filter)].addClass('oe_selected');
+ },
set_filters: function (filters) {
_(filters).map(_.bind(this.append_filter, this));
},
save_current: function () {
var self = this;
var $name = this.$('input:first');
- var private_filter = !this.$('input:last').prop('checked');
+ var private_filter = !this.$('#oe_searchview_custom_public').prop('checked');
+ var set_as_default = this.$('#oe_searchview_custom_default').prop('checked');
var search = this.view.build_search_data();
this.rpc('/web/session/eval_domain_and_context', {
@@ -1546,7 +1627,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({
user_id: private_filter ? instance.session.uid : false,
model_id: self.view.model,
context: results.context,
- domain: results.domain
+ domain: results.domain,
+ is_default: set_as_default
};
// FIXME: current context?
return self.model.call('create_or_replace', [filter]).done(function (id) {
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 906f1d8fe36..867569e2ea5 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -173,10 +173,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
} else {
this.$el.find('.oe_form_buttons').replaceWith(this.$buttons);
}
- this.$buttons.on('click', '.oe_form_button_create', this.on_button_create);
- this.$buttons.on('click', '.oe_form_button_edit', this.on_button_edit);
- this.$buttons.on('click', '.oe_form_button_save', this.on_button_save);
- this.$buttons.on('click', '.oe_form_button_cancel', this.on_button_cancel);
+ this.$buttons.on('click', '.oe_form_button_create',
+ this.guard_active(this.on_button_create));
+ this.$buttons.on('click', '.oe_form_button_edit',
+ this.guard_active(this.on_button_edit));
+ this.$buttons.on('click', '.oe_form_button_save',
+ this.guard_active(this.on_button_save));
+ this.$buttons.on('click', '.oe_form_button_cancel',
+ this.guard_active(this.on_button_cancel));
if (this.options.footer_to_buttons) {
this.$el.find('footer').appendTo(this.$buttons);
}
@@ -1187,14 +1191,36 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
});
}
},
+ view_arch_to_dom_node: function(arch) {
+ // Historic mess for views arch
+ //
+ // server:
+ // -> got xml as string
+ // -> parse to xml and manipulate domains and contexts
+ // -> convert to json
+ // client:
+ // -> got view as json
+ // -> convert back to xml as string
+ // -> parse it as xml doc (manipulate button@type for IE)
+ // -> convert back to string
+ // -> parse it as dom element with jquery
+ // -> for each widget, convert node to json
+ //
+ // Wow !!!
+ var xml = instance.web.json_node_to_xml(arch);
+
+ var doc = $.parseXML('<div class="oe_form">' + xml + '</div>');
+ $('button', doc).each(function() {
+ $(this).attr('data-button-type', $(this).attr('type'));
+ });
+ xml = instance.web.xml_to_str(doc);
+ return $(xml);
+ },
render_to: function($target) {
var self = this;
this.$target = $target;
- // TODO: I know this will save the world and all the kitten for a moment,
- // but one day, we will have to get rid of xml2json
- var xml = instance.web.json_node_to_xml(this.fvg.arch);
- this.$form = $('<div class="oe_form">' + xml + '</div>');
+ this.$form = this.view_arch_to_dom_node(this.fvg.arch);
this.process_version();
@@ -1863,6 +1889,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
template: 'WidgetButton',
init: function(field_manager, node) {
+ node.attrs.type = node.attrs['data-button-type'];
this._super(field_manager, node);
this.force_disabled = false;
this.string = (this.node.attrs.string || '').replace(/_/g, '');
@@ -2152,17 +2179,10 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
},
set_dimensions: function (height, width) {
- // remove width css property
- this.$el.css('width', '');
- // extract style (without width)
- var old_style = this.$el.attr('style');
- // jQuery doesn't understand/use !important
- var style = 'width:' + width + 'px !important;';
- if (old_style) {
- style += old_style
- }
- this.$el.attr('style', style);
- this.$el.css('minHeight', height);
+ this.$el.css({
+ width: width,
+ minHeight: height
+ });
},
commit_value: function() {
return $.when();
@@ -2386,7 +2406,7 @@ instance.web.DateTimeWidget = instance.web.Widget.extend({
self.$input.focus();
return;
}
- self.picker('setDate', self.value ? instance.web.auto_str_to_date(self.value) : new Date());
+ self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date());
self.$input_picker.show();
self.picker('show');
self.$input_picker.hide();
@@ -2639,22 +2659,18 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
}
});
+/**
+ The progressbar field expect a float from 0 to 100.
+*/
instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
template: 'FieldProgressBar',
- start: function() {
- this._super.apply(this, arguments);
+ render_value: function() {
this.$el.progressbar({
- value: this.get('value'),
+ value: this.get('value') || 0,
disabled: this.get("effective_readonly")
});
- },
- render_value: function() {
- var show_value = Number(this.get('value'));
- if (isNaN(show_value)) {
- show_value = 0;
- }
- var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0');
- this.$el.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
+ var formatted_value = instance.web.format_value(this.get('value') || 0, { type : 'float' });
+ this.$('span').html(formatted_value + '%');
}
});
@@ -3999,13 +4015,18 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
self._drop_shown = true;
});
self.tags = self.$text.textext()[0].tags();
- self.$text.focusout(function() {
- self.$text.trigger("setInputData", "");
- }).keydown(function(e) {
- if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
- self.$text.textext()[0].autocomplete().selectFromDropdown();
- }
- });
+ self.$text
+ .focusin(function () {
+ self.trigger('focused');
+ })
+ .focusout(function() {
+ self.$text.trigger("setInputData", "");
+ self.trigger('blurred');
+ }).keydown(function(e) {
+ if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
+ self.$text.textext()[0].autocomplete().selectFromDropdown();
+ }
+ });
},
set_value: function(value_) {
value_ = value_ || [];
@@ -4794,15 +4815,6 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as);
this.$el.find('.oe_form_binary_file_clear').click(this.on_clear);
},
- human_filesize : function(size) {
- var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
- var i = 0;
- while (size >= 1024) {
- size /= 1024;
- ++i;
- }
- return size.toFixed(2) + ' ' + units[i];
- },
on_file_change: function(e) {
var self = this;
var file_node = e.target;
@@ -4850,6 +4862,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
link.href = "data:application/octet-stream;base64," + value;
} else {
instance.web.blockUI();
+ var c = instance.webclient.crashmanager;
this.session.get_file({
url: '/web/binary/saveas_ajax',
data: {data: JSON.stringify({
@@ -4860,7 +4873,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
context: this.view.dataset.get_context()
})},
complete: instance.web.unblockUI,
- error: instance.webclient.crashmanager.on_rpc_error
+ error: c.rpc_error.bind(c)
});
ev.stopPropagation();
return false;
@@ -4919,7 +4932,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
this.binary_value = true;
this.internal_set_value(file_base64);
- var show_value = name + " (" + this.human_filesize(size) + ")";
+ var show_value = name + " (" + instance.web.human_size(size) + ")";
this.$el.find('input').eq(0).val(show_value);
this.set_filename(name);
},
@@ -4943,8 +4956,12 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
var field = this.name;
if (this.options.preview_image)
field = this.options.preview_image;
- url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
- this.view.dataset.model +'&id=' + id + '&field=' + field + '&t=' + (new Date().getTime());
+ url = this.session.url('/web/binary/image', {
+ model: this.view.dataset.model,
+ id: id,
+ field: field,
+ t: (new Date().getTime()),
+ });
} else {
url = this.placeholder;
}
@@ -5004,7 +5021,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
return _.map(this.get('value'), function (value) { return commands.link_to( value.id ); });
},
get_file_url: function (attachment) {
- return instance.origin + '/web/binary/saveas?session_id=' + this.session.session_id + '&model=ir.attachment&field=datas&filename_field=datas_fname&id=' + attachment['id'];
+ return this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'datas_fname', id: attachment['id']});
},
render_value: function () {
var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': this}));
@@ -5030,7 +5047,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
}
// block UI or not
- if(this.node.attrs.blockui) {
+ if(this.node.attrs.blockui>0) {
instance.web.blockUI();
}
@@ -5064,7 +5081,7 @@ instance.web.form.FieldOne2ManyBinaryMultiFiles = instance.web.form.AbstractFiel
},
on_file_loaded: function (event, result) {
// unblock UI
- if(this.node.attrs.blockui) {
+ if(this.node.attrs.blockui>0) {
instance.web.unblockUI();
}
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index 03a48297eca..db8c962efc7 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -503,12 +503,17 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
return this.reload_content();
},
reload_record: function (record) {
+ var self = this;
return this.dataset.read_ids(
[record.get('id')],
_.pluck(_(this.columns).filter(function (r) {
return r.tag === 'field';
}), 'name')
).done(function (records) {
+ if (!records[0]) {
+ self.records.remove(record);
+ return;
+ }
_(records[0]).each(function (value, key) {
record.set(key, value, {silent: true});
});
@@ -1557,9 +1562,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
}
});
-var DataGroup = instance.web.CallbackEnabled.extend({
+var DataGroup = instance.web.Class.extend({
init: function(parent, model, domain, context, group_by, level) {
- this._super(parent, null);
this.model = new instance.web.Model(model, context, domain);
this.group_by = group_by;
this.context = context;
@@ -2152,7 +2156,7 @@ instance.web.list.Boolean = instance.web.list.Column.extend({
* @private
*/
_format: function (row_data, options) {
- return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
+ return _.str.sprintf('<input type="checkbox" %s readonly="readonly"/>',
row_data[this.id].value ? 'checked="checked"' : '');
}
});
@@ -2164,20 +2168,24 @@ instance.web.list.Binary = instance.web.list.Column.extend({
*/
_format: function (row_data, options) {
var text = _t("Download");
- var download_url = _.str.sprintf(
- '/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d',
- instance.session.session_id, options.model, this.id, options.id);
- if (this.filename) {
- download_url += '&filename_field=' + this.filename;
- if (row_data[this.filename]) {
- text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
- row_data[this.filename].value, {type: 'char'}));
+ var value = row_data[this.id].value;
+ var download_url;
+ if (value && value.substr(0, 10).indexOf(' ') == -1) {
+ download_url = "data:application/octet-stream;base64," + value;
+ } else {
+ download_url = this.session.url('/web/binary/saveas', {model: options.model, field: this.id, id: options.id});
+ if (this.filename) {
+ download_url += '&filename_field=' + this.filename;
}
}
- return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
+ if (this.filename && row_data[this.filename]) {
+ text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
+ row_data[this.filename].value, {type: 'char'}));
+ }
+ return _.template('<a href="<%-href%>"><%-text%></a> (<%-size%>)', {
text: text,
href: download_url,
- size: row_data[this.id].value
+ size: instance.web.binary_to_binsize(value),
});
}
});
@@ -2217,7 +2225,7 @@ instance.web.list.Many2OneButton = instance.web.list.Column.extend({
});
instance.web.list.Many2Many = instance.web.list.Column.extend({
_format: function (row_data, options) {
- if (row_data[this.id].value) {
+ if (!_.isEmpty(row_data[this.id].value)) {
// If value, use __display version for printing
row_data[this.id] = row_data[this.id + '__display'];
}
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index 37ce8c4f8b4..8bcf8b5d036 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -117,13 +117,6 @@ openerp.web.list_editable = function (instance) {
e.preventDefault();
self.cancel_edition();
});
- this.$el
- .off('click', 'tbody td:not(.oe_list_field_cell)')
- .on('click', 'tbody td:not(.oe_list_field_cell)', function () {
- if (!self.editor.is_editing()) {
- self.start_edition();
- }
- });
this.editor.destroy();
// Editor is not restartable due to formview not being
// restartable
@@ -164,13 +157,23 @@ openerp.web.list_editable = function (instance) {
* @returns {$.Deferred}
*/
ensure_saved: function () {
- var self = this;
- return this.saving_mutex.exec(function() {
- if (!self.editor.is_editing()) {
- return $.when();
- }
- return self.save_edition();
- });
+ return this.save_edition();
+ },
+ /**
+ * Builds a record with the provided id (``false`` for a creation),
+ * setting all columns with ``false`` value so code which relies on
+ * having an actual value behaves correctly
+ *
+ * @param {*} id
+ * @return {instance.web.list.Record}
+ */
+ make_empty_record: function (id) {
+ var attrs = {id: id};
+ _(this.columns).chain()
+ .filter(function (x) { return x.tag === 'field'})
+ .pluck('name')
+ .each(function (field) { attrs[field] = false; });
+ return new instance.web.list.Record(attrs);
},
/**
* Set up the edition of a record of the list view "inline"
@@ -186,12 +189,7 @@ openerp.web.list_editable = function (instance) {
if (record) {
item = record.attributes;
} else {
- var attrs = {id: false};
- _(this.columns).chain()
- .filter(function (x) { return x.tag === 'field'})
- .pluck('name')
- .each(function (field) { attrs[field] = false; });
- record = new instance.web.list.Record(attrs);
+ record = this.make_empty_record(false);
this.records.add(record, {
at: this.prepends_on_create() ? 0 : null});
}
@@ -269,29 +267,34 @@ openerp.web.list_editable = function (instance) {
*/
save_edition: function () {
var self = this;
- return this.with_event('save', {
- editor: this.editor,
- form: this.editor.form,
- cancel: false
- }, function () {
- return this.editor.save().then(function (attrs) {
- var created = false;
- var record = self.records.get(attrs.id);
- if (!record) {
- // new record
- created = true;
- record = self.records.find(function (r) {
- return !r.get('id');
- }).set('id', attrs.id);
- }
- // onwrite callback could be altering & reloading the
- // record which has *just* been saved, so first perform all
- // onwrites then do a final reload of the record
- return self.handle_onwrite(record)
- .then(function () {
- return self.reload_record(record); })
- .then(function () {
- return { created: created, record: record }; });
+ return self.saving_mutex.exec(function() {
+ if (!self.editor.is_editing()) {
+ return $.when();
+ }
+ return self.with_event('save', {
+ editor: self.editor,
+ form: self.editor.form,
+ cancel: false
+ }, function () {
+ return self.editor.save().then(function (attrs) {
+ var created = false;
+ var record = self.records.get(attrs.id);
+ if (!record) {
+ // new record
+ created = true;
+ record = self.records.find(function (r) {
+ return !r.get('id');
+ }).set('id', attrs.id);
+ }
+ // onwrite callback could be altering & reloading the
+ // record which has *just* been saved, so first perform all
+ // onwrites then do a final reload of the record
+ return self.handle_onwrite(record)
+ .then(function () {
+ return self.reload_record(record); })
+ .then(function () {
+ return { created: created, record: record }; });
+ });
});
});
},
@@ -391,7 +394,7 @@ openerp.web.list_editable = function (instance) {
if (!record) {
// insert after the source record
var index = this.records.indexOf(source_record) + 1;
- record = new instance.web.list.Record({id: id});
+ record = this.make_empty_record(id);
this.records.add(record, {at: index});
this.dataset.ids.splice(index, 0, id);
}
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index c779ac115c3..c9c1f918114 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -213,8 +213,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
if (run_action) {
this.null_action();
action_loaded = this.do_action(state.action);
- instance.webclient.menu.has_been_loaded.done(function() {
- instance.webclient.menu.open_action(state.action);
+ $.when(action_loaded || null).done(function() {
+ instance.webclient.menu.has_been_loaded.done(function() {
+ if (self.inner_action && self.inner_action.id) {
+ instance.webclient.menu.open_action(self.inner_action.id);
+ }
+ });
});
}
}
@@ -397,6 +401,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}).done(function(res) {
action = _.clone(action);
action.context = res.context;
+ var c = instance.webclient.crashmanager;
self.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
@@ -407,7 +412,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
self.dialog_stop();
},
- error: instance.webclient.crashmanager.on_rpc_error
+ error: c.rpc_error.bind(c)
})
});
},
@@ -504,7 +509,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
.parent().addClass('active');
- r = $.when(view_promise).done(function () {
+ return $.when(view_promise).done(function () {
_.each(_.keys(self.views), function(view_name) {
var controller = self.views[view_name].controller;
if (controller) {
@@ -520,7 +525,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
});
self.trigger('switch_mode', view_type, no_store, view_options);
});
- return r;
},
do_create_view: function(view_type) {
// Lazy loading of views
@@ -1098,11 +1102,11 @@ instance.web.Sidebar = instance.web.Widget.extend({
on_attachments_loaded: function(attachments) {
var self = this;
var items = [];
- var prefix = this.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id=';
+ var prefix = this.session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'name'});
_.each(attachments,function(a) {
a.label = a.name;
if(a.type === "binary") {
- a.url = prefix + a.id + '&t=' + (new Date().getTime());
+ a.url = prefix + '&id=' + a.id + '&t=' + (new Date().getTime());
}
});
self.items['files'] = attachments;
@@ -1277,6 +1281,27 @@ instance.web.View = instance.web.Widget.extend({
do_hide: function () {
this.$el.hide();
},
+ is_active: function () {
+ var manager = this.getParent();
+ return !manager || !manager.active_view
+ || manager.views[manager.active_view].controller === this;
+ }, /**
+ * Wraps fn to only call it if the current view is the active one. If the
+ * current view is not active, doesn't call fn.
+ *
+ * fn can not return anything, as a non-call to fn can't return anything
+ * either
+ *
+ * @param {Function} fn function to wrap in the active guard
+ */
+ guard_active: function (fn) {
+ var self = this;
+ return function () {
+ if (self.is_active()) {
+ fn.apply(self, arguments);
+ }
+ }
+ },
do_push_state: function(state) {
if (this.getParent() && this.getParent().do_push_state) {
this.getParent().do_push_state(state);
@@ -1384,14 +1409,16 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) {
} else {
return r + '/>';
}
-}
+};
instance.web.xml_to_str = function(node) {
- if (window.ActiveXObject) {
+ if (window.XMLSerializer) {
+ return (new XMLSerializer()).serializeToString(node);
+ } else if (window.ActiveXObject) {
return node.xml;
} else {
- return (new XMLSerializer()).serializeToString(node);
+ throw new Error("Could not serialize XML");
}
-}
+};
instance.web.str_to_xml = function(s) {
if (window.DOMParser) {
var dp = new DOMParser();
diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml
index 553543f9d0a..a2e7a59a989 100644
--- a/addons/web/static/src/xml/base.xml
+++ b/addons/web/static/src/xml/base.xml
@@ -152,6 +152,32 @@
</tr>
</table>
</form>
+ <form id="db_duplicate" name="duplicate_db_form" style="display: none;">
+ <div class="oe_view_manager oe_view_manager_current">
+ <div class="oe_view_manager_header" style="padding: 8px;">
+ <div class="oe_header_row">
+ <h2 class="oe_view_title">
+ <span class="oe_view_title_text oe_breadcrumb_title">Duplicate Database</span>
+ </h2>
+ <button type="submit" class="oe_button oe_highlight db_duplicate">Duplicate</button>
+ </div>
+ </div>
+ </div>
+ <table align="center" class="db_option_table">
+ <tr>
+ <td><label for="super_admin_pwd">Master password:</label></td>
+ <td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td>
+ </tr>
+ <tr>
+ <td><label for="db_original_name">Original database name:</label></td>
+ <td><input type="text" name="db_original_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" autofocus="true"/></td>
+ </tr>
+ <tr>
+ <td><label for="db_name">New database name:</label></td>
+ <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" /></td>
+ </tr>
+ </table>
+ </form>
<form id="db_drop" name="drop_db_form" style="display: none; ">
<div class="oe_view_manager oe_view_manager_current">
<div class="oe_view_manager_header" style="padding: 8px;">
@@ -279,6 +305,7 @@
<div class="oe_secondary_menu_section">Database Management</div>
<ul class="oe_secondary_submenu">
<li><a href="#db_create">Create</a></li>
+ <li><a href="#db_duplicate">Duplicate</a></li>
<li><a href="#db_drop">Drop</a></li>
<li><a href="#db_backup">Backup</a></li>
<li><a href="#db_restore">Restore</a></li>
@@ -1182,26 +1209,28 @@
</t>
<t t-name="FieldBinaryFileUploader.files">
<div class="oe_attachments">
- <t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
- <div class="oe_attachment">
- <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
- <span class="oe_fileuploader_in_process">...Upload in progress...</span>
- <t t-raw="file.name || file.filename"/>
- </span>
- <a t-if="(!file.upload or file.percent_loaded&gt;=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
- <t t-raw="file.name || file.filename"/>
- </a>
- <t t-if="(!file.upload or file.percent_loaded&gt;=100)">
- <a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
- </t>
- </div>
- </t>
- <t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
- <div>
- <a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
- <t t-raw="file.name || file.filename"/>
- </a>
- </div>
+ <t t-if="widget.get('value')">
+ <t t-if="!widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
+ <div class="oe_attachment">
+ <span t-if="(file.upload or file.percent_loaded&lt;100)" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}" t-attf-name="{file.name || file.filename}">
+ <span class="oe_fileuploader_in_process">...Upload in progress...</span>
+ <t t-raw="file.name || file.filename"/>
+ </span>
+ <a t-if="(!file.upload or file.percent_loaded&gt;=100)" t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
+ <t t-raw="file.name || file.filename"/>
+ </a>
+ <t t-if="(!file.upload or file.percent_loaded&gt;=100)">
+ <a class="oe_right oe_delete oe_e" title="Delete this file" t-attf-data-id="{file.id}">[</a>
+ </t>
+ </div>
+ </t>
+ <t t-if="widget.get('effective_readonly')" t-foreach="widget.get('value')" t-as="file">
+ <div>
+ <a t-att-href="file.url" t-attf-title="{(file.name || file.filename) + (file.date?' \n('+file.date+')':'' )}">
+ <t t-raw="file.name || file.filename"/>
+ </a>
+ </div>
+ </t>
</t>
</div>
</t>
@@ -1512,17 +1541,19 @@
<h3><span class="oe_i">M</span> Custom Filters</h3>
<ul class="oe_searchview_custom_list"/>
<div class="oe_searchview_custom">
- <h4>Save current filter</h4>
- <form>
- <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
- <p><input id="oe_searchview_custom_public" type="checkbox"/>
- <label for="oe_searchview_custom_public">Share with all users</label></p>
- <button>Save</button>
- </form>
+ <h4>Save current filter</h4>
+ <form>
+ <p><input id="oe_searchview_custom_input" placeholder="Filter name"/></p>
+ <p>
+ <input id="oe_searchview_custom_public" type="checkbox"/>
+ <label for="oe_searchview_custom_public">Share with all users</label>
+ <input id="oe_searchview_custom_default" type="checkbox"/>
+ <label for="oe_searchview_custom_default">Use by default</label>
+ </p>
+ <button>Save</button>
+ </form>
</div>
</div>
- <div>
- </div>
</div>
<div t-name="SearchView.advanced" class="oe_searchview_advanced">