summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
authorHarry (OpenERP) <hmo@tinyerp.com>2012-08-09 18:52:39 +0530
committerHarry (OpenERP) <hmo@tinyerp.com>2012-08-09 18:52:39 +0530
commit1b5259b3e374a8f9c385d18706aadbdf0a2403df (patch)
treeb446fae6a43cba80943121cd3fe55f1fada3a954 /addons/web/static/src/js
parent68998d4c827bb080d262da593c7766e25fb3427e (diff)
parente46fb9fda8ba93d3eb8ac2907a75012f7a4d920e (diff)
[MERGE]
bzr revid: hmo@tinyerp.com-20120809132239-blyhpfxwn2szlqec
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/boot.js4
-rw-r--r--addons/web/static/src/js/chrome.js453
-rw-r--r--addons/web/static/src/js/corelib.js171
-rw-r--r--addons/web/static/src/js/coresetup.js93
-rw-r--r--addons/web/static/src/js/data.js6
-rw-r--r--addons/web/static/src/js/data_export.js4
-rw-r--r--addons/web/static/src/js/data_import.js2
-rw-r--r--addons/web/static/src/js/formats.js78
-rw-r--r--addons/web/static/src/js/search.js91
-rw-r--r--addons/web/static/src/js/view_editor.js1212
-rw-r--r--addons/web/static/src/js/view_form.js819
-rw-r--r--addons/web/static/src/js/view_list.js502
-rw-r--r--addons/web/static/src/js/view_list_editable.js968
-rw-r--r--addons/web/static/src/js/views.js272
14 files changed, 2234 insertions, 2441 deletions
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 86f8077b151..844dd88a0ac 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -24,7 +24,7 @@
init: function(modules) {
// By default only web will be loaded, the rest will be by loaded
// by openerp.web.Session on the first session_authenticate
- modules = modules || ["web"];
+ modules = _.union(['web'], modules || []);
var new_instance = {
// links to the global openerp
_openerp: openerp,
@@ -48,7 +48,7 @@
* OpenERP Web web module split
*---------------------------------------------------------*/
openerp.web = function(session) {
- var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"];
+ var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"];
for(var i=0; i<files.length; i++) {
if(openerp.web[files[i]]) {
openerp.web[files[i]](session);
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 094e2f184e6..fbf51dac522 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -57,9 +57,7 @@ instance.web.Dialog = instance.web.Widget.extend({
init: function (parent, options, content) {
var self = this;
this._super(parent);
- if (content) {
- this.$element = content instanceof $ ? content : $(content);
- }
+ this.content_to_set = content;
this.dialog_options = {
modal: true,
destroy_on_close: true,
@@ -83,11 +81,6 @@ instance.web.Dialog = instance.web.Widget.extend({
if (options) {
_.extend(this.dialog_options, options);
}
- if (this.dialog_options.autoOpen) {
- this.open();
- } else {
- instance.web.dialog(this.$element, this.get_options());
- }
},
get_options: function(options) {
var self = this,
@@ -116,27 +109,44 @@ instance.web.Dialog = instance.web.Widget.extend({
} else if (val.slice(-1) == "%") {
return Math.round(available_size / 100 * parseInt(val.slice(0, -1), 10));
} else {
- return parseInt(val, 10);
+ return parseInt(val, 10);
}
},
- open: function(options) {
- // TODO fme: bind window on resize
- if (this.template) {
- this.$element.html(this.renderElement());
+ renderElement: function() {
+ if (this.content_to_set) {
+ this.setElement(this.content_to_set);
+ } else if (this.template) {
+ this._super();
}
+ },
+ open: function(options) {
+ if (! this.dialog_inited)
+ this.init_dialog();
var o = this.get_options(options);
- instance.web.dialog(this.$element, o).dialog('open');
+ instance.web.dialog(this.$element, o).dialog('open');
if (o.height === 'auto' && o.max_height) {
this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' });
}
return this;
},
+ init_dialog: function(options) {
+ this.renderElement();
+ var o = this.get_options(options);
+ instance.web.dialog(this.$element, o);
+ var res = this.start();
+ this.dialog_inited = true;
+ return res;
+ },
close: function() {
this.$element.dialog('close');
},
on_close: function() {
+ if (this.__tmp_dialog_destroying)
+ return;
if (this.dialog_options.destroy_on_close) {
+ this.__tmp_dialog_closing = true;
this.destroy();
+ this.__tmp_dialog_closing = undefined;
}
},
on_resized: function() {
@@ -145,6 +155,11 @@ instance.web.Dialog = instance.web.Widget.extend({
_.each(this.getChildren(), function(el) {
el.destroy();
});
+ if (! this.__tmp_dialog_closing) {
+ this.__tmp_dialog_destroying = true;
+ this.close();
+ this.__tmp_dialog_destroying = undefined;
+ }
if (! this.isDestroyed()) {
this.$element.dialog('destroy');
}
@@ -248,13 +263,17 @@ instance.web.Loading = instance.web.Widget.extend({
// Block UI after 3s
this.long_running_timer = setTimeout(function () {
self.blocked_ui = true;
- $.blockUI();
+ instance.web.blockUI();
}, 3000);
}
this.count += increment;
if (this.count > 0) {
- this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count));
+ if (instance.connection.debug) {
+ this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count));
+ } else {
+ this.$element.text(_t("Loading"));
+ }
this.$element.show();
this.getParent().$element.addClass('oe_wait');
} else {
@@ -263,7 +282,7 @@ instance.web.Loading = instance.web.Widget.extend({
// Don't unblock if blocked by somebody else
if (self.blocked_ui) {
this.blocked_ui = false;
- $.unblockUI();
+ instance.web.unblockUI();
}
this.$element.fadeOut();
this.getParent().$element.removeClass('oe_wait');
@@ -274,7 +293,7 @@ instance.web.Loading = instance.web.Widget.extend({
instance.web.DatabaseManager = instance.web.Widget.extend({
init: function(parent) {
this._super(parent);
- this.unblockUIFunction = $.unblockUI;
+ this.unblockUIFunction = instance.web.unblockUI;
$.validator.addMethod('matches', function (s, _, re) {
return new RegExp(re).test(s);
}, _t("Invalid database name"));
@@ -341,16 +360,16 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
* from unblocking the UI
*/
blockUI: function () {
- $.blockUI();
- $.unblockUI = function () {};
+ instance.web.blockUI();
+ instance.web.unblockUI = function () {};
},
/**
* Reinstates $.unblockUI so third parties can play with blockUI, and
* unblocks the UI
*/
unblockUI: function () {
- $.unblockUI = this.unblockUIFunction;
- $.unblockUI();
+ instance.web.unblockUI = this.unblockUIFunction;
+ instance.web.unblockUI();
},
/**
* Displays an error dialog resulting from the various RPC communications
@@ -374,8 +393,19 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
var fields = $(form).serializeArray();
self.rpc("/web/database/create", {'fields': fields}, function(result) {
var form_obj = self.to_object(fields);
- self.getParent().do_login( form_obj['db_name'], 'admin', form_obj['create_admin_pwd']);
- self.destroy();
+ 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() {
+ instance.webclient.show_application();
+ },
+ },
+ };
+ self.do_action(client_action);
});
},
@@ -460,19 +490,26 @@ instance.web.DatabaseManager = instance.web.Widget.extend({
});
},
do_exit: function () {
+ this.do_action("login");
}
});
+instance.web.client_actions.add("database_manager", "instance.web.DatabaseManager");
instance.web.Login = instance.web.Widget.extend({
template: "Login",
remember_credentials: true,
_db_list: null,
- init: function(parent) {
+ init: function(parent, params) {
this._super(parent);
this.has_local_storage = typeof(localStorage) != 'undefined';
this.selected_db = null;
this.selected_login = null;
+ this.params = params || {};
+
+ if (this.params.login_successful) {
+ this.on('login_successful', this, this.params.login_successful);
+ }
if (this.has_local_storage && this.remember_credentials) {
this.selected_db = localStorage.getItem('last_db_login_success');
@@ -482,26 +519,17 @@ instance.web.Login = instance.web.Widget.extend({
}
}
},
- open_db_manager: function(){
- var self = this;
- self.$element.find('.oe_login_bottom').hide();
- self.$element.find('.oe_login_pane').hide();
- self.databasemanager = new instance.web.DatabaseManager(self);
- self.databasemanager.appendTo(self.$element);
- self.databasemanager.do_exit.add_last(function() {
- self.databasemanager.destroy();
- self.$element.find('.oe_login_bottom').show();
- self.$element.find('.oe_login_pane').show();
- self.load_db_list(true).then(self.on_db_list_loaded);
- });
- },
start: function() {
var self = this;
self.$element.find("form").submit(self.on_submit);
self.$element.find('.oe_login_manage_db').click(function() {
- self.open_db_manager();
+ self.do_action("database_manager");
+ });
+ return self.load_db_list().then(self.on_db_list_loaded).then(function() {
+ if (self.params.db) {
+ self.do_login(self.params.db, self.params.login, self.params.password);
+ }
});
- return self.load_db_list().then(self.on_db_list_loaded);
},
load_db_list: function (force) {
var d = $.when(), self = this;
@@ -522,7 +550,7 @@ instance.web.Login = instance.web.Widget.extend({
var dbdiv = this.$element.find('div.oe_login_dbpane');
this.$element.find("[name=db]").replaceWith(instance.web.qweb.render('Login.dblist', { db_list: list, selected_db: this.selected_db}));
if(list.length === 0) {
- self.open_db_manager();
+ this.do_action("database_manager");
} else if(list && list.length === 1) {
dbdiv.hide();
} else {
@@ -569,13 +597,100 @@ instance.web.Login = instance.web.Widget.extend({
localStorage.setItem('last_password_login_success', '');
}
}
- self.trigger("login");
+ self.trigger('login_successful');
},function () {
self.$(".oe_login_pane").fadeIn("fast");
self.$element.addClass("oe_login_invalid");
});
+ },
+ show: function () {
+ this.$element.show();
+ },
+ hide: function () {
+ this.$element.hide();
+ }
+});
+instance.web.client_actions.add("login", "instance.web.Login");
+
+/**
+ * Client action to reload the whole interface.
+ * If params has an entry 'menu_id', it opens the given menu entry.
+ */
+instance.web.Reload = instance.web.Widget.extend({
+ init: function(parent, params) {
+ this._super(parent);
+ this.menu_id = (params && params.menu_id) || false;
+ },
+ start: function() {
+ var l = window.location;
+ var timestamp = new Date().getTime();
+ var search = "?ts=" + timestamp;
+ if (l.search) {
+ search = l.search + "&ts=" + timestamp;
+ }
+ var hash = l.hash;
+ if (this.menu_id) {
+ hash = "#menu_id=" + this.menu_id;
+ }
+ var url = l.protocol + "//" + l.host + l.pathname + search + hash;
+ window.location = url;
+ }
+});
+instance.web.client_actions.add("reload", "instance.web.Reload");
+
+/**
+ * Client action to go back in breadcrumb history.
+ * If can't go back in history stack, will go back to home.
+ */
+instance.web.HistoryBack = instance.web.Widget.extend({
+ init: function(parent, params) {
+ if (!parent.history_back()) {
+ window.location = '/' + (window.location.search || '');
+ }
+ }
+});
+instance.web.client_actions.add("history_back", "instance.web.HistoryBack");
+
+/**
+ * Client action to go back home.
+ */
+instance.web.Home = instance.web.Widget.extend({
+ init: function(parent, params) {
+ window.location = '/' + (window.location.search || '');
}
});
+instance.web.client_actions.add("home", "instance.web.Home");
+
+instance.web.ChangePassword = instance.web.Widget.extend({
+ template: "ChangePassword",
+ start: function() {
+ var self = this;
+ self.$element.find("form[name=change_password_form]").validate({
+ submitHandler: function (form) {
+ self.rpc("/web/session/change_password",{
+ 'fields': $(form).serializeArray()
+ }, function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ } else {
+ instance.webclient.on_logout();
+ }
+ });
+ }
+ });
+ },
+ display_error: function (error) {
+ return instance.web.dialog($('<div>'), {
+ modal: true,
+ title: error.title,
+ buttons: [
+ {text: _("Ok"), click: function() { $(this).dialog("close"); }}
+ ]
+ }).html(error.error);
+ },
+})
+instance.web.client_actions.add("change_password", "instance.web.ChangePassword");
instance.web.Menu = instance.web.Widget.extend({
template: 'Menu',
@@ -744,37 +859,6 @@ instance.web.UserMenu = instance.web.Widget.extend({
}
});
},
- change_password :function() {
- var self = this;
- this.dialog = new instance.web.Dialog(this, {
- title: _t("Change Password"),
- width : 'auto'
- }).open();
- this.dialog.$element.html(QWeb.render("UserMenu.password", self));
- this.dialog.$element.find("form[name=change_password_form]").validate({
- submitHandler: function (form) {
- self.rpc("/web/session/change_password",{
- 'fields': $(form).serializeArray()
- }, function(result) {
- if (result.error) {
- self.display_error(result);
- return;
- } else {
- instance.webclient.on_logout();
- }
- });
- }
- });
- },
- display_error: function (error) {
- return instance.web.dialog($('<div>'), {
- modal: true,
- title: error.title,
- buttons: [
- {text: _("Ok"), click: function() { $(this).dialog("close"); }}
- ]
- }).html(error.error);
- },
do_update: function () {
var self = this;
var fct = function() {
@@ -790,7 +874,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.$element.find('.oe_topbar_name').text(topbar_name);
- var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=avatar&id=%s', self.session.prefix, self.session.session_id, self.session.uid);
+ 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);
$avatar.attr('src', avatar_src);
});
};
@@ -802,44 +886,10 @@ instance.web.UserMenu = instance.web.Widget.extend({
},
on_menu_settings: function() {
var self = this;
- var action_manager = new instance.web.ActionManager(this);
- var dataset = new instance.web.DataSet (this,'res.users',this.context);
- dataset.call ('action_get','',function (result){
- self.rpc('/web/action/load', {action_id:result}, function(result){
- action_manager.do_action(_.extend(result['result'], {
- target: 'inline',
- res_id: self.session.uid,
- res_model: 'res.users',
- flags: {
- action_buttons: false,
- search_view: false,
- sidebar: false,
- views_switcher: false,
- pager: false
- }
- }));
- });
+ self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) {
+ result.result.res_id = instance.connection.uid;
+ self.getParent().action_manager.do_action(result.result);
});
- this.dialog = new instance.web.Dialog(this,{
- title: _t("Preferences"),
- width: '700px',
- buttons: [
- {text: _t("Change password"), click: function(){ self.change_password(); }},
- {text: _t("Cancel"), click: function(){ $(this).dialog('destroy'); }},
- {text: _t("Save"), click: function(){
- var inner_widget = action_manager.inner_widget;
- inner_widget.views[inner_widget.active_view].controller.do_save()
- .then(function() {
- self.dialog.destroy();
- // needs to refresh interface in case language changed
- window.location.reload();
- });
- }
- }
- ]
- }).open();
- action_manager.appendTo(this.dialog.$element);
- action_manager.renderElement(this.dialog);
},
on_menu_about: function() {
var self = this;
@@ -851,56 +901,36 @@ instance.web.UserMenu = instance.web.Widget.extend({
window.location.href, 'debug');
});
instance.web.dialog($help, {autoOpen: true,
- modal: true, width: 960, title: _t("About")});
+ modal: true, width: 580, height: 290, resizable: false, title: _t("About")});
});
},
});
-instance.web.WebClient = instance.web.Widget.extend({
- init: function(parent) {
- var self = this;
+instance.web.Client = instance.web.Widget.extend({
+ init: function(parent, origin) {
+ instance.client = instance.webclient = this;
this._super(parent);
- instance.webclient = this;
- this.querystring = '?' + jQuery.param.querystring();
- this._current_state = null;
- },
- _get_version_label: function() {
- if (this.session.openerp_entreprise) {
- return 'OpenERP';
- } else {
- return _t("OpenERP - Unsupported/Community Version");
- }
- },
- set_title: function(title) {
- title = _.str.clean(title);
- var sep = _.isEmpty(title) ? '' : ' - ';
- document.title = title + sep + 'OpenERP';
+ this.origin = origin;
},
start: function() {
var self = this;
- this.$element.addClass("openerp openerp_webclient_container");
- if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
- $("body").addClass("kitten-mode-activated");
- if ($.blockUI) {
- $.blockUI.defaults.message = '<img src="http://www.amigrave.com/kitten.gif">';
- }
- }
- this.session.session_bind().then(function() {
- self.destroy_content();
- self.show_common();
- if (!self.session.session_is_valid()) {
- self.show_login();
- } else {
- self.show_application();
- }
+ return instance.connection.session_bind(this.origin).pipe(function() {
+ var $e = $(QWeb.render(self._template, {}));
+ self.replaceElement($e);
+ self.bind_events();
+ return self.show_common();
});
+ },
+ bind_events: function() {
+ var self = this;
this.$element.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() {
$(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter');
});
this.$element.on('click', '.oe_dropdown_toggle', function(ev) {
ev.preventDefault();
var $toggle = $(this);
- var $menu = $toggle.find('.oe_dropdown_menu');
+ var $menu = $toggle.siblings('.oe_dropdown_menu');
+ $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu');
var state = $menu.is('.oe_opened');
setTimeout(function() {
// Do not alter propagation
@@ -910,21 +940,63 @@ instance.web.WebClient = instance.web.Widget.extend({
var doc_width = $(document).width();
var offset = $menu.offset();
var menu_width = $menu.width();
- var x = doc_width - offset.left - menu_width - 15;
+ var x = doc_width - offset.left - menu_width - 2;
if (x < 0) {
$menu.offset({ left: offset.left + x }).width(menu_width);
}
}
}, 0);
});
- instance.web.bus.on('click', this, function() {
- self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened');
+ instance.web.bus.on('click', this, function(ev) {
+ $.fn.tipsy.clear();
+ if (!$(ev.target).is('input[type=file]')) {
+ self.$element.find('.oe_dropdown_menu.oe_opened').removeClass('oe_opened');
+ }
});
},
show_common: function() {
var self = this;
this.crashmanager = new instance.web.CrashManager();
instance.connection.on_rpc_error.add(this.crashmanager.on_rpc_error);
+ self.notification = new instance.web.Notification(this);
+ self.notification.appendTo(self.$element);
+ self.loading = new instance.web.Loading(self);
+ self.loading.appendTo(self.$element);
+ self.action_manager = new instance.web.ActionManager(self);
+ self.action_manager.appendTo(self.$('.oe_application'));
+ },
+});
+
+instance.web.WebClient = instance.web.Client.extend({
+ _template: 'WebClient',
+ init: function(parent) {
+ this._super(parent);
+ this._current_state = null;
+ },
+ start: function() {
+ var self = this;
+ return $.when(this._super()).pipe(function() {
+ if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) {
+ $("body").addClass("kitten-mode-activated");
+ if ($.blockUI) {
+ $.blockUI.defaults.message = '<img src="http://www.amigrave.com/kitten.gif">';
+ }
+ }
+ if (!self.session.session_is_valid()) {
+ self.show_login();
+ } else {
+ self.show_application();
+ }
+ });
+ },
+ set_title: function(title) {
+ title = _.str.clean(title);
+ var sep = _.isEmpty(title) ? '' : ' - ';
+ document.title = title + sep + 'OpenERP';
+ },
+ show_common: function() {
+ var self = this;
+ this._super();
window.onerror = function (message, file, line) {
self.crashmanager.on_traceback({
type: _t("Client Error"),
@@ -932,24 +1004,15 @@ instance.web.WebClient = instance.web.Widget.extend({
data: {debug: file + ':' + line}
});
};
- self.notification = new instance.web.Notification(this);
- self.notification.appendTo(self.$element);
- self.loading = new instance.web.Loading(self);
- self.loading.appendTo(self.$element);
- self.login = new instance.web.Login(self);
- self.login.on("login",self,self.show_application);
- self.$table = $(QWeb.render("WebClient", {}));
- self.action_manager = new instance.web.ActionManager(self);
- self.action_manager.appendTo(self.$table.find('.oe_application'));
},
show_login: function() {
- var self = this;
- self.login.appendTo(self.$element);
+ this.$('.oe_topbar').hide();
+ this.action_manager.do_action("login");
+ this.action_manager.inner_widget.on('login_successful', this, this.show_application);
},
show_application: function() {
var self = this;
- self.$element.append(self.$table);
- self.login.$element.hide();
+ self.$('.oe_topbar').show();
self.menu = new instance.web.Menu(self);
self.menu.replace(this.$element.find('.oe_menu_placeholder'));
self.menu.on('menu_click', this, this.on_menu_action);
@@ -960,7 +1023,7 @@ instance.web.WebClient = instance.web.Widget.extend({
self.user_menu.do_update();
self.bind_hashchange();
if (!self.session.openerp_entreprise) {
- var version_label = self._get_version_label();
+ var version_label = _t("OpenERP - Unsupported/Community Version");
self.$element.find('.oe_footer_powered').append(_.str.sprintf('<span> - <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">%s</a></span>', version_label));
}
self.set_title();
@@ -1001,15 +1064,15 @@ instance.web.WebClient = instance.web.Widget.extend({
$(window).bind('hashchange', this.on_hashchange);
var state = $.bbq.getState(true);
- if (! _.isEmpty(state)) {
- $(window).trigger('hashchange');
- } else {
+ if (_.isEmpty(state) || state.action == "login") {
self.menu.has_been_loaded.then(function() {
var first_menu_id = self.menu.$element.find("a:first").data("menu");
if(first_menu_id) {
self.menu.menu_click(first_menu_id);
}
});
+ } else {
+ $(window).trigger('hashchange');
}
},
on_hashchange: function(event) {
@@ -1030,6 +1093,7 @@ instance.web.WebClient = instance.web.Widget.extend({
},
do_push_state: function(state) {
this.set_title(state.title);
+ delete state.title;
var url = '#' + $.param(state);
this._current_state = _.clone(state);
$.bbq.pushState(url);
@@ -1056,38 +1120,46 @@ instance.web.WebClient = instance.web.Widget.extend({
}
},
set_content_full_screen: function(fullscreen) {
- if (fullscreen)
+ if (fullscreen) {
$(".oe_webclient", this.$element).addClass("oe_content_full_screen");
- else
+ $("body").css({'overflow-y':'hidden'});
+ } else {
$(".oe_webclient", this.$element).removeClass("oe_content_full_screen");
+ $("body").css({'overflow-y':'scroll'});
+ }
}
});
-instance.web.EmbeddedClient = instance.web.Widget.extend({
- template: 'EmptyComponent',
- init: function(parent, action_id, options) {
- this._super(parent);
- // TODO take the xmlid of a action instead of its id
+instance.web.EmbeddedClient = instance.web.Client.extend({
+ _template: 'EmbedClient',
+ init: function(parent, origin, dbname, login, key, action_id, options) {
+ this._super(parent, origin);
+
+ this.dbname = dbname;
+ this.login = login;
+ this.key = key;
this.action_id = action_id;
this.options = options || {};
- this.am = new instance.web.ActionManager(this);
},
start: function() {
var self = this;
- this.am.appendTo(this.$element.addClass('openerp'));
- return this.rpc("/web/action/load", { action_id: this.action_id }, function(result) {
- var action = result.result;
- action.flags = _.extend({
- //views_switcher : false,
- search_view : false,
- action_buttons : false,
- sidebar : false
- //pager : false
- }, self.options, action.flags || {});
+ return $.when(this._super()).pipe(function() {
+ return instance.connection.session_authenticate(self.dbname, self.login, self.key, true).pipe(function() {
+ return self.rpc("/web/action/load", { action_id: self.action_id }, function(result) {
+ var action = result.result;
+ action.flags = _.extend({
+ //views_switcher : false,
+ search_view : false,
+ action_buttons : false,
+ sidebar : false
+ //pager : false
+ }, self.options, action.flags || {});
- self.am.do_action(action);
+ self.action_manager.do_action(action);
+ });
+ });
});
- }
+ },
});
instance.web.embed = function (origin, dbname, login, key, action, options) {
@@ -1101,13 +1173,8 @@ instance.web.embed = function (origin, dbname, login, key, action, options) {
var sc = document.getElementsByTagName('script');
currentScript = sc[sc.length-1];
}
- instance.connection.session_bind(origin).then(function () {
- instance.connection.session_authenticate(dbname, login, key, true).then(function () {
- var client = new instance.web.EmbeddedClient(null, action, options);
- client.insertAfter(currentScript);
- });
- });
-
+ var client = new instance.web.EmbeddedClient(null, origin, dbname, login, key, action, options);
+ client.insertAfter(currentScript);
};
};
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index 1ed4c4788e3..a46d6314ebe 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -378,7 +378,8 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
instance.web.EventDispatcherMixin.init.call(this);
this.__getterSetterInternalMap = {};
},
- set: function(map) {
+ set: function(map, options) {
+ options = options || {};
var self = this;
var changed = false;
_.each(map, function(val, key) {
@@ -387,10 +388,11 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, {
return;
changed = true;
self.__getterSetterInternalMap[key] = val;
- self.trigger("change:" + key, self, {
- oldValue: tmp,
- newValue: val
- });
+ if (! options.silent)
+ self.trigger("change:" + key, self, {
+ oldValue: tmp,
+ newValue: val
+ });
});
if (changed)
self.trigger("change", self);
@@ -489,24 +491,20 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, {
*
* The semantics of this precisely replace closing over the method call.
*
- * @param {String} method_name name of the method to invoke
+ * @param {String|Function} method function or name of the method to invoke
* @returns {Function} proxied method
*/
- proxy: function (method_name) {
+ proxy: function (method) {
var self = this;
return function () {
- return self[method_name].apply(self, arguments);
+ var fn = (typeof method === 'string') ? self[method] : method;
+ return fn.apply(self, arguments);
}
}
});
instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
/**
- * Tag name when creating a default $element.
- * @type string
- */
- tagName: 'div',
- /**
* Constructs the widget and sets its parent if a parent is given.
*
* @constructs instance.web.Widget
@@ -515,14 +513,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
* @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.
- * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
- * to bind the current Widget to an already existing part of the DOM, which is not compatible
- * with the DOM insertion methods provided by the current implementation of Widget. So
- * for new components this argument should not be provided any more.
*/
init: function(parent) {
instance.web.CallbackEnabledMixin.init.call(this);
- this.$element = $(document.createElement(this.tagName));
this.setParent(parent);
},
/**
@@ -532,7 +525,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
_.each(this.getChildren(), function(el) {
el.destroy();
});
- if(this.$element != null) {
+ if(this.$element) {
this.$element.remove();
}
instance.web.PropertiesMixin.destroy.call(this);
@@ -611,6 +604,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
* @returns {jQuery.Deferred}
*/
start: function() {
+ return $.when();
}
});
@@ -671,6 +665,12 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn
* 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.
@@ -687,13 +687,11 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
* @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.
- * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want
- * to bind the current Widget to an already existing part of the DOM, which is not compatible
- * with the DOM insertion methods provided by the current implementation of Widget. So
- * for new components this argument should not be provided any more.
*/
init: function(parent) {
instance.web.WidgetMixin.init.call(this,parent);
+ // FIXME: this should not be
+ this.setElement(this._make_descriptive());
this.session = instance.connection;
},
/**
@@ -702,20 +700,120 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
* key that references `this`.
*/
renderElement: function() {
- var rendered = null;
- if (this.template)
- rendered = instance.web.qweb.render(this.template, {widget: this});
- if (_.str.trim(rendered)) {
- var elem = $(rendered);
- this.$element.replaceWith(elem);
- this.$element = elem;
+ var $el;
+ if (this.template) {
+ $el = $(_.str.trim(instance.web.qweb.render(
+ this.template, {widget: this})));
+ } else {
+ $el = this._make_descriptive();
}
+ this.replaceElement($el);
},
+
/**
- * Shortcut for $element.find() like backbone
+ * Re-sets the widget's root element and replaces the old root element
+ * (if any) by the new one in the DOM.
+ *
+ * @param {HTMLElement | jQuery} $el
+ * @returns {*} this
+ */
+ replaceElement: function ($el) {
+ var $oldel = this.$element;
+ this.setElement($el);
+ if ($oldel && !$oldel.is(this.$element)) {
+ $oldel.replaceWith(this.$element);
+ }
+ return this;
+ },
+ /**
+ * Re-sets the widget's root element (el/$el/$element).
+ *
+ * Includes:
+ * * re-delegating events
+ * * re-binding sub-elements
+ * * if the widget already had a root element, replacing the pre-existing
+ * element in the DOM
+ *
+ * @param {HTMLElement | jQuery} element new root element for the widget
+ * @return {*} this
+ */
+ setElement: function (element) {
+ // NB: completely useless, as WidgetMixin#init creates a $element
+ // always
+ if (this.$element) {
+ this.undelegateEvents();
+ }
+
+ this.$element = (element instanceof $) ? element : $(element);
+ this.el = this.$element[0];
+
+ this.delegateEvents();
+
+ return this;
+ },
+ /**
+ * Utility function to build small DOM elements.
+ *
+ * @param {String} tagName name of the DOM element to create
+ * @param {Object} [attributes] map of DOM attributes to set on the element
+ * @param {String} [content] HTML content to set on the element
+ * @return {Element}
+ */
+ make: function (tagName, attributes, content) {
+ var el = document.createElement(tagName);
+ if (!_.isEmpty(attributes)) {
+ $(el).attr(attributes);
+ }
+ if (content) {
+ $(el).html(content);
+ }
+ return el;
+ },
+ /**
+ * Makes a potential root element from the declarative builder of the
+ * widget
+ *
+ * @return {jQuery}
+ * @private
+ */
+ _make_descriptive: function () {
+ var attrs = _.extend({}, this.attributes || {});
+ if (this.id) { attrs.id = this.id; }
+ if (this.className) { attrs['class'] = this.className; }
+ return $(this.make(this.tagName, attrs));
+ },
+ delegateEvents: function () {
+ var events = this.events;
+ if (_.isEmpty(events)) { return; }
+
+ for(var key in events) {
+ if (!events.hasOwnProperty(key)) { continue; }
+
+ var method = this.proxy(events[key]);
+
+ var match = /^(\S+)(\s+(.*))?$/.exec(key);
+ var event = match[1];
+ var selector = match[3];
+
+ event += '.widget_events';
+ if (!selector) {
+ this.$element.on(event, method);
+ } else {
+ this.$element.on(event, selector, method);
+ }
+ }
+ },
+ undelegateEvents: function () {
+ this.$element.off('.widget_events');
+ },
+ /**
+ * Shortcut for ``this.$element.find(selector)``
+ *
+ * @param {String} selector CSS selector, rooted in $el
+ * @returns {jQuery} selector match
*/
- "$": function() {
- return this.$element.find.apply(this.$element,arguments);
+ $: function(selector) {
+ return this.$element.find(selector);
},
/**
* Informs the action manager to do an action. This supposes that
@@ -1037,7 +1135,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
uid: new py.float(this.uid),
datetime: datetime,
time: time,
- relativedelta: relativedelta
+ relativedelta: relativedelta,
+ current_date: date.today.__call__().strftime(['%Y-%m-%d'])
};
},
/**
@@ -1278,7 +1377,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
processData: false
}, url);
if (this.synch)
- ajax.async = false;
+ ajax.async = false;
return $.ajax(ajax);
},
rpc_jsonp: function(url, payload) {
@@ -1297,7 +1396,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({
data: data
}, url);
if (this.synch)
- ajax.async = false;
+ ajax.async = false;
var payload_str = JSON.stringify(payload);
var payload_url = $.param({r:payload_str});
if(payload_url.length < 2000) {
diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js
index a5a62b9bcc1..6ba62162e93 100644
--- a/addons/web/static/src/js/coresetup.js
+++ b/addons/web/static/src/js/coresetup.js
@@ -19,15 +19,14 @@ instance.web.OldWidget = instance.web.Widget.extend({
this._super(parent);
this.element_id = element_id;
this.element_id = this.element_id || _.uniqueId('widget-');
+
var tmp = document.getElementById(this.element_id);
- this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName));
+ this.setElement(tmp || this._make_descriptive());
},
renderElement: function() {
var rendered = this.render();
if (rendered) {
- var elem = $(rendered);
- this.$element.replaceWith(elem);
- this.$element = elem;
+ this.replaceElement($(rendered));
}
return this;
},
@@ -394,13 +393,13 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess
timer = setTimeout(waitLoop, CHECK_INTERVAL);
},
synchronized_mode: function(to_execute) {
- var synch = this.synch;
- this.synch = true;
- try {
- return to_execute();
- } finally {
- this.synch = synch;
- }
+ var synch = this.synch;
+ this.synch = true;
+ try {
+ return to_execute();
+ } finally {
+ this.synch = synch;
+ }
}
});
@@ -416,12 +415,12 @@ instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin,
// check gtk bindings
// http://unixpapa.com/js/key.html
_.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
- $('html').on(evtype, self, function(ev) {
+ $('html').on(evtype, function(ev) {
self.trigger(evtype, ev);
});
});
_.each('resize,scroll'.split(','), function(evtype) {
- $(window).on(evtype, self, function(ev) {
+ $(window).on(evtype, function(ev) {
self.trigger(evtype, ev);
});
});
@@ -541,16 +540,48 @@ $.async_when = function() {
// special tweak for the web client
var old_async_when = $.async_when;
$.async_when = function() {
- if (instance.connection.synch)
- return $.when.apply(this, arguments);
- else
- return old_async_when.apply(this, arguments);
+ if (instance.connection.synch)
+ return $.when.apply(this, arguments);
+ else
+ return old_async_when.apply(this, arguments);
};
/** Setup blockui */
if ($.blockUI) {
$.blockUI.defaults.baseZ = 1100;
- $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">';
+ $.blockUI.defaults.message = '<div class="oe_blockui_spin" style="height: 50px">';
+ $.blockUI.defaults.css.border = '0';
+ $.blockUI.defaults.css["background-color"] = '';
+ $.blockUI.spinners = [];
+}
+instance.web.blockUI = function() {
+ var tmp = $.blockUI.apply($, arguments);
+ var target = $(".oe_blockui_spin")[0];
+ var opts = {
+ lines: 13, // The number of lines to draw
+ length: 7, // The length of each line
+ width: 4, // The line thickness
+ radius: 10, // The radius of the inner circle
+ rotate: 0, // The rotation offset
+ color: '#FFF', // #rgb or #rrggbb
+ speed: 1, // Rounds per second
+ trail: 60, // Afterglow percentage
+ shadow: false, // Whether to render a shadow
+ hwaccel: false, // Whether to use hardware acceleration
+ className: 'spinner', // The CSS class to assign to the spinner
+ zIndex: 2e9, // The z-index (defaults to 2000000000)
+ top: 'auto', // Top position relative to parent in px
+ left: 'auto' // Left position relative to parent in px
+ };
+ var spinner = new Spinner(opts).spin(target);
+ $.blockUI.spinners.push(spinner);
+ return tmp;
+}
+instance.web.unblockUI = function() {
+ _.each($.blockUI.spinners, function(el) {
+ el.stop();
+ });
+ return $.unblockUI.apply($, arguments);
}
/** Setup default session */
@@ -641,32 +672,6 @@ instance.connection.on('module_loaded', this, function () {
*/
instance.web.client_actions = new instance.web.Registry();
-/**
- * Client action to reload the whole interface.
- * If params has an entry 'menu_id', it opens the given menu entry.
- */
-instance.web.Reload = instance.web.Widget.extend({
- init: function(parent, params) {
- this._super(parent);
- this.menu_id = (params && params.menu_id) || false;
- },
- start: function() {
- var l = window.location;
- var timestamp = new Date().getTime();
- var search = "?ts=" + timestamp;
- if (l.search) {
- search = l.search + "&ts=" + timestamp;
- }
- var hash = l.hash;
- if (this.menu_id) {
- hash = "#menu_id=" + this.menu_id;
- }
- var url = l.protocol + "//" + l.host + l.pathname + search + hash;
- window.location = url;
- }
-});
-instance.web.client_actions.add("reload", "instance.web.Reload");
-
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index fdb15d598af..2589afe5503 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -803,7 +803,7 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da
return this.ids.length;
},
alter_ids: function(n_ids) {
- this.ids = n_ids;
+ this.ids = n_ids;
},
});
instance.web.DataSetStatic = instance.web.DataSet.extend({
@@ -1042,7 +1042,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
self.cache.push({id: id, values: record});
} else {
// I assume cache value is prioritary
- cached.values = _.defaults(_.clone(cached.values), record);
+ cached.values = _.defaults(_.clone(cached.values), record);
}
});
return_records();
@@ -1067,7 +1067,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
return this._super(method, args, callback, error_callback);
},
alter_ids: function(n_ids) {
- this._super(n_ids);
+ this._super(n_ids);
this.on_change();
},
});
diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js
index ebe82ddb4b4..f8e669e1b4a 100644
--- a/addons/web/static/src/js/data_export.js
+++ b/addons/web/static/src/js/data_export.js
@@ -376,7 +376,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
exported_fields.unshift({name: 'id', label: 'External ID'});
var export_format = this.$element.find("#export_format").val();
- $.blockUI();
+ instance.web.blockUI();
this.session.get_file({
url: '/web/export/' + export_format,
data: {data: JSON.stringify({
@@ -387,7 +387,7 @@ instance.web.DataExport = instance.web.Dialog.extend({
import_compat: Boolean(
this.$element.find("#import_compat").val())
})},
- complete: $.unblockUI
+ complete: instance.web.unblockUI
});
},
close: function() {
diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js
index 6059f292df9..620878569ce 100644
--- a/addons/web/static/src/js/data_import.js
+++ b/addons/web/static/src/js/data_import.js
@@ -156,7 +156,7 @@ instance.web.DataImport = instance.web.Dialog.extend({
});
},
toggle_import_button: function (newstate) {
- instance.web.dialog(this.$element, 'widget')
+ instance.web.dialog(this.$element, 'widget')
.find('.oe_import_dialog_button')
.button('option', 'disabled', !newstate);
},
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 6ceb7090e44..797e1a2cbdf 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -271,82 +271,4 @@ instance.web.auto_date_to_str = function(value, type) {
}
};
-/**
- * Formats a provided cell based on its field type. Most of the field types
- * return a correctly formatted value, but some tags and fields are
- * special-cased in their handling:
- *
- * * buttons will return an actual ``<button>`` tag with a bunch of error handling
- *
- * * boolean fields will return a checkbox input, potentially disabled
- *
- * * binary fields will return a link to download the binary data as a file
- *
- * @param {Object} row_data record whose values should be displayed in the cell
- * @param {Object} column column descriptor
- * @param {"button"|"field"} column.tag base control type
- * @param {String} column.type widget type for a field control
- * @param {String} [column.string] button label
- * @param {String} [column.icon] button icon
- * @param {Object} [options]
- * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
- * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
- * @param {String} [options.model] current record's model
- * @param {Number} [options.id] current record's id
- *
- */
-instance.web.format_cell = function (row_data, column, options) {
- options = options || {};
- var attrs = {};
- if (options.process_modifiers !== false) {
- attrs = column.modifiers_for(row_data);
- }
- if (attrs.invisible) { return ''; }
-
- if (column.tag === 'button') {
- return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
- '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
- '</button>', {
- title: column.string || '',
- additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
- 'disabled="disabled" class="oe_list_button_disabled"' : '',
- prefix: instance.connection.prefix,
- icon: column.icon,
- alt: column.string || ''
- });
- }
- if (!row_data[column.id]) {
- return options.value_if_empty === undefined ? '' : options.value_if_empty;
- }
-
- switch (column.widget || column.type) {
- case "boolean":
- return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
- row_data[column.id].value ? 'checked="checked"' : '');
- case "binary":
- var text = _t("Download"),
- download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id);
- if (column.filename) {
- download_url += '&filename_field=' + column.filename;
- if (row_data[column.filename]) {
- text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
- row_data[column.filename].value, {type: 'char'}));
- }
- }
- return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
- text: text,
- href: download_url,
- size: row_data[column.id].value
- });
- case 'progressbar':
- return _.template(
- '<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
- value: _.str.sprintf("%.0f", row_data[column.id].value || 0)
- });
- }
-
- return _.escape(instance.web.format_value(
- row_data[column.id].value, column, options.value_if_empty));
-}
-
};
diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js
index c8b6f797dba..53daa40ae31 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -330,6 +330,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
});
}
+ // Launch a search on clicking the oe_searchview_search button
+ this.$element.on('click', 'button.oe_searchview_search', function (e) {
+ e.stopImmediatePropagation();
+ self.do_search();
+ });
+
this.$element.on('keydown',
'.oe_searchview_input, .oe_searchview_facet', function (e) {
switch(e.which) {
@@ -510,7 +516,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
this.$element.addClass('oe_focused');
},
childBlurred: function () {
- this.$element.removeClass('oe_focused');
+ var val = this.$element.val();
+ this.$element.val('');
+ var complete = this.$element.data('autocomplete');
+ if ((val && complete.term === undefined) || complete.previous !== undefined) {
+ throw new Error("new jquery.ui version altering implementation" +
+ " details relied on");
+ }
+ delete complete.term;
+ this.$element.removeClass('oe_focused')
+ .trigger('blur');
},
/**
*
@@ -646,7 +661,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea
null, _(this.select_for_drawer()).invoke(
'appendTo', this.$element.find('.oe_searchview_drawer')));
- new instance.web.search.AddToDashboard(this).appendTo($('.oe_searchview_drawer', this.$element));
+ new instance.web.search.AddToReporting(this).appendTo($('.oe_searchview_drawer', this.$element));
// load defaults
var defaults_fetched = $.when.apply(null, _(this.inputs).invoke(
@@ -846,13 +861,13 @@ instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web
);
}
});
-instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{
+instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{
template: null,
/**
* Root class of all search widgets
*
* @constructs instance.web.search.Widget
- * @extends instance.web.OldWidget
+ * @extends instance.web.Widget
*
* @param view the ancestor view of this widget
*/
@@ -1475,7 +1490,17 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
facet_for: function (value) {
var self = this;
if (value instanceof Array) {
- return $.when(facet_from(this, value));
+ if (value.length === 2 && _.isString(value[1])) {
+ return $.when(facet_from(this, value));
+ }
+ if (value.length > 1) {
+ // more than one search_default m2o id? Should we OR them?
+ throw new Error(
+ _("M2O search fields do not currently handle multiple default values"));
+ }
+ // there are many cases of {search_default_$m2ofield: [id]}, need
+ // to handle this as if it were a single value.
+ value = value[0];
}
return this.model.call('name_get', [value], {}).pipe(function (names) {
if (_(names).isEmpty()) { return null; }
@@ -1653,41 +1678,39 @@ instance.web.search.Filters = instance.web.search.Input.extend({
}));
}
});
-instance.web.search.AddToDashboard = instance.web.Widget.extend({
- template: 'SearchView.addtodashboard',
+instance.web.search.AddToReporting = instance.web.Widget.extend({
+ template: 'SearchView.addtoreporting',
_in_drawer: true,
start: function () {
var self = this;
- this.data_loaded = $.Deferred();
- this.dashboard_data =[];
this.$element
.on('click', 'h4', this.proxy('show_option'))
.on('submit', 'form', function (e) {
e.preventDefault();
self.add_dashboard();
});
- return $.when(this.load_data(),this.data_loaded).pipe(this.proxy("render_data"));
+ return this.load_data().then(this.proxy("render_data"));
},
load_data:function(){
- var self = this,dashboard_menu = instance.webclient.menu.data.data.children;
- var ir_model_data = new instance.web.Model('ir.model.data',{},[['name','=','menu_reporting_dashboard']]).query(['res_id']);
- var map_data = function(result){
- _.detect(dashboard_menu, function(dash){
- var id = _.pluck(dash.children, "id"),indexof = _.indexOf(id, result.res_id);
- if(indexof !== -1){
- self.dashboard_data = dash.children[indexof].children
- self.data_loaded.resolve();
- return;
- }
- });
- };
- return ir_model_data._execute().done(function(result){map_data(result[0])});
+ if (!instance.webclient) { return $.Deferred().reject(); }
+ var dashboard_menu = instance.webclient.menu.data.data.children;
+ return new instance.web.Model('ir.model.data')
+ .query(['res_id'])
+ .filter([['name','=','menu_reporting_dashboard']])
+ .first().pipe(function (result) {
+ var menu = _(dashboard_menu).chain()
+ .pluck('children')
+ .flatten(true)
+ .find(function (child) { return child.id === result.res_id; })
+ .value();
+ return menu ? menu.children : [];
+ });
},
-
- render_data: function(){
- var self = this;
- var selection = instance.web.qweb.render("SearchView.addtodashboard.selection",{selections:this.dashboard_data});
- this.$element.find("input").before(selection)
+ render_data: function(dashboard_choices){
+ var selection = instance.web.qweb.render(
+ "SearchView.addtoreporting.selection", {
+ selections: dashboard_choices});
+ this.$("input").before(selection)
},
add_dashboard:function(){
var self = this;
@@ -1695,11 +1718,11 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({
var view_parent = this.getParent().getParent();
if (! view_parent.action || ! this.$element.find("select").val())
return this.do_warn("Can't find dashboard action");
- data = getParent.build_search_data(),
- context = new instance.web.CompoundContext(getParent.dataset.get_context() || []),
- domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
- _.each(data.contexts, function(x) {context.add(x);});
- _.each(data.domains, function(x) {domain.add(x);});
+ var data = getParent.build_search_data();
+ var context = new instance.web.CompoundContext(getParent.dataset.get_context() || []);
+ var domain = new instance.web.CompoundDomain(getParent.dataset.get_domain() || []);
+ _.each(data.contexts, context.add, context);
+ _.each(data.domains, domain.add, domain);
this.rpc('/web/searchview/add_to_dashboard', {
menu_id: this.$element.find("select").val(),
action_id: view_parent.action.id,
@@ -1720,7 +1743,7 @@ instance.web.search.AddToDashboard = instance.web.Widget.extend({
this.$element.toggleClass('oe_opened');
if (! this.$element.hasClass('oe_opened'))
return;
- this.$element.find("input").val(this.getParent().fields_view.name || "" );
+ this.$("input").val(this.getParent().fields_view.name || "" );
}
});
diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js
deleted file mode 100644
index 6df78679ecb..00000000000
--- a/addons/web/static/src/js/view_editor.js
+++ /dev/null
@@ -1,1212 +0,0 @@
-openerp.web.view_editor = function(instance) {
-var _t = instance.web._t;
-var QWeb = instance.web.qweb;
-instance.web.ViewEditor = instance.web.OldWidget.extend({
- init: function(parent, element_id, dataset, view, options) {
- this._super(parent);
- this.element_id = element_id;
- this.parent = parent;
- this.dataset = new instance.web.DataSetSearch(this, 'ir.ui.view', null, null),
- this.model = dataset.model;
- this.xml_element_id = 0;
- this.property = instance.web.ViewEditor.property_widget;
- this.one_object = false;
- },
- start: function() {
- this.init_view_editor();
- },
- init_view_editor: function() {
- var self = this,
- action_title = _.str.sprintf(_t("Manage Views (%s)"), this.model);
- var action = {
- name: action_title,
- context: this.session.user_context,
- domain: [["model", "=", this.model]],
- res_model: 'ir.ui.view',
- views: [[false, 'list']],
- type: 'ir.actions.act_window',
- target: "current",
- limit: this.dataset.limit || 80,
- auto_search : true,
- flags: {
- sidebar: false,
- deletable: false,
- views_switcher: false,
- action_buttons: false,
- search_view: false,
- pager: false,
- radio: true,
- select_view_id: self.parent.fields_view.view_id
- }
- };
- this.view_edit_dialog = new instance.web.Dialog(this, {
- title: action_title,
- width: 850,
- buttons: [
- {text: _t("Create"), click: function() { self.on_create_view(); }},
- {text: _t("Edit"), click: function() { self.xml_element_id = 0; self.get_arch(); }},
- {text: _t("Remove"), click: function() { self.do_delete_view(); }},
- {text: _t("Close"), click: function() { self.view_edit_dialog.close(); window.location.reload(); }}
- ]
- }).open();
- this.view_edit_dialog.on_close.add_last(function(){window.location.reload();});
- this.main_view_id = this.parent.fields_view.view_id;
- this.action_manager = new instance.web.ActionManager(this);
- $.when(this.action_manager.do_action(action)).then(function() {
- var viewmanager = self.action_manager.inner_widget,
- controller = viewmanager.views[viewmanager.active_view].controller;
- self.action_manager.appendTo(self.view_edit_dialog.$element);
- self.action_manager.renderElement(self.view_edit_dialog);
- controller.on_loaded.add_last(function(){
- $(controller.groups).bind({
- 'selected': function(e, ids, records) {
- self.main_view_id = ids[0];
- }
- })
- });
- });
- },
- on_create_view: function() {
- var self = this;
- this.create_view_dialog = new instance.web.Dialog(this, {
- title: _.str.sprintf(_t("Create a view (%s)"), self.model),
- buttons: [
- {text: _t("Save"), click: function () {
- var view_values = {};
- var warn = false;
- _.each(self.create_view_widget, function(widget) {
- if (widget.is_invalid) {
- warn = true;
- return false;
- }
- if (widget.dirty && !widget.is_invalid) {
- view_values[widget.name] = widget.get_value();
- }
- });
- if (warn) {
- self.on_valid_create_view(self.create_view_widget);
- } else {
- $.when(self.do_save_view(view_values)).then(function() {
- self.create_view_dialog.close();
- var controller = self.action_manager.inner_widget.views[self.action_manager.inner_widget.active_view].controller;
- controller.reload_content();
- });
- }
- }},
- {text: _t("Cancel"), click: function () { self.create_view_dialog.close(); }}
- ]
- }).open();
- var view_widget = [{'name': 'view_name', 'string':'View Name', 'type': 'char', 'required': true, 'value' : this.model + '.custom_' + Math.round(Math.random() * 1000)},
- {'name': 'view_type', 'string': 'View Type', 'type': 'selection', 'required': true, 'value': 'Form', 'selection': [['',''],['tree', 'Tree'],['form', 'Form'],['graph', 'Graph'],['calendar', 'Calender']]},
- {'name': 'proirity', 'string': 'Priority', 'type': 'float', 'required': true, 'value':'16'}];
- this.create_view_dialog.$element.append('<table id="create_view" style="width:400px" class="oe_form"></table>');
- this.create_view_widget = [];
- _.each(view_widget, function(widget) {
- var type_widget = new (self.property.get_any([widget.type])) (self.create_view_dialog, widget);
- self.create_view_dialog.$element.find('table[id=create_view]').append('<tr><td width="100px" align="right">' + widget.string + ':</td>' + type_widget.render()+'</tr>');
- var value = null;
- if (widget.value) {
- value = widget.value;
- type_widget.dirty = true;
- }
- type_widget.start();
- type_widget.set_value(value);
- self.create_view_widget.push(type_widget);
- });
- },
- do_save_view: function(values) {
- def = $.Deferred();
- var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null);
- var model_dataset = new instance.web.DataSetSearch(this, 'ir.model', null, null);
- var view_string = "", field_name = false, self = this;
- field_dataset.call( 'fields_get', [], function(fields) {
- _.each(['name', 'x_name'], function(value) {
- if (_.include(_.keys(fields), value)) {
- field_name = value;
- return false;
- }
- });
- if (field_name) {
- model_dataset.read_slice(['name','field_id'], {"domain": [['model','=',self.model]]}).then(function(records) {
- if (records) {view_string = records[0].name;}
- var arch = _.str.sprintf("<?xml version='1.0'?>\n<%s string='%s'>\n\t<field name='%s'/>\n</%s>", values.view_type, view_string, field_name, values.view_type);
- var vals = {'model': self.model, 'name': values.view_name, 'priority': values.priority, 'type': values.view_type, 'arch': arch};
- self.dataset.create(vals, function(suc) {
- def.resolve();
- });
- });
- }
- });
- return def.promise();
- },
- on_valid_create_view: function(widgets) {
- var msg = "<ul>";
- _.each(widgets, function(widget) {
- if (widget.is_invalid) {
- msg += "<li>" + widget.name + "</li>";
- }
- });
- msg += "</ul>";
- this.do_warn("The following fields are invalid :", msg);
- },
- add_node_name : function(node) {
- if(node.tagName.toLowerCase() == "button" || node.tagName.toLowerCase() == "field"){
- return (node.getAttribute('name'))?
- _.str.sprintf( "<%s name='%s'>",node.tagName.toLowerCase(), node.getAttribute('name')):
- _.str.sprintf( "<%s>",node.tagName.toLowerCase());
- }else if(node.tagName.toLowerCase() == "group"){
- return (node.getAttribute('string'))?
- _.str.sprintf( "<%s>",node.getAttribute('string')):
- _.str.sprintf( "<%s>",node.tagName.toLowerCase());
- }else{
- return (node.getAttribute('string'))?
- _.str.sprintf( "<%s string='%s'>",node.tagName.toLowerCase(), node.getAttribute('string')):
- _.str.sprintf( "<%s>",node.tagName.toLowerCase());
- }
- },
- do_delete_view: function() {
- var self = this;
- if (confirm(_t("Do you really want to remove this view?"))) {
- var controller = this.action_manager.inner_widget.views[this.action_manager.inner_widget.active_view].controller;
- this.dataset.unlink([this.main_view_id]).then(function() {
- controller.reload_content();
- self.main_view_id = self.parent.fields_view.view_id;
- });
- }
- },
- create_View_Node: function(node){
- ViewNode = {
- 'level': ($(node).parents()).length + 1,
- 'id': this.xml_element_id += 1,
- 'att_list': [],
- 'name': this.add_node_name(node),
- 'child_id': []
- };
- ViewNode.att_list.push(node.tagName.toLowerCase());
- _.each(node.attributes, function(att) {
- ViewNode.att_list.push([att.nodeName, att.nodeValue]);
- });
- return ViewNode;
- },
- append_child_object: function(main_object, parent_id, child_obj_list) {
- var self = this;
- if (main_object.id == parent_id) {
- main_object.child_id = child_obj_list;
- return main_object;
- } else {
- _.each(main_object.child_id, function(child_object) {
- self.append_child_object(child_object, parent_id, child_obj_list);
- });
- }
- },
- convert_arch_to_obj: function(xml_Node, main_object, parent_id) {
- var self = this;
- var child_obj_list = [];
- _.each(xml_Node, function(element) {
- child_obj_list.push(self.create_View_Node(element));
- });
- this.append_child_object(main_object, parent_id, child_obj_list);
- var obj_xml_list = _.zip(xml_Node, child_obj_list);
- _.each(obj_xml_list, function(node) {
- var children = _.filter(node[0].childNodes, function(child) {
- return child.nodeType == 1;
- });
- if (children) {
- self.convert_arch_to_obj(children, main_object, node[1].id);
- }
- });
- return main_object;
- },
- parse_xml: function(arch, view_id) {
- main_object = {
- 'level': 0,
- 'id': this.xml_element_id +=1,
- 'att_list': [],
- 'name': _.str.sprintf("<view view_id = %s>", view_id),
- 'child_id': []
- };
- var xml_arch = QWeb.load_xml(arch);
- return [this.convert_arch_to_obj(xml_arch.childNodes, main_object, this.xml_element_id)];
- },
- get_arch: function() {
- var self = this;
- var view_arch_list = [];
- this.dataset.read_ids([parseInt(self.main_view_id)], ['arch', 'type','priority']).then(function(arch) {
- if (arch.length) {
- var arch_object = self.parse_xml(arch[0].arch, self.main_view_id);
- self.main_view_type = arch[0].type == 'tree'? 'list': arch[0].type;
- view_arch_list.push({"view_id": self.main_view_id, "arch": arch[0].arch,"priority":arch[0].priority});
- self.dataset.read_slice([], {domain: [['inherit_id','=', parseInt(self.main_view_id)]]}).then(function(result) {
- _.each(result, function(res) {
- view_arch_list.push({"view_id": res.id, "arch": res.arch,"priority":res.priority});
- self.inherit_view(arch_object, res);
- });
- return self.edit_view({"main_object": arch_object,
- "parent_child_id": self.parent_child_list(arch_object, []),
- "arch": view_arch_list});
- });
- } else {
- self.do_warn("Please select view in list :");
- }
- });
- },
- parent_child_list : function(one_object, parent_list) {
- var self = this;
- _.each(one_object , function(element) {
- if (element.child_id.length != 0) {
- parent_list.push({"key": element.id, "value": _.pluck(element.child_id, 'id')});
- self.parent_child_list(element.child_id, parent_list);
- }
- });
- return parent_list;
- },
- inherit_view : function(arch_object, result) {
- var self = this, xml_list = [], xml_arch = QWeb.load_xml(result.arch);
- if (xml_arch.childNodes[0].tagName == "data") {
- xml_list = _.filter(xml_arch.childNodes[0].childNodes, function(child) {
- return child.nodeType == 1;
- });
- } else {
- xml_list.push( xml_arch.childNodes[0]);
- }
- _.each(xml_list, function(xml) {
- var expr_to_list = [], xpath_arch_object = self.parse_xml(QWeb.tools.xml_node_to_string(xml), result.id);
- if (xml.tagName == "xpath") {
- var part_expr = _.without(xml.getAttribute('expr').split("/"), "");
- _.each(part_expr, function(part) {
- expr_to_list.push(_.without($.trim(part.replace(/[^a-zA-Z 0-9 _]+/g,'!')).split("!"), ""));
- });
- } else {
- var temp = _.reject(xpath_arch_object[0].child_id[0].att_list, function(list) {
- return list instanceof Array? _.include(list, "position"): false;
- });
- expr_to_list = [_.flatten(temp)];
- }
- self.inherit_apply(expr_to_list, arch_object ,xpath_arch_object);
- });
- },
- inherit_apply: function(expr_list ,arch_object ,xpath_arch_object) {
- var self = this;
- if (xpath_arch_object.length) {
- var check = expr_list[0], obj = false;
- switch (check.length) {
- case 2:
- if (parseInt(check[1])) {
- //for field[3]
- var temp_list = _.select(arch_object, function(element) {
- return _.include(_.flatten(element.att_list), check[0]);
- });
- obj = arch_object[_.indexOf(arch_object, temp_list[parseInt(check[1]) - 1])];
- } else {
- //for notebook[last()]
- obj = _.detect(arch_object, function(element) {
- return _.include(_.flatten(element.att_list), check[0]);
- });
- }
- break;
- case 3:
- //for field[@name='type']
- obj = _.detect(arch_object, function(element){
- if ((_.intersection(_.flatten(element.att_list), _.uniq(check))).length == _.uniq(check).length) {
- return element;
- }
- });
- break;
- case 1:
- //for /form/notebook
- var temp_list = _.select(arch_object, function(element) {
- return _.include(_.flatten(element.att_list), check[0]);
- });
- if (temp_list.length != 0) {
- expr_list.length == 1 ? obj = temp_list[0] : expr_list.shift();
- }
- break;
- }
- if (obj) {
- expr_list.shift();
- if (expr_list.length) {
- self.inherit_apply(expr_list, obj.child_id, xpath_arch_object);
- } else {
- self.increase_level(xpath_arch_object[0], obj.level + 1);
- obj.child_id.push(xpath_arch_object[0]);
- xpath_arch_object.pop();
- }
- } else {
- _.each(arch_object, function(element) {
- self.inherit_apply(expr_list, element.child_id, xpath_arch_object);
- });
- }
- }
- },
- increase_level: function(val, level) {
- var self = this;
- val.level = level;
- _.each(val.child_id, function(val, key) {
- self.increase_level(val, level + 1);
- });
- },
- do_select_row: function(row_id) {
- this.edit_xml_dialog.$element.find("tr[id^='viewedit-']").removeClass('ui-selected');
- this.edit_xml_dialog.$element.find("tr[id=viewedit-" + row_id + "]").addClass('ui-selected');
- },
- do_parent_img_hide_show: function(img) {
- if ($(img).attr('src') == '/web/static/src/img/collapse.gif') {
- $(img).attr('src', '/web/static/src/img/expand.gif');
- this.on_expand(img);
- } else {
- $(img).attr('src', '/web/static/src/img/collapse.gif');
- this.on_collapse(img);
- }
- },
- edit_view: function(one_object) {
- var self = this;
- this.one_object = one_object;
- this.edit_xml_dialog = new instance.web.Dialog(this, {
- title: _.str.sprintf(_t("View Editor %d - %s"), self.main_view_id, self.model),
- height: '90%',
- buttons: [
- {text: _t("Inherited View"), click: function(){
- var selected_row = self.edit_xml_dialog.$element.find('.ui-selected');
- if (selected_row.length) {
- if(selected_row.find('a').text().search("field") != -1){
- if (confirm(_t("Do you really wants to create an inherited view here?"))) {
- self.inherited_view(selected_row);
- }
- }else{
- alert("Can't Update View");
- }
- }else{
- alert("Select an element");
- }
- }},
- {text: _t("Preview"), click: function() {
- var action = {
- context: self.session.user_context,
- res_model: self.model,
- views: [[self.main_view_id, self.main_view_type]],
- type: 'ir.actions.act_window',
- target: "new",
- auto_search: true,
- flags: {
- sidebar: false,
- views_switcher: false,
- action_buttons: false
- }
- };
- var action_manager = new instance.web.ActionManager(self);
- action_manager.do_action(action);
- }},
- {text: _t("Close"), click: function(){
- self.action_manager.inner_widget.views[self.action_manager.inner_widget.active_view].controller.reload_content();
- self.edit_xml_dialog.close();
- }}
- ]
- }).open();
- var no_property_att = [];
- _.each(_PROPERTIES, function(val, key) {
- if (! val.length) no_property_att.push(key);
- });
- this.edit_xml_dialog.$element.html(QWeb.render('view_editor', {'data': one_object['main_object'], 'no_properties': no_property_att}));
- this.edit_xml_dialog.$element.find("tr[id^='viewedit-']").click(function() {
- self.do_select_row(this.id.split('-')[1]);
- });
- this.edit_xml_dialog.$element.find("img[id^='parentimg-']").click(function() {
- self.do_parent_img_hide_show(this);
- });
- this.edit_xml_dialog.$element.find("img[id^='side-']").click(function() {
- self.on_select_img(this);
- });
- },
- inherited_view: function(selected_row){
- var self = this;
- var row_id = parseInt((selected_row.attr('id')).split('-')[1]);
- var obj = self.get_object_by_id(row_id,self.one_object['main_object'], [])[0];
- var view_name = this.model + '.inherit_' + Math.round(Math.random() * 1000);
- var view_find = selected_row;
- var view_id;
- var min_level = parseInt(selected_row.attr('level'));
- while (1) {
- view_find = view_find.prev();
- if (view_find.length == 0 ||
- self.edit_xml_dialog.$element.find(view_find).find('a').text().search("view_id") != -1 &&
- parseInt(view_find.attr('level')) < min_level ) {
- view_id = parseInt($(view_find).find('a').text().replace(/[^0-9]+/g, ''));
- break;
- }
- if (view_find.attr('level') < min_level) {
- min_level = parseInt(view_find.attr('level'));
- }
- }
- var val = _.detect(obj.att_list, function(val) {return val[0] == "name";});
- var priority = _.detect(self.one_object['arch'], function(val) {return val.view_id == view_id;});
- var arch = _.str.sprintf("<?xml version='1.0'?>\n\t <field name='%s' position='after'> </field>", val[1]);
- var vals = {'model': self.model, 'name': view_name, 'priority': priority.priority + 1, 'type': "form", 'arch': arch,'inherit_id':self.main_view_id};
- this.dataset.create(vals, function(suc) {
- var arch_to_obj = self.parse_xml(arch,suc.result);
- obj.child_id.push(arch_to_obj[0]);
- self.one_object['parent_child_id'] = self.parent_child_list(self.one_object['main_object'],[]);
- self.one_object['arch'].push({'view_id':suc.result,"arch":arch,'priority': priority.priority + 1});
- self.increase_level(arch_to_obj[0],obj.level+1);
- self.render_inherited_view(selected_row,arch_to_obj[0]);
- });
- },
- render_inherited_view: function(selected_row,obj){
- var self = this,row_id = parseInt((selected_row.attr('id')).split('-')[1]);
- var clone = this.create_clone(selected_row.clone(),obj);
- if (selected_row.find("img[id^='parentimg-']").length == 0) {
- ($(selected_row.find('a').parent()).siblings('td'))
- .append($('<img width="16" height="16"></img>').attr('src', '/web/static/src/img/collapse.gif').
- attr('id','parentimg-'+ row_id).click(function(){
- self.do_parent_img_hide_show(this);
- }));
- }
- self.edit_xml_dialog.$element.
- find("tr[id='viewedit-"+row_id+"']").after(clone.removeClass('ui-selected'));
- _.each(obj.child_id,function(obj){self.render_inherited_view(clone,obj);});
- },
- on_select_img: function(element_img) {
- var self = this;
- var side = $(element_img).closest("tr[id^='viewedit-']");
- this.one_object.clicked_tr_id = parseInt((side.attr('id')).split('-')[1]);
- this.one_object.clicked_tr_level = parseInt(side.attr('level'));
- var img = side.find("img[id='parentimg-" + this.one_object.clicked_tr_id + "']").attr('src');
- var view_id = 0, view_xml_id = 0, view_find = side;
- //for view id finding
- var min_level = this.one_object.clicked_tr_id;
- if (($(side).find('a').text()).search("view_id") != -1) {
- view_id = parseInt(($(view_find).find('a').text()).replace(/[^0-9]+/g, ''));
- view_xml_id = (view_find.attr('id')).split('-')[1];
- this.one_object.clicked_tr_id += 1;
- this.one_object.clicked_tr_level += 1;
- }else{
- while (1) {
- view_find = view_find.prev();
- if (view_find.length == 0 ||
- (self.edit_xml_dialog.$element.find(view_find).find('a').text()).search("view_id") != -1
- && parseInt(view_find.attr('level')) < min_level ) {
- view_id = parseInt(($(view_find).find('a').text()).replace(/[^0-9]+/g, ''));
- view_xml_id = parseInt((view_find.attr('id')).split('-')[1]);
- break;
- }
- if (view_find.attr('level') < min_level) {
- min_level = parseInt(view_find.attr('level'));
- }
- }
- }
- this.one_object.clicked_tr_view = [view_id, view_xml_id];
- switch (element_img.id) {
- case "side-add":
- self.do_node_add(side);
- break;
- case "side-remove":
- if (confirm(_t("Do you really want to remove this node?"))) {
- self.do_save_update_arch("remove_node");
- }
- break;
- case "side-edit":
- self.do_node_edit(side);
- break;
- case "side-up":
- self.do_node_up(side, img);
- break;
- case "side-down":
- self.do_node_down(side, img);
- break;
- }
- },
- do_node_add: function(side){
- var self = this,property_to_check = [];
- var tr = self.get_object_by_id(this.one_object.clicked_tr_id, this.one_object['main_object'], [])[0].att_list[0];
- var parent_tr = ($(side).prevAll("tr[level=" + String(this.one_object.clicked_tr_level - 1) + "]"))[0];
- var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null);
- parent_tr = self.get_object_by_id(parseInt($(parent_tr).attr('id').replace(/[^0-9]+/g, '')), this.one_object['main_object'], [])[0].att_list[0];
- _.each([tr, parent_tr],function(element) {
- var value = _.has(_CHILDREN, element) ? element : _.str.include(html_tag, element)?"html_tag":false;
- property_to_check.push(value);
- });
- field_dataset.call( 'fields_get', [], function(result) {
- var fields = _.keys(result);
- fields.push(" "),fields.sort();
- self.on_add_node(property_to_check, fields);
- });
- },
- do_node_edit: function(side) {
- var self = this;
- var result = self.get_object_by_id(this.one_object.clicked_tr_id, this.one_object['main_object'], []);
- if (result.length && result[0] && result[0].att_list) {
- var properties = _PROPERTIES[result[0].att_list[0]];
- self.on_edit_node(properties);
- }
- },
- do_node_down: function(cur_tr, img) {
- var self = this, next_tr, last_tr, tr_to_move = [];
- tr_to_move.push(cur_tr);
- if (img) {
- while (1) {
- next_tr = cur_tr.next();
- if ( parseInt(next_tr.attr('level')) <= this.one_object.clicked_tr_level || next_tr.length == 0) {
- last_tr = next_tr;
- break;
- } else {
- tr_to_move.push(next_tr);
- cur_tr = next_tr;
- }
- }
- } else {
- last_tr = cur_tr.next();
- }
- if ((self.edit_xml_dialog.$element.find(last_tr).find('a').text()).search("view_id") != -1) {
- return false;
- }
- if (last_tr.length != 0 && parseInt(last_tr.attr('level')) == this.one_object.clicked_tr_level) {
- var last_tr_id = (last_tr.attr('id')).split('-')[1];
- img = last_tr.find("img[id='parentimg-" + last_tr_id + "']").attr('src');
- if (img) {
- self.edit_xml_dialog.$element.find("img[id='parentimg-" + last_tr_id + "']").
- attr('src', '/web/static/src/img/expand.gif');
- while (1) {
- var next_tr = last_tr.next();
- if (next_tr.attr('level') <= this.one_object.clicked_tr_level || next_tr.length == 0) break;
- next_tr.hide();
- last_tr = next_tr;
- }
- }
- tr_to_move.reverse();
- _.each(tr_to_move, function(rec) {
- $(last_tr).after(rec);
- });
- self.do_save_update_arch("down");
- }
- },
- do_node_up: function(cur_tr, img) {
- var self = this, side = cur_tr, tr_to_move = [];
- tr_to_move.push(side);
- while (1) {
- var prev_tr = cur_tr.prev();
- if (this.one_object.clicked_tr_level >= parseInt(prev_tr.attr('level')) || prev_tr.length == 0) {
- last_tr = prev_tr;
- break;
- }
- cur_tr = prev_tr;
- }
- if (img) {
- self.edit_xml_dialog.$element.find("img[id='parentimg-" + this.one_object.clicked_tr_id + "']").
- attr('src', '/web/static/src/img/expand.gif');
- while (1) {
- next_tr = side.next();
- if (parseInt(next_tr.attr('level')) <= this.one_object.clicked_tr_level || next_tr.length == 0) {
- break;
- } else {
- next_tr.hide();
- tr_to_move.push(next_tr);
- side = next_tr;
- }
- }
- }
- if (last_tr.length != 0 && parseInt(last_tr.attr('level')) == this.one_object.clicked_tr_level &&
- (self.edit_xml_dialog.$element.find(last_tr).find('a').text()).search("view_id") == -1) {
- _.each(tr_to_move, function(rec) {
- $(last_tr).before(rec);
- });
- self.do_save_update_arch("up");
- }
- },
- do_save_update_arch: function(move_direct, update_values) {
- var self = this;
- var arch = _.detect(self.one_object['arch'], function(element)
- {return element.view_id == self.one_object.clicked_tr_view[0]});
- var obj = self.get_object_by_id(this.one_object.clicked_tr_view[1],this.one_object['main_object'], []);
- //for finding xpath tag from inherit view
- var xml_arch = QWeb.load_xml(arch.arch);
- if (xml_arch.childNodes[0].tagName == "data") {
- var check_list = _.flatten(obj[0].child_id[0].att_list);
- var children = _.filter(xml_arch.childNodes[0].childNodes, function (child) {
- return child.nodeType == 1;
- });
- arch.arch = _.detect(children, function(xml_child) {
- var temp_obj = self.create_View_Node(xml_child),
- insert = _.intersection(_.flatten(temp_obj.att_list),_.uniq(check_list));
- if (insert.length == _.uniq(check_list).length ) {return xml_child;}
- });
- xml_arch = QWeb.load_xml(arch.arch);
- }
- return self.do_save_xml(xml_arch.documentElement, obj[0].child_id[0],obj[0].child_id, move_direct, update_values,arch);
- },
- get_object_by_id: function(id, one_object, result) {
- var self = this;
- if (result.length == 0 ) {
- var check = _.detect(one_object , function(obj) {
- return id == obj.id;
- });
- if (check) {result.push(check);}
- _.each(one_object, function(obj) {
- self.get_object_by_id(id,obj.child_id, result);
- });
- }
- return result;
- },
- create_clone: function(clone, new_node_obj){
- var self = this;
- clone.find('a').text(new_node_obj.name);
- ($(clone.find('a').parent()).siblings('td')).css( "padding-left", 20 * new_node_obj.level);
- clone.attr("id", "viewedit-" + new_node_obj.id);
- clone.attr("level", new_node_obj.level);
- clone.find("img[id^='parentimg-']").remove();
- clone.bind("click",function(){
- self.do_select_row(this.id.split('-')[1]);
- });
- clone.find("img[id^='side-']").click(function() {
- self.on_select_img(this);
- });
- return clone;
- },
- do_save_xml: function(arch1, obj, child_list, move_direct, update_values, arch){
- var self = this, children_list = $(arch1).children(),list_obj_xml;
- try{list_obj_xml = _.zip(children_list, obj.child_id);}catch(err){return;}
- if (this.one_object.clicked_tr_id) {
- if (obj.id == this.one_object.clicked_tr_id) {
- var parent = false, index = _.indexOf(child_list, obj);
- if (move_direct == "down") {
- var next = $(arch1).next();
- $(next).after(arch1);
- var re_insert_obj = child_list.splice(index, 1);
- child_list.splice(index+1, 0, re_insert_obj[0]);
- parent = $(arch1).parents();
- } else if (move_direct == "up") {
- var prev = $(arch1).prev();
- $(prev).before(arch1);
- var re_insert_obj = child_list.splice(index, 1);
- child_list.splice(index-1, 0, re_insert_obj[0]);
- parent = $(arch1).parents();
- } else if (move_direct == "update_node") {
- _.each(update_values, function(val){
- if (val[1]) $(arch1)[0].setAttribute(val[0], val[1]);
- else $(arch1)[0].removeAttribute(val[0]);
- });
- var new_obj = self.create_View_Node(arch1);
- new_obj.id = obj.id,new_obj.child_id = obj.child_id;
- self.edit_xml_dialog.$element.
- find("tr[id='viewedit-"+this.one_object.clicked_tr_id+"']").
- find('a').text(new_obj.name);
- child_list.splice(index, 1, new_obj);
- parent = $(arch1).parents();
- } else if(move_direct == "add_node") {
- var tr_click = self.edit_xml_dialog.$element.find("tr[id='viewedit-"+self.one_object.clicked_tr_id+"']"),
- temp_xml = QWeb.load_xml(update_values[0]),
- object_xml = self.create_View_Node(temp_xml.childNodes[0]);
- (update_values[1] == "Inside")? object_xml.level = obj.level + 1:object_xml.level = obj.level;
- var clone = self.create_clone(tr_click.clone(),object_xml),
- after_append = _.detect(self.one_object['parent_child_id'],function(ele){
- return self.one_object.clicked_tr_id == ele.key;
- });
- after_append = (after_append)?_.last(after_append.value):self.one_object.clicked_tr_id;
- switch (update_values[1]) {
- case "After":
- self.edit_xml_dialog.$element.
- find("tr[id='viewedit-"+after_append+"']").after(clone);
- $(arch1).after($(update_values[0]));
- child_list.splice(index + 1, 0, object_xml);
- break;
- case "Before":
- tr_click.before(clone);
- $(arch1).before($(update_values[0]));
- child_list.splice(index - 1, 0, object_xml);
- break;
- case "Inside":
- if (tr_click.find("img[id^='parentimg-']").length == 0) {
- ($(tr_click.find('a').parent()).siblings('td'))
- .append($('<img width="16" height="16"></img>').attr('src', '/web/static/src/img/collapse.gif').
- attr('id','parentimg-'+ self.one_object.clicked_tr_id).click(function(){
- self.do_parent_img_hide_show(this);
- }));
- }
- $(arch1).append($(update_values[0]));
- self.edit_xml_dialog.$element.
- find("tr[id='viewedit-"+after_append+"']").after(clone);
- obj.child_id.push(object_xml);
- break;
- }
- self.edit_xml_dialog.$element.
- find("tr[id='viewedit-" + object_xml.id + "']").removeClass('ui-selected');
- parent = $(arch1).parents();
- } else if (move_direct == "remove_node") {
- parent = $(arch1).parents();
- if (parent.length == 0 || (parent[0].tagName.toLowerCase() == "data")) {
- self.one_object.clicked_tr_id = self.one_object.clicked_tr_id -1;
- self.one_object.clicked_tr_level = self.one_object.clicked_tr_level - 1;
- (parent.length == 0)?parent.push("remove_view"):false;
- }
- $(arch1).remove();
- child_list.splice(index,1);
- var cur_tr = self.edit_xml_dialog.$element.
- find("tr[id='viewedit-" + self.one_object.clicked_tr_id + "']");
- _.each(self.get_list_tr(cur_tr,self.one_object.clicked_tr_level), function(tr_element){
- tr_element.remove();
- });
- cur_tr.remove();
- var parent_img = _.detect(self.one_object['parent_child_id'],function(element){
- return _.include(element.value, self.one_object.clicked_tr_id);
- });
- if(parent_img.value.length == 1){
- self.edit_xml_dialog.$element.
- find("tr[id='viewedit-"+parent_img.key+"']").
- find("img[id^='parentimg-']").remove();
- }
- self.one_object['parent_child_id'] = self.parent_child_list(self.one_object['main_object'],[]);
- }
- var convert_to_utf = (parent.length != 0)? parent[parent.length-1]: arch1;
- if (convert_to_utf != "remove_view") {
- convert_to_utf = QWeb.tools.xml_node_to_string(convert_to_utf);
- convert_to_utf = convert_to_utf.replace('xmlns="http://www.w3.org/1999/xhtml"', "");
- convert_to_utf = '<?xml version="1.0"?>' + convert_to_utf;
- arch.arch = convert_to_utf;
- this.dataset.write(this.one_object.clicked_tr_view[0] ,{"arch":convert_to_utf}, function(r) {});
- } else {
- this.dataset.unlink([this.one_object.clicked_tr_view[0]],function(res) {});
- }
- if(move_direct == "add_node"){
- self.add_node_dialog.close();
- self.on_select_img(clone.find("img[id='side-edit']")[0]);
- self.one_object['parent_child_id'] = self.parent_child_list(self.one_object['main_object'],[]);
- }
- }
- if (obj.level <= this.one_object.clicked_tr_level) {
- _.each(list_obj_xml, function(child_node) {
- self.do_save_xml(child_node[0], child_node[1], obj.child_id, move_direct, update_values, arch);
- });
- }
- }
- },
- on_expand: function(expand_img){
- var level = parseInt($(expand_img).closest("tr[id^='viewedit-']").attr('level'));
- var cur_tr = $(expand_img).closest("tr[id^='viewedit-']");
- _.each(this.get_list_tr(cur_tr,level), function(tr_element){
- tr_element.hide();
- });
- },
- get_list_tr: function(cur_tr,level){
- tr_list = [];
- while (1) {
- var nxt_tr = cur_tr.next();
- if (parseInt(nxt_tr.attr('level')) > level) {
- cur_tr = nxt_tr;
- tr_list.push(nxt_tr);
- } else return tr_list;
- }
- },
- on_collapse: function(collapse_img) {
- var self = this, id = collapse_img.id.split('-')[1];
- var datas = _.detect(self.one_object['parent_child_id'] , function(res) {
- return res.key == id;
- });
- _.each(datas.value, function (rec) {
- var tr = self.edit_xml_dialog.$element.find("tr[id='viewedit-" + rec + "']");
- tr.find("img[id='parentimg-" + rec + "']").attr('src', '/web/static/src/img/expand.gif');
- tr.show();
- });
- },
- on_edit_node: function(properties){
- var self = this;
- this.edit_node_dialog = new instance.web.Dialog(this,{
- title: _t("Properties"),
- width: 450,
- buttons: [
- {text: _t("Update"), click: function () {
- var warn = false, update_values = [];
- _.each(self.edit_widget, function(widget) {
- if (widget.is_invalid) {
- warn = true;
- return false;
- }
- if (widget.dirty && !widget.is_invalid) {
- update_values.push([widget.name, widget.get_value()]);
- }
- });
- if (warn) {
- self.on_valid_create_view(self.edit_widget);
- } else {
- self.do_save_update_arch("update_node", update_values);
- self.edit_node_dialog.close();
- }
- }},
- {text: _t("Cancel"), click: function () { self.edit_node_dialog.close(); }}
- ]
- }).open();
- var _PROPERTIES_ATTRIBUTES = {
- 'name' : {'name':'name', 'string': 'Name', 'type': 'char'},
- 'string' : {'name':'string', 'string': 'String', 'type': 'char'},
- 'required' : {'name':'required', 'string': 'Required', 'type': 'boolean'},
- 'readonly' : {'name':'readonly', 'string': 'Readonly', 'type': 'boolean'},
- 'invisible' : {'name':'invisible', 'string': 'Invisible', 'type': 'boolean'},
- 'domain' : {'name':'domain', 'string': 'Domain', 'type': 'char'},
- 'context' : {'name':'context', 'string': 'Context', 'type': 'char'},
- 'limit' : {'name':'limit', 'string': 'Limit', 'type': 'float'},
- 'min_rows' : {'name':'min_rows', 'string': 'Minimum rows', 'type': 'float'},
- 'date_start' : {'name':'date_start', 'string': 'Start date', 'type': 'char'},
- 'date_delay' : {'name':'date_delay', 'string': 'Delay date', 'type': 'char'},
- 'day_length' : {'name':'day_length', 'string': 'Day length', 'type': 'char'},
- 'mode' : {'name':'mode', 'string': 'Mode', 'type': 'char'},
- 'align' : {'name':'align', 'string': 'Alignment ', 'type': 'selection', 'selection': [['', ''], ['0.0', 'Left'], ['0.5', 'Center'], ['1.0', 'Right']]},
- 'icon' : {'name':'icon', 'string': 'Icon', 'type': 'selection', 'selection': _ICONS},
- 'type' : {'name':'type', 'string': 'Type', 'type': 'selection', 'selection': [['', ''], ['action', 'Action'], ['object', 'Object'], ['workflow', 'Workflow'], ['server_action', 'Server Action']]},
- 'special' : {'name':'special', 'string': 'Special', 'type': 'selection', 'selection': [['',''],['save', 'Save Button'], ['cancel', 'Cancel Button'], ['open', 'Open Button']]},
- 'target' : {'name':'target', 'string': 'Target', 'type': 'selection', 'selection': [['', ''], ['new', 'New Window']]},
- 'confirm' : {'name':'confirm', 'string': 'Confirm', 'type': 'char'},
- 'style' : {'name':'style', 'string': 'Style', 'type': 'selection', 'selection':[["",""],["1", "1"],["1-1", "1-1"],["1-2", "1-2"],["2-1", "2-1"],["1-1-1", "1-1-1"]]},
- 'filename' : {'name':'filename', 'string': 'File Name', 'type': 'char'},
- 'width' : {'name':'width', 'string': 'Width', 'type': 'float'},
- 'height' : {'name':'height', 'string': 'Height', 'type': 'float'},
- 'attrs' : {'name':'attrs', 'string': 'Attrs', 'type': 'char'},
- 'col' : {'name':'col', 'string': 'col', 'type': 'float'},
- 'link' : {'name':'link', 'string': 'Link', 'type': 'char'},
- 'position' : {'name':'position', 'string': 'Position', 'type': 'selection', 'selection': [['',''],['after', 'After'],['before', 'Before'],['inside', 'Inside'],['replace', 'Replace']]},
- 'states' : {'name':'states', 'string': 'states', 'type': 'char'},
- 'eval' : {'name':'eval', 'string': 'Eval', 'type': 'char'},
- 'ref' : {'name':'ref', 'string': 'Ref', 'type': 'char'},
- 'on_change' : {'name':'on_change', 'string': 'On change', 'type': 'char'},
- 'nolabel' : {'name':'nolabel', 'string': 'No label', 'type': 'boolean'},
- 'completion' : {'name':'completion', 'string': 'Completion', 'type': 'boolean'},
- 'colspan' : {'name':'colspan', 'string': 'Colspan', 'type': 'float'},
- 'widget' : {'name':'widget', 'string': 'widget', 'type': 'selection'},
- 'colors' : {'name':'colors', 'string': 'Colors', 'type': 'char'},
- 'editable' : {'name':'editable', 'string': 'Editable', 'type': 'selection', 'selection': [["",""],["top","Top"],["bottom", "Bottom"]]},
- 'groups' : {'name':'groups', 'string': 'Groups', 'type': 'selection_multi'},
- 'fonts' : {'name':'fonts', 'string': 'fonts', 'type': 'char'},
- };
- var arch_val = self.get_object_by_id(this.one_object.clicked_tr_id,this.one_object['main_object'], []);
- this.edit_node_dialog.$element.append('<table id="rec_table" style="width:400px" class="oe_form"></table>');
- this.edit_widget = [];
- self.ready = $.when(self.on_groups(properties)).then(function () {
- _PROPERTIES_ATTRIBUTES['groups']['selection'] = self.groups;
- var values = _.keys( instance.web.form.widgets.map);
- values.push('');
- values.sort();
- _PROPERTIES_ATTRIBUTES['widget']['selection'] = values;
- var widgets = _.filter(_PROPERTIES_ATTRIBUTES, function (property) { return _.include(properties, property.name)});
- _.each(widgets, function(widget) {
- var type_widget = new (self.property.get_any([widget.type])) (self.edit_node_dialog, widget);
- var value = _.detect(arch_val[0]['att_list'],function(res) {
- return res instanceof Array? _.include(res, widget.name): false;
- });
-
- value = value instanceof Array ? value[1] : value;
- self.edit_node_dialog.$element.find('table[id=rec_table]').append('<tr><td align="right">' + widget.string + ':</td>' + type_widget.render() + '</tr>');
- type_widget.start();
- type_widget.set_value(value);
- self.edit_widget.push(type_widget);
- });
- });
- },
- //for getting groups
- on_groups: function(properties){
- var self = this,
- def = $.Deferred();
- if (!_.include(properties, 'groups')) {
- self.groups = false;
- def.resolve();
- }
- var group_ids = [], group_names = {}, groups = [];
- var res_groups = new instance.web.DataSetSearch(this,'res.groups', null, null),
- model_data = new instance.web.DataSetSearch(self,'ir.model.data', null, null);
- res_groups.read_slice([], {}).done(function (res_grp) {
- _.each(res_grp, function (res) {
- var key = res.id;
- group_names[key]=res.full_name;
- group_ids.push(res.id);
- });
- model_data.read_slice([], {domain: [
- ['res_id', 'in', group_ids],
- ['model', '=', 'res.groups']
- ]}).done(function (model_grp) {
- _.each(model_grp, function (res_group) {
- groups.push([res_group.module + "." + res_group.name, group_names[res_group.res_id]]);
- });
- self.groups = groups;
- def.resolve();
- });
- });
- return def.promise();
- },
- on_add_node: function(properties, fields){
- var self = this;
- var render_list = [{'name': 'node_type','selection': _.keys(_CHILDREN).sort(), 'value': 'field', 'string': 'Node Type','type': 'selection'},
- {'name': 'field_value','selection': fields, 'value': false, 'string': '','type': 'selection'},
- {'name': 'position','selection': ['After','Before','Inside'], 'value': false, 'string': 'Position','type': 'selection'}];
- this.add_widget = [];
- this.add_node_dialog = new instance.web.Dialog(this,{
- title: _t("Properties"),
- width: 450,
- buttons: [
- {text: _t("Update"), click: function() {
- var check_add_node = true, values = {};
- _.each(self.add_widget, function(widget) {
- values[widget.name] = widget.get_value() || false;
- });
- (values.position == "Inside")?
- check_add_node =(_.include(_CHILDREN[properties[0]],values.node_type))?true:false:
- check_add_node =(_.include(_CHILDREN[properties[1]],values.node_type))?true:false;
- if(values.node_type == "field" && check_add_node )
- {check_add_node = (values.field_value != " ")?true:false;
- }
- if(check_add_node){
- var tag = (values.node_type == "field")?
- _.str.sprintf("<%s name='%s'> </%s>",values.node_type,values.field_value,values.node_type):
- _.str.sprintf("<%s> </%s>",values.node_type,values.node_type);
- self.do_save_update_arch("add_node", [tag, values.position]);
- } else {
- alert("Can't Update View");
- }
- }},
- {text: _t("Cancel"), click: function() { self.add_node_dialog.close(); }}
- ]
- }).open();
- this.add_node_dialog.$element.append('<table id="rec_table" style="width:420px" class="oe_form"><tbody><tr></tbody></table>');
- var table_selector = self.add_node_dialog.$element.find('table[id=rec_table] tbody');
- _.each(render_list, function(node) {
- type_widget = new (self.property.get_any([node.type])) (self.add_node_dialog, node);
- if (node.name == "position") {
- table_selector.append('</tr><tr><td align="right" width="100px">' + node.string + '</td>' + type_widget.render() + '</tr>');
- } else {
- table_selector.append('<td align="right">' + node.string + '</td>' + type_widget.render() );
- if (node.name == "field_value") {
- table_selector.append('<td id="new_field" align="right" width="100px"> <button>New Field</button></td>');
- }
- }
- type_widget.start();
- type_widget.set_value(node.value);
- self.add_widget.push(type_widget);
- });
- table_selector.find("td[id^=]").attr("width","100px");
- self.add_node_dialog.$element.find('#new_field').click(function() {
- model_data = new instance.web.DataSetSearch(self,'ir.model', null, null);
- model_data.read_slice([], {domain: [['model','=', self.model]]}).then(function(result) {
- self.render_new_field(result[0]);
- });
- });
- },
- render_new_field :function( result ) {
- var self = this;
- var action = {
- context: {'default_model_id': result.id, 'manual': true, 'module' : result.model},
- res_model: "ir.model.fields",
- views: [[false, 'form']],
- type: 'ir.actions.act_window',
- target: "new",
- flags: {
- action_buttons: true
- }
- };
- var action_manager = new instance.web.ActionManager(self);
- $.when(action_manager.do_action(action)).then(function() {
- var controller = action_manager.dialog_widget.views['form'].controller;
- controller.on_button_cancel.add_last(function(){
- action_manager.destroy()
- });
- controller.do_save.add_last(function(){
- action_manager.destroy();
- var value =controller.fields.name.get('value');
- self.add_node_dialog.$element.find('select[id=field_value]').append($("<option selected></option>").attr("value",value).text(value));
- _.detect(self.add_widget,function(widget){
- widget.name == "field_value"? widget.selection.push(value): false;
- });
- });
- });
- }
-});
-instance.web.ViewEditor.Field = instance.web.Class.extend({
- init: function(view, widget) {
- this.$element = view.$element;
- this.dirty = false;
- this.name = widget.name;
- this.selection = widget.selection || [];
- this.required = widget.required || false;
- this.string = widget.string || "";
- this.type = widget.type;
- this.is_invalid = false;
- },
- start: function () {
- this.update_dom();
- },
- update_dom: function() {
- this.$element.find("td[id=" + this.name + "]").toggleClass('invalid', this.is_invalid);
- this.$element.find("td[id=" + this.name + "]").toggleClass('required', this.required);
- },
- on_ui_change: function() {
- this.validate();
- this.dirty = true;
- this.update_dom();
- },
- validate: function() {
- this.is_invalid = false;
- try {
- var value = instance.web.parse_value(this.get_value(), this, '');
- this.is_invalid = this.required && value === '';
- } catch(e) {
- this.is_invalid = true;
- }
- },
- render: function() {
- return _.str.sprintf("<td id = %s>%s</td>", this.name, QWeb.render(this.template, {widget: this}))
- }
-});
-instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({
- template : "vieweditor_boolean",
- start: function() {
- var self = this;
- this._super();
- this.$element.find("input[id="+ self.name+"]").change(function() {
- self.on_ui_change();
- });
- },
- set_value: function(value) {
- if (value) {
- this.$element.find("input[id=" + this.name+ "]").attr('checked', true);
- }
- },
- get_value: function() {
- return this.$element.find("input[id=" + this.name + "]").is(':checked')? "1" : null;
- }
-});
-instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({
- template : "vieweditor_char",
- start: function () {
- var self = this;
- this._super();
- this.$element.find("input[id="+ this.name+"]").css('width','100%').change(function() {
- self.on_ui_change();
- });
- },
- set_value: function(value) {
- this.$element.find("input[id=" + this.name + "]").val(value);
- },
- get_value: function() {
- return this.$element.find("input[id=" + this.name + "]").val();
- }
-});
-instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({
- template : "vieweditor_selection",
- start: function () {
- var self = this;
- this._super();
- this.$element.find("select[id=" + this.name + "]").css('width', '100%').change(function() {
- self.on_ui_change();
- if (self.name == "node_type") {
- if (self.get_value() == "field") {
- self.$element.find('#new_field').show();
- self.$element.find("select[id=field_value]").show();
- } else {
- self.$element.find('#new_field').hide();
- self.$element.find("select[id=field_value]").hide();
- }
- }
- });
- },
- set_value: function(value) {
- var index = 0;
- value = value === null? false: value;
- for (var i = 0, ii = this.selection.length; i < ii; i++) {
- if ((this.selection[i] instanceof Array && this.selection[i][0] === value) || this.selection[i] === value) index = i;
- }
- this.$element.find("select[id=" + this.name + "]")[0].selectedIndex = index;
- },
- get_value: function() {
- return this.$element.find("select[id=" + this.name + "]").val();
- }
-});
-instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.extend({
- start: function () {
- this._super();
- this.$element.find("select[id=" + this.name + "]").css('height', '100px').attr("multiple", true);
- },
- set_value: function(value) {
- var self = this;
- self.$element.find("#groups option").attr("selected",false);
- if (!value) return false;
- _.each(this.selection, function(item) {
- if (_.include(value.split(','), item[0])) {
- self.$element.find("select[id="+self.name+"] option[value='" + item[0] +"']").attr("selected",1)
- }
- });
- }
-});
-instance.web.ViewEditor.FieldFloat = instance.web.ViewEditor.FieldChar.extend({
-});
-
-var _PROPERTIES = {
- 'field' : ['name', 'string', 'required', 'readonly','invisible', 'domain', 'context', 'nolabel', 'completion',
- 'colspan', 'widget', 'eval', 'ref', 'on_change', 'attrs', 'groups'],
- 'form' : ['string', 'col', 'link'],
- 'notebook' : ['colspan', 'position', 'groups'],
- 'page' : ['string', 'states', 'attrs', 'groups'],
- 'group' : ['string', 'col', 'colspan','invisible', 'states', 'attrs', 'groups'],
- 'image' : ['filename', 'width', 'height', 'groups'],
- 'separator' : ['string', 'colspan', 'groups'],
- 'label': ['string', 'align', 'colspan', 'groups'],
- 'button': ['name', 'string', 'icon', 'type', 'states', 'readonly', 'special', 'target', 'confirm', 'context', 'attrs', 'colspan', 'groups'],
- 'newline' : [],
- 'board': ['style'],
- 'column' : [],
- 'action' : ['name', 'string', 'colspan', 'groups'],
- 'tree' : ['string', 'colors', 'editable', 'link', 'limit', 'min_rows', 'fonts'],
- 'graph' : ['string', 'type'],
- 'calendar' : ['string', 'date_start', 'date_stop', 'date_delay', 'day_length', 'color', 'mode']
-};
-var _CHILDREN = {
- 'form': ['notebook', 'group', 'field', 'label', 'button','board', 'newline', 'separator'],
- 'tree': ['field'],
- 'graph': ['field'],
- 'calendar': ['field'],
- 'notebook': ['page'],
- 'page': ['notebook', 'group', 'field', 'label', 'button', 'newline', 'separator'],
- 'group': ['field', 'label', 'button', 'separator', 'newline','group'],
- 'board': ['column'],
- 'action': [],
- 'field': ['form', 'tree', 'graph','field'],
- 'label': [],
- 'button' : [],
- 'newline': [],
- 'separator': [],
- 'sheet' :['group','field','notebook','label','separator','div','page'],
- 'kanban' : ['field'],
- 'html_tag':['notebook', 'group', 'field', 'label', 'button','board', 'newline', 'separator']
-//e.g.:xyz 'td' : ['field']
-};
-// Generic html_tag list and can be added html tag in future. It's support above _CHILDREN dict's *html_tag* by default.
-// For specific child node one has to define tag above and specify children tag in list. Like above xyz example.
-var html_tag = ['div','h1','h2','h3','h4','h5','h6','td','tr'];
-
-var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
- 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER',
- 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE',
- 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO',
- 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT',
- 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE',
- 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM',
- 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK',
- 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK',
- 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC',
- 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL',
- 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD',
- 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY',
- 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND',
- 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW',
- 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES',
- 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT',
- 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED',
- 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT',
- 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK',
- 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE',
- 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100',
- 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT',
- 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase',
- 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
- 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph'
-];
-instance.web.ViewEditor.property_widget = new instance.web.Registry({
- 'boolean' : 'instance.web.ViewEditor.FieldBoolean',
- 'selection_multi' : 'instance.web.ViewEditor.FieldSelectMulti',
- 'selection' : 'instance.web.ViewEditor.FieldSelect',
- 'char' : 'instance.web.ViewEditor.FieldChar',
- 'float' : 'instance.web.ViewEditor.FieldFloat'
-});
-};
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index b103ce83699..b8acdfea4cd 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -33,6 +33,11 @@ instance.web.form.FieldManagerMixin = {
};
instance.web.views.add('form', 'instance.web.FormView');
+/**
+ * Properties:
+ * - actual_mode: always "view", "edit" or "create". Read-only property. Determines
+ * the mode used by the view.
+ */
instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, {
/**
* Indicates that this view is not searchable, and thus that no search
@@ -55,6 +60,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
* @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
*/
init: function(parent, dataset, view_id, options) {
+ var self = this;
this._super(parent);
this.set_default_options(options);
this.dataset = dataset;
@@ -73,6 +79,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
_.defaults(this.options, {
"not_interactible_on_create": false,
"initial_mode": "view",
+ "disable_autofocus": false,
});
this.is_initialized = $.Deferred();
this.mutating_mutex = new $.Mutex();
@@ -80,15 +87,24 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.reload_mutex = new $.Mutex();
this.__clicked_inside = false;
this.__blur_timeout = null;
- this.rendering_engine = new instance.web.form.FormRenderingEngineReadonly(this);
+ this.rendering_engine = new instance.web.form.FormRenderingEngine(this);
this.qweb = null; // A QWeb instance will be created if the view is a QWeb template
+ self.set({actual_mode: self.options.initial_mode});
+ this.has_been_loaded.then(function() {
+ self.on("change:actual_mode", self, self.check_actual_mode);
+ self.check_actual_mode();
+ self.on("change:actual_mode", self, self.init_pager);
+ self.init_pager();
+ });
},
destroy: function() {
_.each(this.get_widgets(), function(w) {
w.off('focused blurred');
w.destroy();
});
- this.$element.off('.formBlur');
+ if (this.$element) {
+ this.$element.off('.formBlur');
+ }
this._super();
},
on_loaded: function(data) {
@@ -125,17 +141,6 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
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.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
- if (this.options.$pager) {
- this.$pager.appendTo(this.options.$pager);
- } else {
- this.$element.find('.oe_form_pager').replaceWith(this.$pager);
- }
- this.$pager.on('click','a[data-pager-action]',function() {
- var action = $(this).data('pager-action');
- self.on_pager_action(action);
- });
-
this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar');
if (!this.sidebar && this.options.$sidebar) {
this.sidebar = new instance.web.Sidebar(this);
@@ -146,12 +151,20 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.sidebar.add_items('other', [
{ label: _t('Delete'), callback: self.on_button_delete },
{ label: _t('Duplicate'), callback: self.on_button_duplicate },
- { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } },
+ { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } }
]);
}
- this.on("change:mode", this, this.switch_mode);
- this.set({mode: this.options.initial_mode});
+
this.has_been_loaded.resolve();
+
+ // Add bounce effect on button 'Edit' when click on readonly page view.
+ this.$element.find(".oe_form_field,label").on('click', function (e) {
+ if(self.get("actual_mode") == "view") {
+ var $button = self.options.$buttons.find(".oe_form_button_edit");
+ $button.effect('bounce', {distance: 18, times: 5}, 150)
+ }
+ });
+
return $.when();
},
extract_qweb_template: function(fvg) {
@@ -239,6 +252,13 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
}
}
},
+ /**
+ *
+ * @param {Object} [options]
+ * @param {Boolean} [editable=false] whether the form should be switched to edition mode. A value of ``false`` will keep the current mode.
+ * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record
+ * @return {$.Deferred}
+ */
do_show: function (options) {
var self = this;
options = options || {};
@@ -251,25 +271,34 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
if (this.$pager) {
this.$pager.show();
}
- this.$element.show().css('visibility', 'hidden');
+ this.$element.show().css({
+ opacity: '0',
+ filter: 'alpha(opacity = 0)'
+ });
this.$element.add(this.$buttons).removeClass('oe_form_dirty');
- return this.has_been_loaded.pipe(function() {
- var result;
- if (self.dataset.index === null) {
- // null index means we should start a new record
- result = self.on_button_new();
- } else {
- result = self.dataset.read_index(_.keys(self.fields_view.fields), {
- context : { 'bin_size' : true }
+
+ var shown = this.has_been_loaded;
+ if (options.reload !== false) {
+ shown = shown.pipe(function() {
+ if (self.dataset.index === null) {
+ // null index means we should start a new record
+ return self.on_button_new();
+ }
+ var fields = _.keys(self.fields_view.fields);
+ fields.push('display_name');
+ return self.dataset.read_index(fields, {
+ context: { 'bin_size': true, 'future_display_name' : true }
}).pipe(self.on_record_loaded);
+ });
+ }
+ return shown.pipe(function() {
+ if (options.editable) {
+ self.to_edit_mode();
}
- result.pipe(function() {
- if (options.editable) {
- self.set({mode: "edit"});
- }
- self.$element.css('visibility', 'visible');
+ self.$element.css({
+ opacity: '1',
+ filter: 'alpha(opacity = 100)'
});
- return result;
});
},
do_hide: function () {
@@ -292,7 +321,8 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return $.Deferred().reject();
}
this.datarecord = record;
- this.set({ 'title' : record.id ? record.name : "New record" });
+ this._actualize_mode();
+ this.set({ 'title' : record.id ? record.display_name : "New record" });
if (this.qweb) {
this.kill_current_form();
@@ -326,10 +356,27 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
}
if (record.id) {
self.do_push_state({id:record.id});
+ } else {
+ self.do_push_state({});
}
self.$element.add(self.$buttons).removeClass('oe_form_dirty');
+ self.autofocus();
});
},
+ /**
+ * Loads and sets up the default values for the model as the current
+ * record
+ *
+ * @return {$.Deferred}
+ */
+ load_defaults: function () {
+ var keys = _.keys(this.fields_view.fields);
+ if (keys.length) {
+ return this.dataset.default_get(keys)
+ .pipe(this.on_record_loaded);
+ }
+ return this.on_record_loaded({});
+ },
on_form_changed: function() {
this.trigger("view_content_has_changed");
},
@@ -355,6 +402,24 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
this.reload();
}
},
+ init_pager: function() {
+ var self = this;
+ if (this.$pager)
+ this.$pager.remove();
+ if (this.get("actual_mode") === "create")
+ return;
+ this.$pager = $(QWeb.render("FormView.pager", {'widget':self}));
+ if (this.options.$pager) {
+ this.$pager.appendTo(this.options.$pager);
+ } else {
+ this.$element.find('.oe_form_pager').replaceWith(this.$pager);
+ }
+ this.$pager.on('click','a[data-pager-action]',function() {
+ var action = $(this).data('pager-action');
+ self.on_pager_action(action);
+ });
+ this.do_update_pager();
+ },
do_update_pager: function(hide_index) {
var index = hide_index ? '-' : this.dataset.index + 1;
this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end()
@@ -554,9 +619,33 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return $.Deferred().reject();
}
},
- switch_mode: function() {
+ /**
+ * Ask the view to switch to view mode if possible. The view may not do it
+ * if the current record is not yet saved. It will then stay in create mode.
+ */
+ to_view_mode: function() {
+ this._actualize_mode("view");
+ },
+ /**
+ * Ask the view to switch to edit mode if possible. The view may not do it
+ * if the current record is not yet saved. It will then stay in create mode.
+ */
+ to_edit_mode: function() {
+ this._actualize_mode("edit");
+ },
+ /**
+ * Reactualize actual_mode.
+ */
+ _actualize_mode: function(switch_to) {
+ var mode = switch_to || this.get("actual_mode");
+ if (! this.datarecord.id) {
+ mode = "create";
+ }
+ this.set({actual_mode: mode});
+ },
+ check_actual_mode: function(source, options) {
var self = this;
- if(this.get("mode") == "view") {
+ if(this.get("actual_mode") === "view") {
self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly');
self.$buttons.find('.oe_form_buttons_edit').hide();
self.$buttons.find('.oe_form_buttons_view').show();
@@ -572,53 +661,53 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
_.each(this.fields,function(field){
field.set({"force_readonly": false});
});
- var fields_order = self.fields_order.slice(0);
- if (self.default_focus_field) {
- fields_order.unshift(self.default_focus_field);
+ this.autofocus();
+ }
+ },
+ autofocus: function() {
+ if (this.get("actual_mode") !== "view" && !this.options.disable_autofocus) {
+ var fields_order = this.fields_order.slice(0);
+ if (this.default_focus_field) {
+ fields_order.unshift(this.default_focus_field.name);
}
for (var i = 0; i < fields_order.length; i += 1) {
- var field = self.fields[fields_order[i]];
- if (!field.get('effective_invisible') && !field.get('effective_readonly') && field.focus() !== false) {
- break;
+ var field = this.fields[fields_order[i]];
+ if (!field.get('effective_invisible') && !field.get('effective_readonly')) {
+ if (field.focus() !== false) {
+ break;
+ }
}
}
}
},
on_button_save: function() {
var self = this;
- return this.do_save().then(function(result) {
- self.set({mode: "view"});
+ return this.do_save().then(function(result) {
+ self.to_view_mode();
});
},
on_button_cancel: function(event) {
if (this.can_be_discarded()) {
- this.set({mode: "view"});
- this.on_record_loaded(this.datarecord);
+ if (this.get('actual_mode') === 'create') {
+ this.trigger('history_back');
+ } else {
+ this.to_view_mode();
+ this.on_record_loaded(this.datarecord);
+ }
}
return false;
},
on_button_new: function() {
var self = this;
- this.set({mode: "edit"});
- var def = $.Deferred();
- $.when(this.has_been_loaded).then(function() {
+ this.to_edit_mode();
+ return $.when(this.has_been_loaded).pipe(function() {
if (self.can_be_discarded()) {
- var keys = _.keys(self.fields_view.fields);
- if (keys.length) {
- self.dataset.default_get(keys).pipe(self.on_record_loaded).then(function() {
- def.resolve();
- });
- } else {
- self.on_record_loaded({}).then(function() {
- def.resolve();
- });
- }
+ return self.load_defaults();
}
});
- return def.promise();
},
on_button_edit: function() {
- return this.set({mode: "edit"});
+ return this.to_edit_mode();
},
on_button_create: function() {
this.dataset.index = null;
@@ -631,7 +720,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) {
return self.on_created({ result : new_id });
}).then(function() {
- return self.set({mode: "edit"});
+ return self.to_edit_mode();
}).then(function() {
def.resolve();
});
@@ -674,13 +763,14 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
values = {},
first_invalid_field = null;
for (var f in self.fields) {
+ if (!self.fields.hasOwnProperty(f)) { continue; }
f = self.fields[f];
if (!f.is_valid()) {
form_invalid = true;
if (!first_invalid_field) {
first_invalid_field = f;
}
- } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) {
+ } else if (f.name !== 'id' && (!self.datarecord.id || (!f.get("readonly") && f._dirty_flag))) {
// Special case 'id' field, do not save this field
// on 'create' : save all non readonly fields
// on 'edit' : save non readonly modified fields
@@ -689,8 +779,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
}
if (form_invalid) {
self.set({'display_invalid_fields': true});
- for (var f in self.fields) {
- self.fields[f]._check_css_flags();
+ for (var g in self.fields) {
+ if (!self.fields.hasOwnProperty(g)) { continue; }
+ self.fields[g]._check_css_flags();
}
first_invalid_field.focus();
self.on_invalid();
@@ -699,16 +790,16 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
self.set({'display_invalid_fields': false});
var save_deferral;
if (!self.datarecord.id) {
- //console.log("FormView(", self, ") : About to create", values);
+ // console.log("FormView(", self, ") : About to create", values);
save_deferral = self.dataset.create(values).pipe(function(r) {
return self.on_created(r, undefined, prepend_on_create);
}, null);
} else if (_.isEmpty(values) && ! self.force_dirty) {
- //console.log("FormView(", self, ") : Nothing to save");
+ // console.log("FormView(", self, ") : Nothing to save");
save_deferral = $.Deferred().resolve({}).promise();
} else {
self.force_dirty = false;
- //console.log("FormView(", self, ") : About to save", values);
+ // console.log("FormView(", self, ") : About to save", values);
save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) {
return self.on_saved(r);
}, null);
@@ -722,14 +813,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
});});
},
on_invalid: function() {
- var msg = "<ul>";
- _.each(this.fields, function(f) {
- if (!f.is_valid()) {
- msg += "<li>" + f.string + "</li>";
- }
- });
- msg += "</ul>";
- this.do_warn("The following fields are invalid :", msg);
+ var warnings = _(this.fields).chain()
+ .filter(function (f) { return !f.is_valid(); })
+ .map(function (f) {
+ return _.str.sprintf('<li>%s</li>',
+ _.escape(f.string));
+ }).value();
+ warnings.unshift('<ul>');
+ warnings.push('</ul>');
+ this.do_warn("The following fields are invalid :", warnings.join(''));
},
on_saved: function(r, success) {
if (!r.result) {
@@ -790,8 +882,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
if (self.dataset.index == null || self.dataset.index < 0) {
return $.when(self.on_button_new());
} else {
- return self.dataset.read_index(_.keys(self.fields_view.fields), {
- context : { 'bin_size' : true }
+ var fields = _.keys(self.fields_view.fields);
+ fields.push('display_name');
+ return self.dataset.read_index(fields, {
+ context : { 'bin_size' : true, 'future_display_name' : true }
}).pipe(self.on_record_loaded);
}
});
@@ -802,15 +896,15 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
});
},
get_fields_values: function(blacklist) {
- blacklist = blacklist || [];
+ blacklist = blacklist || [];
var values = {};
var ids = this.get_selected_ids();
values["id"] = ids.length > 0 ? ids[0] : false;
_.each(this.fields, function(value_, key) {
- if (_.include(blacklist, key))
- return;
- var val = value_.get_value();
- values[key] = val;
+ if (_.include(blacklist, key)) {
+ return;
+ }
+ values[key] = value_.get_value();
});
return values;
},
@@ -865,6 +959,9 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return option[0] === value;
})[1];
break;
+ case 'many2one':
+ displayed = field.get_displayed();
+ break;
}
return {
@@ -918,7 +1015,7 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
register_field: function(field, name) {
this.fields[name] = field;
this.fields_order.push(name);
- if (field.node.attrs.default_focus == '1') {
+ if (JSON.parse(field.node.attrs.default_focus || "0")) {
this.default_focus_field = field;
}
@@ -940,7 +1037,10 @@ instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerM
return this.fields_view.fields[field_name];
},
is_create_mode: function() {
- return !this.datarecord.id;
+ return this.get("actual_mode") === "create";
+ },
+ open_translate_dialog: function(field) {
+ return this._super(field);
},
});
@@ -985,7 +1085,6 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
}
});
}
- selector = 'form[version!="7.0"] page,form[version!="7.0"]';
},
render_to: function($target) {
var self = this;
@@ -1119,7 +1218,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
if (found)
return;
- $label = $('<label/>').attr({
+ var $label = $('<label/>').attr({
'for' : name,
"modifiers": JSON.stringify({invisible: field_modifiers.invisible}),
"string": $field.attr('string'),
@@ -1186,8 +1285,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
row_cols = cols;
} else if (tagName==='group') {
// When <group> <group/><group/> </group>, we need a spacing between the two groups
- $child.addClass('oe_group_right')
-
+ $td.addClass('oe_group_right')
}
row_cols -= colspan;
@@ -1213,12 +1311,6 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
$child = $td.children(':first');
switch ($child[0].tagName.toLowerCase()) {
case 'separator':
- if ($child.attr('orientation') === 'vertical') {
- $td.addClass('oe_vertical_separator').attr('width', '1');
- $td.empty();
- row_cols-= $td.attr('colspan') || 1;
- total--;
- }
break;
case 'label':
if ($child.attr('for')) {
@@ -1340,7 +1432,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
return $new_label;
},
handle_common_properties: function($new_element, $node) {
- var str_modifiers = $node.attr("modifiers") || "{}"
+ var str_modifiers = $node.attr("modifiers") || "{}";
var modifiers = JSON.parse(str_modifiers);
var ic = null;
if (modifiers.invisible !== undefined)
@@ -1351,12 +1443,6 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt
},
});
-instance.web.form.FormRenderingEngineReadonly = instance.web.form.FormRenderingEngine.extend({
- alter_field: function(field) {
- field.set({"force_readonly": true});
- },
-});
-
instance.web.form.FormDialog = instance.web.Dialog.extend({
init: function(parent, options, view_id, dataset) {
this._super(parent, options);
@@ -1465,24 +1551,27 @@ instance.web.form.compute_domain = function(expr, fields) {
*/
instance.web.form.InvisibilityChangerMixin = {
init: function(field_manager, invisible_domain) {
- this._ic_field_manager = field_manager
+ var self = this;
+ this._ic_field_manager = field_manager;
this._ic_invisible_modifier = invisible_domain;
this._ic_field_manager.on("view_content_has_changed", this, function() {
- var result = this._ic_invisible_modifier === undefined ? false :
- instance.web.form.compute_domain(this._ic_invisible_modifier, this._ic_field_manager.fields);
- this.set({"invisible": result});
+ var result = self._ic_invisible_modifier === undefined ? false :
+ instance.web.form.compute_domain(
+ self._ic_invisible_modifier,
+ self._ic_field_manager.fields);
+ self.set({"invisible": result});
});
this.set({invisible: this._ic_invisible_modifier === true, force_invisible: false});
var check = function() {
- if (this.get("invisible") || this.get('force_invisible')) {
- this.set({"effective_invisible": true});
+ if (self.get("invisible") || self.get('force_invisible')) {
+ self.set({"effective_invisible": true});
} else {
- this.set({"effective_invisible": false});
+ self.set({"effective_invisible": false});
}
};
this.on('change:invisible', this, check);
this.on('change:force_invisible', this, check);
- _.bind(check, this)();
+ check.call(this);
},
start: function() {
this.on("change:effective_invisible", this, this._check_visibility);
@@ -1546,6 +1635,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
var compute_domain = instance.web.form.compute_domain;
var to_set = {};
for (var a in this.modifiers) {
+ if (!this.modifiers.hasOwnProperty(a)) { continue; }
if (!_.include(["invisible"], a)) {
var val = compute_domain(this.modifiers[a], this.view.fields);
to_set[a] = val;
@@ -1587,7 +1677,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.Invi
parent: {}
});
if (a_dataset.parent_view) {
- fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]);
+ fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]);
}
return fields_values;
},
@@ -1631,10 +1721,13 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
this._super(view, node);
this.force_disabled = false;
this.string = (this.node.attrs.string || '').replace(/_/g, '');
- if (this.node.attrs.default_focus == '1') {
+ if (JSON.parse(this.node.attrs.default_focus || "0")) {
// TODO fme: provide enter key binding to widgets
this.view.default_focus_button = this;
}
+ if (this.node.attrs.icon && (! /\//.test(this.node.attrs.icon))) {
+ this.node.attrs.icon = '/web/static/src/img/icons/' + this.node.attrs.icon + '.png';
+ }
this.view.on('view_content_has_changed', this, this.check_disable);
},
start: function() {
@@ -1692,11 +1785,7 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({
on_confirmed: function() {
var self = this;
- var context = this.node.attrs.context;
- if (context && context.__ref) {
- context = new instance.web.CompoundContext(context);
- context.set_eval_context(this._build_eval_context());
- }
+ var context = this.build_context();
return this.view.do_execute_action(
_.extend({}, this.node.attrs, {context: context}),
@@ -1749,18 +1838,18 @@ instance.web.form.FieldInterface = {
/**
* Get the current value of the widget.
*
- * Must always return a syntaxically correct value to be passed to the "write" method of the osv class in
+ * Must always return a syntactically correct value to be passed to the "write" method of the osv class in
* the OpenERP server, although it is not assumed to respect the constraints applied to the field.
- * For example if the field is marqued as "required", a call to get_value() can return false.
+ * For example if the field is marked as "required", a call to get_value() can return false.
*
* get_value() can also be called *before* a call to set_value() and, in that case, is supposed to
- * return a defaut value according to the type of field.
+ * return a default value according to the type of field.
*
* This method is always assumed to perform synchronously, it can not return a promise.
*
* If there was no user interaction to modify the value of the field, it is always assumed that
* get_value() return the same semantic value than the one passed in the last call to set_value(),
- * altough the syntax can be different. This can be the case for type of fields that have a different
+ * although the syntax can be different. This can be the case for type of fields that have a different
* syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0).
*/
get_value: function() {},
@@ -1775,7 +1864,7 @@ instance.web.form.FieldInterface = {
*/
is_valid: function() {},
/**
- * Returns true if the field holds a value which is syntaxically correct, ignoring
+ * Returns true if the field holds a value which is syntactically correct, ignoring
* the potential semantic restrictions applied to the field.
*/
is_syntax_valid: function() {},
@@ -1805,6 +1894,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
* @param node
*/
init: function(field_manager, node) {
+ var self = this
this._super(field_manager, node);
this.field_manager = field_manager;
this.name = this.node.attrs.name;
@@ -1817,13 +1907,13 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
// some events to make the property "effective_readonly" sync automatically with "readonly" and
// "force_readonly"
this.set({"readonly": this.modifiers['readonly'] === true});
+ this.set({"force_readonly": false});
var test_effective_readonly = function() {
- this.set({"effective_readonly": this.get("readonly") || !!this.get("force_readonly")});
+ self.set({"effective_readonly": self.get("readonly") || !!self.get("force_readonly")});
};
this.on("change:readonly", this, test_effective_readonly);
this.on("change:force_readonly", this, test_effective_readonly);
- _.bind(test_effective_readonly, this)();
-
+ test_effective_readonly.call(this);
this.on("change:value", this, function() {
if (! this._inhibit_on_change)
this.trigger('changed_value');
@@ -1894,14 +1984,7 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.w
}
},
focus: function() {
- },
- /**
- * Utility method to focus an element, but only after a small amount of time.
- */
- delay_focus: function($elem) {
- setTimeout(function() {
- $elem.focus();
- }, 50);
+ return false;
},
/**
* Utility method to get the widget options defined in the field xml description.
@@ -2000,7 +2083,7 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.we
return this.get('value') === '' || this._super();
},
focus: function() {
- this.delay_focus(this.$element.find('input:first'));
+ this.$('input:first').focus();
}
});
@@ -2084,6 +2167,9 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
value_ = 0;
}
this._super.apply(this, [value_]);
+ },
+ focus: function () {
+ this.$('input:first').select();
}
});
@@ -2220,8 +2306,9 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instanc
return this.get('value') === '' || this._super();
},
focus: function() {
- if (this.datewidget && this.datewidget.$input)
- this.delay_focus(this.datewidget.$input);
+ if (this.datewidget && this.datewidget.$input) {
+ this.datewidget.$input.focus();
+ }
}
});
@@ -2243,6 +2330,11 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
} else {
this.$textarea.attr('disabled', 'disabled');
}
+ this.$element.keyup(function (e) {
+ if (e.which === $.ui.keyCode.ENTER) {
+ e.stopPropagation();
+ }
+ });
this.setupFocus(this.$textarea);
},
set_value: function(value_) {
@@ -2271,7 +2363,7 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
return this.get('value') === '' || this._super();
},
focus: function($element) {
- this.delay_focus(this.$textarea);
+ this.$textarea.focus();
},
do_resize: function(max_height) {
max_height = parseInt(max_height, 10);
@@ -2295,9 +2387,58 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.we
},
});
+/**
+ * FieldTextHtml Widget
+ * Intended for FieldText widgets meant to display HTML content. This
+ * widget will instantiate the CLEditor (see cleditor in static/src/lib)
+ * To find more information about CLEditor configutation: go to
+ * http://premiumsoftware.net/cleditor/docs/GettingStarted.html
+ */
+instance.web.form.FieldTextHtml = instance.web.form.FieldText.extend({
+
+ initialize_content: function() {
+ this.$textarea = this.$element.find('textarea');
+ var width = ((this.node.attrs || {}).editor_width || 468);
+ var height = ((this.node.attrs || {}).editor_height || 100);
+ this.$textarea.cleditor({
+ width: width, // width not including margins, borders or padding
+ height: height, // height not including margins, borders or padding
+ controls: // controls to add to the toolbar
+ "bold italic underline strikethrough | size " +
+ "| removeformat | bullets numbering | outdent " +
+ "indent | link unlink",
+ sizes: // sizes in the font size popup
+ "1,2,3,4,5,6,7",
+ bodyStyle: // style to assign to document body contained within the editor
+ "margin:4px; font:12px monospace; cursor:text; color:#1F1F1F"
+ });
+ this.$cleditor = this.$textarea.cleditor()[0];
+ // call super now, because cleditor resets the disable attr
+ this._super.apply(this, arguments);
+ // propagate disabled property to cleditor
+ this.$cleditor.disable(this.$textarea.prop('disabled'));
+ },
+
+ set_value: function(value_) {
+ this._super.apply(this, arguments);
+ this._dirty_flag = true;
+ },
+
+ render_value: function() {
+ this._super.apply(this, arguments);
+ this.$cleditor.updateFrame();
+ },
+
+ get_value: function() {
+ this.$cleditor.updateTextArea();
+ return this.$textarea.val();
+ },
+});
+
instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
template: 'FieldBoolean',
start: function() {
+ var self = this;
this._super.apply(this, arguments);
this.$checkbox = $("input", this.$element);
this.setupFocus(this.$checkbox);
@@ -2305,17 +2446,17 @@ instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
this.set({'value': this.$checkbox.is(':checked')});
}, this));
var check_readonly = function() {
- this.$checkbox.prop('disabled', this.get("effective_readonly"));
+ self.$checkbox.prop('disabled', self.get("effective_readonly"));
};
this.on("change:effective_readonly", this, check_readonly);
- _.bind(check_readonly, this)();
+ check_readonly.call(this);
},
set_value: function(value_) {
this._super.apply(this, arguments);
this.$checkbox[0].checked = value_;
},
focus: function() {
- this.delay_focus(this.$checkbox);
+ this.$checkbox.focus();
}
});
@@ -2339,9 +2480,6 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
}
});
-instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({
-// to replace view editor
-});
instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, {
template: 'FieldSelection',
@@ -2410,7 +2548,7 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instan
return !! value_;
},
focus: function() {
- this.delay_focus(this.$element.find('select:first'));
+ this.$element.find('select:first').focus();
}
});
@@ -2580,10 +2718,20 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
self.$input.tipsy({
title: function() {
- return "No element was selected, you should create or select one from the dropdown list.";
+ return QWeb.render('Tipsy.alert', {
+ message: "No element was selected, you should create or select one from the dropdown list."
+ });
},
trigger:'manual',
fade: true,
+ gravity: 's',
+ html: true,
+ opacity: 1,
+ offset: 4,
+ });
+
+ self.$input.on('focus', function() {
+ self.$input.tipsy("hide");
});
this.$drop_down = this.$element.find(".oe_m2o_drop_down_button");
@@ -2611,7 +2759,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
});
// some behavior for input
- this.$input.keydown(function() {
+ var input_changed = function() {
if (self.current_display !== self.$input.val()) {
self.current_display = self.$input.val();
if (self.$input.val() === "") {
@@ -2621,7 +2769,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
self.floating = true;
}
}
- });
+ };
+ this.$input.keydown(input_changed);
+ this.$input.change(input_changed);
this.$drop_down.click(function() {
if (self.$input.autocomplete("widget").is(":visible")) {
self.$input.autocomplete("close");
@@ -2637,7 +2787,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
var tip_def = $.Deferred();
var untip_def = $.Deferred();
var tip_delay = 200;
- var tip_duration = 3000;
+ var tip_duration = 15000;
var anyoneLoosesFocus = function() {
var used = false;
if (self.floating) {
@@ -2697,6 +2847,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
} else if (item.action) {
self.floating = true;
item.action();
+ // Cancel widget blurring, to avoid form blur event
+ self.trigger('focused');
return false;
}
},
@@ -2720,7 +2872,6 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
});
this.setupFocus(this.$input.add(this.$follow_button));
},
-
render_value: function(no_recurse) {
var self = this;
if (! this.get("value")) {
@@ -2745,12 +2896,24 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
if (!this.get("effective_readonly")) {
this.$input.val(str.split("\n")[0]);
this.current_display = this.$input.val();
+ this.$('.oe_m2o_cm_button').css({'visibility': this.is_false() ? 'hidden' : 'visible'});
} else {
- str = _.escape(str).split("\n").join("<br />");
- this.$element.find('a')
+ var lines = _.escape(str).split("\n");
+ var link = "";
+ var follow = "";
+ if (! this.get_definition_options().highlight_first_line) {
+ link = lines.join("<br />");
+ } else {
+ link = lines[0];
+ follow = _.rest(lines).join("<br />");
+ if (follow)
+ link += "<br />";
+ }
+ var $link = this.$element.find('.oe_form_uri')
.unbind('click')
- .html(str)
- .click(function () {
+ .html(link);
+ if (! this.get_definition_options().no_open)
+ $link.click(function () {
self.do_action({
type: 'ir.actions.act_window',
res_model: self.field.relation,
@@ -2761,6 +2924,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
});
return false;
});
+ $(".oe_form_m2o_follow", this.$element).html(follow);
}
},
set_value: function(value_) {
@@ -2777,6 +2941,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
this._super(value_);
this.inhibit_on_change = false;
},
+ get_displayed: function() {
+ return this.display_value["" + this.get("value")];
+ },
add_id: function(id) {
this.display_value = {};
this.set({value: id});
@@ -2785,7 +2952,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instanc
return ! this.get("value");
},
focus: function () {
- this.delay_focus(this.$input);
+ this.$input.focus();
}
});
@@ -2848,7 +3015,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
},
start: function() {
this._super.apply(this, arguments);
- this.$element.addClass('oe_form_field_one2many');
+ this.$element.addClass('oe_form_field oe_form_field_one2many');
var self = this;
@@ -2904,13 +3071,14 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
}
if(view.view_type === "list") {
_.extend(view.options, {
+ addable: null,
selectable: self.multi_selection,
sortable: false,
import_enabled: false,
+ deletable: true
});
if (self.get("effective_readonly")) {
_.extend(view.options, {
- addable: null,
deletable: null,
reorderable: false,
});
@@ -2940,7 +3108,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
this.views = views;
this.viewmanager = new instance.web.form.One2ManyViewManager(this, this.dataset, views, {});
- this.viewmanager.$element.addClass("oe_view_manager_one2many");
this.viewmanager.o2m = self;
var once = $.Deferred().then(function() {
self.init_form_last_update.resolve();
@@ -2951,8 +3118,11 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
controller.o2m = self;
if (view_type == "list") {
- if (self.get("effective_readonly"))
- controller.set_editable(false);
+ if (self.get("effective_readonly")) {
+ controller.on('edit:before', self, function (e) {
+ e.cancel = true;
+ });
+ }
} else if (view_type === "form") {
if (self.get("effective_readonly")) {
$(".oe_form_buttons", controller.$element).children().remove();
@@ -3084,30 +3254,30 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
save_any_view: function() {
if (this.doing_on_change)
return false;
- return this.session.synchronized_mode(_.bind(function() {
- if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
- this.viewmanager.views[this.viewmanager.active_view] &&
- this.viewmanager.views[this.viewmanager.active_view].controller) {
- var view = this.viewmanager.views[this.viewmanager.active_view].controller;
- if (this.viewmanager.active_view === "form") {
- if (!view.is_initialized.isResolved()) {
- return false;
- }
- var res = $.when(view.do_save());
- if (!res.isResolved() && !res.isRejected()) {
- console.warn("Asynchronous get_value() is not supported in form view.");
- }
- return res;
- } else if (this.viewmanager.active_view === "list") {
- var res = $.when(view.ensure_saved());
- if (!res.isResolved() && !res.isRejected()) {
- console.warn("Asynchronous get_value() is not supported in list view.");
- }
- return res;
- }
- }
- return false;
- }, this));
+ return this.session.synchronized_mode(_.bind(function() {
+ if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view &&
+ this.viewmanager.views[this.viewmanager.active_view] &&
+ this.viewmanager.views[this.viewmanager.active_view].controller) {
+ var view = this.viewmanager.views[this.viewmanager.active_view].controller;
+ if (this.viewmanager.active_view === "form") {
+ if (!view.is_initialized.isResolved()) {
+ return false;
+ }
+ var res = $.when(view.do_save());
+ if (!res.isResolved() && !res.isRejected()) {
+ console.warn("Asynchronous get_value() is not supported in form view.");
+ }
+ return res;
+ } else if (this.viewmanager.active_view === "list") {
+ var res = $.when(view.ensure_saved());
+ if (!res.isResolved() && !res.isRejected()) {
+ console.warn("Asynchronous get_value() is not supported in list view.");
+ }
+ return res;
+ }
+ }
+ return false;
+ }, this));
},
is_syntax_valid: function() {
if (!this.viewmanager.views[this.viewmanager.active_view])
@@ -3136,6 +3306,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({
form: 'instance.web.form.One2ManyFormView',
kanban: 'instance.web.form.One2ManyKanbanView',
});
+ this.__ignore_blur = false;
},
switch_view: function(mode, unused) {
if (mode !== 'form') {
@@ -3185,19 +3356,35 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
this._super(parent, dataset, view_id, _.extend(options || {}, {
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'));
+
+ this.records
+ .bind('add', this.proxy("changed_records"))
+ .bind('edit', this.proxy("changed_records"))
+ .bind('remove', this.proxy("changed_records"));
+ },
+ start: function () {
+ var ret = this._super();
+ this.$element
+ .off('mousedown.handleButtons')
+ .on('mousedown.handleButtons', 'table button', this.proxy('_button_down'));
+ return ret;
+ },
+ changed_records: function () {
+ this.o2m.trigger_on_change();
},
is_valid: function () {
- var form;
- // A list not being edited is always valid
- if (!(form = this.first_edition_form())) {
- return true;
- }
+ var form = this.editor.form;
+
// If the form has not been modified, the view can only be valid
// NB: is_dirty will also be set on defaults/onchanges/whatever?
// oe_form_dirty seems to only be set on actual user actions
if (!form.$element.is('.oe_form_dirty')) {
return true;
}
+ this.o2m._dirty_flag = true;
// Otherwise validate internal form
return _(form.fields).chain()
@@ -3208,21 +3395,8 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
.all(_.identity)
.value();
},
- first_edition_form: function () {
- var get_form = function (group_or_list) {
- if (group_or_list.edition) {
- return group_or_list.edition_form;
- }
- return _(group_or_list.children).chain()
- .map(get_form)
- .compact()
- .first()
- .value();
- };
- return get_form(this.groups);
- },
do_add_record: function () {
- if (this.options.editable) {
+ if (this.editable()) {
this._super.apply(this, arguments);
} else {
var self = this;
@@ -3275,55 +3449,134 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({
});
},
do_button_action: function (name, id, callback) {
- var _super = _.bind(this._super, this);
+ if (!_.isNumber(id)) {
+ instance.webclient.notification.warn(
+ _t("Action Button"),
+ _t("The o2m record must be saved before an action can be used"));
+ return;
+ }
+ var parent_form = this.o2m.view;
+ var self = this;
+ this.ensure_saved().pipe(function () {
+ return parent_form.do_save();
+ }).then(function () {
+ self.handle_button(name, id, callback);
+ });
+ },
- this.o2m.view.do_save().then(function () {
- _super(name, id, callback);
+ _before_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
+ this.editor.form.widgetFocused();
+ },
+ _before_unedit: function () {
+ this.editor.form.off('blurred', this, this._on_form_blur);
+ },
+ _button_down: function () {
+ // If a button is clicked (usually some sort of action button), it's
+ // the button's responsibility to ensure the editable list is in the
+ // correct state -> ignore form blurring
+ this.__ignore_blur = true;
+ },
+ /**
+ * Handles blurring of the nested form (saves the currently edited row),
+ * unless the flag to ignore the event is set to ``true``
+ *
+ * Makes the internal form go away
+ */
+ _on_form_blur: function () {
+ if (this.__ignore_blur) {
+ this.__ignore_blur = false;
+ return;
+ }
+ // FIXME: why isn't there an API for this?
+ if (this.editor.form.$element.hasClass('oe_form_dirty')) {
+ this.save_edition();
+ return;
+ }
+ this.cancel_edition();
+ },
+ keyup_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)
+ this.__ignore_blur = true;
+ this._super.apply(this, arguments);
+ },
+ do_delete: function (ids) {
+ var self = this;
+ var next = $.when();
+ var _super = this._super;
+ // handle deletion of an item which does not exist
+ // TODO: better handle that in the editable list?
+ var false_id_index = _(ids).indexOf(false);
+ if (false_id_index !== -1) {
+ ids.splice(false_id_index, 1);
+ next = this.cancel_edition(true);
+ }
+ return next.pipe(function () {
+ // wheeee
+ var confirm = window.confirm;
+ window.confirm = function () { return true; };
+ try {
+ return _super.call(self, ids);
+ } finally {
+ window.confirm = confirm;
+ }
});
}
});
instance.web.form.One2ManyList = instance.web.ListView.List.extend({
- KEY_RETURN: 13,
- // blurring caused by hitting the [Return] key, should skip the
- // autosave-on-blur and let the handler for [Return] do its thing
- __return_blur: false,
- render_row_as_form: function () {
- var self = this;
- return this._super.apply(this, arguments).then(function () {
- // Replace the "Save Row" button with "Cancel Edition"
- self.edition_form.$element
- .undelegate('button.oe_list_edit_row_save', 'click')
- .delegate('button.oe_list_edit_row_save', 'click', function () {
- self.cancel_pending_edition();
- });
+ pad_table_to: function (count) {
+ this._super(count > 0 ? count - 1 : 0);
- // Overload execute_action on the edition form to perform a simple
- // reload_record after the action is done, rather than fully
- // reload the parent view (or something)
- var _execute_action = self.edition_form.do_execute_action;
- self.edition_form.do_execute_action = function (action, dataset, record_id, _callback) {
- return _execute_action.call(this, action, dataset, record_id, function () {
- self.view.reload_record(
- self.view.records.get(record_id));
- });
- };
+ // magical invocation of wtf does that do
+ if (this.view.o2m.get('effective_readonly')) {
+ return;
+ }
- self.edition_form.on('blurred', null, function () {
- if (self.__return_blur) {
- delete self.__return_blur;
- return;
- }
- if (!self.edition_form.widget_is_stopped) {
- self.view.ensure_saved();
- }
- });
- });
- },
- on_row_keyup: function (e) {
- if (e.which === this.KEY_RETURN) {
- this.__return_blur = true;
+ 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': 'oe_form_field_one2many_list_row_add'
+ }).append(
+ $('<a>', {href: '#'}).text(_t("Add a row"))
+ .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().then(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)
}
- this._super(e);
}
});
@@ -3431,7 +3684,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
$("textarea", this.$element).focusout(function() {
self.$text.trigger("setInputData", "");
}).keydown(function(e) {
- if (event.keyCode === 9 && self._drop_shown) {
+ if (e.which === $.ui.keyCode.TAB && self._drop_shown) {
self.$text.textext()[0].autocomplete().selectFromDropdown();
}
});
@@ -3453,12 +3706,13 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
render_value: function() {
var self = this;
var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
+ var values = self.get("value")
var handle_names = function(data) {
var indexed = {};
_.each(data, function(el) {
indexed[el[0]] = el;
});
- data = _.map(self.get("value"), function(el) { return indexed[el]; });
+ data = _.map(values, function(el) { return indexed[el]; });
if (! self.get("effective_readonly")) {
self.tags.containerElement().children().remove();
$("textarea", self.$element).css("padding-left", "3px");
@@ -3467,8 +3721,8 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(in
self.$element.html(QWeb.render("FieldMany2ManyTag", {elements: data}));
}
};
- if (! self.get('values') || self.get('values').length > 0) {
- this._display_orderer.add(dataset.name_get(self.get("value"))).then(handle_names);
+ if (! values || values.length > 0) {
+ this._display_orderer.add(dataset.name_get(values)).then(handle_names);
} else {
handle_names([]);
}
@@ -3492,7 +3746,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({
},
start: function() {
this._super.apply(this, arguments);
- this.$element.addClass('oe_form_field_many2many');
+ this.$element.addClass('oe_form_field oe_form_field_many2many');
var self = this;
@@ -3867,14 +4121,16 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({
display_popup: function() {
var self = this;
this.renderElement();
- new instance.web.Dialog(this, {
+ var dialog = new instance.web.Dialog(this, {
min_width: '800px',
- dialogClass: 'oe_act_window',
+ dialogClass: 'oe_act_window',
close: function() {
self.check_exit(true);
},
title: this.options.title || "",
+ buttons: [{text:"tmp"}],
}, this.$element).open();
+ this.$buttonpane = dialog.$element.dialog("widget").find(".ui-dialog-buttonpane").html("");
this.start();
},
on_write_completed: function() {},
@@ -3890,16 +4146,18 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({
if (this.row_id !== null) {
options.initial_mode = this.options.readonly ? "view" : "edit";
}
+ _.extend(options, {
+ $buttons: this.$buttonpane,
+ });
this.view_form = new instance.web.FormView(this, this.dataset, false, options);
if (this.options.alternative_form_view) {
this.view_form.set_embedded_view(this.options.alternative_form_view);
}
this.view_form.appendTo(this.$element.find(".oe_popup_form"));
this.view_form.on_loaded.add_last(function() {
- var $buttons = self.view_form.$element.find(".oe_form_buttons");
var multi_select = self.row_id === null && ! self.options.disable_multiple_selection;
- $buttons.html(QWeb.render("AbstractFormPopup.buttons", {multi_select: multi_select}));
- var $snbutton = $buttons.find(".oe_abstractformpopup-form-save-new");
+ self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", {multi_select: multi_select}));
+ var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new");
$snbutton.click(function() {
$.when(self.view_form.do_save()).then(function() {
self.view_form.reload_mutex.exec(function() {
@@ -3907,7 +4165,7 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({
});
});
});
- var $sbutton = $buttons.find(".oe_abstractformpopup-form-save");
+ var $sbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save");
$sbutton.click(function() {
$.when(self.view_form.do_save()).then(function() {
self.view_form.reload_mutex.exec(function() {
@@ -3915,7 +4173,7 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({
});
});
});
- var $cbutton = $buttons.find(".oe_abstractformpopup-form-close");
+ var $cbutton = self.$buttonpane.find(".oe_abstractformpopup-form-close");
$cbutton.click(function() {
self.check_exit();
});
@@ -4022,9 +4280,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
self.dataset, false,
_.extend({'deletable': false,
'selectable': !self.options.disable_multiple_selection,
- 'read_only': true,
'import_enabled': false,
+ '$buttons': self.$buttonpane,
}, self.options.list_view_options || {}));
+ self.view_list.on('edit:before', self, function (e) {
+ e.cancel = true;
+ });
self.view_list.popup = self;
self.view_list.appendTo($(".oe_popup_list", self.$element)).pipe(function() {
self.view_list.do_show();
@@ -4032,16 +4293,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend
self.searchview.do_search();
});
self.view_list.on_loaded.add_last(function() {
- var $buttons = self.view_list.$element.find(".oe-actions");
- $buttons.prepend(QWeb.render("SelectCreatePopup.search.buttons"));
- var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close");
+ self.$buttonpane.html(QWeb.render("SelectCreatePopup.search.buttons", {widget:self}));
+ var $cbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-close");
$cbutton.click(function() {
self.destroy();
});
- var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select");
- if(self.options.disable_multiple_selection) {
- $sbutton.hide();
- }
+ var $sbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-select");
$sbutton.click(function() {
self.on_select_elements(self.selected_ids);
self.destroy();
@@ -4127,7 +4384,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
this.selection.view = this.view;
this.selection.set({force_readonly: this.get('effective_readonly')});
this.selection.on("change:value", this, this.on_selection_changed);
- this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
+ this.selection.setElement(this.$(".oe_form_view_reference_selection"));
this.selection.renderElement();
this.selection.start();
this.selection
@@ -4140,7 +4397,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instan
this.m2o.view = this.view;
this.m2o.set({force_readonly: this.get("effective_readonly")});
this.m2o.on("change:value", this, this.data_changed);
- this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element);
+ this.m2o.setElement(this.$(".oe_form_view_reference_m2o"));
this.m2o.renderElement();
this.m2o.start();
this.m2o
@@ -4260,7 +4517,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
//link.target = '_blank';
link.href = "data:application/octet-stream;base64," + value;
} else {
- $.blockUI();
+ instance.web.blockUI();
this.session.get_file({
url: '/web/binary/saveas_ajax',
data: {data: JSON.stringify({
@@ -4270,7 +4527,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.
filename_field: (this.node.attrs.filename || ''),
context: this.view.dataset.get_context()
})},
- complete: $.unblockUI,
+ complete: instance.web.unblockUI,
error: instance.webclient.crashmanager.on_rpc_error
});
ev.stopPropagation();
@@ -4356,7 +4613,7 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
},
render_value: function() {
var url;
- if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) {
+ if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) {
url = 'data:image/png;base64,' + this.get('value');
} else if (this.get('value')) {
url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
@@ -4385,9 +4642,11 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
template: "FieldStatus",
+ clickable: false,
start: function() {
this._super();
this.selected_value = null;
+ this.clickable = !!this.node.attrs.clickable;
if (this.$element.parent().is('header')) {
this.$element.after('<div class="oe_clear"/>');
}
@@ -4492,19 +4751,34 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
* state (given by the key of (key, label)).
*/
render_elements: function () {
+ var self = this;
var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_});
this.$element.html(content);
-
+ if (this.clickable) {
+ this.$element.addClass("oe_form_steps_clickable");
+ $('.oe_form_steps_arrow').remove();
+ var elemts = this.$element.find('.oe_form_steps_button');
+ _.each(elemts, function(element){
+ $item = $(element);
+ if ($item.attr("data-id") != self.selected_value) {
+ $item.click(function(event){
+ var data_id = parseInt($(this).attr("data-id"))
+ self.view.dataset.call('stage_set', [[self.view.datarecord.id],data_id]).then(function() {
+ return self.view.reload();
+ });
+ });
+ }
+ });
+ } else {
+ this.$element.addClass("oe_form_steps");
+ }
var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}");
var color = colors[this.selected_value];
- if (color) {
+ if (color) {
var elem = this.$element.find("li.oe_form_steps_active span");
- elem.css("color", color);
+ elem.css("color", color);
}
},
- focus: function() {
- return false;
- },
});
/**
@@ -4519,6 +4793,7 @@ instance.web.form.widgets = new instance.web.Registry({
'email' : 'instance.web.form.FieldEmail',
'url' : 'instance.web.form.FieldUrl',
'text' : 'instance.web.form.FieldText',
+ 'text_html' : 'instance.web.form.FieldTextHtml',
'date' : 'instance.web.form.FieldDate',
'datetime' : 'instance.web.form.FieldDatetime',
'selection' : 'instance.web.form.FieldSelection',
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index edc53f82042..986b0e49faf 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -21,9 +21,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
// whether the view rows can be reordered (via vertical drag & drop)
'reorderable': true,
'action_buttons': true,
- // if true, the view can't be editable, ignoring the view's and the context's
- // instructions
- 'read_only': false,
// if true, the 'Import', 'Export', etc... buttons will be shown
'import_enabled': true,
},
@@ -142,7 +139,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
});
},
/**
- * View startup method, the default behavior is to set the ``oe_listw``
+ * View startup method, the default behavior is to set the ``oe_list``
* class on its root element and to perform an RPC load call.
*
* @returns {$.Deferred} loading promise
@@ -288,7 +285,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
}
this.$buttons.find('.oe_list_add')
.click(this.proxy('do_add_record'))
- .prop('disabled', grouped && this.options.editable);
+ .prop('disabled', grouped);
this.$buttons.on('click', '.oe_list_button_import', function() {
self.on_sidebar_import();
return false;
@@ -357,9 +354,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
this.sidebar.add_items('other', [
{ label: _t("Import"), callback: this.on_sidebar_import },
{ label: _t("Export"), callback: this.on_sidebar_export },
- { label: _t('Delete'), callback: this.do_delete_selected },
+ { label: _t('Delete'), callback: this.do_delete_selected }
]);
this.sidebar.add_toolbar(this.fields_view.toolbar);
+ this.sidebar.$element.hide();
}
},
/**
@@ -378,6 +376,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
var total = dataset.size();
var limit = this.limit() || total;
+ this.$pager.toggle(total !== 0);
this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit));
var spager = '-';
if (total) {
@@ -400,68 +399,23 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
* @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed
*/
setup_columns: function (fields, grouped) {
- var domain_computer = instance.web.form.compute_domain;
-
- var noop = function () { return {}; };
- var field_to_column = function (field) {
- var name = field.attrs.name;
- var column = _.extend({id: name, tag: field.tag},
- fields[name], field.attrs);
- // modifiers computer
- if (column.modifiers) {
- var modifiers = JSON.parse(column.modifiers);
- column.modifiers_for = function (fields) {
- if (!modifiers.invisible) {
- return {};
- }
- return {
- 'invisible': domain_computer(modifiers.invisible, fields)
- };
- };
- if (modifiers['tree_invisible']) {
- column.invisible = '1';
- } else {
- delete column.invisible;
- }
- } else {
- column.modifiers_for = noop;
- }
- return column;
- };
-
+ var registry = instance.web.list.columns;
this.columns.splice(0, this.columns.length);
- this.columns.push.apply(
- this.columns,
- _(this.fields_view.arch.children).map(field_to_column));
+ this.columns.push.apply(this.columns,
+ _(this.fields_view.arch.children).map(function (field) {
+ var id = field.attrs.name;
+ return registry.for_(id, fields[id], field);
+ }));
if (grouped) {
- this.columns.unshift({
- id: '_group', tag: '', string: _t("Group"), meta: true,
- modifiers_for: function () { return {}; }
- }, {
- id: '_count', tag: '', string: '#', meta: true,
- modifiers_for: function () { return {}; }
- });
+ this.columns.unshift(
+ new instance.web.list.MetaColumn('_group', _t("Group")));
}
this.visible_columns = _.filter(this.columns, function (column) {
return column.invisible !== '1';
});
- this.aggregate_columns = _(this.visible_columns)
- .map(function (column) {
- if (column.type !== 'integer' && column.type !== 'float') {
- return {};
- }
- var aggregation_func = column['group_operator'] || 'sum';
- if (!(aggregation_func in column)) {
- return {};
- }
-
- return _.extend({}, column, {
- 'function': aggregation_func,
- label: column[aggregation_func]
- });
- });
+ this.aggregate_columns = _(this.visible_columns).invoke('to_aggregate');
},
/**
* Used to handle a click on a table row, if no other handler caught the
@@ -487,9 +441,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
},
do_show: function () {
this._super();
- if (this.sidebar) {
- this.sidebar.$element.show();
- }
if (this.$buttons) {
this.$buttons.show();
}
@@ -667,6 +618,23 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
* @param {Function} callback should be called after the action is executed, if non-null
*/
do_button_action: function (name, id, callback) {
+ this.handle_button(name, id, callback);
+ },
+ /**
+ * Base handling of buttons, can be called when overriding do_button_action
+ * in order to bypass parent overrides.
+ *
+ * The callback will be provided with the ``id`` as its parameter, in case
+ * handle_button's caller had to alter the ``id`` (or even create one)
+ * while not being ``callback``'s creator.
+ *
+ * This method should not be overridden.
+ *
+ * @param {String} name action name
+ * @param {Object} id id of the record the action should be called on
+ * @param {Function} callback should be called after the action is executed, if non-null
+ */
+ handle_button: function (name, id, callback) {
var action = _.detect(this.columns, function (field) {
return field.name === name;
});
@@ -685,7 +653,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
c.add(action.context);
}
action.context = c;
- this.do_execute_action(action, this.dataset, id, callback);
+ this.do_execute_action(
+ action, this.dataset, id, _.bind(callback, null, id));
},
/**
* Handles the activation of a record (clicking on it)
@@ -801,9 +770,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
}
$footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id))
- .html(instance.web.format_cell(aggregation, column, {
- process_modifiers: false
- }));
+ .html(column.format(aggregation, { process_modifiers: false }));
});
},
get_selected_ids: function() {
@@ -862,9 +829,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi
}
this.$element.find('table:first').hide();
this.$element.prepend(
- $('<div class="oe_view_nocontent">')
- .append($('<img>', { src: '/web/static/src/img/view_empty_arrow.png' }))
- .append($('<div>').html(this.options.action.help))
+ $('<div class="oe_view_nocontent">').html(this.options.action.help)
);
}
});
@@ -911,32 +876,44 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
this.record_callbacks = {
'remove': function (event, record) {
- var $row = self.$current.find(
- '[data-id=' + record.get('id') + ']');
+ var $row = self.$current.children(
+ '[data-id=' + record.get('id') + ']');
var index = $row.data('index');
$row.remove();
- self.refresh_zebra(index);
},
'reset': function () { return self.on_records_reset(); },
- 'change': function (event, record) {
- var $row = self.$current.find('[data-id=' + record.get('id') + ']');
+ 'change': function (event, record, attribute, value, old_value) {
+ var $row;
+ if (attribute === 'id') {
+ if (old_value) {
+ throw new Error("Setting 'id' attribute on existing record "
+ + JSON.stringify(record.attributes));
+ }
+ if (!_.contains(self.dataset.ids, value)) {
+ // add record to dataset if not already in (added by
+ // the form view?)
+ self.dataset.ids.splice(
+ self.records.indexOf(record), 0, value);
+ }
+ // Set id on new record
+ $row = self.$current.children('[data-id=false]');
+ } else {
+ $row = self.$current.children(
+ '[data-id=' + record.get('id') + ']');
+ }
$row.replaceWith(self.render_record(record));
},
'add': function (ev, records, record, index) {
- var $new_row = $('<tr>').attr({
- 'data-id': record.get('id')
- });
+ var $new_row = $(self.render_record(record));
if (index === 0) {
$new_row.prependTo(self.$current);
} else {
var previous_record = records.at(index-1),
- $previous_sibling = self.$current.find(
+ $previous_sibling = self.$current.children(
'[data-id=' + previous_record.get('id') + ']');
$new_row.insertAfter($previous_sibling);
}
-
- self.refresh_zebra(index, 1);
}
};
_(this.record_callbacks).each(function (callback, event) {
@@ -967,19 +944,19 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
// of digits, nice when storing actual numbers, not nice when
// storing strings composed only of digits. Force the action
// name to be a string
- $(self).trigger('action', [field.toString(), record_id, function () {
- return self.reload_record(self.records.get(record_id));
+ $(self).trigger('action', [field.toString(), record_id, function (id) {
+ return self.reload_record(self.records.get(id));
}]);
})
.delegate('a', 'click', function (e) {
e.stopPropagation();
})
.delegate('tr', 'click', function (e) {
- e.stopPropagation();
var row_id = self.row_id(e.currentTarget);
- if (row_id !== undefined) {
+ if (row_id) {
+ e.stopPropagation();
if (!self.dataset.select_id(row_id)) {
- throw "Could not find id in dataset"
+ throw new Error("Could not find id in dataset");
}
self.row_clicked(e);
}
@@ -1029,7 +1006,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
});
}
}
- return instance.web.format_cell(record.toForm().data, column, {
+ return column.format(record.toForm().data, {
model: this.dataset.model,
id: record.get('id')
});
@@ -1076,7 +1053,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
this.$current
.children('tr:not([data-id])').remove().end()
.append(new Array(count - this.records.length + 1).join(row));
- this.refresh_zebra(this.records.length);
},
/**
* Gets the ids of all currently selected records, if any
@@ -1139,6 +1115,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
* @returns {String} QWeb rendering of the selected record
*/
render_record: function (record) {
+ var self = this;
var index = this.records.indexOf(record);
return QWeb.render('ListView.row', {
columns: this.columns,
@@ -1147,26 +1124,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.
row_parity: (index % 2 === 0) ? 'even' : 'odd',
view: this.view,
render_cell: function () {
- return this.render_cell.apply(this, arguments); }
- });
- },
- /**
- * Fixes fixes the even/odd classes
- *
- * @param {Number} [from_index] index from which to resequence
- * @param {Number} [offset = 0] selection offset for DOM, in case there are rows to ignore in the table
- */
- refresh_zebra: function (from_index, offset) {
- offset = offset || 0;
- from_index = from_index || 0;
- var dom_offset = offset + from_index;
- var sel = dom_offset ? ':gt(' + (dom_offset - 1) + ')' : null;
- this.$current.children(sel).each(function (i, e) {
- var index = from_index + i;
- // reset record-index accelerators on rows and even/odd
- var even = index%2 === 0;
- $(e).toggleClass('even', even)
- .toggleClass('odd', !even);
+ return self.render_cell.apply(self, arguments); }
});
}
});
@@ -1333,15 +1291,18 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
_t("Grouping on field '%s' is not possible because that field does not appear in the list view."),
group.grouped_on));
}
+ var group_label;
try {
- $group_column.html(instance.web.format_cell(
- row_data, group_column, {
- value_if_empty: _t("Undefined"),
- process_modifiers: false
- }));
+ group_label = group_column.format(row_data, {
+ value_if_empty: _t("Undefined"),
+ process_modifiers: false
+ });
} catch (e) {
- $group_column.html(row_data[group_column.id].value);
+ group_label = row_data[group_column.id].value;
}
+ $group_column.text(_.str.sprintf("%s (%d)",
+ group_label, group.length));
+
if (group.length && group.openable) {
// Make openable if not terminal group & group_by_no_leaf
$group_column.prepend('<span class="ui-icon ui-icon-triangle-1-e" style="float: left;">');
@@ -1353,14 +1314,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
}
}
self.indent($group_column, group.level);
- // count column
- $('<td>').text(group.length).appendTo($row);
if (self.options.selectable) {
$row.append('<td>');
}
_(self.columns).chain()
- .filter(function (column) {return !column.invisible;})
+ .filter(function (column) { return column.invisible !== '1'; })
.each(function (column) {
if (column.meta) {
// do not do anything
@@ -1368,8 +1327,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var r = {};
r[column.id] = {value: group.aggregates[column.id]};
$('<td class="oe_number">')
- .html(instance.web.format_cell(
- r, column, {process_modifiers: false}))
+ .html(column.format(r, {process_modifiers: false}))
.appendTo($row);
} else {
$row.append('<td>');
@@ -1456,18 +1414,31 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
},
setup_resequence_rows: function (list, dataset) {
// drag and drop enabled if list is not sorted and there is a
- // "sequence" column in the view.
+ // visible column with @widget=handle or "sequence" column in the view.
if ((dataset.sort && dataset.sort())
|| !_(this.columns).any(function (column) {
- return column.name === 'sequence'; })) {
+ return column.widget === 'handle'
+ || column.name === 'sequence'; })) {
return;
}
+ var sequence_field = _(this.columns).find(function (c) {
+ return c.widget === 'handle';
+ });
+ var seqname = sequence_field ? sequence_field.name : 'sequence';
+
// ondrop, move relevant record & fix sequences
list.$current.sortable({
axis: 'y',
items: '> tr[data-id]',
- containment: 'parent',
- helper: 'clone',
+ helper: 'clone'
+ });
+ if (sequence_field) {
+ list.$current.sortable('option', 'handle', '.oe_list_field_handle');
+ }
+ list.$current.sortable('option', {
+ start: function (e, ui) {
+ ui.placeholder.height(ui.item.height());
+ },
stop: function (event, ui) {
var to_move = list.records.get(ui.item.data('id')),
target_id = ui.item.prev().data('id'),
@@ -1485,7 +1456,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
var record, index = to,
// if drag to 1st row (to = 0), start sequencing from 0
// (exclusive lower bound)
- seq = to ? list.records.at(to - 1).get('sequence') : 0;
+ seq = to ? list.records.at(to - 1).get(seqname) : 0;
while (++seq, record = list.records.at(index++)) {
// write are independent from one another, so we can just
// launch them all at the same time and we don't really
@@ -1495,13 +1466,13 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we
// when synchronous (without setTimeout)
(function (dataset, id, seq) {
$.async_when().then(function () {
- dataset.write(id, {sequence: seq});
+ var attrs = {};
+ attrs[seqname] = seq;
+ dataset.write(id, attrs);
});
}(dataset, record.get('id'), seq));
- record.set('sequence', seq);
+ record.set(seqname, seq);
}
-
- list.refresh_zebra();
}
});
},
@@ -1831,7 +1802,7 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
* @returns this
*/
remove: function (record) {
- var index = _(this.records).indexOf(record);
+ var index = this.indexOf(record);
if (index === -1) {
_(this._proxies).each(function (proxy) {
proxy.remove(record);
@@ -1847,13 +1818,42 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
return this;
},
- _onRecordEvent: function (event, record, options) {
+ _onRecordEvent: function (event) {
+ switch(event) {
// don't propagate reset events
- if (event === 'reset') { return; }
+ case 'reset': return;
+ case 'change:id':
+ var record = arguments[1];
+ var new_value = arguments[2];
+ var old_value = arguments[3];
+ // [change:id, record, new_value, old_value]
+ if (this._byId[old_value] === record) {
+ delete this._byId[old_value];
+ this._byId[new_value] = record;
+ }
+ break;
+ }
this.trigger.apply(this, arguments);
},
// underscore-type methods
+ find: function (callback) {
+ var record;
+ for(var section in this._proxies) {
+ if (!this._proxies.hasOwnProperty(section)) {
+ continue
+ }
+ if ((record = this._proxies[section].find(callback))) {
+ return record;
+ }
+ }
+ for(var i=0; i<this.length; ++i) {
+ record = this.records[i];
+ if (callback(record)) {
+ return record;
+ }
+ }
+ },
each: function (callback) {
for(var section in this._proxies) {
if (this._proxies.hasOwnProperty(section)) {
@@ -1878,6 +1878,46 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{
},
indexOf: function (record) {
return _(this.records).indexOf(record);
+ },
+ succ: function (record, options) {
+ options = options || {wraparound: false};
+ var result;
+ for(var section in this._proxies) {
+ if (!this._proxies.hasOwnProperty(section)) {
+ continue;
+ }
+ if ((result = this._proxies[section].succ(record, options))) {
+ return result;
+ }
+ }
+ var index = this.indexOf(record);
+ if (index === -1) { return null; }
+ var next_index = index + 1;
+ if (options.wraparound && (next_index === this.length)) {
+ return this.at(0);
+ }
+ return this.at(next_index);
+ },
+ pred: function (record, options) {
+ options = options || {wraparound: false};
+
+ var result;
+ for (var section in this._proxies) {
+ if (!this._proxies.hasOwnProperty(section)) {
+ continue;
+ }
+ if ((result = this._proxies[section].pred(record, options))) {
+ return result;
+ }
+ }
+
+ var index = this.indexOf(record);
+ if (index === -1) { return null; }
+ var next_index = index - 1;
+ if (options.wraparound && (next_index === -1)) {
+ return this.at(this.length - 1);
+ }
+ return this.at(next_index);
}
});
Collection.include(Events);
@@ -1885,6 +1925,204 @@ instance.web.list = {
Events: Events,
Record: Record,
Collection: Collection
-}
+};
+/**
+ * Registry for column objects used to format table cells (and some other tasks
+ * e.g. aggregation computations).
+ *
+ * Maps a field or button to a Column type via its ``$tag.$widget``,
+ * ``$tag.$type`` or its ``$tag`` (alone).
+ *
+ * This specific registry has a dedicated utility method ``for_`` taking a
+ * field (from fields_get/fields_view_get.field) and a node (from a view) and
+ * returning the right object *already instantiated from the data provided*.
+ *
+ * @type {instance.web.Registry}
+ */
+instance.web.list.columns = new instance.web.Registry({
+ 'field': 'instance.web.list.Column',
+ 'field.boolean': 'instance.web.list.Boolean',
+ 'field.binary': 'instance.web.list.Binary',
+ 'field.progressbar': 'instance.web.list.ProgressBar',
+ 'field.handle': 'instance.web.list.Handle',
+ 'button': 'instance.web.list.Button',
+});
+instance.web.list.columns.for_ = function (id, field, node) {
+ var description = _.extend({tag: node.tag}, field, node.attrs);
+ var tag = description.tag;
+ var Type = this.get_any([
+ tag + '.' + description.widget,
+ tag + '.'+ description.type,
+ tag
+ ]);
+ return new Type(id, node.tag, description)
+};
+
+instance.web.list.Column = instance.web.Class.extend({
+ init: function (id, tag, attrs) {
+ _.extend(attrs, {
+ id: id,
+ tag: tag
+ });
+
+ this.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {};
+ delete attrs.modifiers;
+ _.extend(this, attrs);
+
+ if (this.modifiers['tree_invisible']) {
+ this.invisible = '1';
+ } else { delete this.invisible; }
+ },
+ modifiers_for: function (fields) {
+ var out = {};
+ var domain_computer = instance.web.form.compute_domain;
+
+ for (var attr in this.modifiers) {
+ if (!this.modifiers.hasOwnProperty(attr)) { continue; }
+ var modifier = this.modifiers[attr];
+ out[attr] = _.isBoolean(modifier)
+ ? modifier
+ : domain_computer(modifier, fields);
+ }
+
+ return out;
+ },
+ to_aggregate: function () {
+ if (this.type !== 'integer' && this.type !== 'float') {
+ return {};
+ }
+ var aggregation_func = this['group_operator'] || 'sum';
+ if (!(aggregation_func in this)) {
+ return {};
+ }
+ var C = function (label, fn) {
+ this['function'] = fn;
+ this.label = label;
+ };
+ C.prototype = this;
+ return new C(aggregation_func, this[aggregation_func]);
+ },
+ /**
+ *
+ * @param row_data record whose values should be displayed in the cell
+ * @param {Object} [options]
+ * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false``
+ * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ?
+ * @param {String} [options.model] current record's model
+ * @param {Number} [options.id] current record's id
+ * @return {String}
+ */
+ format: function (row_data, options) {
+ options = options || {};
+ var attrs = {};
+ if (options.process_modifiers !== false) {
+ attrs = this.modifiers_for(row_data);
+ }
+ if (attrs.invisible) { return ''; }
+
+ if (!row_data[this.id]) {
+ return options.value_if_empty === undefined
+ ? ''
+ : options.value_if_empty;
+ }
+ return this._format(row_data, options);
+ },
+ /**
+ * Method to override in order to provide alternative HTML content for the
+ * cell. Column._format will simply call ``instance.web.format_value`` and
+ * escape the output.
+ *
+ * The output of ``_format`` will *not* be escaped by ``format``, any
+ * escaping *must be done* by ``format``.
+ *
+ * @private
+ */
+ _format: function (row_data, options) {
+ return _.escape(instance.web.format_value(
+ row_data[this.id].value, this, options.value_if_empty));
+ }
+});
+instance.web.list.MetaColumn = instance.web.list.Column.extend({
+ meta: true,
+ init: function (id, string) {
+ this._super(id, '', {string: string});
+ }
+});
+instance.web.list.Button = instance.web.list.Column.extend({
+ /**
+ * Return an actual ``<button>`` tag
+ */
+ format: function (row_data, options) {
+ return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' +
+ '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
+ '</button>', {
+ title: this.string || '',
+ additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
+ 'disabled="disabled" class="oe_list_button_disabled"' : '',
+ prefix: instance.connection.prefix,
+ icon: this.icon,
+ alt: this.string || ''
+ });
+ }
+});
+instance.web.list.Boolean = instance.web.list.Column.extend({
+ /**
+ * Return a potentially disabled checkbox input
+ *
+ * @private
+ */
+ _format: function (row_data, options) {
+ return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>',
+ row_data[this.id].value ? 'checked="checked"' : '');
+ }
+});
+instance.web.list.Binary = instance.web.list.Column.extend({
+ /**
+ * Return a link to the binary data as a file
+ *
+ * @private
+ */
+ _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.connection.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'}));
+ }
+ }
+ return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', {
+ text: text,
+ href: download_url,
+ size: row_data[this.id].value
+ });
+ }
+});
+instance.web.list.ProgressBar = instance.web.list.Column.extend({
+ /**
+ * Return a formatted progress bar display
+ *
+ * @private
+ */
+ _format: function (row_data, options) {
+ return _.template(
+ '<progress value="<%-value%>" max="100"><%-value%>%</progress>', {
+ value: _.str.sprintf("%.0f", row_data[this.id].value || 0)
+ });
+ }
+});
+instance.web.list.Handle = instance.web.list.Column.extend({
+ /**
+ * Return styling hooks for a drag handle
+ *
+ * @private
+ */
+ _format: function (row_data, options) {
+ return '<div class="oe_list_handle">';
+ }
+});
};
// 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 c370aeaf7fd..9a101a5ac1a 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -3,10 +3,6 @@
* @namespace
*/
openerp.web.list_editable = function (instance) {
- var KEY_RETURN = 13,
- KEY_ESCAPE = 27;
- var QWeb = instance.web.qweb;
-
// editability status of list rows
instance.web.ListView.prototype.defaults.editable = null;
@@ -15,6 +11,15 @@ openerp.web.list_editable = function (instance) {
init: function () {
var self = this;
this._super.apply(this, arguments);
+
+ this._force_editability = null;
+ this._context_editable = false;
+ this.editor = this.make_editor();
+ // Stores records of {field, cell}, allows for re-rendering fields
+ // depending on cell state during and after resize events
+ this.fields_for_resize = [];
+ instance.web.bus.on('resize', this, this.resize_fields);
+
$(this.groups).bind({
'edit': function (e, id, dataset) {
self.do_edit(dataset.index, id, dataset);
@@ -26,7 +31,29 @@ openerp.web.list_editable = function (instance) {
self.configure_pager(self.dataset);
self.compute_aggregates();
}
- })
+ });
+
+ this.records.bind('remove', function () {
+ if (self.editor.is_editing()) {
+ self.cancel_edition();
+ }
+ });
+
+ this.on('edit:before', this, function (event) {
+ if (!self.editable() || self.editor.is_editing()) {
+ event.cancel = true;
+ }
+ });
+ this.on('edit:after', this, function () {
+ self.$element.add(self.$buttons).addClass('oe_editing');
+ });
+ this.on('save:after cancel:after', this, function () {
+ self.$element.add(self.$buttons).removeClass('oe_editing');
+ });
+ },
+ destroy: function () {
+ instance.web.bus.off('resize', this, this.resize_fields);
+ this._super();
},
/**
* Handles the activation of a record in editable mode (making a record
@@ -42,25 +69,16 @@ openerp.web.list_editable = function (instance) {
do_edit: function (index, id, dataset) {
_.extend(this.dataset, dataset);
},
- /**
- * Sets editability status for the list, based on defaults, view
- * architecture and the provided flag, if any.
- *
- * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom".
- */
- set_editable: function (force) {
- // If ``force``, set editability to bottom
- // otherwise rely on view default
- // view' @editable is handled separately as we have not yet
- // fetched and processed the view at this point.
- this.options.editable = (
- ! this.options.read_only && ((force && "bottom") || this.defaults.editable));
+ editable: function () {
+ return this.fields_view.arch.attrs.editable
+ || this._context_editable
+ || this.options.editable;
},
/**
* Replace do_search to handle editability process
*/
do_search: function(domain, context, group_by) {
- this.set_editable(context['set_editable']);
+ this._context_editable = !!context.set_editable;
this._super.apply(this, arguments);
},
/**
@@ -68,18 +86,66 @@ openerp.web.list_editable = function (instance) {
* as an editable row at the top or bottom of the list)
*/
do_add_record: function () {
- if (this.options.editable) {
+ if (this.editable()) {
this.$element.find('table:first').show();
this.$element.find('.oe_view_nocontent').remove();
- this.groups.new_record();
+ this.start_edition();
} else {
this._super();
}
},
on_loaded: function (data, grouped) {
+ var self = this;
// tree/@editable takes priority on everything else if present.
- this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable);
- return this._super(data, grouped);
+ var result = this._super(data, grouped);
+ if (this.editable()) {
+ this.$element.addClass('oe_list_editable');
+ // FIXME: any hook available to ensure this is only done once?
+ this.$buttons
+ .off('click', '.oe_list_save')
+ .on('click', '.oe_list_save', this.proxy('save_edition'))
+ .off('click', '.oe_list_discard')
+ .on('click', '.oe_list_discard', function (e) {
+ e.preventDefault();
+ self.cancel_edition();
+ });
+ this.$element
+ .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
+ this.editor = this.make_editor();
+ var editor_ready = this.editor.prependTo(this.$element)
+ .then(this.proxy('setup_events'));
+
+ return $.when(result, editor_ready);
+ } else {
+ this.$element.removeClass('oe_list_editable');
+ }
+
+ return result;
+ },
+ /**
+ * Builds a new editor object
+ *
+ * @return {instance.web.list.Editor}
+ */
+ make_editor: function () {
+ return new instance.web.list.Editor(this);
+ },
+ do_button_action: function (name, id, callback) {
+ var self = this, args = arguments;
+ this.ensure_saved().then(function (done) {
+ if (!id && done.created) {
+ id = done.record.get('id');
+ }
+ self.handle_button.call(self, name, id, callback);
+ });
},
/**
* Ensures the editable list is saved (saves any pending edition if
@@ -90,354 +156,624 @@ openerp.web.list_editable = function (instance) {
* @returns {$.Deferred}
*/
ensure_saved: function () {
- return this.groups.ensure_saved();
- }
- });
-
- instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{
- passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved",
- new_record: function () {
- // TODO: handle multiple children
- this.children[null].new_record();
+ if (!this.editor.is_editing()) {
+ return $.when();
+ }
+ return this.save_edition();
},
/**
- * Ensures descendant editable List instances are all saved if they have
- * pending editions.
+ * Set up the edition of a record of the list view "inline"
*
- * @returns {$.Deferred}
+ * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record
+ * @param {Object} [options]
+ * @param {String} [options.focus_field] field to focus at start of edition
+ * @return {jQuery.Deferred}
*/
- ensure_saved: function () {
- return $.when.apply(null,
- _.invoke(
- _.values(this.children),
- 'ensure_saved'));
- }
- });
-
- instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{
- row_clicked: function (event) {
- if (!this.options.editable) {
- return this._super.apply(this, arguments);
+ start_edition: function (record, options) {
+ var self = this;
+ var item = false;
+ 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);
+ this.records.add(record, {
+ at: this.prepends_on_create() ? 0 : null});
}
- this.edit_record($(event.currentTarget).data('id'));
+ var $recordRow = this.groups.get_row_for(record);
+ var cells = this.get_cells_for($recordRow);
+
+ return this.ensure_saved().pipe(function () {
+ self.fields_for_resize.splice(0, self.fields_for_resize.length);
+ return self.with_event('edit', {
+ record: record.attributes,
+ cancel: false
+ }, function () {
+ return self.editor.edit(item, function (field_name, field) {
+ var cell = cells[field_name];
+ if (!cell || field.get('effective_readonly')) {
+ // Readonly fields can just remain the list's,
+ // form's usually don't have backgrounds &al
+ field.set({invisible: true});
+ return;
+ }
+
+ // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow
+ field.$element.attr('data-fieldname', field_name);
+ self.fields_for_resize.push({field: field, cell: cell});
+ }, options).pipe(function () {
+ $recordRow.addClass('oe_edition');
+ self.resize_fields();
+ return record.attributes;
+ });
+ }).fail(function () {
+ // if the start_edition event is cancelled and it was a
+ // creation, remove the newly-created empty record
+ if (!record.get('id')) {
+ self.records.remove(record);
+ }
+ });
+ });
+ },
+ get_cells_for: function ($row) {
+ var cells = {};
+ $row.children('td').each(function (index, el) {
+ cells[el.getAttribute('data-field')] = el
+ });
+ return cells;
},
/**
- * Checks if a record is being edited, and if so cancels it
+ * If currently editing a row, resizes all registered form fields based
+ * on the corresponding row cell
*/
- cancel_pending_edition: function () {
- var self = this, cancelled;
- if (!this.edition) {
- return $.when();
- }
-
- if (this.edition_id) {
- cancelled = this.reload_record(this.records.get(this.edition_id));
- } else {
- cancelled = $.when();
+ resize_fields: function () {
+ 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];
+ this.resize_field(item.field, item.cell);
}
- cancelled.then(function () {
- self.view.unpad_columns();
- self.edition_form.destroy();
- self.edition_form.$element.remove();
- delete self.edition_form;
- self.dataset.index = null;
- delete self.edition_id;
- delete self.edition;
- });
- this.pad_table_to(5);
- return cancelled;
},
/**
- * Adapts this list's view description to be suitable to the inner form
- * view of a row being edited.
+ * Resizes a field's root element based on the corresponding cell of
+ * a listview row
*
- * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows.
+ * @param {instance.web.form.AbstractField} field
+ * @param {jQuery} cell
*/
- get_form_fields_view: function () {
- // deep copy of view
- var view = $.extend(true, {}, this.group.view.fields_view);
- _(view.arch.children).each(function (widget) {
- widget.attrs.nolabel = true;
- if (widget.tag === 'button') {
- delete widget.attrs.string;
- }
+ resize_field: function (field, cell) {
+ var $cell = $(cell);
+ var position = $cell.position();
+
+ // jquery does not understand !important
+ field.$element.attr('style', 'width: '+$cell.outerWidth()+'px !important')
+ field.$element.css({
+ top: position.top,
+ left: position.left,
+ minHeight: $cell.outerHeight()
});
- view.arch.attrs.col = 2 * view.arch.children.length;
- return view;
},
- on_row_keyup: function (e) {
+ /**
+ * @return {jQuery.Deferred}
+ */
+ save_edition: function () {
var self = this;
- switch (e.which) {
- case KEY_RETURN:
- $(e.target).blur();
- e.preventDefault();
- //e.stopImmediatePropagation();
- setTimeout(function () {
- self.save_row().then(function (result) {
- if (result.created) {
- self.new_record();
- return;
- }
-
- var next_record_id,
- next_record = self.records.at(
- self.records.indexOf(result.edited_record) + 1);
- if (next_record) {
- next_record_id = next_record.get('id');
- self.dataset.index = _(self.dataset.ids)
- .indexOf(next_record_id);
- } else {
- self.dataset.index = 0;
- next_record_id = self.records.at(0).get('id');
- }
- self.edit_record(next_record_id);
- }, 0);
+ return this.with_event('save', {
+ editor: this.editor,
+ form: this.editor.form,
+ cancel: false
+ }, function () {
+ return this.editor.save().pipe(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)
+ .pipe(function () {
+ return self.reload_record(record); })
+ .pipe(function () {
+ return { created: created, record: record }; });
});
- break;
- case KEY_ESCAPE:
- this.cancel_edition();
- break;
- }
+ });
},
- render_row_as_form: function (row) {
+ /**
+ * @param {Boolean} [force=false] discards the data even if the form has been edited
+ * @return {jQuery.Deferred}
+ */
+ cancel_edition: function (force) {
var self = this;
- return this.ensure_saved().pipe(function () {
- var record_id = $(row).data('id');
- var $new_row = $('<tr>', {
- id: _.uniqueId('oe-editable-row-'),
- 'data-id': record_id,
- 'class': (row ? $(row).attr('class') : ''),
- click: function (e) {e.stopPropagation();}
- })
- .addClass('oe_form oe_form_container')
- .delegate('button.oe_list_edit_row_save', 'click', function () {
- self.save_row();
- })
- .delegate('button', 'keyup', function (e) {
- e.stopImmediatePropagation();
- })
- .keyup(function () {
- return self.on_row_keyup.apply(self, arguments); })
- .keydown(function (e) { e.stopPropagation(); })
- .keypress(function (e) {
- if (e.which === KEY_RETURN) {
- return false;
+ return this.with_event('cancel', {
+ editor: this.editor,
+ form: this.editor.form,
+ cancel: false
+ }, function () {
+ return this.editor.cancel(force).pipe(function (attrs) {
+ if (attrs.id) {
+ var record = self.records.get(attrs.id);
+ if (!record) {
+ // Record removed by third party during edition
+ return
}
- });
-
- if (row) {
- $new_row.replaceAll(row);
- } else if (self.options.editable) {
- var $last_child = self.$current.children('tr:last');
- if (self.records.length) {
- if (self.options.editable === 'top') {
- $new_row.insertBefore(
- self.$current.children('[data-id]:first'));
- } else {
- $new_row.insertAfter(
- self.$current.children('[data-id]:last'));
- }
- } else {
- $new_row.prependTo(self.$current);
- }
- if ($last_child.is(':not([data-id])')) {
- $last_child.remove();
+ return self.reload_record(record);
}
- }
- self.edition = true;
- self.edition_id = record_id;
- self.dataset.index = _(self.dataset.ids).indexOf(record_id);
- if (self.dataset.index === -1) {
- self.dataset.index = null;
- }
- self.edition_form = _.extend(new instance.web.ListEditableFormView(self.view, self.dataset, false), {
- $element: $new_row,
- editable_list: self
- });
- // put in $.when just in case FormView.on_loaded becomes asynchronous
- return $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () {
- $new_row.find('> td')
- .end()
- .find('td:last').removeClass('oe_list_field_cell').end();
- // pad in case of groupby
- _(self.columns).each(function (column) {
- if (column.meta) {
- $new_row.prepend('<td>');
- }
+ var to_delete = self.records.find(function (r) {
+ return !r.get('id');
});
- // Add column for the save, if
- // there is none in the list
- if (!self.options.deletable) {
- self.view.pad_columns(
- 1, {except: $new_row});
+ if (to_delete) {
+ self.records.remove(to_delete);
}
-
- self.edition_form.do_show();
});
});
},
- handle_onwrite: function (source_record_id) {
+ /**
+ * Executes an action on the view's editor bracketed by a cancellable
+ * event of the name provided.
+ *
+ * The event name provided will be post-fixed with ``:before`` and
+ * ``:after``, the ``event`` parameter will be passed alongside the
+ * ``:before`` variant and if the parameter's ``cancel`` key is set to
+ * ``true`` the action *will not be called* and the method will return
+ * a rejection
+ *
+ * @param {String} event_name name of the event
+ * @param {Object} event event object, provided to ``:before`` sub-event
+ * @param {Function} action callable, called with the view's editor as its context
+ * @param {Array} [args] supplementary arguments provided to the action
+ * @param {Array} [trigger_params] supplementary arguments provided to the ``:after`` sub-event, before anything fetched by the ``action`` function
+ * @return {jQuery.Deferred}
+ */
+ with_event: function (event_name, event, action) {
var self = this;
- var on_write_callback = self.view.fields_view.arch.attrs.on_write;
- if (!on_write_callback) { return; }
- this.dataset.call(on_write_callback, [source_record_id], function (ids) {
- _(ids).each(function (id) {
- var record = self.records.get(id);
- if (!record) {
- // insert after the source record
- var index = self.records.indexOf(
- self.records.get(source_record_id)) + 1;
- record = new instance.web.list.Record({id: id});
- self.records.add(record, {at: index});
- self.dataset.ids.splice(index, 0, id);
- }
- self.reload_record(record);
+ event = event || {};
+ this.trigger(event_name + ':before', event);
+ if (event.cancel) {
+ return $.Deferred().reject({
+ message: _.str.sprintf("Event %s:before cancelled",
+ event_name)});
+ }
+ return $.when(action.call(this)).then(function () {
+ self.trigger.apply(self, [event_name + ':after']
+ .concat(_.toArray(arguments)));
+ });
+ },
+ edition_view: function (editor) {
+ var view = $.extend(true, {}, this.fields_view);
+ view.arch.tag = 'form';
+ _.extend(view.arch.attrs, {
+ 'class': 'oe_form_container',
+ version: '7.0'
+ });
+ _(view.arch.children).each(function (widget) {
+ var modifiers = JSON.parse(widget.attrs.modifiers || '{}');
+ widget.attrs.nolabel = true;
+ if (modifiers['tree_invisible'] || widget.tag === 'button') {
+ modifiers.invisible = true;
+ }
+ widget.attrs.modifiers = JSON.stringify(modifiers);
+ });
+ return view;
+ },
+ handle_onwrite: function (source_record) {
+ var self = this;
+ var on_write_callback = self.fields_view.arch.attrs.on_write;
+ if (!on_write_callback) { return $.when(); }
+ return this.dataset.call(on_write_callback, [source_record.get('id')])
+ .pipe(function (ids) {
+ return $.when.apply(
+ null, _(ids).map(
+ _.bind(self.handle_onwrite_record, self, source_record)));
});
+ },
+ handle_onwrite_record: function (source_record, id) {
+ var record = this.records.get(id);
+ if (!record) {
+ // insert after the source record
+ var index = this.records.indexOf(source_record) + 1;
+ record = new instance.web.list.Record({id: id});
+ this.records.add(record, {at: index});
+ this.dataset.ids.splice(index, 0, id);
+ }
+ return this.reload_record(record);
+ },
+ prepends_on_create: function () {
+ return this.editable() === 'top';
+ },
+ setup_events: function () {
+ var self = this;
+ this.editor.$element.on('keyup keydown', function (e) {
+ if (!self.editor.is_editing()) { return; }
+ var key = _($.ui.keyCode).chain()
+ .map(function (v, k) { return {name: k, code: v}; })
+ .find(function (o) { return o.code === e.which; })
+ .value();
+ if (!key) { return; }
+ var method = e.type + '_' + key.name;
+ if (!(method in self)) { return; }
+ self[method](e);
});
},
/**
- * Saves the current row, and returns a Deferred resolving to an object
- * with the following properties:
- *
- * ``created``
- * Boolean flag indicating whether the record saved was being created
- * (``true`` or edited (``false``)
- * ``edited_record``
- * The result of saving the record (either the newly created record,
- * or the post-edition record), after insertion in the Collection if
- * needs be.
+ * Saves the current record, and goes to the next one (creation or
+ * edition)
*
- * @returns {$.Deferred<{created: Boolean, edited_record: Record}>}
+ * @private
+ * @param {String} [next_record='succ'] method to call on the records collection to get the next record to edit
+ * @param {Object} [options]
+ * @param {String} [options.focus_field]
+ * @return {*}
*/
- save_row: function () {
- //noinspection JSPotentiallyInvalidConstructorUsage
+ _next: function (next_record, options) {
+ next_record = next_record || 'succ';
var self = this;
- return this.edition_form
- .do_save(null, this.options.editable === 'top')
- .pipe(function (result) {
- if (result.created && !self.edition_id) {
- self.records.add({id: result.result},
- {at: self.options.editable === 'top' ? 0 : null});
- self.edition_id = result.result;
- }
- var edited_record = self.records.get(self.edition_id);
-
- return $.when(
- self.handle_onwrite(self.edition_id),
- self.cancel_pending_edition().then(function () {
- $(self).trigger('saved', [self.dataset]);
- })).pipe(function () {
- return {
- created: result.created || false,
- edited_record: edited_record
- };
- });
- });
+ return this.save_edition().pipe(function (saveInfo) {
+ if (saveInfo.created) {
+ return self.start_edition();
+ }
+ var record = self.records[next_record](
+ saveInfo.record, {wraparound: true});
+ return self.start_edition(record, options);
+ });
+ },
+ keyup_ENTER: function () {
+ return this._next();
+ },
+ keyup_ESCAPE: function () {
+ return this.cancel_edition();
},
/**
- * If the current list is being edited, ensures it's saved
+ * Gets the selection range (start, end) for the provided element,
+ * returns ``null`` if it can't get a range.
+ *
+ * @private
*/
- ensure_saved: function () {
- if (this.edition) {
- // kinda-hack-ish: if the user has entered data in a field,
- // oe_form_dirty will be set on the form so save, otherwise
- // discard the current (entirely empty) line
- if (this.edition_form.$element.is('.oe_form_dirty')) {
- return this.save_row();
+ _text_selection_range: function (el) {
+ var selectionStart;
+ try {
+ selectionStart = el.selectionStart;
+ } catch (e) {
+ // radio or checkbox throw on selectionStart access
+ return null;
+ }
+ if (selectionStart !== undefined) {
+ return {
+ start: selectionStart,
+ end: el.selectionEnd
+ };
+ } else if (document.body.createTextRange) {
+ throw new Error("Implement text range handling for MSIE");
+ var sel = document.body.createTextRange();
+ if (sel.parentElement() === el) {
+
}
- return this.cancel_pending_edition();
}
- //noinspection JSPotentiallyInvalidConstructorUsage
- return $.when();
+ // Element without selection ranges (select, div/@contenteditable)
+ return null;
+ },
+ _text_cursor: function (el) {
+ var selection = this._text_selection_range(el);
+ if (!selection) {
+ return null;
+ }
+ if (selection.start !== selection.end) {
+ return {position: null, collapsed: false};
+ }
+ return {position: selection.start, collapsed: true};
+ },
+ /**
+ * Checks if the cursor is at the start of the provided el
+ *
+ * @param {HTMLInputElement | HTMLTextAreaElement}
+ * @returns {Boolean}
+ * @private
+ */
+ _at_start: function (cursor, el) {
+ return cursor.collapsed && (cursor.position === 0);
},
/**
- * Cancels the edition of the row for the current dataset index
+ * Checks if the cursor is at the end of the provided el
+ *
+ * @param {HTMLInputElement | HTMLTextAreaElement}
+ * @returns {Boolean}
+ * @private
*/
- cancel_edition: function () {
- this.cancel_pending_edition();
+ _at_end: function (cursor, el) {
+ return cursor.collapsed && (cursor.position === el.value.length);
},
/**
- * Edits record currently selected via dataset
+ * @param DOMEvent event
+ * @param {String} record_direction direction to move into to get the next record (pred | succ)
+ * @param {Function} is_valid_move whether the edition should be moved to the next record
+ * @private
*/
- edit_record: function (record_id) {
- this.render_row_as_form(
- this.$current.find('[data-id=' + record_id + ']'));
- $(this).trigger(
- 'edit',
- [record_id, this.dataset]);
- },
- new_record: function () {
- this.render_row_as_form();
- },
- render_record: function (record) {
- var index = this.records.indexOf(record),
- self = this;
- // FIXME: context dict should probably be extracted cleanly
- return QWeb.render('ListView.row', {
- columns: this.columns,
- options: this.options,
- record: record,
- row_parity: (index % 2 === 0) ? 'even' : 'odd',
- view: this.view,
- render_cell: function () {
- return self.render_cell.apply(self, arguments); },
- edited: !!this.edition_form
+ _key_move_record: function (event, record_direction, is_valid_move) {
+ if (!this.editor.is_editing('edit')) { return $.when(); }
+ var cursor = this._text_cursor(event.target);
+ // if text-based input (has a cursor)
+ // and selecting (not collapsed) or not at a field boundary
+ // don't move to the next record
+ if (cursor && !is_valid_move(event.target, cursor)) { return $.when(); }
+
+ event.preventDefault();
+ var source_field = $(event.target).closest('[data-fieldname]')
+ .attr('data-fieldname');
+ return this._next(record_direction, {focus_field: source_field});
+
+ },
+ keydown_UP: function (e) {
+ var self = this;
+ return this._key_move_record(e, 'pred', function (el, cursor) {
+ return self._at_start(cursor, el);
+ });
+ },
+ keydown_DOWN: function (e) {
+ var self = this;
+ return this._key_move_record(e, 'succ', function (el, cursor) {
+ return self._at_end(cursor, el);
});
+ },
+
+ keydown_LEFT: function (e) {
+ // If the cursor is at the beginning of the field
+ var source_field = $(e.target).closest('[data-fieldname]')
+ .attr('data-fieldname');
+ var cursor = this._text_cursor(e.target);
+ if (cursor && !this._at_start(cursor, e.target)) { return $.when(); }
+
+ var fields_order = this.editor.form.fields_order;
+ var field_index = _(fields_order).indexOf(source_field);
+
+ // Look for the closest visible form field to the left
+ var fields = this.editor.form.fields;
+ var field;
+ do {
+ if (--field_index < 0) { return $.when(); }
+
+ field = fields[fields_order[field_index]];
+ } while (!field.$element.is(':visible'));
+
+ // and focus it
+ field.focus();
+ return $.when();
+ },
+ keydown_RIGHT: function (e) {
+ // same as above, but with cursor at the end of the field and
+ // looking for new fields at the right
+ var source_field = $(e.target).closest('[data-fieldname]')
+ .attr('data-fieldname');
+ var cursor = this._text_cursor(e.target);
+ if (cursor && !this._at_end(cursor, e.target)) { return $.when(); }
+
+ var fields_order = this.editor.form.fields_order;
+ var field_index = _(fields_order).indexOf(source_field);
+
+ var fields = this.editor.form.fields;
+ var field;
+ do {
+ if (++field_index >= fields_order.length) { return $.when(); }
+
+ field = fields[fields_order[field_index]];
+ } while (!field.$element.is(':visible'));
+
+ field.focus();
+ return $.when();
+ },
+ keydown_TAB: function (e) {
+ var form = this.editor.form;
+ var last_field = _(form.fields_order).chain()
+ .map(function (name) { return form.fields[name]; })
+ .filter(function (field) { return field.$element.is(':visible'); })
+ .last()
+ .value();
+ // tabbed from last field in form
+ if (last_field && last_field.$element.has(e.target).length) {
+ e.preventDefault();
+ return this._next();
+ }
+ return $.when();
}
});
-
- instance.web.ListEditableFormView = instance.web.FormView.extend({
- init: function() {
- this._super.apply(this, arguments);
- this.rendering_engine = new instance.web.ListEditableRenderingEngine(this);
- this.options.initial_mode = "edit";
+
+ instance.web.list.Editor = instance.web.Widget.extend({
+ /**
+ * @constructs instance.web.list.Editor
+ * @extends instance.web.Widget
+ *
+ * Adapter between listview and formview for editable-listview purposes
+ *
+ * @param {instance.web.Widget} parent
+ * @param {Object} options
+ * @param {instance.web.FormView} [options.formView=instance.web.FormView]
+ * @param {Object} [options.delegate]
+ */
+ init: function (parent, options) {
+ this._super(parent);
+ this.options = options || {};
+ _.defaults(this.options, {
+ formView: instance.web.FormView,
+ delegate: this.getParent()
+ });
+ this.delegate = this.options.delegate;
+
+ this.record = null;
+
+ this.form = new (this.options.formView)(
+ this, this.delegate.dataset, false, {
+ initial_mode: 'edit',
+ disable_autofocus: true,
+ $buttons: $(),
+ $pager: $()
+ });
},
- renderElement: function() {}
- });
-
- instance.web.ListEditableRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({
- init: function(view) {
- this.view = view;
+ start: function () {
+ var self = this;
+ var _super = this._super();
+ this.form.embedded_view = this._validate_view(
+ this.delegate.edition_view(this));
+ var form_ready = this.form.appendTo(this.$element).then(
+ self.form.proxy('do_hide'));
+ return $.when(_super, form_ready);
},
- set_fields_view: function(fields_view) {
- this.fvg = fields_view;
+ _validate_view: function (edition_view) {
+ if (!edition_view) {
+ throw new Error("editor delegate's #edition_view must return "
+ + "a view descriptor");
+ }
+ var arch = edition_view.arch;
+ if (!(arch && arch.children instanceof Array)) {
+ throw new Error("Editor delegate's #edition_view must have a" +
+ " non-empty arch")
+ }
+ if (!(arch.tag === "form")) {
+ throw new Error("Editor delegate's #edition_view must have a" +
+ " 'form' root node");
+ }
+ if (!(arch.attrs && arch.attrs.version === "7.0")) {
+ throw new Error("Editor delegate's #edition_view must be a" +
+ " version 7 view");
+ }
+ if (!/\boe_form_container\b/.test(arch.attrs['class'])) {
+ throw new Error("Editor delegate's #edition_view must have the" +
+ " class 'oe_form_container' on its root" +
+ " element");
+ }
+
+ return edition_view;
},
- set_tags_registry: function(tags_registry) {
- this.tags_registry = tags_registry;
+
+ /**
+ *
+ * @param {String} [state] either ``new`` or ``edit``
+ * @return {Boolean}
+ */
+ is_editing: function (state) {
+ if (!this.record) {
+ return false;
+ }
+ switch(state) {
+ case null: case undefined:
+ return true;
+ case 'new': return !this.record.id;
+ case 'edit': return !!this.record.id;
+ }
+ throw new Error("is_editing's state filter must be either `new` or" +
+ " `edit` if provided");
},
- set_fields_registry: function(fields_registry) {
- this.fields_registry = fields_registry;
+ _focus_setup: function (focus_field) {
+ var form = this.form;
+
+ var field;
+ // If a field to focus was specified
+ if (focus_field
+ // Is actually in the form
+ && (field = form.fields[focus_field])
+ // And is visible
+ && field.$element.is(':visible')) {
+ // focus it
+ field.focus();
+ return;
+ }
+
+ _(form.fields_order).detect(function (name) {
+ // look for first visible field in fields_order, focus it
+ var field = form.fields[name];
+ if (!field.$element.is(':visible')) {
+ return false;
+ }
+ // Stop as soon as a field got focused
+ return field.focus() !== false;
+ });
},
- render_to: function($element) {
+ edit: function (record, configureField, options) {
+ // TODO: specify sequence of edit calls
var self = this;
-
- var xml = instance.web.json_node_to_xml(this.fvg.arch);
- var $xml = $(xml);
-
- if (this.view.editable_list.options.selectable)
- $("<td>").appendTo($element);
-
- $xml.children().each(function(i, el) {
- var modifiers = JSON.parse($(el).attr("modifiers") || "{}");
- var $td = $("<td>");
- if (modifiers.tree_invisible === true)
- $td.hide();
- var tag_name = el.tagName.toLowerCase();
- var w;
- if (tag_name === "field") {
- var name = $(el).attr("name");
- var key = $(el).attr('widget') || self.fvg.fields[name].type;
- var obj = self.view.fields_registry.get_object(key);
- w = new (obj)(self.view, instance.web.xml_to_json(el));
- self.view.register_field(w, $(el).attr("name"));
- } else {
- var obj = self.tags_registry.get_object(tag_name);
- w = new (obj)(self.view, instance.web.xml_to_json(el));
- }
- w.appendTo($td);
- $td.appendTo($element);
+ var form = self.form;
+ var loaded = record
+ ? form.on_record_loaded(_.extend({}, record))
+ : form.load_defaults();
+
+ return loaded.pipe(function () {
+ return form.do_show({reload: false});
+ }).pipe(function () {
+ self.record = form.datarecord;
+ _(form.fields).each(function (field, name) {
+ configureField(name, field);
+ });
+ self._focus_setup(options && options.focus_field);
+ return form;
});
- $(QWeb.render('ListView.row.save')).appendTo($element);
},
+ save: function () {
+ var self = this;
+ return this.form
+ .do_save(null, this.delegate.prepends_on_create())
+ .pipe(function (result) {
+ var created = result.created && !self.record.id;
+ if (created) {
+ self.record.id = result.result;
+ }
+ return self.cancel();
+ });
+ },
+ cancel: function (force) {
+ if (!(force || this.form.can_be_discarded())) {
+ return $.Deferred().reject({
+ message: "The form's data can not be discarded"}).promise();
+ }
+ var record = this.record;
+ this.record = null;
+ this.form.do_hide();
+ return $.when(record);
+ }
+ });
+
+ instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{
+ passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved",
+ get_row_for: function (record) {
+ return _(this.children).chain()
+ .invoke('get_row_for', record)
+ .compact()
+ .first()
+ .value();
+ }
+ });
+
+ instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{
+ row_clicked: function (event) {
+ if (!this.view.editable()) {
+ return this._super.apply(this, arguments);
+ }
+ var record_id = $(event.currentTarget).data('id');
+ this.view.start_edition(
+ record_id ? this.records.get(record_id) : null, {
+ focus_field: $(event.target).data('field')
+ });
+ },
+ /**
+ * If a row mapping to the record (@data-id matching the record's id or
+ * no @data-id if the record has no id), returns it. Otherwise returns
+ * ``null``.
+ *
+ * @param {Record} record the record to get a row for
+ * @return {jQuery|null}
+ */
+ get_row_for: function (record) {
+ var id;
+ var $row = this.$current.children('[data-id=' + record.get('id') + ']');
+ if ($row.length) {
+ return $row;
+ }
+ return null;
+ }
});
};
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index e11adb9d595..2845d9afb73 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -14,6 +14,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog = null;
this.dialog_widget = null;
this.breadcrumbs = [];
+ this.on('history_back', this, function() {
+ return this.history_back();
+ });
},
start: function() {
this._super.apply(this, arguments);
@@ -65,21 +68,46 @@ instance.web.ActionManager = instance.web.Widget.extend({
item.id = _.uniqueId('breadcrumb_');
this.breadcrumbs.push(item);
},
+ history_back: function() {
+ var last = this.breadcrumbs.slice(-1)[0];
+ if (!last) {
+ return false;
+ }
+ var title = last.get_title();
+ if (_.isArray(title) && title.length > 1) {
+ return this.select_breadcrumb(this.breadcrumbs.length - 1, title.length - 2);
+ } else if (this.breadcrumbs.length === 1) {
+ // Only one single titled item in breadcrumb, most of the time you want to trigger back to home
+ return false;
+ } else {
+ var prev = this.breadcrumbs[this.breadcrumbs.length - 2];
+ title = prev.get_title();
+ return this.select_breadcrumb(this.breadcrumbs.length - 2, _.isArray(title) ? title.length - 1 : undefined);
+ }
+ },
on_breadcrumb_clicked: function(ev) {
var $e = $(ev.target);
var id = $e.data('id');
- var item;
+ var index;
for (var i = this.breadcrumbs.length - 1; i >= 0; i--) {
- var it = this.breadcrumbs[i];
- if (it.id == id) {
- item = it;
+ if (this.breadcrumbs[i].id == id) {
+ index = i;
break;
}
- this.remove_breadcrumb(i);
}
- var index = $e.parent().find('.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e);
- item.show(index, $e);
+ var subindex = $e.parent().find('.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e);
+ this.select_breadcrumb(index, subindex);
+ },
+ select_breadcrumb: function(index, subindex) {
+ for (var i = this.breadcrumbs.length - 1; i >= 0; i--) {
+ if (i > index) {
+ this.remove_breadcrumb(i);
+ }
+ }
+ var item = this.breadcrumbs[index];
+ item.show(subindex);
this.inner_widget = item.widget;
+ return true;
},
clear_breadcrumbs: function() {
while (this.breadcrumbs.length) {
@@ -114,15 +142,21 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
}
}
- return titles.join(' / ');
+ return titles.join(' <span class="oe_fade">/</span> ');
},
do_push_state: function(state) {
+ state = state || {};
if (this.getParent() && this.getParent().do_push_state) {
if (this.inner_action) {
state['title'] = this.inner_action.name;
- state['model'] = this.inner_action.res_model;
+ if(this.inner_action.type == 'ir.actions.act_window') {
+ state['model'] = this.inner_action.res_model;
+ }
if (this.inner_action.id) {
- state['action_id'] = this.inner_action.id;
+ state['action'] = this.inner_action.id;
+ } else if (this.inner_action.type == 'ir.actions.client') {
+ state['action'] = this.inner_action.tag;
+ //state = _.extend(this.inner_action.params || {}, state);
}
}
this.getParent().do_push_state(state);
@@ -131,14 +165,20 @@ instance.web.ActionManager = instance.web.Widget.extend({
do_load_state: function(state, warm) {
var self = this,
action_loaded;
- if (state.action_id) {
- var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action_id;
- if (run_action) {
+ if (state.action) {
+ if (_.isString(state.action) && instance.web.client_actions.contains(state.action)) {
+ var action_client = {type: "ir.actions.client", tag: state.action, params: state};
this.null_action();
- action_loaded = this.do_action(state.action_id);
- instance.webclient.menu.has_been_loaded.then(function() {
- instance.webclient.menu.open_action(state.action_id);
- });
+ 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;
+ if (run_action) {
+ this.null_action();
+ action_loaded = this.do_action(state.action);
+ instance.webclient.menu.has_been_loaded.then(function() {
+ instance.webclient.menu.open_action(state.action);
+ });
+ }
}
} else if (state.model && state.id) {
// TODO handle context & domain ?
@@ -152,23 +192,12 @@ instance.web.ActionManager = instance.web.Widget.extend({
action_loaded = this.do_action(action);
} else if (state.sa) {
// load session action
- var self = this;
this.null_action();
action_loaded = this.rpc('/web/session/get_session_action', {key: state.sa}).pipe(function(action) {
if (action) {
return self.do_action(action);
}
});
- } else if (state.client_action) {
- this.null_action();
- var action = state.client_action;
- if(_.isString(action)) {
- action = {
- tag: action,
- params: state,
- };
- }
- this.ir_actions_client(action);
}
$.when(action_loaded || null).then(function() {
@@ -178,7 +207,10 @@ instance.web.ActionManager = instance.web.Widget.extend({
});
},
do_action: function(action, on_close) {
- if (_.isNumber(action) || _.isString(action)) {
+ if (_.isString(action) && instance.web.client_actions.contains(action)) {
+ var action_client = { type: "ir.actions.client", tag: action };
+ return this.do_action(action_client);
+ } else if (_.isNumber(action) || _.isString(action)) {
var self = this;
return self.rpc("/web/action/load", { action_id: action }, function(result) {
self.do_action(result.result, on_close);
@@ -186,7 +218,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}
if (!action.type) {
console.error("No type for action", action);
- return;
+ return null;
}
var type = action.type.replace(/\./g,'_');
var popup = action.target === 'new';
@@ -201,7 +233,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
}, action.flags || {});
if (!(type in this)) {
console.error("Action manager can't handle action of type " + action.type, action);
- return;
+ return null;
}
return this[type](action, on_close);
},
@@ -209,21 +241,33 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog_stop();
this.clear_breadcrumbs();
},
- ir_actions_act_window: function (action, on_close) {
- var self = this;
- if (_(['base.module.upgrade', 'base.setup.installer'])
- .contains(action.res_model)) {
- var old_close = on_close;
- on_close = function () {
- instance.webclient.do_reload().then(old_close);
+
+ do_ir_actions_common: function(action, on_close) {
+ var self = this, klass, widget, post_process;
+ if (action.type === 'ir.actions.client') {
+ var ClientWidget = instance.web.client_actions.get_object(action.tag);
+ widget = new ClientWidget(this, action.params);
+ klass = 'oe_act_client';
+ post_process = function() {
+ self.push_breadcrumb({
+ widget: widget,
+ title: action.name
+ });
+ if (action.tag !== 'reload') {
+ self.do_push_state({});
+ }
};
+ } else {
+ widget = new instance.web.ViewManagerAction(this, action);
+ klass = 'oe_act_window';
+ post_process = widget.proxy('add_breadcrumb');
}
if (action.target === 'new') {
if (this.dialog === null) {
// These buttons will be overwrited by <footer> if any
this.dialog = new instance.web.Dialog(this, {
buttons: { "Close": function() { $(this).dialog("close"); }},
- dialogClass: 'oe_act_window'
+ dialogClass: klass
});
if(on_close)
this.dialog.on_close.add(on_close);
@@ -231,21 +275,39 @@ instance.web.ActionManager = instance.web.Widget.extend({
this.dialog_widget.destroy();
}
this.dialog.dialog_title = action.name;
- this.dialog_widget = new instance.web.ViewManagerAction(this, action);
+ this.dialog_widget = widget;
this.dialog_widget.appendTo(this.dialog.$element);
this.dialog.open();
} else {
this.dialog_stop();
+ this.inner_action = action;
+ this.inner_widget = widget;
+ post_process();
+ this.inner_widget.appendTo(this.$element);
+ }
+ },
+
+ ir_actions_act_window: function (action, on_close) {
+ var self = this;
+ if (_(['base.module.upgrade', 'base.setup.installer'])
+ .contains(action.res_model)) {
+ var old_close = on_close;
+ on_close = function () {
+ instance.webclient.do_reload().then(old_close);
+ };
+ }
+ if (action.target !== 'new') {
if(action.menu_id) {
+ this.dialog_stop();
return this.getParent().do_action(action, function () {
instance.webclient.menu.open_menu(action.menu_id);
});
}
- this.inner_action = action;
- var inner_widget = this.inner_widget = new instance.web.ViewManagerAction(this, action);
- inner_widget.add_breadcrumb();
- this.inner_widget.appendTo(this.$element);
}
+ return this.do_ir_actions_common(action, on_close);
+ },
+ ir_actions_client: function (action, on_close) {
+ return this.do_ir_actions_common(action, on_close);
},
ir_actions_act_window_close: function (action, on_closed) {
if (!this.dialog && on_closed) {
@@ -262,19 +324,9 @@ instance.web.ActionManager = instance.web.Widget.extend({
self.do_action(action, on_closed)
});
},
- ir_actions_client: function (action) {
- this.dialog_stop();
- var ClientWidget = instance.web.client_actions.get_object(action.tag);
- this.inner_widget = new ClientWidget(this, action.params);
- this.push_breadcrumb({
- widget: this.inner_widget,
- title: action.name
- });
- this.inner_widget.appendTo(this.$element);
- },
ir_actions_report_xml: function(action, on_closed) {
var self = this;
- $.blockUI();
+ instance.web.blockUI();
self.rpc("/web/session/eval_domain_and_context", {
contexts: [action.context],
domains: []
@@ -284,7 +336,7 @@ instance.web.ActionManager = instance.web.Widget.extend({
self.session.get_file({
url: '/web/report',
data: {action: JSON.stringify(action)},
- complete: $.unblockUI,
+ complete: instance.web.unblockUI,
success: function(){
if (!self.dialog && on_closed) {
on_closed();
@@ -396,7 +448,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
.find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
.parent().addClass('active');
- $.when(view_promise).then(function () {
+ return $.when(view_promise).then(function () {
_.each(_.keys(self.views), function(view_name) {
var controller = self.views[view_name].controller;
if (controller) {
@@ -408,7 +460,7 @@ instance.web.ViewManager = instance.web.Widget.extend({
container.hide();
controller.do_hide();
}
- // put the <footer> in the dialog's buttonpane
+ // put the <footer> in the dialog's buttonpane
if (self.$element.parent('.ui-dialog-content') && self.$element.find('footer')) {
self.$element.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide()
self.$element.find('footer').appendTo(
@@ -418,7 +470,6 @@ instance.web.ViewManager = instance.web.Widget.extend({
}
});
});
- return view_promise;
},
do_create_view: function(view_type) {
// Lazy loading of views
@@ -436,6 +487,13 @@ instance.web.ViewManager = instance.web.Widget.extend({
}
var controller = new controllerclass(this, this.dataset, view.view_id, options);
+ controller.on('history_back', this, function() {
+ var am = self.getParent();
+ if (am && am.trigger) {
+ return am.trigger('history_back');
+ }
+ });
+
controller.on("change:title", this, function() {
if (self.active_view === view_type) {
self.set_title(controller.get('title'));
@@ -478,7 +536,8 @@ instance.web.ViewManager = instance.web.Widget.extend({
});
this.getParent().push_breadcrumb({
widget: this,
- show: function(index, $e) {
+ action: this.action,
+ show: function(index) {
var view_to_select = views[index];
self.$element.show();
if (self.active_view !== view_to_select) {
@@ -486,9 +545,27 @@ instance.web.ViewManager = instance.web.Widget.extend({
}
},
get_title: function() {
- return _.map(views, function(v) {
- return self.views[v].controller.get('title');
+ var id;
+ var currentIndex;
+ _.each(self.getParent().breadcrumbs, function(bc, i) {
+ if (bc.widget === self) {
+ currentIndex = i;
+ }
});
+ var next = self.getParent().breadcrumbs.slice(currentIndex + 1)[0];
+ var titles = _.map(views, function(v) {
+ var controller = self.views[v].controller;
+ if (v === 'form') {
+ id = controller.datarecord.id;
+ }
+ return controller.get('title');
+ });
+ if (next && next.action && next.action.res_id && self.active_view === 'form' && self.model === next.action.res_model && id === next.action.res_id) {
+ // If the current active view is a formview and the next item in the breadcrumbs
+ // is an action on same object (model / res_id), then we omit the current formview's title
+ titles.pop();
+ }
+ return titles;
}
});
},
@@ -657,32 +734,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
this.$element.find('.oe_debug_view').change(this.on_debug_changed);
this.$element.addClass("oe_view_manager_" + (this.action.target || 'current'));
-
- if (this.action.help && !this.flags.low_profile) {
- var Users = new instance.web.DataSet(self, 'res.users'),
- $tips = this.$element.find('.oe_view_manager_menu_tips');
- $tips.delegate('blockquote button', 'click', function() {
- var $this = $(this);
- //noinspection FallthroughInSwitchStatementJS
- switch ($this.attr('name')) {
- case 'disable':
- Users.write(self.session.uid, {menu_tips:false});
- case 'hide':
- $this.closest('blockquote').hide();
- self.session.hidden_menutips[self.action.id] = true;
- }
- });
- if (!(self.action.id in self.session.hidden_menutips)) {
- Users.read_ids([this.session.uid], ['menu_tips']).then(function(users) {
- var user = users[0];
- if (!(user && user.id === self.session.uid)) {
- return;
- }
- $tips.find('blockquote').toggle(user.menu_tips);
- });
- }
- }
-
return manager_ready;
},
on_debug_changed: function (evt) {
@@ -736,15 +787,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({
width: '95%'}, $root).open();
});
break;
- case 'manage_views':
- if (current_view.fields_view && current_view.fields_view.arch) {
- var view_editor = new instance.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch);
- view_editor.start();
- } else {
- this.do_warn(_t("Manage Views"),
- _t("Could not find current view declaration"));
- }
- break;
case 'edit_workflow':
return this.do_action({
res_model : 'workflow',
@@ -867,7 +909,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
} else {
self.do_attachement_update(self.dataset, self.model_id);
}
- $.unblockUI();
+ instance.web.unblockUI();
});
},
start: function() {
@@ -1006,7 +1048,7 @@ instance.web.Sidebar = instance.web.Widget.extend({
$e.parent().find('input[type=file]').prop('disabled', true);
$e.parent().find('button').prop('disabled', true).find('img, span').toggle();
this.$('.oe_sidebar_add_attachment span').text(_t('Uploading...'));
- $.blockUI();
+ instance.web.blockUI();
}
},
on_attachment_delete: function(e) {
@@ -1033,14 +1075,15 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
this['on_button_' + _t("Close")] = this.on_btn_close;
this._super(view, {
width: '80%',
- height: '80%'
+ height: '80%',
+ destroy_on_close: false,
});
this.view = view;
this.view_type = view.fields_view.type || '';
this.$fields_form = null;
this.$view_form = null;
this.$sidebar_form = null;
- this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name });
+ this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name; });
this.languages = null;
this.languages_loaded = $.Deferred();
(new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
@@ -1071,7 +1114,8 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
deffered.push(deff);
var callback = function(values) {
_.each(self.translatable_fields_keys, function(f) {
- self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || '').attr('data-value', values[0][f] || '');
+ var value = values[0][f] || '';
+ self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(value).attr('data-value', value);
});
deff.resolve();
};
@@ -1082,24 +1126,19 @@ instance.web.TranslateDialog = instance.web.Dialog.extend({
});
callback([values]);
} else {
- self.rpc('/web/dataset/get', {
- model: self.view.dataset.model,
- ids: [self.view.datarecord.id],
- fields: self.translatable_fields_keys,
- context: self.view.dataset.get_context({
- 'lang': lg.code
- })}, callback);
+ self.view.dataset.read_ids([self.view.datarecord.id], self.translatable_fields_keys, {
+ context: { 'lang': lg.code }
+ }).then(callback);
}
});
$.when.apply(null, deffered).then(callback);
},
open: function(field) {
- var self = this,
- sup = this._super;
+ var self = this;
+ this._super();
$.when(this.languages_loaded).then(function() {
if (self.view.translatable_fields && self.view.translatable_fields.length) {
self.do_load_fields_values(function() {
- sup.call(self);
if (field) {
var $field_input = self.$element.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child');
self.$element.scrollTo($field_input);
@@ -1153,7 +1192,7 @@ instance.web.View = instance.web.Widget.extend({
this.view_id = view_id;
this.set_default_options(options);
},
- start: function() {
+ start: function () {
return this.load_view();
},
load_view: function() {
@@ -1193,7 +1232,7 @@ instance.web.View = instance.web.Widget.extend({
},
open_translate_dialog: function(field) {
if (!this.translate_dialog) {
- this.translate_dialog = new instance.web.TranslateDialog(this).start();
+ this.translate_dialog = new instance.web.TranslateDialog(this);
}
this.translate_dialog.open(field);
},
@@ -1363,7 +1402,6 @@ instance.web.xml_to_json = function(node) {
}
instance.web.json_node_to_xml = function(node, human_readable, indent) {
// For debugging purpose, this function will convert a json node back to xml
- // Maybe useful for xml view editor
indent = indent || 0;
var sindent = (human_readable ? (new Array(indent + 1).join('\t')) : ''),
r = sindent + '<' + node.tag,
@@ -1372,7 +1410,9 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) {
if (typeof(node) === 'string') {
return sindent + node;
} else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) {
- throw("Node a json node");
+ throw new Error(
+ _.str.sprintf("Node [%s] is not a JSONified XML node",
+ JSON.stringify(node)));
}
for (var attr in node.attrs) {
var vattr = node.attrs[attr];