summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/chrome.js295
-rw-r--r--addons/web/static/src/js/core.js6
-rw-r--r--addons/web/static/src/js/data.js204
-rw-r--r--addons/web/static/src/js/formats.js7
-rw-r--r--addons/web/static/src/js/openerpframework.js69
-rw-r--r--addons/web/static/src/js/pyeval.js60
-rw-r--r--addons/web/static/src/js/search.js32
-rw-r--r--addons/web/static/src/js/testing.js114
-rw-r--r--addons/web/static/src/js/view_form.js310
-rw-r--r--addons/web/static/src/js/view_list.js47
-rw-r--r--addons/web/static/src/js/view_list_editable.js29
-rw-r--r--addons/web/static/src/js/views.js144
12 files changed, 767 insertions, 550 deletions
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index f019972fd14..9b87d416206 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -256,7 +256,7 @@ instance.web.CrashManager = instance.web.Class.extend({
new (handler)(this, error).display();
return;
}
- if (error.data.name === "openerp.addons.web.session SessionExpiredException") {
+ if (error.data.name === "openerp.http.SessionExpiredException") {
this.show_warning({type: "Session Expired", data: { message: _t("Your OpenERP session expired. Please refresh the current web page.") }});
return;
}
@@ -442,10 +442,10 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
self.$el.html(QWeb.render("DatabaseManager", { widget : self }));
$('.oe_user_menu_placeholder').append(QWeb.render("DatabaseManager.user_menu",{ widget : self }));
$('.oe_secondary_menus_container').append(QWeb.render("DatabaseManager.menu",{ widget : self }));
- $('ul.oe_secondary_submenu > li:first').addClass('oe_active');
+ $('ul.oe_secondary_submenu > li:first').addClass('active');
$('ul.oe_secondary_submenu > li').bind('click', function (event) {
var menuitem = $(this);
- menuitem.addClass('oe_active').siblings().removeClass('oe_active');
+ menuitem.addClass('active').siblings().removeClass('active');
var form_id =menuitem.find('a').attr('href');
$(form_id).show().siblings().hide();
event.preventDefault();
@@ -476,19 +476,6 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
this._super();
},
/**
- * Converts a .serializeArray() result into a dict. Does not bother folding
- * multiple identical keys into an array, last key wins.
- *
- * @param {Array} array
- */
- to_object: function (array) {
- var result = {};
- _(array).each(function (record) {
- result[record.name] = record.value;
- });
- return result;
- },
- /**
* Blocks UI and replaces $.unblockUI by a noop to prevent third parties
* from unblocking the UI
*/
@@ -525,25 +512,11 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
var self = this;
var fields = $(form).serializeArray();
self.rpc("/web/database/create", {'fields': fields}).done(function(result) {
- var form_obj = self.to_object(fields);
- var client_action = {
- type: 'ir.actions.client',
- tag: 'login',
- params: {
- 'db': form_obj['db_name'],
- 'login': 'admin',
- 'password': form_obj['create_admin_pwd'],
- 'login_successful': function() {
- var url = '/web?db=' + form_obj['db_name'];
- if (self.session.debug) {
- url += '&debug';
- }
- instance.web.redirect(url);
- },
- },
- _push_me: false,
- };
- self.do_action(client_action);
+ if (result) {
+ instance.web.redirect('/web');
+ } else {
+ alert("Failed to create database");
+ }
});
},
do_duplicate: function(form) {
@@ -641,156 +614,20 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
},
do_exit: function () {
this.$el.remove();
- instance.webclient.show_login();
+ instance.web.redirect('/web');
}
});
instance.web.client_actions.add("database_manager", "instance.web.DatabaseManager");
-instance.web.Login = instance.web.Widget.extend({
- template: "Login",
- remember_credentials: true,
- events: {
- 'change input[name=db],select[name=db]': function(ev) {
- this.set('database_selector', $(ev.currentTarget).val());
- },
- },
-
- init: function(parent, action) {
- this._super(parent);
- this.has_local_storage = typeof(localStorage) != 'undefined';
- this.db_list = null;
- this.selected_db = null;
- this.selected_login = null;
- this.params = action.params || {};
- if (_.isEmpty(this.params)) {
- this.params = $.bbq.getState(true);
- }
- if (action && action.params && action.params.db) {
- this.params.db = action.params.db;
- } else if ($.deparam.querystring().db) {
- this.params.db = $.deparam.querystring().db;
- }
- if (this.params.db) {
- this.selected_db = this.params.db;
- }
-
- if (this.params.login_successful) {
- this.on('login_successful', this, this.params.login_successful);
- }
- // some cleanup to remove any trace of that last login feature
- if (typeof(localStorage) != 'undefined') {
- var toRemove = [];
- _.each(_.range(localStorage.length), function(i) {
- var key = localStorage.key(i);
- if (key.match(/^.*?\|last_password$/)) {
- toRemove.push(key);
- }
- });
- _.each(toRemove, function(k) {
- localStorage.removeItem(k);
- });
- }
- },
- start: function() {
- var self = this;
- self.$el.find("form").submit(self.on_submit);
- self.$el.find('.oe_login_manage_db').click(function() {
- self.do_action("database_manager");
- });
- self.on('change:database_selector', this, function() {
- this.database_selected(this.get('database_selector'));
- });
- var d = $.when();
- if ($.param.fragment().token) {
- self.params.token = $.param.fragment().token;
- }
- // used by dbmanager.do_create via internal client action
- if (self.params.db && self.params.login && self.params.password) {
- d = self.do_login(self.params.db, self.params.login, self.params.password);
- } else {
- d = self.rpc("/web/database/get_list", {})
- .done(self.on_db_loaded)
- .fail(self.on_db_failed)
- .always(function() {
- if (self.selected_db && self.has_local_storage && self.remember_credentials) {
- self.$("[name=login]").val(localStorage.getItem(self.selected_db + '|last_login') || '');
- }
- });
- }
- return d;
- },
- database_selected: function(db) {
- var params = $.deparam.querystring();
- params.db = db;
- this.$('.oe_login_dbpane').empty().text(_t('Loading...'));
- this.$('[name=login], [name=password]').prop('readonly', true);
- instance.web.redirect('/web?' + $.param(params));
- },
- on_db_loaded: function (result) {
- var self = this;
- this.db_list = result;
- if (!this.selected_db) {
- this.selected_db = result[0];
- }
- 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");
- } else if(this.db_list.length === 1) {
- this.$('div.oe_login_dbpane').hide();
- } else {
- this.$('div.oe_login_dbpane').show();
- }
- },
- on_db_failed: function (error, event) {
- if (error.data.name === 'openerp.exceptions.AccessDenied') {
- event.preventDefault();
- }
- },
- on_submit: function(ev) {
- if(ev) {
- ev.preventDefault();
- }
- var db = this.$("form [name=db]").val();
- if (!db) {
- this.do_warn(_t("Login"), _t("No database selected !"));
- return false;
- }
- var login = this.$("form input[name=login]").val();
- var password = this.$("form input[name=password]").val();
+instance.web.login = function() {
+ instance.web.redirect('/web/login');
+};
+instance.web.client_actions.add("login", "instance.web.login");
- this.do_login(db, login, password);
- },
- /**
- * Performs actual login operation, and UI-related stuff
- *
- * @param {String} db database to log in
- * @param {String} login user login
- * @param {String} password user password
- */
- do_login: function (db, login, password) {
- var self = this;
- self.hide_error();
- self.$(".oe_login_pane").fadeOut("slow");
- return this.session.session_authenticate(db, login, password).then(function() {
- if (self.has_local_storage && self.remember_credentials) {
- localStorage.setItem(db + '|last_login', login);
- }
- self.trigger('login_successful');
- }, function () {
- self.$(".oe_login_pane").fadeIn("fast", function() {
- self.show_error(_t("Invalid username or password"));
- });
- });
- },
- show_error: function(message) {
- this.$el.addClass("oe_login_invalid");
- this.$(".oe_login_error_message").text(message);
- },
- hide_error: function() {
- this.$el.removeClass('oe_login_invalid');
- },
-});
-instance.web.client_actions.add("login", "instance.web.Login");
+instance.web.logout = function() {
+ instance.web.redirect('/web/session/logout');
+};
+instance.web.client_actions.add("logout", "instance.web.logout");
/**
@@ -868,7 +705,7 @@ instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
*/
instance.web.Home = function(parent, action) {
var url = '/' + (window.location.search || '');
- instance.web.redirect(url, action.params && action.params.wait);
+ instance.web.redirect(url, action && action.params && action.params.wait);
};
instance.web.client_actions.add("home", "instance.web.Home");
@@ -969,7 +806,7 @@ instance.web.Menu = instance.web.Widget.extend({
this.needaction_data = data;
_.each(this.needaction_data, function (item, menu_id) {
var $item = self.$secondary_menus.find('a[data-menu="' + menu_id + '"]');
- $item.find('.oe_menu_counter').remove();
+ $item.find('.badge').remove();
if (item.needaction_counter && item.needaction_counter > 0) {
$item.append(QWeb.render("Menu.needaction_counter", { widget : item }));
}
@@ -982,8 +819,8 @@ instance.web.Menu = instance.web.Widget.extend({
reflow: function() {
var self = this;
this.$el.height('auto').show();
- var $more_container = this.$('.oe_menu_more_container').hide();
- var $more = this.$('.oe_menu_more');
+ var $more_container = this.$('#menu_more_container').hide();
+ var $more = this.$('#menu_more');
$more.children('li').insertBefore($more_container);
var $toplevel_items = this.$el.children('li').not($more_container).hide();
$toplevel_items.each(function() {
@@ -1026,8 +863,8 @@ instance.web.Menu = instance.web.Widget.extend({
}
// Activate current main menu
- this.$el.find('.oe_active').removeClass('oe_active');
- $main_menu.addClass('oe_active');
+ this.$el.find('.active').removeClass('active');
+ $main_menu.addClass('active');
// Show current sub menu
this.$secondary_menus.find('.oe_secondary_menu').hide();
@@ -1037,13 +874,13 @@ instance.web.Menu = instance.web.Widget.extend({
this.$secondary_menus.parent('.oe_leftbar').toggle(!!$sub_menu.children().length);
// Activate current menu item and show parents
- this.$secondary_menus.find('.oe_active').removeClass('oe_active');
+ this.$secondary_menus.find('.active').removeClass('active');
if ($main_menu !== $clicked_menu) {
$clicked_menu.parents().show();
if ($clicked_menu.is('.oe_menu_toggler')) {
$clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggle();
} else {
- $clicked_menu.parent().addClass('oe_active');
+ $clicked_menu.parent().addClass('active');
}
}
},
@@ -1129,7 +966,7 @@ instance.web.Menu = instance.web.Widget.extend({
},
on_menu_click: function(ev) {
ev.preventDefault();
- var needaction = $(ev.target).is('div.oe_menu_counter');
+ var needaction = $(ev.target).is('div#menu_counter');
this.menu_click($(ev.currentTarget).data('menu'), needaction);
},
});
@@ -1143,7 +980,7 @@ instance.web.UserMenu = instance.web.Widget.extend({
start: function() {
var self = this;
this._super.apply(this, arguments);
- this.$el.on('click', '.oe_dropdown_menu li a[data-menu]', function(ev) {
+ this.$el.on('click', '.dropdown-menu li a[data-menu]', function(ev) {
ev.preventDefault();
var f = self['on_menu_' + $(this).data('menu')];
if (f) {
@@ -1223,9 +1060,30 @@ instance.web.UserMenu = instance.web.Widget.extend({
},
});
+instance.web.FullscreenWidget = instance.web.Widget.extend({
+ /**
+ * Widgets extending the FullscreenWidget will be displayed fullscreen,
+ * and will have a fixed 1:1 zoom level on mobile devices.
+ */
+ start: function(){
+ if(!$('#oe-fullscreenwidget-viewport').length){
+ $('head').append('<meta id="oe-fullscreenwidget-viewport" name="viewport" content="initial-scale=1.0; maximum-scale=1.0; user-scalable=0;">');
+ }
+ instance.webclient.set_content_full_screen(true);
+ return this._super();
+ },
+ destroy: function(){
+ instance.webclient.set_content_full_screen(false);
+ $('#oe-fullscreenwidget-viewport').remove();
+ return this._super();
+ },
+
+});
+
instance.web.Client = instance.web.Widget.extend({
init: function(parent, origin) {
instance.client = instance.webclient = this;
+ this.client_options = {};
this._super(parent);
this.origin = origin;
},
@@ -1284,7 +1142,7 @@ instance.web.Client = instance.web.Widget.extend({
self.action_manager.appendTo(self.$('.oe_application'));
},
toggle_bars: function(value) {
- this.$('tr:has(td.oe_topbar),.oe_leftbar').toggle(value);
+ this.$('tr:has(td.navbar),.oe_leftbar').toggle(value);
},
has_uncommitted_changes: function() {
return false;
@@ -1296,8 +1154,11 @@ instance.web.WebClient = instance.web.Client.extend({
events: {
'click .oe_logo_edit_admin': 'logo_edit'
},
- init: function(parent) {
+ init: function(parent, client_options) {
this._super(parent);
+ if (client_options) {
+ _.extend(this.client_options, client_options);
+ }
this._current_state = null;
this.menu_dm = new instance.web.DropMisordered();
this.action_mutex = new $.Mutex();
@@ -1311,11 +1172,13 @@ instance.web.WebClient = instance.web.Client.extend({
if (jQuery.deparam !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
self.to_kitten();
}
- if (!self.session.session_is_valid()) {
- self.show_login();
- } else {
+ if (self.session.session_is_valid()) {
self.show_application();
}
+ if (self.client_options.action) {
+ self.action_manager.do_action(self.client_options.action);
+ delete(self.client_options.action);
+ }
});
},
to_kitten: function() {
@@ -1365,25 +1228,6 @@ instance.web.WebClient = instance.web.Client.extend({
});
};
},
- show_login: function() {
- this.toggle_bars(false);
-
- var state = $.bbq.getState(true);
- var action = {
- type: 'ir.actions.client',
- tag: 'login',
- _push_me: false,
- };
-
- this.action_manager.do_action(action);
- this.action_manager.inner_widget.on('login_successful', this, function() {
- if ('redirect' in state) {
- openerp.web.redirect(state.redirect);
- } else {
- this.show_application(); // will load the state we just pushed
- }
- });
- },
show_application: function() {
var self = this;
self.toggle_bars(true);
@@ -1398,6 +1242,10 @@ instance.web.WebClient = instance.web.Client.extend({
self.bind_hashchange();
self.set_title();
self.check_timezone();
+ if (self.client_options.action_post_login) {
+ self.action_manager.do_action(self.client_options.action_post_login);
+ delete(self.client_options.action_post_login);
+ }
},
update_logo: function() {
var img = this.session.url('/web/binary/company_logo');
@@ -1478,11 +1326,7 @@ instance.web.WebClient = instance.web.Client.extend({
on_logout: function() {
var self = this;
if (!this.has_uncommitted_changes()) {
- this.session.session_logout().done(function () {
- $(window).unbind('hashchange', self.on_hashchange);
- self.do_push_state({});
- window.location.reload();
- });
+ self.action_manager.do_action('logout');
}
},
bind_hashchange: function() {
@@ -1492,10 +1336,15 @@ instance.web.WebClient = instance.web.Client.extend({
var state = $.bbq.getState(true);
if (_.isEmpty(state) || state.action == "login") {
self.menu.has_been_loaded.done(function() {
- var first_menu_id = self.menu.$el.find("a:first").data("menu");
- if(first_menu_id) {
- self.menu.menu_click(first_menu_id);
- }
+ new instance.web.Model("res.users").call("read", [self.session.uid, ["action_id"]]).done(function(data) {
+ var first_menu_id = self.menu.$el.find("a:first").data("menu");
+ if(first_menu_id)
+ self.menu.menu_click(first_menu_id);
+
+ if(data.action_id) {
+ self.action_manager.do_action(data.action_id[0]);
+ }
+ });
});
} else {
$(window).trigger('hashchange');
diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js
index 37a217a8664..b92ca1d3ba5 100644
--- a/addons/web/static/src/js/core.js
+++ b/addons/web/static/src/js/core.js
@@ -252,7 +252,7 @@ instance.web.Session.include( /** @lends instance.web.Session# */{
return !!this.uid;
},
/**
- * The session is validated either by login or by restoration of a previous session
+ * The session is validated by restoration of a previous session
*/
session_authenticate: function() {
var self = this;
@@ -561,6 +561,10 @@ instance.web.TranslationDataBase.include({
});
/** Custom jQuery plugins */
+$.browser = $.browser || {};
+if(navigator.appVersion.indexOf("MSIE") !== -1) {
+ $.browser.msie = 1;
+}
$.fn.getAttributes = function() {
var o = {};
if (this.length) {
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index 04f657d91f7..cc595c30688 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -129,11 +129,14 @@ instance.web.Query = instance.web.Class.extend({
if (_.isEmpty(grouping) && !ctx['group_by_no_leaf']) {
return null;
}
+ var raw_fields = _.map(grouping.concat(this._fields || []), function (field) {
+ return field.split(':')[0];
+ });
var self = this;
return this._model.call('read_group', {
groupby: grouping,
- fields: _.uniq(grouping.concat(this._fields || [])),
+ fields: _.uniq(raw_fields),
domain: this._model.domain(this._filter),
context: ctx,
offset: this._offset,
@@ -231,14 +234,16 @@ instance.web.QueryGroup = instance.web.Class.extend({
this.model = new instance.web.Model(
model, fixed_group.__context, fixed_group.__domain);
- var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0;
+ var raw_field = grouping_field && grouping_field.split(':')[0];
+ var group_size = fixed_group[raw_field + '_count'] || fixed_group.__count || 0;
var leaf_group = fixed_group.__context.group_by.length === 0;
+
this.attributes = {
folded: !!(fixed_group.__fold),
grouped_on: grouping_field,
// if terminal group (or no group) and group_by_no_leaf => use group.__count
length: group_size,
- value: fixed_group[grouping_field],
+ value: fixed_group[raw_field],
// A group is open-able if it's not a leaf in group_by_no_leaf mode
has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']),
@@ -447,6 +452,9 @@ instance.web.DataSet = instance.web.Class.extend(instance.web.PropertiesMixin,
* @returns {$.Deferred}
*/
read_ids: function (ids, fields, options) {
+ if (_.isEmpty(ids))
+ return $.Deferred().resolve([]);
+
options = options || {};
// TODO: reorder results to match ids list
return this._model.call('read',
@@ -743,7 +751,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend({
});
},
get_domain: function (other_domain) {
- this._model.domain(other_domain);
+ return this._model.domain(other_domain);
},
alter_ids: function (ids) {
this._super(ids);
@@ -780,6 +788,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
this._super.apply(this, arguments);
this.reset_ids([]);
this.last_default_get = {};
+ this.running_reads = [];
},
default_get: function(fields, options) {
var self = this;
@@ -846,6 +855,9 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
this.to_write = [];
this.cache = [];
this.delete_all = false;
+ _.each(_.clone(this.running_reads), function(el) {
+ el.reject();
+ });
},
read_ids: function (ids, fields, options) {
var self = this;
@@ -861,10 +873,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
to_get.push(id);
}
});
- var completion = $.Deferred();
var return_records = function() {
var records = _.map(ids, function(id) {
- return _.extend({}, _.detect(self.cache, function(c) {return c.id === id;}).values, {"id": id});
+ var c = _.find(self.cache, function(c) {return c.id === id;});
+ return _.isUndefined(c) ? c : _.extend({}, c.values, {"id": id});
});
if (self.debug_mode) {
if (_.include(records, undefined)) {
@@ -896,10 +908,20 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
}, 0);
});
}
- completion.resolve(records);
+ return $.when(records);
};
if(to_get.length > 0) {
- var rpc_promise = this._super(to_get, fields, options).done(function(records) {
+ var def = $.Deferred();
+ self.running_reads.push(def);
+ def.always(function() {
+ self.running_reads = _.without(self.running_reads, def);
+ });
+ this._super(to_get, fields, options).then(function() {
+ def.resolve.apply(def, arguments);
+ }, function() {
+ def.reject.apply(def, arguments);
+ });
+ return def.then(function(records) {
_.each(records, function(record, index) {
var id = to_get[index];
var cached = _.detect(self.cache, function(x) {return x.id === id;});
@@ -910,13 +932,11 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
cached.values = _.defaults(_.clone(cached.values), record);
}
});
- return_records();
+ return return_records();
});
- $.when(rpc_promise).fail(function() {completion.reject();});
} else {
- return_records();
+ return return_records();
}
- return completion.promise();
},
/**
* Invalidates caching of a record in the dataset to ensure the next read
@@ -928,6 +948,15 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
* @param {Object} id record to remove from the BDS's cache
*/
evict_record: function (id) {
+ // Don't evict records which haven't yet been saved: there is no more
+ // recent data on the server (and there potentially isn't any data),
+ // and this breaks the assumptions of other methods (that the data
+ // for new and altered records is both in the cache and in the to_write
+ // or to_create collection)
+ if (_(this.to_create.concat(this.to_write)).find(function (record) {
+ return record.id === id; })) {
+ return;
+ }
for(var i=0, len=this.cache.length; i<len; ++i) {
var record = this.cache[i];
// if record we call the button upon is in the cache
@@ -1090,6 +1119,157 @@ instance.web.DropMisordered = instance.web.Class.extend({
}
});
+instance.web.SimpleIndexedDB = instance.web.Class.extend({
+ /**
+ * A simple wrapper around IndexedDB that provides an asynchronous
+ * localStorage-like Key-Value api that persists between browser
+ * restarts.
+ *
+ * It will not work if the browser doesn't support a recent version
+ * of IndexedDB, and it may fail if the user refuses db access.
+ *
+ * All instances of SimpleIndexedDB will by default refer to the same
+ * IndexedDB database, if you want to pick another one, use the 'name'
+ * option on instanciation.
+ */
+ init: function(opts){
+ var self = this;
+ var opts = opts || {};
+
+ this.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
+ this.db = undefined;
+ this._ready = new $.Deferred();
+
+ if(this.indexedDB && this.indexedDB.open){
+ var request = this.indexedDB.open( opts.name || "SimpleIndexedDB" ,1);
+
+ request.onerror = function(event){
+ self.db = null;
+ self._ready.reject(event.target.error);
+ };
+
+ request.onsuccess = function(event){
+ self.db = request.result;
+ self._ready.resolve();
+ };
+
+ request.onupgradeneeded = function(event){
+ self.db = event.target.result;
+
+ var objectStore = self.db.createObjectStore("keyvals", { keyPath:"key" });
+ self._ready.resolve();
+ };
+ }else{
+ this.db = null;
+ this._ready.reject({type:'UnknownError', message:'IndexedDB is not supported by your Browser'});
+ }
+ },
+
+ /**
+ * returns true if the browser supports the necessary IndexedDB API
+ * (but doesn't tell if the db can be created, check ready() for that)
+ */
+ isSupportedByBrowser: function(){
+ return this.indexedDB && this.indexedDB.open;
+ },
+
+ /**
+ * returns a deferred that resolves when the db has been successfully
+ * initialized. done/failed callbacks are optional.
+ */
+ ready: function(done,failed){
+ this._ready.then(done,failed);
+ return this._ready.promise();
+ },
+
+ /**
+ * fetches the value associated to 'key' in the db. if the key doesn't
+ * exists, it returns undefined. The returned value is provided as a
+ * deferred, or as the first parameter of the optional 'done' callback.
+ */
+ getItem: function(key,done,failed){
+ var self = this;
+ var def = new $.Deferred();
+ def.then(done,failed);
+
+ this._ready.then(function(){
+ var request = self.db.transaction(["keyvals"],"readwrite")
+ .objectStore("keyvals")
+ .get(key);
+
+ request.onsuccess = function(){
+ def.resolve(request.result ? request.result.value : undefined);
+ };
+
+ request.onerror = function(event){
+ def.reject(event.target.error);
+ };
+ },function(){
+ def.reject({type:'UnknownError', message:'Could not initialize the IndexedDB database'});
+ });
+
+ return def.promise();
+ },
+
+ /**
+ * Associates a value to 'key' in the db, overwriting previous value if
+ * necessary. Contrary to localStorage, the value is not limited to strings and
+ * can be any javascript object, even with cyclic references !
+ *
+ * Be sure to check for failure as the user may refuse to have data localy stored.
+ */
+ setItem: function(key,value,done,failed){
+ var self = this;
+ var def = new $.Deferred();
+ def.then(done,failed);
+
+ this._ready.then(function(){
+ var request = self.db.transaction(["keyvals"],"readwrite")
+ .objectStore("keyvals")
+ .put( {key:key, value:value} );
+
+ request.onsuccess = function(){
+ def.resolve();
+ };
+
+ request.onerror = function(event){
+ def.reject(event.target.error);
+ };
+ },function(){
+ def.reject({type:'UnknownError', message:'Could not initialize the IndexedDB database'});
+ });
+
+ return def.promise();
+ },
+
+ /**
+ * Removes the value associated with 'key' from the db.
+ */
+ removeItem: function(key,done,failed){
+ var self = this;
+ var def = new $.Deferred();
+ def.then(done,failed);
+
+ this._ready.then(function(){
+ var request = self.db.transaction(["keyvals"],"readwrite")
+ .objectStore("keyvals")
+ .delete(key);
+
+ request.onsuccess = function(){
+ def.resolve();
+ };
+
+ request.onerror = function(event){
+ def.reject(event.target.error);
+ };
+ },function(){
+ def.reject({type:'UnknownError', message:'Could not initialize the IndexedDB database'});
+ });
+
+ return def.promise();
+ },
+});
+
})();
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index f8394068c7c..3c32dbe0426 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -141,12 +141,13 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
//noinspection FallthroughInSwitchStatementJS
switch (value) {
case '':
- if (descriptor.type === 'char') {
+ if (descriptor.type === 'char' || descriptor.type === 'text') {
return '';
}
console.warn('Field', descriptor, 'had an empty string as value, treating as false...');
return value_if_empty === undefined ? '' : value_if_empty;
case false:
+ case undefined:
case Infinity:
case -Infinity:
return value_if_empty === undefined ? '' : value_if_empty;
@@ -195,7 +196,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
+ ' ' + normalize_format(l10n.time_format));
case 'date':
if (typeof(value) == "string")
- value = instance.web.auto_str_to_date(value);
+ value = instance.web.str_to_date(value.substring(0,10));
return value.toString(normalize_format(l10n.date_format));
case 'time':
if (typeof(value) == "string")
@@ -204,7 +205,7 @@ instance.web.format_value = function (value, descriptor, value_if_empty) {
case 'selection': case 'statusbar':
// Each choice is [value, label]
if(_.isArray(value)) {
- value = value[0];
+ return value[1];
}
var result = _(descriptor.selection).detect(function (choice) {
return choice[0] === value;
diff --git a/addons/web/static/src/js/openerpframework.js b/addons/web/static/src/js/openerpframework.js
index e986a2f4112..5dcc2197944 100644
--- a/addons/web/static/src/js/openerpframework.js
+++ b/addons/web/static/src/js/openerpframework.js
@@ -809,7 +809,8 @@ var genericJsonRpc = function(fct_name, params, fct) {
params: params,
id: Math.floor(Math.random() * 1000 * 1000 * 1000)
};
- return fct(data).pipe(function(result) {
+ var xhr = fct(data);
+ var result = xhr.pipe(function(result) {
if (result.error !== undefined) {
console.error("Server application error", result.error);
return $.Deferred().reject("server", result.error);
@@ -821,15 +822,38 @@ var genericJsonRpc = function(fct_name, params, fct) {
var def = $.Deferred();
return def.reject.apply(def, ["communication"].concat(_.toArray(arguments)));
});
+ // FIXME: jsonp?
+ result.abort = function () { xhr.abort && xhr.abort(); };
+ return result;
};
+/**
+ * Replacer function for JSON.stringify, serializes Date objects to UTC
+ * datetime in the OpenERP Server format.
+ *
+ * However, if a serialized value has a toJSON method that method is called
+ * *before* the replacer is invoked. Date#toJSON exists, and thus the value
+ * passed to the replacer is a string, the original Date has to be fetched
+ * on the parent object (which is provided as the replacer's context).
+ *
+ * @param {String} k
+ * @param {Object} v
+ * @returns {Object}
+ */
+function date_to_utc(k, v) {
+ var value = this[k];
+ if (!(value instanceof Date)) { return v; }
+
+ return openerp.datetime_to_str(value);
+}
+
openerp.jsonRpc = function(url, fct_name, params, settings) {
return genericJsonRpc(fct_name, params, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'json',
type: 'POST',
- data: JSON.stringify(data),
+ data: JSON.stringify(data, date_to_utc),
contentType: 'application/json'
}));
});
@@ -838,7 +862,7 @@ openerp.jsonRpc = function(url, fct_name, params, settings) {
openerp.jsonpRpc = function(url, fct_name, params, settings) {
settings = settings || {};
return genericJsonRpc(fct_name, params, function(data) {
- var payload_str = JSON.stringify(data);
+ var payload_str = JSON.stringify(data, date_to_utc);
var payload_url = $.param({r:payload_str});
var force2step = settings.force2step || false;
delete settings.force2step;
@@ -950,6 +974,7 @@ openerp.Session = openerp.Class.extend(openerp.PropertiesMixin, {
this.session_id = options.session_id || null;
this.override_session = options.override_session || !!options.session_id || false;
this.avoid_recursion = false;
+ this.use_cors = options.use_cors || false;
this.setup(origin);
},
setup: function(origin) {
@@ -993,7 +1018,7 @@ openerp.Session = openerp.Class.extend(openerp.PropertiesMixin, {
},
check_session_id: function() {
var self = this;
- if (this.avoid_recursion)
+ if (this.avoid_recursion || self.use_cors)
return $.when();
if (this.session_id)
return $.when(); // we already have the session id
@@ -1050,6 +1075,15 @@ openerp.Session = openerp.Class.extend(openerp.PropertiesMixin, {
"X-Openerp-Session-Id": self.override_session ? self.session_id || '' : ''
});
}
+ } else if (self.use_cors) {
+ fct = openerp.jsonRpc;
+ url = self.url(url, null);
+ options.session_id = self.session_id || '';
+ if (self.override_session) {
+ options.headers = _.extend({}, options.headers, {
+ "X-Openerp-Session-Id": self.override_session ? self.session_id || '' : ''
+ });
+ }
} else {
fct = openerp.jsonpRpc;
url = self.url(url, null);
@@ -1155,7 +1189,8 @@ openerp.Model = openerp.Class.extend({
kwargs = args;
args = [];
}
- return this.session().rpc('/web/dataset/call_kw', {
+ var call_kw = '/web/dataset/call_kw/' + this.name + '/' + method;
+ return this.session().rpc(call_kw, {
model: this.name,
method: method,
args: args,
@@ -1236,15 +1271,14 @@ openerp.TranslationDataBase = openerp.Class.extend(/** @lends instance.Translati
openerp._t = new openerp.TranslationDataBase().build_translation_function();
openerp.get_cookie = function(c_name) {
- if (document.cookie.length > 0) {
- var c_start = document.cookie.indexOf(c_name + "=");
- if (c_start != -1) {
- c_start = c_start + c_name.length + 1;
- var c_end = document.cookie.indexOf(";", c_start);
- if (c_end == -1) {
- c_end = document.cookie.length;
- }
- return unescape(document.cookie.substring(c_start, c_end));
+ var cookies = document.cookie ? document.cookie.split('; ') : [];
+ for (var i = 0, l = cookies.length; i < l; i++) {
+ var parts = cookies[i].split('=');
+ var name = parts.shift();
+ var cookie = parts.join('=');
+
+ if (c_name && c_name === name) {
+ return cookie;
}
}
return "";
@@ -1292,7 +1326,10 @@ openerp.str_to_datetime = function(str) {
if ( !res ) {
throw new Error("'" + str + "' is not a valid datetime");
}
- var tmp = new Date(0);
+ var tmp = new Date(2000,0,1);
+ tmp.setUTCMonth(1970);
+ tmp.setUTCMonth(0);
+ tmp.setUTCDate(1);
tmp.setUTCFullYear(parseFloat(res[1]));
tmp.setUTCMonth(parseFloat(res[2]) - 1);
tmp.setUTCDate(parseFloat(res[3]));
@@ -1324,7 +1361,7 @@ openerp.str_to_date = function(str) {
if ( !res ) {
throw new Error("'" + str + "' is not a valid date");
}
- var tmp = new Date(0);
+ var tmp = new Date(2000,0,1);
tmp.setFullYear(parseFloat(res[1]));
tmp.setMonth(parseFloat(res[2]) - 1);
tmp.setDate(parseFloat(res[3]));
diff --git a/addons/web/static/src/js/pyeval.js b/addons/web/static/src/js/pyeval.js
index 0b0e9725af9..d5499a028b7 100644
--- a/addons/web/static/src/js/pyeval.js
+++ b/addons/web/static/src/js/pyeval.js
@@ -382,13 +382,28 @@
this[key] = asJS(args[key]);
}
},
+ replace: function () {
+ var args = py.PY_parseArgs(arguments, [
+ ['year', py.None], ['month', py.None], ['day', py.None],
+ ['hour', py.None], ['minute', py.None], ['second', py.None],
+ ['microsecond', py.None] // FIXME: tzinfo, can't use None as valid input
+ ]);
+ var params = {};
+ for(var key in args) {
+ if (!args.hasOwnProperty(key)) { continue; }
+
+ var arg = args[key];
+ params[key] = (arg === py.None ? this[key] : asJS(arg));
+ }
+ return py.PY_call(datetime.datetime, params);
+ },
strftime: function () {
var self = this;
var args = py.PY_parseArgs(arguments, 'format');
return py.str.fromJSON(args.format.toJSON()
.replace(/%([A-Za-z])/g, function (m, c) {
switch (c) {
- case 'Y': return self.year;
+ case 'Y': return _.str.sprintf('%04d', self.year);
case 'm': return _.str.sprintf('%02d', self.month);
case 'd': return _.str.sprintf('%02d', self.day);
case 'H': return _.str.sprintf('%02d', self.hour);
@@ -399,6 +414,17 @@
}));
},
now: py.classmethod.fromJSON(function () {
+ var d = new Date;
+ return py.PY_call(datetime.datetime, [
+ d.getFullYear(), d.getMonth() + 1, d.getDate(),
+ d.getHours(), d.getMinutes(), d.getSeconds(),
+ d.getMilliseconds() * 1000]);
+ }),
+ today: py.classmethod.fromJSON(function () {
+ var dt_class = py.PY_getAttr(datetime, 'datetime');
+ return py.PY_call(py.PY_getAttr(dt_class, 'now'));
+ }),
+ utcnow: py.classmethod.fromJSON(function () {
var d = new Date();
return py.PY_call(datetime.datetime,
[d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
@@ -415,7 +441,17 @@
py.PY_getAttr(args.time, 'minute'),
py.PY_getAttr(args.time, 'second')
]);
- })
+ }),
+ toJSON: function () {
+ return new Date(
+ this.year,
+ this.month - 1,
+ this.day,
+ this.hour,
+ this.minute,
+ this.second,
+ this.microsecond / 1000);
+ },
});
datetime.date = py.type('date', null, {
__init__: function () {
@@ -470,7 +506,12 @@
},
fromJSON: function (year, month, day) {
return py.PY_call(datetime.date, [year, month, day]);
- }
+ },
+ today: py.classmethod.fromJSON(function () {
+ var d = new Date;
+ return py.PY_call(datetime.date, [
+ d.getFullYear(), d.getMonth() + 1, d.getDate()]);
+ }),
});
/**
Returns the current local date, which means the date on the client (which can be different
@@ -501,7 +542,7 @@
time.strftime = py.PY_def.fromJSON(function () {
var args = py.PY_parseArgs(arguments, 'format');
var dt_class = py.PY_getAttr(datetime, 'datetime');
- var d = py.PY_call(py.PY_getAttr(dt_class, 'now'));
+ var d = py.PY_call(py.PY_getAttr(dt_class, 'utcnow'));
return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
});
@@ -845,16 +886,18 @@
};
instance.web.pyeval.eval_domains_and_contexts = function (source) {
return new $.Deferred(function (d) {setTimeout(function () {
+ var result;
try {
var contexts = ([instance.session.user_context] || []).concat(source.contexts);
// see Session.eval_context in Python
- d.resolve({
+ result = {
context: instance.web.pyeval.eval('contexts', contexts),
domain: instance.web.pyeval.eval('domains', source.domains),
group_by: instance.web.pyeval.eval('groupbys', source.group_by_seq || [])
- });
+ };
+
} catch (e) {
- d.resolve({ error: {
+ result = { error: {
code: 400,
message: instance.web._t("Evaluation Error"),
data: {
@@ -863,8 +906,9 @@
instance.web._t("Local evaluation failure\n%s\n\n%s"),
e.message, JSON.stringify(source))
}
- }});
+ }};
}
+ d.resolve(result);
}, 0); });
};
})();
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index fcfcfb08f8e..b667d135bdd 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -71,7 +71,10 @@ my.SearchQuery = B.Collection.extend({
},
add: function (values, options) {
options = options || {};
- if (!(values instanceof Array)) {
+
+ if (!values) {
+ values = [];
+ } else if (!(values instanceof Array)) {
values = [values];
}
@@ -82,11 +85,15 @@ my.SearchQuery = B.Collection.extend({
&& facet.get('field') === model.get('field');
});
if (previous) {
- previous.values.add(model.get('values'));
+ previous.values.add(model.get('values'), _.omit(options, 'at', 'merge'));
return;
}
B.Collection.prototype.add.call(this, model, options);
}, this);
+ // warning: in backbone 1.0+ add is supposed to return the added models,
+ // but here toggle may delegate to add and return its value directly.
+ // return value of neither seems actually used but should be tested
+ // before change, probably
return this;
},
toggle: function (value, options) {
@@ -407,7 +414,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
context: this.dataset.get_context(),
});
- $.when(load_view).then(function (r) {
+ this.alive($.when(load_view)).then(function (r) {
return self.search_view_loaded(r);
}).fail(function () {
self.ready.reject.apply(null, arguments);
@@ -466,19 +473,20 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
* Sets up search view's view-wide auto-completion widget
*/
setup_global_completion: function () {
- var self = this;
-
var autocomplete = this.$el.autocomplete({
source: this.proxy('complete_global_search'),
select: this.proxy('select_completion'),
- search: function () { self.$el.autocomplete('close'); },
focus: function (e) { e.preventDefault(); },
html: true,
autoFocus: true,
minLength: 1,
- delay: 0,
+ delay: 250,
}).data('autocomplete');
+ this.$el.on('input', function () {
+ this.$el.autocomplete('close');
+ }.bind(this));
+
// MonkeyPatch autocomplete instance
_.extend(autocomplete, {
_renderItem: function (ul, item) {
@@ -528,7 +536,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
resp(_(arguments).chain()
.compact()
.map(function (completion) {
- console.log(completion);
if (completion.length && completion[0].facet !== undefined) {
completion[0].first = true;
}
@@ -1864,9 +1871,14 @@ instance.web.search.Advanced = instance.web.search.Input.extend({
new instance.web.Model(this.view.model).call('fields_get', {
context: this.view.dataset.context
}).done(function(data) {
- self.fields = _.extend({
+ self.fields = {
id: { string: 'ID', type: 'id' }
- }, data);
+ };
+ _.each(data, function(field_def, field_name) {
+ if (field_def.selectable !== false && field_name != 'id') {
+ self.fields[field_name] = field_def;
+ }
+ });
})).done(function () {
self.append_proposition();
});
diff --git a/addons/web/static/src/js/testing.js b/addons/web/static/src/js/testing.js
index a7768f0ca24..1f1c718ad5f 100644
--- a/addons/web/static/src/js/testing.js
+++ b/addons/web/static/src/js/testing.js
@@ -15,6 +15,37 @@ openerp.testing = {};
list_editable: ['list', 'form', 'data'],
};
+ var initialized = [];
+ /**
+ * openerp.init is a broken-ass piece of shit, makes it impossible to
+ * progressively add modules, using it, retarded openerp_inited flag
+ * means the first tests being run get their dependencies loaded
+ * (basically just base and web), and for *every other module* the tests
+ * run without the dependencies being correctly loaded
+ */
+ function init(modules) {
+ if (!initialized.length) {
+ openerp.init(modules);
+ initialized = openerp._modules.slice();
+ return;
+ }
+ var to_initialize = _.difference(modules, initialized);
+ for (var i = 0; i < to_initialize.length; i++) {
+ var modname = to_initialize[i];
+ var fct = openerp[modname];
+ if (typeof fct === 'function') {
+ var module = openerp[modname] = {};
+ for(var k in fct) {
+ if (!fct.hasOwnProperty(k)) { continue; }
+ module[k] = fct[k];
+ }
+ fct(openerp, module)
+ }
+ initialized.push(modname);
+ openerp._modules.push(modname);
+ }
+ }
+
testing.dependencies = window['oe_all_dependencies'] || [];
testing.current_module = null;
testing.templates = { };
@@ -58,8 +89,7 @@ openerp.testing = {};
}
var fn, params;
var needle = rparams.model + ':' + rparams.method;
- if (url.url === '/web/dataset/call_kw'
- && needle in this.responses) {
+ if (url.url.substr(0, 20) === '/web/dataset/call_kw' && needle in this.responses) {
fn = this.responses[needle];
params = [
rparams.args || [],
@@ -166,9 +196,6 @@ openerp.testing = {};
});
};
- var openerp_inited = false;
-
- var db = window['oe_db_info'];
testing.section = function (name, options, body) {
if (_.isFunction(options)) {
body = options;
@@ -207,47 +234,10 @@ openerp.testing = {};
.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 = openerp;
- if (!openerp_inited) {
- openerp.init(module_deps);
- openerp_inited = true;
- }
+ init(module_deps);
instance.session = new instance.web.Session();
instance.session.uid = 42;
if (_.isNumber(opts.asserts)) {
@@ -277,44 +267,6 @@ openerp.testing = {};
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
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 2cb318dcb17..f1fc377fa94 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -399,9 +399,11 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.dataset.index = this.dataset.ids.length - 1;
break;
}
- this.reload();
+ var def = this.reload();
this.trigger('pager_action_executed');
+ return def;
}
+ return $.when();
},
init_pager: function() {
var self = this;
@@ -416,8 +418,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.$el.find('.oe_form_pager').replaceWith(this.$pager);
}
this.$pager.on('click','a[data-pager-action]',function() {
- var action = $(this).data('pager-action');
- self.execute_pager_action(action);
+ var $el = $(this);
+ if ($el.attr("disabled"))
+ return;
+ var action = $el.data('pager-action');
+ var def = $.when(self.execute_pager_action(action));
+ $el.attr("disabled");
+ def.always(function() {
+ $el.removeAttr("disabled");
+ });
});
this.do_update_pager();
},
@@ -722,12 +731,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
var self = this;
return this.save().done(function(result) {
self.trigger("save", result);
- self.to_view_mode();
- }).then(function(result) {
- var parent = self.ViewManager.ActionManager.getParent();
- if(parent){
- parent.menu.do_reload_needaction();
- }
+ self.reload().then(function() {
+ self.to_view_mode();
+ var parent = self.ViewManager.ActionManager.getParent();
+ if(parent){
+ parent.menu.do_reload_needaction();
+ }
+ });
});
},
on_button_cancel: function(event) {
@@ -891,17 +901,12 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* @param {Object} r result of the write function.
*/
record_saved: function(r) {
- var self = this;
+ this.trigger('record_saved', r);
if (!r) {
// should not happen in the server, but may happen for internal purpose
- this.trigger('record_saved', r);
return $.Deferred().reject();
- } else {
- return $.when(this.reload()).then(function () {
- self.trigger('record_saved', r);
- return r;
- });
}
+ return r;
},
/**
* Updates the form' dataset to contain the new record:
@@ -1025,8 +1030,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
open_defaults_dialog: function () {
var self = this;
var display = function (field, value) {
+ if (!value) { return value; }
if (field instanceof instance.web.form.FieldSelection) {
- return _(field.values).find(function (option) {
+ return _(field.get('values')).find(function (option) {
return option[0] === value;
})[1];
} else if (field instanceof instance.web.form.FieldMany2One) {
@@ -2619,6 +2625,11 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
e.stopPropagation();
}
},
+ 'keypress': function (e) {
+ if (e.which === $.ui.keyCode.ENTER) {
+ e.stopPropagation();
+ }
+ },
'change textarea': 'store_dom_value',
},
initialize_content: function() {
@@ -3071,7 +3082,7 @@ instance.web.form.CompletionFieldMixin = {
values.push({
label: _t("Search More..."),
action: function() {
- dataset.name_search(search_val, self.build_domain(), 'ilike', false).done(function(data) {
+ dataset.name_search(search_val, self.build_domain(), 'ilike', 160).done(function(data) {
self._search_create_popup("search", data);
});
},
@@ -3081,7 +3092,7 @@ instance.web.form.CompletionFieldMixin = {
// quick create
var raw_result = _(data.result).map(function(x) {return x[1];});
if (search_val.length > 0 && !_.include(raw_result, search_val) &&
- ! (self.options && self.options.no_quick_create)) {
+ ! (self.options && (self.options.no_create || self.options.no_quick_create))) {
values.push({
label: _.str.sprintf(_t('Create "<strong>%s</strong>"'),
$('<span />').text(search_val).html()),
@@ -3092,13 +3103,15 @@ instance.web.form.CompletionFieldMixin = {
});
}
// create...
- values.push({
- label: _t("Create and Edit..."),
- action: function() {
- self._search_create_popup("form", undefined, self._create_context(search_val));
- },
- classname: 'oe_m2o_dropdown_option'
- });
+ if (!(self.options && self.options.no_create)){
+ values.push({
+ label: _t("Create and Edit..."),
+ action: function() {
+ self._search_create_popup("form", undefined, self._create_context(search_val));
+ },
+ classname: 'oe_m2o_dropdown_option'
+ });
+ }
return values;
});
@@ -3418,7 +3431,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
// disabled to solve a bug, but may cause others
//close: anyoneLoosesFocus,
minLength: 0,
- delay: 0
+ delay: 250
});
this.$input.autocomplete("widget").openerpClass();
// used to correct a bug when selecting an element by pushing 'enter' in an editable list
@@ -3445,6 +3458,10 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
if (! no_recurse) {
var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
this.alive(dataset.name_get([self.get("value")])).done(function(data) {
+ if (!data[0]) {
+ self.do_warn(_t("Render"), _t("No value found for the field "+self.field.string+" for value "+self.get("value")));
+ return;
+ }
self.display_value["" + self.get("value")] = data[0][1];
self.render_value(true);
}).fail( function (data, event) {
@@ -3583,6 +3600,67 @@ instance.web.form.Many2OneButton = instance.web.form.AbstractField.extend({
},
});
+/**
+ * Abstract-ish ListView.List subclass adding an "Add an item" row to replace
+ * the big ugly button in the header.
+ *
+ * Requires the implementation of a ``is_readonly`` method (usually a proxy to
+ * the corresponding field's readonly or effective_readonly property) to
+ * decide whether the special row should or should not be inserted.
+ *
+ * Optionally an ``_add_row_class`` attribute can be set for the class(es) to
+ * set on the insertion row.
+ */
+instance.web.form.AddAnItemList = instance.web.ListView.List.extend({
+ pad_table_to: function (count) {
+ if (!this.view.is_action_enabled('create') || this.is_readonly()) {
+ this._super(count);
+ return;
+ }
+
+ this._super(count > 0 ? count - 1 : 0);
+
+ var self = this;
+ var columns = _(this.columns).filter(function (column) {
+ return column.invisible !== '1';
+ }).length;
+ if (this.options.selectable) { columns++; }
+ if (this.options.deletable) { columns++; }
+
+ var $cell = $('<td>', {
+ colspan: columns,
+ 'class': this._add_row_class || ''
+ }).append(
+ $('<a>', {href: '#'}).text(_t("Add an item"))
+ .mousedown(function () {
+ // FIXME: needs to be an official API somehow
+ if (self.view.editor.is_editing()) {
+ self.view.__ignore_blur = true;
+ }
+ })
+ .click(function (e) {
+ e.preventDefault();
+ e.stopPropagation();
+ // FIXME: there should also be an API for that one
+ if (self.view.editor.form.__blur_timeout) {
+ clearTimeout(self.view.editor.form.__blur_timeout);
+ self.view.editor.form.__blur_timeout = false;
+ }
+ self.view.ensure_saved().done(function () {
+ self.view.do_add_record();
+ });
+ }));
+
+ var $padding = this.$current.find('tr:not([data-id]):first');
+ var $newrow = $('<tr>').append($cell);
+ if ($padding.length) {
+ $padding.before($newrow);
+ } else {
+ this.$current.append($newrow)
+ }
+ }
+});
+
/*
# Values: (0, 0, { fields }) create
# (1, ID, { fields }) update
@@ -3979,7 +4057,6 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
GroupsType: instance.web.form.One2ManyGroups,
ListType: instance.web.form.One2ManyList
}));
- this.on('edit:before', this, this.proxy('_before_edit'));
this.on('edit:after', this, this.proxy('_after_edit'));
this.on('save:before cancel:before', this, this.proxy('_before_unedit'));
@@ -3992,7 +4069,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
var ret = this._super();
this.$el
.off('mousedown.handleButtons')
- .on('mousedown.handleButtons', 'table button', this.proxy('_button_down'));
+ .on('mousedown.handleButtons', 'table button, div a.oe_m2o_cm_button', this.proxy('_button_down'));
return ret;
},
changed_records: function () {
@@ -4100,11 +4177,10 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
});
},
- _before_edit: function () {
+ _after_edit: function () {
this.__ignore_blur = false;
this.editor.form.on('blurred', this, this._on_form_blur);
- },
- _after_edit: function () {
+
// The form's blur thing may be jiggered during the edition setup,
// potentially leading to the o2m instasaving the row. Cancel any
// blurring triggered the edition startup here
@@ -4137,7 +4213,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
}
this.cancel_edition();
},
- keyup_ENTER: function () {
+ keypress_ENTER: function () {
// blurring caused by hitting the [Return] key, should skip the
// autosave-on-blur and let the handler for [Return] do its thing (save
// the current row *anyway*, then create a new one/edit the next one)
@@ -4152,6 +4228,12 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
} finally {
window.confirm = confirm;
}
+ },
+ reload_record: function (record) {
+ // Evict record.id from cache to ensure it will be reloaded correctly
+ this.dataset.evict_record(record.get('id'));
+
+ return this._super(record);
}
});
instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({
@@ -4161,62 +4243,11 @@ instance.web.form.One2ManyGroups = instance.web.ListView.Groups.extend({
}
}
});
-instance.web.form.One2ManyList = instance.web.ListView.List.extend({
- pad_table_to: function (count) {
- if (!this.view.is_action_enabled('create')) {
- this._super(count);
- } else {
- this._super(count > 0 ? count - 1 : 0);
- }
-
- // magical invocation of wtf does that do
- if (this.view.o2m.get('effective_readonly')) {
- return;
- }
-
- var self = this;
- var columns = _(this.columns).filter(function (column) {
- return column.invisible !== '1';
- }).length;
- if (this.options.selectable) { columns++; }
- if (this.options.deletable) { columns++; }
-
- if (!this.view.is_action_enabled('create')) {
- return;
- }
-
- var $cell = $('<td>', {
- colspan: columns,
- 'class': 'oe_form_field_one2many_list_row_add'
- }).append(
- $('<a>', {href: '#'}).text(_t("Add an item"))
- .mousedown(function () {
- // FIXME: needs to be an official API somehow
- if (self.view.editor.is_editing()) {
- self.view.__ignore_blur = true;
- }
- })
- .click(function (e) {
- e.preventDefault();
- e.stopPropagation();
- // FIXME: there should also be an API for that one
- if (self.view.editor.form.__blur_timeout) {
- clearTimeout(self.view.editor.form.__blur_timeout);
- self.view.editor.form.__blur_timeout = false;
- }
- self.view.ensure_saved().done(function () {
- self.view.do_add_record();
- });
- }));
-
- var $padding = this.$current.find('tr:not([data-id]):first');
- var $newrow = $('<tr>').append($cell);
- if ($padding.length) {
- $padding.before($newrow);
- } else {
- this.$current.append($newrow);
- }
- }
+instance.web.form.One2ManyList = instance.web.form.AddAnItemList.extend({
+ _add_row_class: 'oe_form_field_one2many_list_row_add',
+ is_readonly: function () {
+ return this.view.o2m.get('effective_readonly');
+ },
});
instance.web.form.One2ManyFormView = instance.web.FormView.extend({
@@ -4246,6 +4277,7 @@ var lazy_build_o2m_kanban_view = function() {
instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, {
template: "FieldMany2ManyTags",
+ tag_template: "FieldMany2ManyTag",
init: function() {
this._super.apply(this, arguments);
instance.web.form.CompletionFieldMixin.init.call(this);
@@ -4253,13 +4285,9 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
this._display_orderer = new instance.web.DropMisordered();
this._drop_shown = false;
},
- initialize_content: function() {
- if (this.get("effective_readonly"))
- return;
+ initialize_texttext: function(){
var self = this;
- var ignore_blur = false;
- self.$text = this.$("textarea");
- self.$text.textext({
+ return {
plugins : 'tags arrow autocomplete',
autocomplete: {
render: function(suggestion) {
@@ -4310,7 +4338,15 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
},
},
},
- }).bind('getSuggestions', function(e, data) {
+ }
+ },
+ initialize_content: function() {
+ if (this.get("effective_readonly"))
+ return;
+ var self = this;
+ var ignore_blur = false;
+ self.$text = this.$("textarea");
+ self.$text.textext(self.initialize_texttext()).bind('getSuggestions', function(e, data) {
var _this = this;
var str = !!data ? data.query || '' : '';
self.get_search_result(str).done(function(result) {
@@ -4358,6 +4394,24 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
get_search_blacklist: function() {
return this.get("value");
},
+ map_tag: function(data){
+ return _.map(data, function(el) {return {name: el[1], id:el[0]};})
+ },
+ get_render_data: function(ids){
+ var self = this;
+ var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
+ return dataset.name_get(ids);
+ },
+ render_tag: function(data) {
+ var self = this;
+ if (! self.get("effective_readonly")) {
+ self.tags.containerElement().children().remove();
+ self.$('textarea').css("padding-left", "3px");
+ self.tags.addTags(self.map_tag(data));
+ } else {
+ self.$el.html(QWeb.render(self.tag_template, {elements: data}));
+ }
+ },
render_value: function() {
var self = this;
var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context());
@@ -4370,17 +4424,12 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
indexed[el[0]] = el;
});
data = _.map(values, function(el) { return indexed[el]; });
- if (! self.get("effective_readonly")) {
- self.tags.containerElement().children().remove();
- self.$('textarea').css("padding-left", "3px");
- self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};}));
- } else {
- self.$el.html(QWeb.render("FieldMany2ManyTag", {elements: data}));
- }
- };
+ self.render_tag(data);
+ }
if (! values || values.length > 0) {
- this._display_orderer.add(dataset.name_get(values)).done(handle_names);
- } else {
+ this._display_orderer.add(self.get_render_data(values)).done(handle_names);
+ }
+ else{
handle_names([]);
}
},
@@ -4428,7 +4477,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend(instan
this.$el.addClass('oe_form_field oe_form_field_many2many');
this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, {
- 'addable': this.get("effective_readonly") ? null : _t("Add"),
+ 'addable': null,
'deletable': this.get("effective_readonly") ? false : true,
'selectable': this.multi_selection,
'sortable': false,
@@ -4495,6 +4544,11 @@ instance.web.form.Many2ManyDataSet = instance.web.DataSetStatic.extend({
* @extends instance.web.ListView
*/
instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{
+ init: function (parent, dataset, view_id, options) {
+ this._super(parent, dataset, view_id, _.extend(options || {}, {
+ ListType: instance.web.form.Many2ManyList,
+ }));
+ },
do_add_record: function () {
var pop = new instance.web.form.SelectCreatePopup(this);
pop.select_element(
@@ -4544,6 +4598,12 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in
},
is_action_enabled: function () { return true; },
});
+instance.web.form.Many2ManyList = instance.web.form.AddAnItemList.extend({
+ _add_row_class: 'oe_form_field_many2many_list_row_add',
+ is_readonly: function () {
+ return this.view.m2m_field.get('effective_readonly');
+ }
+});
instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, {
disable_utility_classes: true,
@@ -5072,7 +5132,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
.on('blurred', null, function () {self.trigger('blurred');});
this.m2o = new instance.web.form.FieldMany2One(fm, { attrs: {
- name: 'm2o',
+ name: 'Referenced Document',
modifiers: JSON.stringify({readonly: this.get('effective_readonly')}),
}});
this.m2o.on("change:value", this, this.data_changed);
@@ -5370,25 +5430,26 @@ instance.web.form.FieldMany2ManyBinaryMultiFiles = instance.web.form.AbstractFie
read_name_values : function () {
var self = this;
// don't reset know values
- var _value = _.filter(this.get('value'), function (id) { return typeof self.data[id] == 'undefined'; } );
+ var ids = this.get('value');
+ var _value = _.filter(ids, function (id) { return typeof self.data[id] == 'undefined'; } );
// send request for get_name
if (_value.length) {
- return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname']]).done(function (datas) {
+ return this.ds_file.call('read', [_value, ['id', 'name', 'datas_fname']]).then(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});
self.data[data.id] = data;
});
+ return ids;
});
} else {
- return $.when();
+ return $.when(ids);
}
},
render_value: function () {
var self = this;
- this.read_name_values().then(function () {
-
- var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self}));
+ this.read_name_values().then(function (ids) {
+ var render = $(instance.web.qweb.render('FieldBinaryFileUploader.files', {'widget': self, 'values': ids}));
render.on('click', '.oe_delete', _.bind(self.on_file_delete, self));
self.$('.oe_placeholder_files, .oe_attachments').replaceWith( render );
@@ -5498,6 +5559,7 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
start: function() {
this.field_manager.on("view_content_has_changed", this, this.calc_domain);
this.calc_domain();
+ this.on("change:value", this, this.get_selection);
this.on("change:evaluated_selection_domain", this, this.get_selection);
this.on("change:selection", this, function() {
this.selection = this.get("selection");
@@ -5547,18 +5609,18 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
var self = this;
var selection_unfolded = [];
var selection_folded = [];
+ var fold_field = this.options.fold_field;
var calculation = _.bind(function() {
if (this.field.type == "many2one") {
- return self.get_distant_fields().then(function(fields) {
+ return self.get_distant_fields().then(function (fields) {
return new instance.web.DataSetSearch(self, self.field.relation, self.build_context(), self.get("evaluated_selection_domain"))
- .read_slice(fields.fold ? ['fold'] : ['id'], {}).then(function (records) {
-
- var ids = _.map(records, function (val) {return val.id;});
+ .read_slice(_.union(_.keys(self.distant_fields), ['id']), {}).then(function (records) {
+ var ids = _.pluck(records, 'id');
return self.dataset.name_get(ids).then(function (records_name) {
_.each(records, function (record) {
var name = _.find(records_name, function (val) {return val[0] == record.id;})[1];
- if (record.fold && record.id != self.get('value')) {
+ if (fold_field && record[fold_field] && record.id != self.get('value')) {
selection_folded.push([record.id, name]);
} else {
selection_unfolded.push([record.id, name]);
@@ -5566,7 +5628,7 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
});
});
});
- });
+ });
} else {
// For field type selection filter values according to
// statusbar_visible attribute of the field. For example:
@@ -5588,12 +5650,18 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
}
});
},
+ /*
+ * :deprecated: this feature will probably be removed with OpenERP v8
+ */
get_distant_fields: function() {
var self = this;
+ if (! this.options.fold_field) {
+ this.distant_fields = {}
+ }
if (this.distant_fields) {
return $.when(this.distant_fields);
}
- return new instance.web.Model(self.field.relation).call("fields_get", [["fold"]]).then(function(fields) {
+ return new instance.web.Model(self.field.relation).call("fields_get", [[this.options.fold_field]]).then(function(fields) {
self.distant_fields = fields;
return fields;
});
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index 99e87725dce..e01ab29f914 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -508,7 +508,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
var reloaded = $.Deferred();
this.$el.find('.oe_list_content').append(
this.groups.render(function () {
- // Keep '=='. This is not a mistake, this is a wanted behaviour to match null & undefined
if (self.dataset.index == null) {
if (self.records.length) {
self.dataset.index = 0;
@@ -1057,7 +1056,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
id = parseInt(ref_match[2], 10);
new instance.web.DataSet(this.view, model).name_get([id]).done(function(names) {
if (!names.length) { return; }
- record.set(column.id, names[0][1]);
+ record.set(column.id + '__display', names[0][1]);
});
}
} else if (column.type === 'many2one') {
@@ -1131,11 +1130,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
if (column.invisible === '1') {
return;
}
- if (column.tag === 'button') {
- cells.push('<td class="oe_button" title="' + column.string + '">&nbsp;</td>');
- } else {
- cells.push('<td title="' + column.string + '">&nbsp;</td>');
- }
+ cells.push('<td title="' + column.string + '">&nbsp;</td>');
});
if (this.options.deletable) {
cells.push('<td class="oe_list_record_delete"><button type="button" style="visibility: hidden"> </button></td>');
@@ -1319,7 +1314,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
}
},
close: function () {
- this.$row.children().last().empty();
+ this.$row.children().last().find('button').remove();
+ this.$row.children().last().find('span').remove();
this.records.reset();
},
/**
@@ -1474,14 +1470,13 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var view = this.view,
limit = view.limit(),
- d = new $.Deferred(),
page = this.datagroup.openable ? this.page : view.page;
var fields = _.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name');
var options = { offset: page * limit, limit: limit, context: {bin_size: true} };
//TODO xmo: investigate why we need to put the setTimeout
- $.async_when().done(function() {
- dataset.read_slice(fields, options).done(function (records) {
+ return $.async_when().then(function() {
+ return dataset.read_slice(fields, options).then(function (records) {
// FIXME: ignominious hacks, parents (aka form view) should not send two ListView#reload_content concurrently
if (self.records.length) {
self.records.reset(null, {silent: true});
@@ -1491,7 +1486,8 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
} else {
if (dataset.size() == records.length) {
// only one page
- self.$row.find('td.oe_list_group_pagination').empty();
+ self.$row.find('td.oe_list_group_pagination').find('button').remove();
+ self.$row.find('td.oe_list_group_pagination').find('span').remove();
} else {
var pages = Math.ceil(dataset.size() / limit);
self.$row
@@ -1513,13 +1509,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
self.records.add(records, {silent: true});
list.render();
- d.resolve(list);
if (_.isEmpty(records)) {
view.no_result();
}
+ return list;
});
});
- return d.promise();
},
setup_resequence_rows: function (list, dataset) {
// drag and drop enabled if list is not sorted and there is a
@@ -1600,11 +1595,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
self.render_groups(groups));
if (post_render) { post_render(); }
}, function (dataset) {
- self.render_dataset(dataset).done(function (list) {
+ self.render_dataset(dataset).then(function (list) {
self.children[null] = list;
self.elements =
[list.$current.replaceAll($el)[0]];
self.setup_resequence_rows(list, dataset);
+ }).always(function() {
if (post_render) { post_render(); }
});
});
@@ -1658,9 +1654,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
function synchronized(fn) {
var fn_mutex = new $.Mutex();
return function () {
+ var obj = this;
var args = _.toArray(arguments);
- args.unshift(this);
- return fn_mutex.exec(fn.bind.apply(fn, args));
+ return fn_mutex.exec(function () {
+ if (obj.isDestroyed()) { return $.when(); }
+ return fn.apply(obj, args)
+ });
};
}
var DataGroup = instance.web.Class.extend({
@@ -2133,6 +2132,7 @@ instance.web.list.columns = new instance.web.Registry({
'field.handle': 'instance.web.list.Handle',
'button': 'instance.web.list.Button',
'field.many2onebutton': 'instance.web.list.Many2OneButton',
+ 'field.reference': 'instance.web.list.Reference',
'field.many2many': 'instance.web.list.Many2Many'
});
instance.web.list.columns.for_ = function (id, field, node) {
@@ -2359,5 +2359,18 @@ instance.web.list.Many2Many = instance.web.list.Column.extend({
return this._super(row_data, options);
}
});
+instance.web.list.Reference = instance.web.list.Column.extend({
+ _format: function (row_data, options) {
+ if (!_.isEmpty(row_data[this.id].value)) {
+ // If value, use __display version for printing
+ if (!!row_data[this.id + '__display']) {
+ row_data[this.id] = row_data[this.id + '__display'];
+ } else {
+ row_data[this.id] = {'value': ''};
+ }
+ }
+ return this._super(row_data, options);
+ }
+});
})();
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index 0f529b7554a..fc9af47dfe6 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -53,8 +53,10 @@
});
this.on('edit:after', this, function () {
self.$el.add(self.$buttons).addClass('oe_editing');
+ self.$('.ui-sortable').sortable('disable');
});
this.on('save:after cancel:after', this, function () {
+ self.$('.ui-sortable').sortable('enable');
self.$el.add(self.$buttons).removeClass('oe_editing');
});
},
@@ -275,7 +277,7 @@
if (!this.editor.is_editing()) { return; }
for(var i=0, len=this.fields_for_resize.length; i<len; ++i) {
var item = this.fields_for_resize[i];
- if (!item.field.get('invisible')) {
+ if (!item.field.get('effective_invisible')) {
this.resize_field(item.field, item.cell);
}
}
@@ -441,20 +443,26 @@
setup_events: function () {
var self = this;
_.each(this.editor.form.fields, function(field, field_name) {
- var setting = false;
var set_invisible = function() {
- if (!setting && field.get("effective_readonly")) {
- setting = true;
- field.set({invisible: true});
- setting = false;
- }
+ field.set({'force_invisible': field.get('effective_readonly')});
};
field.on("change:effective_readonly", self, set_invisible);
- field.on("change:invisible", self, set_invisible);
set_invisible();
+ field.on('change:effective_invisible', self, function () {
+ if (field.get('effective_invisible')) { return; }
+ var item = _(self.fields_for_resize).find(function (item) {
+ return item.field === field;
+ });
+ if (item) {
+ setTimeout(function() {
+ self.resize_field(item.field, item.cell);
+ }, 0);
+ }
+
+ });
});
- this.editor.$el.on('keyup keydown', function (e) {
+ this.editor.$el.on('keyup keypress keydown', function (e) {
if (!self.editor.is_editing()) { return true; }
var key = _($.ui.keyCode).chain()
.map(function (v, k) { return {name: k, code: v}; })
@@ -480,6 +488,7 @@
next_record = next_record || 'succ';
var self = this;
return this.save_edition().then(function (saveInfo) {
+ if (!saveInfo) { return null; }
if (saveInfo.created) {
return self.start_edition();
}
@@ -488,7 +497,7 @@
return self.start_edition(record, options);
});
},
- keyup_ENTER: function () {
+ keypress_ENTER: function () {
return this._next();
},
keydown_ESCAPE: function (e) {
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index ecdbaf8b465..c9229c3c70a 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -147,6 +147,19 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.inner_action = last_widget.action;
}
},
+ add_breadcrumb_url: function (url, label) {
+ // Add a pseudo breadcrumb that will redirect to an url
+ this.push_breadcrumb({
+ show: function() {
+ instance.web.redirect(url);
+ },
+ hide: function() {},
+ destroy: function() {},
+ get_title: function() {
+ return label;
+ }
+ });
+ },
get_title: function() {
var titles = [];
for (var i = 0; i < this.breadcrumbs.length; i += 1) {
@@ -217,6 +230,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
do_load_state: function(state, warm) {
var self = this,
action_loaded;
+ if (!warm && 'return_label' in state) {
+ var return_url = state.return_url || document.referrer;
+ if (return_url) {
+ this.add_breadcrumb_url(return_url, state.return_label);
+ }
+ }
if (state.action) {
if (_.isString(state.action) && instance.web.client_actions.contains(state.action)) {
var action_client = {
@@ -225,7 +244,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
params: state,
_push_me: state._push_me,
};
- this.null_action();
+ if (warm) {
+ this.null_action();
+ }
action_loaded = this.do_action(action_client);
} else {
var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action;
@@ -245,7 +266,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
add_context.active_ids = [state.active_id];
}
add_context.params = state;
- this.null_action();
+ if (warm) {
+ this.null_action();
+ }
action_loaded = this.do_action(state.action, { additional_context: add_context });
$.when(action_loaded || null).done(function() {
instance.webclient.menu.has_been_loaded.done(function() {
@@ -258,7 +281,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
} else if (state.model && state.id) {
// TODO handle context & domain ?
- this.null_action();
+ if (warm) {
+ this.null_action();
+ }
var action = {
res_model: state.model,
res_id: state.id,
@@ -268,7 +293,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
action_loaded = this.do_action(action);
} else if (state.sa) {
// load session action
- this.null_action();
+ if (warm) {
+ this.null_action();
+ }
action_loaded = this.rpc('/web/session/get_session_action', {key: state.sa}).then(function(action) {
if (action) {
return self.do_action(action);
@@ -304,6 +331,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
action_menu_id: null,
additional_context: {},
});
+
if (action === false) {
action = { type: 'ir.actions.act_window_close' };
} else if (_.isString(action) && instance.web.client_actions.contains(action)) {
@@ -468,49 +496,48 @@ instance.web.ActionManager = instance.web.Widget.extend({
ir_actions_report_xml: function(action, options) {
var self = this;
instance.web.blockUI();
- return instance.web.pyeval.eval_domains_and_contexts({
- contexts: [action.context],
- domains: []
- }).then(function(res) {
- action = _.clone(action);
- action.context = res.context;
-
- // iOS devices doesn't allow iframe use the way we do it,
- // opening a new window seems the best way to workaround
- if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
- var params = {
- action: JSON.stringify(action),
- token: new Date().getTime()
- };
- var url = self.session.url('/web/report', params);
- instance.web.unblockUI();
- $('<a href="'+url+'" target="_blank"></a>')[0].click();
- return;
- }
+ action = _.clone(action);
+ var eval_contexts = ([instance.session.user_context] || []).concat([action.context]);
+ action.context = instance.web.pyeval.eval('contexts',eval_contexts);
- var c = instance.webclient.crashmanager;
- return $.Deferred(function (d) {
- self.session.get_file({
- url: '/web/report',
- data: {action: JSON.stringify(action)},
- complete: instance.web.unblockUI,
- success: function(){
- if (!self.dialog) {
- options.on_close();
- }
- self.dialog_stop();
- d.resolve();
- },
- error: function () {
- c.rpc_error.apply(c, arguments);
- d.reject();
+ // iOS devices doesn't allow iframe use the way we do it,
+ // opening a new window seems the best way to workaround
+ if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)) {
+ var params = {
+ action: JSON.stringify(action),
+ token: new Date().getTime()
+ };
+ var url = self.session.url('/web/report', params);
+ instance.web.unblockUI();
+ $('<a href="'+url+'" target="_blank"></a>')[0].click();
+ return;
+ }
+ var c = instance.webclient.crashmanager;
+ return $.Deferred(function (d) {
+ self.session.get_file({
+ url: '/web/report',
+ data: {action: JSON.stringify(action)},
+ complete: instance.web.unblockUI,
+ success: function(){
+ if (!self.dialog) {
+ options.on_close();
}
- });
+ self.dialog_stop();
+ d.resolve();
+ },
+ error: function () {
+ c.rpc_error.apply(c, arguments);
+ d.reject();
+ }
});
});
},
ir_actions_act_url: function (action) {
- window.open(action.url, action.target === 'self' ? '_self' : '_blank');
+ if (action.target === 'self') {
+ instance.web.redirect(action.url);
+ } else {
+ window.open(action.url, '_blank');
+ }
return $.when();
},
});
@@ -568,6 +595,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
action_views_ids : views_ids
}, self.flags, self.flags[view.view_type] || {}, view.options || {})
});
+
views_ids[view.view_type] = view.view_id;
});
if (this.flags.views_switcher === false) {
@@ -575,7 +603,11 @@ instance.web.ViewManager = instance.web.Widget.extend({
}
// If no default view defined, switch to the first one in sequence
var default_view = this.flags.default_view || this.views_src[0].view_type;
- return this.switch_mode(default_view);
+
+
+ return this.switch_mode(default_view, null, this.flags[default_view] && this.flags[default_view].options);
+
+
},
switch_mode: function(view_type, no_store, view_options) {
var self = this;
@@ -783,6 +815,11 @@ instance.web.ViewManager = instance.web.Widget.extend({
contexts: [action_context].concat(contexts || []),
group_by_seq: groupbys || []
}).done(function (results) {
+ if (results.error) {
+ throw new Error(
+ _.str.sprintf(_t("Failed to evaluate search criterions")+": \n%s",
+ JSON.stringify(results.error)));
+ }
self.dataset._model = new instance.web.Model(
self.dataset.model, results.context, results.domain);
var groupby = results.group_by.length
@@ -1178,11 +1215,12 @@ instance.web.Sidebar = instance.web.Widget.extend({
var self = this;
self.getParent().sidebar_eval_context().done(function (sidebar_eval_context) {
var ids = self.getParent().get_selected_ids();
+ var domain;
if (self.getParent().get_active_domain) {
- var domain = self.getParent().get_active_domain();
+ domain = self.getParent().get_active_domain();
}
else {
- var domain = $.Deferred().resolve(undefined);
+ domain = $.Deferred().resolve(undefined);
}
if (ids.length === 0) {
instance.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true });
@@ -1226,7 +1264,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
this.dataset = dataset;
this.model_id = model_id;
if (args && args[0].error) {
- this.do_warn( instance.web.qweb.render('message_error_uploading'), args[0].error);
+ this.do_warn(_t('Uploading Error'), args[0].error);
}
if (!model_id) {
this.on_attachments_loaded([]);
@@ -1310,7 +1348,7 @@ instance.web.View = instance.web.Widget.extend({
"context": this.dataset.get_context(),
});
}
- return view_loaded_def.then(function(r) {
+ return this.alive(view_loaded_def).then(function(r) {
self.fields_view = r;
// add css classes that reflect the (absence of) access rights
self.$el.addClass('oe_view')
@@ -1355,14 +1393,24 @@ instance.web.View = instance.web.Widget.extend({
}
};
var context = new instance.web.CompoundContext(dataset.get_context(), action_data.context || {});
+
+ // response handler
var handler = function (action) {
if (action && action.constructor == Object) {
- var ncontext = new instance.web.CompoundContext(context);
+ // filter out context keys that are specific to the current action.
+ // Wrong default_* and search_default_* values will no give the expected result
+ // Wrong group_by values will simply fail and forbid rendering of the destination view
+ var ncontext = new instance.web.CompoundContext(
+ _.object(_.reject(_.pairs(dataset.get_context().eval()), function(pair) {
+ return pair[0].match('^(?:(?:default_|search_default_).+|group_by|group_by_no_leaf|active_id|active_ids)$') !== null;
+ }))
+ );
+ ncontext.add(action_data.context || {});
+ ncontext.add({active_model: dataset.model});
if (record_id) {
ncontext.add({
active_id: record_id,
active_ids: [record_id],
- active_model: dataset.model
});
}
ncontext.add(action.context || {});