summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
Diffstat (limited to 'addons/web/static/src/js')
-rw-r--r--addons/web/static/src/js/boot.js21
-rw-r--r--addons/web/static/src/js/chrome.js465
-rw-r--r--addons/web/static/src/js/corelib.js (renamed from addons/web/static/src/js/core.js)1306
-rw-r--r--addons/web/static/src/js/coresetup.js142
-rw-r--r--addons/web/static/src/js/data.js120
-rw-r--r--addons/web/static/src/js/data_export.js10
-rw-r--r--addons/web/static/src/js/data_import.js16
-rw-r--r--addons/web/static/src/js/dates.js14
-rw-r--r--addons/web/static/src/js/formats.js86
-rw-r--r--addons/web/static/src/js/search.js1496
-rw-r--r--addons/web/static/src/js/view_editor.js68
-rw-r--r--addons/web/static/src/js/view_form.js2829
-rw-r--r--addons/web/static/src/js/view_list.js303
-rw-r--r--addons/web/static/src/js/view_list_editable.js114
-rw-r--r--addons/web/static/src/js/view_page.js288
-rw-r--r--addons/web/static/src/js/view_tree.js21
-rw-r--r--addons/web/static/src/js/views.js521
17 files changed, 4157 insertions, 3663 deletions
diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js
index 282435f2a71..1251975f41c 100644
--- a/addons/web/static/src/js/boot.js
+++ b/addons/web/static/src/js/boot.js
@@ -47,26 +47,15 @@
/*---------------------------------------------------------
* OpenERP Web web module split
*---------------------------------------------------------*/
-
-/**
- * @namespace
- */
-openerp.web = function(instance) {
- openerp.web.core(instance);
- if (openerp.web.dates) {
- openerp.web.dates(instance);
- }
- openerp.web.formats(instance);
- openerp.web.chrome(instance);
- openerp.web.data(instance);
- var files = ["views","search","list","form", "page","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"];
+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"];
for(var i=0; i<files.length; i++) {
if(openerp.web[files[i]]) {
- openerp.web[files[i]](instance);
+ openerp.web[files[i]](session);
}
}
- instance.log = function() {
- if (instance.connection.debug && window.console) {
+ session.log = function() {
+ if (session.connection.debug && window.console) {
console.log.apply(console, arguments);
}
}
diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js
index 7743e2b5777..f1d3faef367 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -1,18 +1,17 @@
/*---------------------------------------------------------
* OpenERP Web chrome
*---------------------------------------------------------*/
-openerp.web.chrome = function(openerp) {
-var QWeb = openerp.web.qweb,
- _t = openerp.web._t;
+openerp.web.chrome = function(instance) {
+var QWeb = instance.web.qweb,
+ _t = instance.web._t;
-openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web.Notification# */{
+instance.web.Notification = instance.web.Widget.extend({
template: 'Notification',
-
init: function() {
this._super.apply(this, arguments);
- openerp.notification = this;
+ // move to instance.web.notification
+ instance.notification = this;
},
-
start: function() {
this._super.apply(this, arguments);
this.$element.notify({
@@ -42,24 +41,16 @@ openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web.
text: text
}, opts);
}
-
});
-openerp.web.dialog = function(element) {
+instance.web.dialog = function(element) {
var result = element.dialog.apply(element, _.rest(_.toArray(arguments)));
result.dialog("widget").addClass("openerp");
return result;
}
-openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{
+instance.web.Dialog = instance.web.Widget.extend({
dialog_title: "",
- /**
- * @constructs openerp.web.Dialog
- * @extends openerp.web.OldWidget
- *
- * @param parent
- * @param options
- */
init: function (parent, options, content) {
var self = this;
this._super(parent);
@@ -92,7 +83,7 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
if (this.dialog_options.autoOpen) {
this.open();
} else {
- openerp.web.dialog(this.$element, this.get_options());
+ instance.web.dialog(this.$element, this.get_options());
}
},
get_options: function(options) {
@@ -128,10 +119,10 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
open: function(options) {
// TODO fme: bind window on resize
if (this.template) {
- this.$element.html(this.render());
+ this.$element.html(this.renderElement());
}
var o = this.get_options(options);
- openerp.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' });
}
@@ -149,14 +140,13 @@ openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog#
//openerp.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height());
},
destroy: function () {
- // Destroy widget
this.close();
this.$element.dialog('destroy');
this._super();
}
});
-openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
+instance.web.CrashManager = instance.web.CallbackEnabled.extend({
on_rpc_error: function(error) {
if (error.data.fault_code) {
var split = ("" + error.data.fault_code).split('\n')[0].split(' -- ');
@@ -172,7 +162,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
}
},
on_managed_error: function(error) {
- openerp.web.dialog($('<div>' + QWeb.render('CrashManagerWarning', {error: error}) + '</div>'), {
+ instance.web.dialog($('<div>' + QWeb.render('CrashManager.warning', {error: error}) + '</div>'), {
title: "OpenERP " + _.str.capitalize(error.type),
buttons: [
{text: _t("Ok"), click: function() { $(this).dialog("close"); }}
@@ -182,14 +172,14 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
on_traceback: function(error) {
var self = this;
var buttons = {};
- if (openerp.connection.openerp_entreprise) {
+ if (instance.connection.openerp_entreprise) {
buttons[_t("Send OpenERP Enterprise Report")] = function() {
var $this = $(this);
var issuename = $('#issuename').val();
var explanation = $('#explanation').val();
var remark = $('#remark').val();
// Call the send method from server to send mail with details
- new openerp.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){
+ new instance.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){
if (result === false) {
alert('There was a communication error.')
} else {
@@ -205,7 +195,7 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
$(this).dialog("close");
};
}
- var dialog = new openerp.web.Dialog(this, {
+ var dialog = new instance.web.Dialog(this, {
title: "OpenERP " + _.str.capitalize(error.type),
width: '80%',
height: '50%',
@@ -213,19 +203,12 @@ openerp.web.CrashManager = openerp.web.CallbackEnabled.extend({
min_height: '600px',
buttons: buttons
}).open();
- dialog.$element.html(QWeb.render('CrashManagerError', {session: openerp.connection, error: error}));
+ dialog.$element.html(QWeb.render('CrashManager.error', {session: instance.connection, error: error}));
}
});
-openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loading# */{
+instance.web.Loading = instance.web.Widget.extend({
template: 'Loading',
- /**
- * @constructs openerp.web.Loading
- * @extends openerp.web.OldWidget
- *
- * @param parent
- * @param element_id
- */
init: function(parent) {
this._super(parent);
this.count = 0;
@@ -258,10 +241,9 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin
this.count += increment;
if (this.count > 0) {
- $(".loading",this.$element).text(_.str.sprintf(
- _t("Loading (%d)"), this.count));
- $(".loading",this.$element).show();
- this.getParent().$element.addClass('loading');
+ this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count));
+ this.$element.show();
+ this.getParent().$element.addClass('oe_wait');
} else {
this.count = 0;
clearTimeout(this.long_running_timer);
@@ -270,73 +252,60 @@ openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loadin
this.blocked_ui = false;
$.unblockUI();
}
- $(".loading",this.$element).fadeOut();
- this.getParent().$element.removeClass('loading');
+ this.$element.fadeOut();
+ this.getParent().$element.removeClass('oe_wait');
}
}
});
-openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Database# */{
- template: "DatabaseManager",
- /**
- * @constructs openerp.web.Database
- * @extends openerp.web.OldWidget
- *
- * @param parent
- * @param element_id
- * @param option_id
- */
- init: function(parent, element_id, option_id) {
- this._super(parent, element_id);
+instance.web.DatabaseManager = instance.web.Widget.extend({
+ init: function(parent) {
+ this._super(parent);
this.unblockUIFunction = $.unblockUI;
$.validator.addMethod('matches', function (s, _, re) {
return new RegExp(re).test(s);
}, _t("Invalid database name"));
},
start: function() {
- this.$option_id = $("#oe_db_options");
-
var self = this;
var fetch_db = this.rpc("/web/database/get_list", {}).pipe(
- function(result) { self.db_list = result.db_list; },
- function (_, ev) { ev.preventDefault(); self.db_list = null; });
- var fetch_langs = this.rpc("/web/session/get_lang_list", {}, function(result) {
- if (result.error) {
- self.display_error(result);
- return;
- }
+ function(result) {
+ self.db_list = result.db_list;
+ },
+ function (_, ev) {
+ ev.preventDefault();
+ self.db_list = null;
+ });
+ var fetch_langs = this.rpc("/web/session/get_lang_list", {}).then(function(result) {
self.lang_list = result.lang_list;
});
- $.when(fetch_db, fetch_langs).then(function () {self.do_create();});
-
- this.$element.find('#db-create').click(this.do_create);
- this.$element.find('#db-drop').click(this.do_drop);
- this.$element.find('#db-backup').click(this.do_backup);
- this.$element.find('#db-restore').click(this.do_restore);
- this.$element.find('#db-change-password').click(this.do_change_password);
- this.$element.find('#back-to-login').click(function() {
- self.hide();
+ return $.when(fetch_db, fetch_langs).then(self.do_render);
+ },
+ do_render: function() {
+ var self = this;
+ self.$element.html(QWeb.render("DatabaseManager", { widget : self }));
+ self.$element.find(".oe_database_manager_menu").tabs();
+ self.$element.find("form[name=create_db_form]").validate({ submitHandler: self.do_create });
+ self.$element.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop });
+ self.$element.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup });
+ self.$element.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore });
+ self.$element.find("form[name=change_pwd_form]").validate({
+ messages: {
+ old_pwd: "Please enter your previous password",
+ new_pwd: "Please enter your new password",
+ confirm_pwd: {
+ required: "Please confirm your new password",
+ equalTo: "The confirmation does not match the password"
+ }
+ },
+ submitHandler: self.do_change_password
});
+ self.$element.find("#back_to_login").click(self.do_exit);
},
destroy: function () {
- this.hide();
- this.$option_id.empty();
-
- this.$element
- .find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login')
- .unbind('click')
- .end()
- .empty();
+ this.$element.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty();
this._super();
},
- show: function () {
- this.$element.closest(".login")
- .addClass("database_block");
- },
- hide: function () {
- this.$element.closest(".login")
- .removeClass("database_block")
- },
/**
* Converts a .serializeArray() result into a dict. Does not bother folding
* multiple identical keys into an array, last key wins.
@@ -375,7 +344,7 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
* @param {String} error.error message of the error dialog
*/
display_error: function (error) {
- return openerp.web.dialog($('<div>'), {
+ return instance.web.dialog($('<div>'), {
modal: true,
title: error.title,
buttons: [
@@ -383,157 +352,96 @@ openerp.web.Database = openerp.web.OldWidget.extend(/** @lends openerp.web.Datab
]
}).html(error.error);
},
- do_create: function() {
+ do_create: function(form) {
var self = this;
- self.$option_id.html(QWeb.render("Database.CreateDB", self));
- self.$option_id.find("form[name=create_db_form]").validate({
- submitHandler: function (form) {
- var fields = $(form).serializeArray();
- self.rpc("/web/database/create", {'fields': fields}, function(result) {
- if (self.db_list) {
- self.db_list.push(self.to_object(fields)['db_name']);
- self.db_list.sort();
- self.getParent().set_db_list(self.db_list);
- }
-
- var form_obj = self.to_object(fields);
- self.getParent().do_login(
- form_obj['db_name'],
- 'admin',
- form_obj['create_admin_pwd']);
- self.destroy();
- });
- }
+ 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();
});
+
},
- do_drop: function() {
+ do_drop: function(form) {
var self = this;
- self.$option_id.html(QWeb.render("DropDB", self));
- self.$option_id.find("form[name=drop_db_form]").validate({
- submitHandler: function (form) {
- var $form = $(form),
- fields = $form.serializeArray(),
- $db_list = $form.find('[name=drop_db]'),
- db = $db_list.val();
-
- if (!db || !confirm("Do you really want to delete the database: " + db + " ?")) {
- return;
- }
- self.rpc("/web/database/drop", {'fields': fields}, function(result) {
- if (result.error) {
- self.display_error(result);
- return;
- }
- $db_list.find(':selected').remove();
- if (self.db_list) {
- self.db_list.splice(_.indexOf(self.db_list, db, true), 1);
- self.getParent().set_db_list(self.db_list);
- }
- self.do_notify("Dropping database", "The database '" + db + "' has been dropped");
- });
+ var $form = $(form),
+ fields = $form.serializeArray(),
+ $db_list = $form.find('[name=drop_db]'),
+ db = $db_list.val();
+ if (!db || !confirm("Do you really want to delete the database: " + db + " ?")) {
+ return;
+ }
+ self.rpc("/web/database/drop", {'fields': fields}, function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
}
+ self.do_notify("Dropping database", "The database '" + db + "' has been dropped");
+ self.start();
});
},
- do_backup: function() {
+ do_backup: function(form) {
var self = this;
- self.$option_id
- .html(QWeb.render("BackupDB", self))
- .find("form[name=backup_db_form]").validate({
- submitHandler: function (form) {
- self.blockUI();
- self.session.get_file({
- form: form,
- success: function () {
- self.do_notify(_t("Backed"),
- _t("Database backed up successfully"));
- },
- error: openerp.webclient.crashmanager.on_rpc_error,
- complete: function() {
- self.unblockUI();
- }
- });
+ self.blockUI();
+ self.session.get_file({
+ form: form,
+ success: function () {
+ self.do_notify(_t("Backed"), _t("Database backed up successfully"));
+ },
+ error: instance.webclient.crashmanager.on_rpc_error,
+ complete: function() {
+ self.unblockUI();
}
});
},
- do_restore: function() {
+ do_restore: function(form) {
var self = this;
- self.$option_id.html(QWeb.render("RestoreDB", self));
+ self.blockUI();
+ $(form).ajaxSubmit({
+ url: '/web/database/restore',
+ type: 'POST',
+ resetForm: true,
+ success: function (body) {
+ // If empty body, everything went fine
+ if (!body) { return; }
- self.$option_id.find("form[name=restore_db_form]").validate({
- submitHandler: function (form) {
- self.blockUI();
- $(form).ajaxSubmit({
- url: '/web/database/restore',
- type: 'POST',
- resetForm: true,
- success: function (body) {
- // TODO: ui manipulations
- // note: response objects don't work, but we have the
- // HTTP body of the response~~
-
- // If empty body, everything went fine
- if (!body) { return; }
-
- if (body.indexOf('403 Forbidden') !== -1) {
- self.display_error({
- title: 'Access Denied',
- error: 'Incorrect super-administrator password'
- })
- } else {
- self.display_error({
- title: 'Restore Database',
- error: 'Could not restore the database'
- })
- }
- },
- complete: function() {
- self.unblockUI();
- self.do_notify(_t("Restored"), _t("Database restored successfully"));
- }
- });
+ if (body.indexOf('403 Forbidden') !== -1) {
+ self.display_error({
+ title: 'Access Denied',
+ error: 'Incorrect super-administrator password'
+ })
+ } else {
+ self.display_error({
+ title: 'Restore Database',
+ error: 'Could not restore the database'
+ })
+ }
+ },
+ complete: function() {
+ self.unblockUI();
+ self.do_notify(_t("Restored"), _t("Database restored successfully"));
}
});
},
- do_change_password: function() {
+ do_change_password: function(form) {
var self = this;
- self.$option_id.html(QWeb.render("Change_DB_Pwd", self));
-
- self.$option_id.find("form[name=change_pwd_form]").validate({
- messages: {
- old_pwd: "Please enter your previous password",
- new_pwd: "Please enter your new password",
- confirm_pwd: {
- required: "Please confirm your new password",
- equalTo: "The confirmation does not match the password"
- }
- },
- submitHandler: function (form) {
- self.rpc("/web/database/change_password", {
- 'fields': $(form).serializeArray()
- }, function(result) {
- if (result.error) {
- self.display_error(result);
- return;
- }
- self.do_notify("Changed Password", "Password has been changed successfully");
- });
+ self.rpc("/web/database/change_password", {
+ 'fields': $(form).serializeArray()
+ }, function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
}
+ self.do_notify("Changed Password", "Password has been changed successfully");
});
+ },
+ do_exit: function () {
}
});
-openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login# */{
- remember_credentials: true,
-
+instance.web.Login = instance.web.Widget.extend({
template: "Login",
- /**
- * @constructs openerp.web.Login
- * @extends openerp.web.OldWidget
- *
- * @param parent
- * @param element_id
- */
-
+ remember_credentials: true,
init: function(parent) {
this._super(parent);
this.has_local_storage = typeof(localStorage) != 'undefined';
@@ -550,29 +458,35 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login#
},
start: function() {
var self = this;
- this.database = new openerp.web.Database(this);
- this.database.appendTo(this.$element);
- this.$element.find('#oe-db-config').click(function() {
- self.database.show();
- });
+ self.$element.find("form").submit(self.on_submit);
- this.$element.find("form").submit(this.on_submit);
-
- this.rpc("/web/database/get_list", {}, function(result) {
+ self.$element.find('.oe_login_manage_db').click(function() {
+ 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();
+ })
+ });
+ self.load_db_list();
+ },
+ load_db_list: function () {
+ var self = this;
+ self.rpc("/web/database/get_list", {}, function(result) {
self.set_db_list(result.db_list);
- },
- function(error, event) {
+ }, function(error, event) {
if (error.data.fault_code === 'AccessDenied') {
event.preventDefault();
}
});
-
},
set_db_list: function (list) {
- this.$element.find("[name=db]").replaceWith(
- openerp.web.qweb.render('Login_dblist', {
- db_list: list, selected_db: this.selected_db}))
+ this.$element.find("[name=db]").replaceWith(instance.web.qweb.render('Login.dblist', { db_list: list, selected_db: this.selected_db}))
},
on_submit: function(ev) {
if(ev) {
@@ -598,15 +512,15 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login#
*/
do_login: function (db, login, password) {
var self = this;
- this.$element.removeClass('login_invalid');
+ this.$element.removeClass('oe_login_invalid');
this.session.on_session_invalid.add({
callback: function () {
- self.$element.addClass("login_invalid");
+ self.$element.addClass("oe_login_invalid");
},
unique: true
});
this.session.session_authenticate(db, login, password).then(function() {
- self.$element.removeClass("login_invalid");
+ self.$element.removeClass("oe_login_invalid");
if (self.has_local_storage) {
if(self.remember_credentials) {
localStorage.setItem('last_db_login_success', db);
@@ -624,13 +538,7 @@ openerp.web.Login = openerp.web.OldWidget.extend(/** @lends openerp.web.Login#
}
});
-openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
- /**
- * @constructs openerp.web.Menu
- * @extends openerp.web.Widget
- *
- * @param parent
- */
+instance.web.Menu = instance.web.Widget.extend({
template: 'Menu',
init: function() {
this._super.apply(this, arguments);
@@ -774,14 +682,8 @@ openerp.web.Menu = openerp.web.Widget.extend(/** @lends openerp.web.Menu# */{
}
});
-openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMenu# */{
+instance.web.UserMenu = instance.web.Widget.extend({
template: "UserMenu",
- /**
- * @constructs openerp.web.UserMenu
- * @extends openerp.web.Widget
- *
- * @param parent
- */
init: function(parent) {
this._super(parent);
this.update_promise = $.Deferred().resolve();
@@ -805,7 +707,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
},
change_password :function() {
var self = this;
- this.dialog = new openerp.web.Dialog(this, {
+ this.dialog = new instance.web.Dialog(this, {
title: _t("Change Password"),
width : 'auto'
}).open();
@@ -819,14 +721,14 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
self.display_error(result);
return;
} else {
- openerp.webclient.on_logout();
+ instance.webclient.on_logout();
}
});
}
});
},
display_error: function (error) {
- return openerp.web.dialog($('<div>'), {
+ return instance.web.dialog($('<div>'), {
modal: true,
title: error.title,
buttons: [
@@ -841,10 +743,10 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
$avatar.attr('src', $avatar.data('default-src'));
if (!self.session.uid)
return;
- var func = new openerp.web.Model("res.users").get_func("read");
+ var func = new instance.web.Model("res.users").get_func("read");
return func(self.session.uid, ["name", "company_id"]).pipe(function(res) {
// TODO: Show company if multicompany is in use
- var topbar_name = _.str.sprintf("%s (%s)", res.name, openerp.connection.db, res.company_id[1]);
+ var topbar_name = _.str.sprintf("%s (%s)", res.name, instance.connection.db, 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);
$avatar.attr('src', avatar_src);
@@ -858,7 +760,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
shortcut_load :function(){
var self = this,
sc = self.session.shortcuts,
- shortcuts_ds = new openerp.web.DataSet(this, 'ir.ui.view_sc');
+ shortcuts_ds = new instance.web.DataSet(this, 'ir.ui.view_sc');
self.$element.find('.oe_dropdown_options a[data-menu=shortcut]').each(function() {
$(this).parent().remove();
});
@@ -905,11 +807,12 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
},
on_menu_settings: function() {
var self = this;
- var action_manager = new openerp.web.ActionManager(this);
- var dataset = new openerp.web.DataSet (this,'res.users',this.context);
+ 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: {
@@ -922,7 +825,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
}));
});
});
- this.dialog = new openerp.web.Dialog(this,{
+ this.dialog = new instance.web.Dialog(this,{
title: _t("Preferences"),
width: '700px',
buttons: [
@@ -941,7 +844,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
]
}).open();
action_manager.appendTo(this.dialog.$element);
- action_manager.render(this.dialog);
+ action_manager.renderElement(this.dialog);
},
on_menu_about: function() {
var self = this;
@@ -952,7 +855,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
window.location = $.param.querystring(
window.location.href, 'debug');
});
- openerp.web.dialog($help, {autoOpen: true,
+ instance.web.dialog($help, {autoOpen: true,
modal: true, width: 960, title: _t("About")});
});
},
@@ -968,21 +871,17 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
}
});
-openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClient */{
- /**
- * @constructs openerp.web.WebClient
- * @extends openerp.web.Widget
- */
+instance.web.WebClient = instance.web.Widget.extend({
init: function(parent) {
var self = this;
this._super(parent);
- openerp.webclient = this;
+ instance.webclient = this;
this.querystring = '?' + jQuery.param.querystring();
this._current_state = null;
},
start: function() {
var self = this;
- this.$element.addClass("openerp openerp2");
+ this.$element.addClass("openerp openerp-web-client-container");
if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
this.$element.addClass("kitten-mode-activated");
this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) {
@@ -1001,7 +900,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
self.menu.do_reload();
if(self.action_manager)
self.action_manager.destroy();
- self.action_manager = new openerp.web.ActionManager(self);
+ self.action_manager = new instance.web.ActionManager(self);
self.action_manager.appendTo(self.$element.find('.oe_application'));
self.bind_hashchange();
var version_label = _t("OpenERP - Unsupported/Community Version");
@@ -1018,7 +917,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
var self = this;
this.destroy_content();
this.show_common();
- self.login = new openerp.web.Login(self);
+ self.login = new instance.web.Login(self);
self.login.appendTo(self.$element);
},
show_application: function() {
@@ -1027,10 +926,10 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
this.show_common();
self.$table = $(QWeb.render("WebClient", {}));
self.$element.append(self.$table);
- self.menu = new openerp.web.Menu(self);
+ self.menu = new instance.web.Menu(self);
self.menu.replace(this.$element.find('.oe_menu_placeholder'));
self.menu.on_action.add(this.proxy('on_menu_action'));
- self.user_menu = new openerp.web.UserMenu(self);
+ self.user_menu = new instance.web.UserMenu(self);
self.user_menu.replace(this.$element.find('.oe_user_menu_placeholder'));
self.user_menu.on_menu_logout.add(this.proxy('on_logout'));
self.user_menu.on_action.add(this.proxy('on_menu_action'));
@@ -1038,8 +937,8 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
show_common: function() {
var self = this;
if (!this.crashmanager) {
- this.crashmanager = new openerp.web.CrashManager();
- openerp.connection.on_rpc_error.add(this.crashmanager.on_rpc_error);
+ this.crashmanager = new instance.web.CrashManager();
+ instance.connection.on_rpc_error.add(this.crashmanager.on_rpc_error);
window.onerror = function (message, file, line) {
self.crashmanager.on_traceback({
type: _t("Client Error"),
@@ -1048,9 +947,9 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
});
}
}
- this.notification = new openerp.web.Notification(this);
+ this.notification = new instance.web.Notification(this);
this.notification.appendTo(this.$element);
- this.loading = new openerp.web.Loading(this);
+ this.loading = new instance.web.Loading(this);
this.loading.appendTo(this.$element);
},
destroy_content: function() {
@@ -1062,7 +961,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
do_reload: function() {
var self = this;
return this.session.session_reload().pipe(function () {
- openerp.connection.load_modules(true).pipe(
+ instance.connection.load_modules(true).pipe(
self.menu.proxy('do_reload')); });
},
@@ -1126,16 +1025,15 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
}
});
-openerp.web.EmbeddedClient = openerp.web.OldWidget.extend({
+instance.web.EmbeddedClient = instance.web.Widget.extend({
template: 'EmptyComponent',
- init: function(action_id, options) {
- this._super();
+ init: function(parent, action_id, options) {
+ this._super(parent);
// TODO take the xmlid of a action instead of its id
this.action_id = action_id;
this.options = options || {};
- this.am = new openerp.web.ActionManager(this);
+ this.am = new instance.web.ActionManager(this);
},
-
start: function() {
var self = this;
this.am.appendTo(this.$element.addClass('openerp'));
@@ -1152,10 +1050,9 @@ openerp.web.EmbeddedClient = openerp.web.OldWidget.extend({
self.am.do_action(action);
});
}
-
});
-openerp.web.embed = function (origin, dbname, login, key, action, options) {
+instance.web.embed = function (origin, dbname, login, key, action, options) {
$('head').append($('<link>', {
'rel': 'stylesheet',
'type': 'text/css',
@@ -1166,9 +1063,9 @@ openerp.web.embed = function (origin, dbname, login, key, action, options) {
var sc = document.getElementsByTagName('script');
currentScript = sc[sc.length-1];
}
- openerp.connection.session_bind(origin).then(function () {
- openerp.connection.session_authenticate(dbname, login, key, true).then(function () {
- var client = new openerp.web.EmbeddedClient(action, options);
+ 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);
});
});
diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/corelib.js
index 44e15f6dd13..cbd28aca8a3 100644
--- a/addons/web/static/src/js/core.js
+++ b/addons/web/static/src/js/corelib.js
@@ -1,60 +1,134 @@
-/*---------------------------------------------------------
- * OpenERP Web core
- *--------------------------------------------------------*/
-var console;
-if (!console) {
- console = {log: function () {}};
-}
-if (!console.debug) {
- console.debug = console.log;
-}
+/*
+ * Copyright (c) 2012, OpenERP S.A.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
-openerp.web.core = function(openerp) {
+openerp.web.corelib = function(instance) {
-// a function to override the "extend()" method of JR's inheritance, allowing
-// the usage of "include()"
-oe_override_class = function(claz){
- var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ?
- /\b_super\b/ : /.*/;
-
- // Create a new Class that inherits from this class
- claz.extend = function(prop) {
+/**
+ * Improved John Resig's inheritance, based on:
+ *
+ * Simple JavaScript Inheritance
+ * By John Resig http://ejohn.org/
+ * MIT Licensed.
+ *
+ * Adds "include()"
+ *
+ * Defines The Class object. That object can be used to define and inherit classes using
+ * the extend() method.
+ *
+ * Example:
+ *
+ * var Person = instance.web.Class.extend({
+ * init: function(isDancing){
+ * this.dancing = isDancing;
+ * },
+ * dance: function(){
+ * return this.dancing;
+ * }
+ * });
+ *
+ * The init() method act as a constructor. This class can be instancied this way:
+ *
+ * var person = new Person(true);
+ * person.dance();
+ *
+ * The Person class can also be extended again:
+ *
+ * var Ninja = Person.extend({
+ * init: function(){
+ * this._super( false );
+ * },
+ * dance: function(){
+ * // Call the inherited version of dance()
+ * return this._super();
+ * },
+ * swingSword: function(){
+ * return true;
+ * }
+ * });
+ *
+ * When extending a class, each re-defined method can use this._super() to call the previous
+ * implementation of that method.
+ */
+(function() {
+ var initializing = false,
+ fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
+ // The web Class implementation (does nothing)
+ instance.web.Class = function(){};
+
+ /**
+ * Subclass an existing class
+ *
+ * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
+ */
+ instance.web.Class.extend = function() {
var _super = this.prototype;
-
- // Instantiate a base class (but only create the instance, don't run the
- // init constructor)
- initializing = true; var prototype = new this(); initializing = false;
-
+ // Support mixins arguments
+ var args = _.toArray(arguments);
+ args.unshift({});
+ var prop = _.extend.apply(_,args);
+
+ // Instantiate a web class (but only create the instance,
+ // don't run the init constructor)
+ initializing = true;
+ var prototype = new this();
+ initializing = false;
+
// Copy the properties over onto the new prototype
for (var name in prop) {
- // Check if we're overwriting an existing function
- prototype[name] = typeof prop[name] == "function" &&
- typeof _super[name] == "function" && fnTest.test(prop[name]) ?
- (function(name, fn){
- return function() {
- var tmp = this._super;
-
- // Add a new ._super() method that is the same method but on the
- // super-class
- this._super = _super[name];
-
- // The method only need to be bound temporarily, so we remove it
- // when we're done executing
- var ret = fn.apply(this, arguments); this._super = tmp;
-
- return ret;
- };
- })(name, prop[name]) : prop[name];
+ // Check if we're overwriting an existing function
+ prototype[name] = typeof prop[name] == "function" &&
+ typeof _super[name] == "function" &&
+ fnTest.test(prop[name]) ?
+ (function(name, fn) {
+ return function() {
+ var tmp = this._super;
+
+ // Add a new ._super() method that is the same
+ // method but on the super-class
+ this._super = _super[name];
+
+ // The method only need to be bound temporarily, so
+ // we remove it when we're done executing
+ var ret = fn.apply(this, arguments);
+ this._super = tmp;
+
+ return ret;
+ };
+ })(name, prop[name]) :
+ prop[name];
}
-
+
// The dummy class constructor
function Class() {
// All construction is actually done in the init method
if (!initializing && this.init) {
- var ret = this.init.apply(this, arguments); if (ret) { return ret;}
- } return this;
+ var ret = this.init.apply(this, arguments);
+ if (ret) { return ret; }
+ }
+ return this;
}
-
Class.include = function (properties) {
for (var name in properties) {
if (typeof properties[name] !== 'function'
@@ -64,138 +138,617 @@ oe_override_class = function(claz){
&& prototype.hasOwnProperty(name)) {
prototype[name] = (function (name, fn, previous) {
return function () {
- var tmp = this._super; this._super = previous; var
- ret = fn.apply(this, arguments); this._super = tmp;
+ var tmp = this._super;
+ this._super = previous;
+ var ret = fn.apply(this, arguments);
+ this._super = tmp;
return ret;
}
})(name, properties[name], prototype[name]);
} else if (typeof _super[name] === 'function') {
prototype[name] = (function (name, fn) {
return function () {
- var tmp = this._super; this._super = _super[name];
- var ret = fn.apply(this, arguments); this._super =
- tmp; return ret;
+ var tmp = this._super;
+ this._super = _super[name];
+ var ret = fn.apply(this, arguments);
+ this._super = tmp;
+ return ret;
}
})(name, properties[name]);
}
}
};
-
+
// Populate our constructed prototype object
Class.prototype = prototype;
-
+
// Enforce the constructor to be what we expect
- Class.prototype.constructor = Class;
-
+ Class.constructor = Class;
+
// And make this class extendable
Class.extend = arguments.callee;
-
+
return Class;
};
-};
-oe_override_class(nova.Class);
-oe_override_class(nova.Widget);
+})();
-openerp.web.Class = nova.Class;
+// Mixins
-openerp.web.callback = function(obj, method) {
- var callback = function() {
- var args = Array.prototype.slice.call(arguments);
- var r;
- for(var i = 0; i < callback.callback_chain.length; i++) {
- var c = callback.callback_chain[i];
- if(c.unique) {
- callback.callback_chain.splice(i, 1);
- i -= 1;
- }
- var result = c.callback.apply(c.self, c.args.concat(args));
- if (c.callback === method) {
- // return the result of the original method
- r = result;
+/**
+ * Mixin to structure objects' life-cycles folowing a parent-children
+ * relationship. Each object can a have a parent and multiple children.
+ * When an object is destroyed, all its children are destroyed too releasing
+ * any resource they could have reserved before.
+ */
+instance.web.ParentedMixin = {
+ __parentedMixin : true,
+ init: function() {
+ this.__parentedDestroyed = false;
+ this.__parentedChildren = [];
+ this.__parentedParent = null;
+ },
+ /**
+ * Set the parent of the current object. When calling this method, the
+ * parent will also be informed and will return the current object
+ * when its getChildren() method is called. If the current object did
+ * already have a parent, it is unregistered before, which means the
+ * previous parent will not return the current object anymore when its
+ * getChildren() method is called.
+ */
+ setParent : function(parent) {
+ if (this.getParent()) {
+ if (this.getParent().__parentedMixin) {
+ this.getParent().__parentedChildren = _.without(this
+ .getParent().getChildren(), this);
}
- // TODO special value to stop the chain
- // openerp.web.callback_stop
- }
- return r;
- };
- callback.callback_chain = [];
- callback.add = function(f) {
- if(typeof(f) == 'function') {
- f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
}
- f.self = f.self || null;
- f.args = f.args || [];
- f.unique = !!f.unique;
- if(f.position == 'last') {
- callback.callback_chain.push(f);
- } else {
- callback.callback_chain.unshift(f);
+ this.__parentedParent = parent;
+ if (parent && parent.__parentedMixin) {
+ parent.__parentedChildren.push(this);
}
- return callback;
- };
- callback.add_first = function(f) {
- return callback.add.apply(null,arguments);
- };
- callback.add_last = function(f) {
- return callback.add({
- callback: f,
- args: Array.prototype.slice.call(arguments, 1),
- position: "last"
+ },
+ /**
+ * Return the current parent of the object (or null).
+ */
+ getParent : function() {
+ return this.__parentedParent;
+ },
+ /**
+ * Return a list of the children of the current object.
+ */
+ getChildren : function() {
+ return _.clone(this.__parentedChildren);
+ },
+ /**
+ * Returns true if destroy() was called on the current object.
+ */
+ isDestroyed : function() {
+ return this.__parentedDestroyed;
+ },
+ /**
+ * Inform the object it should destroy itself, releasing any
+ * resource it could have reserved.
+ */
+ destroy : function() {
+ _.each(this.getChildren(), function(el) {
+ el.destroy();
});
- };
- callback.remove = function(f) {
- callback.callback_chain = _.difference(callback.callback_chain, _.filter(callback.callback_chain, function(el) {
- return el.callback === f;
- }));
- return callback;
- };
-
- return callback.add({
- callback: method,
- self:obj,
- args:Array.prototype.slice.call(arguments, 2)
- });
+ this.setParent(undefined);
+ this.__parentedDestroyed = true;
+ }
};
/**
- * web error for lookup failure
+ * TODO al: move into the the mixin
+ *
+ * Backbone's events
+ *
+ * (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+ * Backbone may be freely distributed under the MIT license.
+ * For all details and documentation:
+ * http://backbonejs.org
+ *
+ * This class just handle the dispatching of events, it is not meant to be extended,
+ * nor used directly. All integration with parenting and automatic unregistration of
+ * events is done in EventDispatcherMixin.
*
- * @class
*/
-openerp.web.NotFound = openerp.web.Class.extend( /** @lends openerp.web.NotFound# */ {
+instance.web.Events = instance.web.Class.extend({
+
+ on : function(events, callback, context) {
+ var ev;
+ events = events.split(/\s+/);
+ var calls = this._callbacks || (this._callbacks = {});
+ while (ev = events.shift()) {
+ var list = calls[ev] || (calls[ev] = {});
+ var tail = list.tail || (list.tail = list.next = {});
+ tail.callback = callback;
+ tail.context = context;
+ list.tail = tail.next = {};
+ }
+ return this;
+ },
+
+ off : function(events, callback, context) {
+ var ev, calls, node;
+ if (!events) {
+ delete this._callbacks;
+ } else if (calls = this._callbacks) {
+ events = events.split(/\s+/);
+ while (ev = events.shift()) {
+ node = calls[ev];
+ delete calls[ev];
+ if (!callback || !node)
+ continue;
+ while ((node = node.next) && node.next) {
+ if (node.callback === callback
+ && (!context || node.context === context))
+ continue;
+ this.on(ev, node.callback, node.context);
+ }
+ }
+ }
+ return this;
+ },
+
+ trigger : function(events) {
+ var event, node, calls, tail, args, all, rest;
+ if (!(calls = this._callbacks))
+ return this;
+ all = calls['all'];
+ (events = events.split(/\s+/)).push(null);
+ // Save references to the current heads & tails.
+ while (event = events.shift()) {
+ if (all)
+ events.push({
+ next : all.next,
+ tail : all.tail,
+ event : event
+ });
+ if (!(node = calls[event]))
+ continue;
+ events.push({
+ next : node.next,
+ tail : node.tail
+ });
+ }
+ rest = Array.prototype.slice.call(arguments, 1);
+ while (node = events.pop()) {
+ tail = node.tail;
+ args = node.event ? [ node.event ].concat(rest) : rest;
+ while ((node = node.next) !== tail) {
+ node.callback.apply(node.context || this, args);
+ }
+ }
+ return this;
+ }
+});
+
+instance.web.EventDispatcherMixin = _.extend({}, instance.web.ParentedMixin, {
+ __eventDispatcherMixin: true,
+ init: function() {
+ instance.web.ParentedMixin.init.call(this);
+ this.__edispatcherEvents = new instance.web.Events();
+ this.__edispatcherRegisteredEvents = [];
+ },
+ on: function(events, dest, func) {
+ var self = this;
+ events = events.split(/\s+/);
+ _.each(events, function(eventName) {
+ self.__edispatcherEvents.on(eventName, func, dest);
+ if (dest && dest.__eventDispatcherMixin) {
+ dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
+ }
+ });
+ return this;
+ },
+ off: function(events, dest, func) {
+ var self = this;
+ events = events.split(/\s+/);
+ _.each(events, function(eventName) {
+ self.__edispatcherEvents.off(eventName, func, dest);
+ if (dest && dest.__eventDispatcherMixin) {
+ dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
+ return !(el.name === eventName && el.func === func && el.source === self);
+ });
+ }
+ });
+ return this;
+ },
+ trigger: function(events) {
+ this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
+ return this;
+ },
+ destroy: function() {
+ var self = this;
+ _.each(this.__edispatcherRegisteredEvents, function(event) {
+ event.source.__edispatcherEvents.off(event.name, event.func, self);
+ });
+ this.__edispatcherRegisteredEvents = [];
+ if(!this.__edispatcherEvents) {
+ debugger;
+ }
+ this.__edispatcherEvents.off();
+ instance.web.ParentedMixin.destroy.call(this);
+ }
+});
+
+instance.web.GetterSetterMixin = _.extend({}, instance.web.EventDispatcherMixin, {
+ init: function() {
+ instance.web.EventDispatcherMixin.init.call(this);
+ this.__getterSetterInternalMap = {};
+ },
+ set: function(map) {
+ var self = this;
+ var changed = false;
+ _.each(map, function(val, key) {
+ var tmp = self.__getterSetterInternalMap[key];
+ if (tmp === val)
+ return;
+ changed = true;
+ self.__getterSetterInternalMap[key] = val;
+ self.trigger("change:" + key, self, {
+ oldValue: tmp,
+ newValue: val
+ });
+ });
+ if (changed)
+ self.trigger("change", self);
+ },
+ get: function(key) {
+ return this.__getterSetterInternalMap[key];
+ }
+});
+
+instance.web.CallbackEnabledMixin = _.extend({}, instance.web.GetterSetterMixin, {
+ init: function() {
+ instance.web.GetterSetterMixin.init.call(this);
+ var self = this;
+ var callback_maker = function(obj, name, method) {
+ var callback = function() {
+ var args = Array.prototype.slice.call(arguments);
+ self.trigger.apply(self, [name].concat(args));
+ var r;
+ for(var i = 0; i < callback.callback_chain.length; i++) {
+ var c = callback.callback_chain[i];
+ if(c.unique) {
+ callback.callback_chain.splice(i, 1);
+ i -= 1;
+ }
+ var result = c.callback.apply(c.self, c.args.concat(args));
+ if (c.callback === method) {
+ // return the result of the original method
+ r = result;
+ }
+ // TODO special value to stop the chain
+ // instance.web.callback_stop
+ }
+ return r;
+ };
+ callback.callback_chain = [];
+ callback.add = function(f) {
+ if(typeof(f) == 'function') {
+ f = { callback: f, args: Array.prototype.slice.call(arguments, 1) };
+ }
+ f.self = f.self || null;
+ f.args = f.args || [];
+ f.unique = !!f.unique;
+ if(f.position == 'last') {
+ callback.callback_chain.push(f);
+ } else {
+ callback.callback_chain.unshift(f);
+ }
+ return callback;
+ };
+ callback.add_first = function(f) {
+ return callback.add.apply(null,arguments);
+ };
+ callback.add_last = function(f) {
+ return callback.add({
+ callback: f,
+ args: Array.prototype.slice.call(arguments, 1),
+ position: "last"
+ });
+ };
+ callback.remove = function(f) {
+ callback.callback_chain = _.difference(callback.callback_chain, _.filter(callback.callback_chain, function(el) {
+ return el.callback === f;
+ }));
+ return callback;
+ };
+
+ return callback.add({
+ callback: method,
+ self:obj,
+ args:Array.prototype.slice.call(arguments, 3)
+ });
+ };
+ // Transform on_/do_* methods into callbacks
+ for (var name in this) {
+ if(typeof(this[name]) == "function") {
+ this[name].debug_name = name;
+ if((/^on_|^do_/).test(name)) {
+ this[name] = callback_maker(this, name, this[name]);
+ }
+ }
+ }
+ },
+ /**
+ * Proxies a method of the object, in order to keep the right ``this`` on
+ * method invocations.
+ *
+ * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
+ * even more so to ``jQuery.proxy`` with a fundamental difference: its
+ * resolution of the method being called is lazy, meaning it will use the
+ * method as it is when the proxy is called, not when the proxy is created.
+ *
+ * Other methods will fix the bound method to what it is when creating the
+ * binding/proxy, which is fine in most javascript code but problematic in
+ * OpenERP Web where developers may want to replace existing callbacks with
+ * theirs.
+ *
+ * The semantics of this precisely replace closing over the method call.
+ *
+ * @param {String} method_name name of the method to invoke
+ * @returns {Function} proxied method
+ */
+ proxy: function (method_name) {
+ var self = this;
+ return function () {
+ return self[method_name].apply(self, arguments);
+ }
+ }
});
-openerp.web.KeyNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.KeyNotFound# */ {
+
+instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, {
+ /**
+ * Tag name when creating a default $element.
+ * @type string
+ */
+ tagName: 'div',
/**
- * Thrown when a key could not be found in a mapping
+ * Constructs the widget and sets its parent if a parent is given.
+ *
+ * @constructs instance.web.Widget
+ * @extends instance.web.CallbackEnabled
*
- * @constructs openerp.web.KeyNotFound
- * @extends openerp.web.NotFound
- * @param {String} key the key which could not be found
+ * @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 (key) {
- this.key = key;
+ init: function(parent) {
+ instance.web.CallbackEnabledMixin.init.call(this);
+ this.$element = $(document.createElement(this.tagName));
+ this.setParent(parent);
+ },
+ /**
+ * Destroys the current widget, also destroys all its children before destroying itself.
+ */
+ destroy: function() {
+ _.each(this.getChildren(), function(el) {
+ el.destroy();
+ });
+ if(this.$element != null) {
+ this.$element.remove();
+ }
+ instance.web.GetterSetterMixin.destroy.call(this);
+ },
+ /**
+ * Renders the current widget and appends it to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ appendTo: function(target) {
+ var self = this;
+ return this.__widgetRenderAndInsert(function(t) {
+ self.$element.appendTo(t);
+ }, target);
+ },
+ /**
+ * Renders the current widget and prepends it to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ prependTo: function(target) {
+ var self = this;
+ return this.__widgetRenderAndInsert(function(t) {
+ self.$element.prependTo(t);
+ }, target);
+ },
+ /**
+ * Renders the current widget and inserts it after to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ insertAfter: function(target) {
+ var self = this;
+ return this.__widgetRenderAndInsert(function(t) {
+ self.$element.insertAfter(t);
+ }, target);
+ },
+ /**
+ * Renders the current widget and inserts it before to the given jQuery object or Widget.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ insertBefore: function(target) {
+ var self = this;
+ return this.__widgetRenderAndInsert(function(t) {
+ self.$element.insertBefore(t);
+ }, target);
+ },
+ /**
+ * Renders the current widget and replaces the given jQuery object.
+ *
+ * @param target A jQuery object or a Widget instance.
+ */
+ replace: function(target) {
+ return this.__widgetRenderAndInsert(_.bind(function(t) {
+ this.$element.replaceAll(t);
+ }, this), target);
},
- toString: function () {
- return "The key " + this.key + " was not found";
+ __widgetRenderAndInsert: function(insertion, target) {
+ this.renderElement();
+ insertion(target);
+ return this.start();
+ },
+ /**
+ * This is the method to implement to render the Widget.
+ */
+ renderElement: function() {
+ },
+ /**
+ * Method called after rendering. Mostly used to bind actions, perform asynchronous
+ * calls, etc...
+ *
+ * By convention, the method should return a promise to inform the caller when
+ * this widget has been initialized.
+ *
+ * @returns {jQuery.Deferred}
+ */
+ start: function() {
+ }
+});
+
+// Classes
+
+instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEnabledMixin, {
+ init: function() {
+ instance.web.CallbackEnabledMixin.init.call(this);
}
});
-openerp.web.ObjectNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.ObjectNotFound# */ {
+
+/**
+ * Base class for all visual components. Provides a lot of functionalities helpful
+ * for the management of a part of the DOM.
+ *
+ * Widget handles:
+ * - Rendering with QWeb.
+ * - Life-cycle management and parenting (when a parent is destroyed, all its children are
+ * destroyed too).
+ * - Insertion in DOM.
+ *
+ * Guide to create implementations of the Widget class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = instance.base.Widget.extend({
+ * // the name of the QWeb template to use for rendering
+ * template: "MyQWebTemplate",
+ *
+ * init: function(parent) {
+ * this._super(parent);
+ * // stuff that you want to init before the rendering
+ * },
+ * start: function() {
+ * // stuff you want to make after the rendering, `this.$element` holds a correct value
+ * this.$element.find(".my_button").click(/* an example of event binding * /);
+ *
+ * // if you have some asynchronous operations, it's a good idea to return
+ * // a promise in start()
+ * var promise = this.rpc(...);
+ * return promise;
+ * }
+ * });
+ *
+ * Now this class can simply be used with the following syntax:
+ *
+ * var my_widget = new MyWidget(this);
+ * my_widget.appendTo($(".some-div"));
+ *
+ * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
+ * DOM inside the ".some-div" div and its events were binded.
+ *
+ * And of course, when you don't need that widget anymore, just do:
+ *
+ * my_widget.destroy();
+ *
+ * That will kill the widget in a clean way and erase its content from the dom.
+ */
+instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, {
/**
- * Thrown when an object path does not designate a valid class or object
- * in the openerp hierarchy.
+ * 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.
+ *
+ * @type string
+ */
+ template: null,
+ /**
+ * Constructs the widget and sets its parent if a parent is given.
*
- * @constructs openerp.web.ObjectNotFound
- * @extends openerp.web.NotFound
- * @param {String} path the invalid object path
+ * @constructs instance.web.Widget
+ * @extends instance.web.CallbackEnabled
+ *
+ * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance.
+ * When that widget is destroyed by calling destroy(), the current instance will be
+ * destroyed too. Can be null.
+ * @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);
+ this.session = instance.connection;
+ },
+ /**
+ * Renders the element. The default implementation renders the widget using QWeb,
+ * `this.template` must be defined. The context given to QWeb contains the "widget"
+ * key that references `this`.
*/
- init: function (path) {
- this.path = path;
+ 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;
+ }
},
- toString: function () {
- return "Could not find any object of path " + this.path;
+ /**
+ * Informs the action manager to do an action. This supposes that
+ * the action manager can be found amongst the ancestors of the current widget.
+ * If that's not the case this method will simply return `false`.
+ */
+ do_action: function(action, on_finished) {
+ if (this.getParent()) {
+ return this.getParent().do_action(action, on_finished);
+ }
+ return false;
+ },
+ do_notify: function() {
+ if (this.getParent()) {
+ return this.getParent().do_notify.apply(this,arguments);
+ }
+ return false;
+ },
+ do_warn: function() {
+ if (this.getParent()) {
+ return this.getParent().do_warn.apply(this,arguments);
+ }
+ return false;
+ },
+ rpc: function(url, data, success, error) {
+ var def = $.Deferred().then(success, error);
+ var self = this;
+ instance.connection.rpc(url, data). then(function() {
+ if (!self.isDestroyed())
+ def.resolve.apply(def, arguments);
+ }, function() {
+ if (!self.isDestroyed())
+ def.reject.apply(def, arguments);
+ });
+ return def.promise();
}
});
-openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry# */ {
+
+instance.web.Registry = instance.web.Class.extend({
/**
* Stores a mapping of arbitrary key (strings) to object paths (as strings
* as well).
@@ -204,11 +757,11 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
* object, even if those objects have been overloaded/replaced after the
* registry was created.
*
- * An object path is simply a dotted name from the openerp root to the
- * object pointed to (e.g. ``"openerp.web.Connection"`` for an OpenERP
+ * An object path is simply a dotted name from the instance root to the
+ * object pointed to (e.g. ``"instance.web.Connection"`` for an OpenERP
* connection object).
*
- * @constructs openerp.web.Registry
+ * @constructs instance.web.Registry
* @param {Object} mapping a mapping of keys to object-paths
*/
init: function (mapping) {
@@ -220,10 +773,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
*
* @param {String} key the key to fetch the object for
* @param {Boolean} [silent_error=false] returns undefined if the key or object is not found, rather than throwing an exception
- * @returns {Class} the stored class, to initialize
- *
- * @throws {openerp.web.KeyNotFound} if the object was not in the mapping
- * @throws {openerp.web.ObjectNotFound} if the object path was invalid
+ * @returns {Class} the stored class, to initialize or null if not found
*/
get_object: function (key, silent_error) {
var path_string = this.map[key];
@@ -232,10 +782,10 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
return this.parent.get_object(key, silent_error);
}
if (silent_error) { return void 'nooo'; }
- throw new openerp.web.KeyNotFound(key);
+ return null;
}
- var object_match = openerp;
+ var object_match = instance;
var path = path_string.split('.');
// ignore first section
for(var i=1; i<path.length; ++i) {
@@ -243,7 +793,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
if (object_match === undefined) {
if (silent_error) { return void 'noooooo'; }
- throw new openerp.web.ObjectNotFound(path_string);
+ return null;
}
}
return object_match;
@@ -269,9 +819,6 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
*
* @param {Array} keys a sequence of keys to fetch the object for
* @returns {Class} the first class found matching an object
- *
- * @throws {openerp.web.KeyNotFound} if none of the keys was in the mapping
- * @trows {openerp.web.ObjectNotFound} if a found object path was invalid
*/
get_any: function (keys) {
for (var i=0; i<keys.length; ++i) {
@@ -282,7 +829,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
return this.get_object(key);
}
- throw new openerp.web.KeyNotFound(keys.join(','));
+ return null;
},
/**
* Adds a new key and value to the registry.
@@ -291,7 +838,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
*
* @param {String} key
* @param {String} object_path fully qualified dotted object path
- * @returns {openerp.web.Registry} itself
+ * @returns {instance.web.Registry} itself
*/
add: function (key, object_path) {
this.map[key] = object_path;
@@ -307,7 +854,7 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
* @param {Object} [mapping={}] a mapping of keys to object-paths
*/
extend: function (mapping) {
- var child = new openerp.web.Registry(mapping);
+ var child = new instance.web.Registry(mapping);
child.parent = this;
return child;
},
@@ -320,57 +867,10 @@ openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry
}
});
-openerp.web.CallbackEnabledMixin = {
- init: function() {
- // Transform on_* method into openerp.web.callbacks
- for (var name in this) {
- if(typeof(this[name]) == "function") {
- this[name].debug_name = name;
- // bind ALL function to this not only on_and _do ?
- if((/^on_|^do_/).test(name)) {
- this[name] = openerp.web.callback(this, this[name]);
- }
- }
- }
- },
- /**
- * Proxies a method of the object, in order to keep the right ``this`` on
- * method invocations.
- *
- * This method is similar to ``Function.prototype.bind`` or ``_.bind``, and
- * even more so to ``jQuery.proxy`` with a fundamental difference: its
- * resolution of the method being called is lazy, meaning it will use the
- * method as it is when the proxy is called, not when the proxy is created.
- *
- * Other methods will fix the bound method to what it is when creating the
- * binding/proxy, which is fine in most javascript code but problematic in
- * OpenERP Web where developers may want to replace existing callbacks with
- * theirs.
- *
- * The semantics of this precisely replace closing over the method call.
- *
- * @param {String} method_name name of the method to invoke
- * @returns {Function} proxied method
- */
- proxy: function (method_name) {
- var self = this;
- return function () {
- return self[method_name].apply(self, arguments);
- }
- }
-};
-
-openerp.web.CallbackEnabled = openerp.web.Class.extend(_.extend({}, nova.GetterSetterMixin, openerp.web.CallbackEnabledMixin, {
- init: function() {
- nova.GetterSetterMixin.init.call(this);
- openerp.web.CallbackEnabledMixin.init.call(this);
- }
-}));
-
-openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.web.Connection# */{
+instance.web.Connection = instance.web.CallbackEnabled.extend( /** @lends instance.web.Connection# */{
/**
- * @constructs openerp.web.Connection
- * @extends openerp.web.CallbackEnabled
+ * @constructs instance.web.Connection
+ * @extends instance.web.CallbackEnabled
*
* @param {String} [server] JSON-RPC endpoint hostname
* @param {String} [port] JSON-RPC endpoint port
@@ -380,7 +880,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
this.server = null;
this.debug = ($.deparam($.param.querystring()).debug != undefined);
// TODO: session store in cookie should be optional
- this.name = openerp._session_id;
+ this.name = instance._session_id;
this.qweb_mutex = new $.Mutex();
},
session_bind: function(origin) {
@@ -388,7 +888,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
this.origin = origin ? _.str.rtrim(origin,'/') : window_origin;
this.prefix = this.origin;
this.server = this.origin; // keep chs happy
- openerp.web.qweb.default_dict['_s'] = this.origin;
+ instance.web.qweb.default_dict['_s'] = this.origin;
this.rpc_function = (this.origin == window_origin) ? this.rpc_json : this.rpc_jsonp;
this.session_id = false;
this.uid = false;
@@ -396,7 +896,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
this.user_context= {};
this.db = false;
this.openerp_entreprise = false;
- this.module_list = openerp._modules.slice();
+ this.module_list = instance._modules.slice();
this.module_loaded = {};
_(this.module_list).each(function (mod) {
self.module_loaded[mod] = true;
@@ -569,7 +1069,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
try {
var ctx = this.test_eval_contexts(source.contexts);
if (!_.isEqual(ctx, expected.context)) {
- openerp.webclient.notification.warn('Context mismatch, report to xmo',
+ instance.webclient.notification.warn('Context mismatch, report to xmo',
_.str.sprintf(match_template, {
source: JSON.stringify(source.contexts),
local: JSON.stringify(ctx),
@@ -577,17 +1077,17 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
}), true);
}
} catch (e) {
- openerp.webclient.notification.warn('Context fail, report to xmo',
+ instance.webclient.notification.warn('Context fail, report to xmo',
_.str.sprintf(fail_template, {
error: e.message,
- source: source.contexts
+ source: JSON.stringify(source.contexts)
}), true);
}
try {
var dom = this.test_eval_domains(source.domains, this.test_eval_get_context());
if (!_.isEqual(dom, expected.domain)) {
- openerp.webclient.notification.warn('Domains mismatch, report to xmo',
+ instance.webclient.notification.warn('Domains mismatch, report to xmo',
_.str.sprintf(match_template, {
source: JSON.stringify(source.domains),
local: JSON.stringify(dom),
@@ -595,17 +1095,17 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
}), true);
}
} catch (e) {
- openerp.webclient.notification.warn('Domain fail, report to xmo',
+ instance.webclient.notification.warn('Domain fail, report to xmo',
_.str.sprintf(fail_template, {
error: e.message,
- source: source.domains
+ source: JSON.stringify(source.domains)
}), true);
}
try {
var groups = this.test_eval_groupby(source.group_by_seq);
if (!_.isEqual(groups, expected.group_by)) {
- openerp.webclient.notification.warn('GroupBy mismatch, report to xmo',
+ instance.webclient.notification.warn('GroupBy mismatch, report to xmo',
_.str.sprintf(match_template, {
source: JSON.stringify(source.group_by_seq),
local: JSON.stringify(groups),
@@ -613,43 +1113,51 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
}), true);
}
} catch (e) {
- openerp.webclient.notification.warn('GroupBy fail, report to xmo',
+ instance.webclient.notification.warn('GroupBy fail, report to xmo',
_.str.sprintf(fail_template, {
error: e.message,
- source: source.group_by_seq
+ source: JSON.stringify(source.group_by_seq)
}), true);
}
},
- test_eval_contexts: function (contexts) {
- var result_context = _.extend({}, this.user_context),
- self = this;
- _(contexts).each(function (ctx) {
+ test_eval_contexts: function (contexts, evaluation_context) {
+ evaluation_context = evaluation_context || {};
+ var self = this;
+ return _(contexts).reduce(function (result_context, ctx) {
+ // __eval_context evaluations can lead to some of `contexts`'s
+ // values being null, skip them as well as empty contexts
+ if (_.isEmpty(ctx)) { return result_context; }
+ var evaluated = ctx;
switch(ctx.__ref) {
case 'context':
- _.extend(result_context, py.eval(ctx.__debug));
+ evaluated = py.eval(ctx.__debug, evaluation_context);
break;
case 'compound_context':
- _.extend(
- result_context, self.test_eval_contexts(ctx.__contexts));
+ var eval_context = self.test_eval_contexts([ctx.__eval_context]);
+ evaluated = self.test_eval_contexts(
+ ctx.__contexts, _.extend({}, evaluation_context, eval_context));
break;
- default:
- _.extend(result_context, ctx);
}
- });
- return result_context;
+ // add newly evaluated context to evaluation context for following
+ // siblings
+ _.extend(evaluation_context, evaluated);
+ return _.extend(result_context, evaluated);
+ }, _.extend({}, this.user_context));
},
- test_eval_domains: function (domains, eval_context) {
+ test_eval_domains: function (domains, evaluation_context) {
var result_domain = [], self = this;
_(domains).each(function (dom) {
switch(dom.__ref) {
case 'domain':
result_domain.push.apply(
- result_domain, py.eval(dom.__debug, eval_context));
+ result_domain, py.eval(dom.__debug, evaluation_context));
break;
case 'compound_domain':
+ var eval_context = self.test_eval_contexts([dom.__eval_context]);
result_domain.push.apply(
result_domain, self.test_eval_domains(
- dom.__domains, eval_context));
+ dom.__domains, _.extend(
+ {}, evaluation_context, eval_context)));
break;
default:
result_domain.push.apply(
@@ -667,7 +1175,8 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
group = py.eval(ctx.__debug).group_by;
break;
case 'compound_context':
- group = self.test_eval_contexts(ctx.__contexts).group_by;
+ group = self.test_eval_contexts(
+ ctx.__contexts, ctx.__eval_context).group_by;
break;
default:
group = ctx.group_by
@@ -691,7 +1200,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
* setting the correct session id and session context in the parameter
* objects
*
- * @param {Object} url RPC endpoint
+ * @param {String} url RPC endpoint
* @param {Object} params call parameters
* @param {Function} success_callback function to execute on RPC call success
* @param {Function} error_callback function to execute on RPC call failure
@@ -715,7 +1224,9 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
};
var deferred = $.Deferred();
this.on_rpc_request();
- this.rpc_function(url, payload).then(
+ var aborter = params.aborter;
+ delete params.aborter;
+ var request = this.rpc_function(url, payload).then(
function (response, textStatus, jqXHR) {
self.on_rpc_response();
if (!response.error) {
@@ -744,6 +1255,16 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
};
deferred.reject(error, $.Event());
});
+ if (aborter) {
+ aborter.abort_last = function () {
+ if (!(request.isResolved() || request.isRejected())) {
+ deferred.fail(function (error, event) {
+ event.preventDefault();
+ });
+ request.abort();
+ }
+ };
+ }
// Allow deferred user to disable on_rpc_error in fail
deferred.fail(function() {
deferred.fail(function(error, event) {
@@ -848,7 +1369,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
// TODO: session store in cookie should be optional
this.session_id = this.get_cookie('session_id');
return this.session_reload().pipe(function(result) {
- var modules = openerp._modules.join(',');
+ var modules = instance._modules.join(',');
var deferred = self.rpc('/web/webclient/qweblist', {mods: modules}).pipe(self.do_load_qweb);
if(self.session_is_valid()) {
return deferred.pipe(function() { return self.load_modules(); });
@@ -971,7 +1492,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
self.rpc('/web/webclient/csslist', {mods: to_load}, self.do_load_css),
self.rpc('/web/webclient/qweblist', {mods: to_load}).pipe(self.do_load_qweb),
self.rpc('/web/webclient/translations', params).pipe(function(trans) {
- openerp.web._t.database.set_bundle(trans);
+ instance.web._t.database.set_bundle(trans);
var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"];
return self.rpc('/web/webclient/jslist', {mods: to_load}).pipe(function(files) {
return self.do_load_js(file_list.concat(files));
@@ -1033,7 +1554,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
self.qweb_mutex.exec(function() {
return self.rpc('/web/proxy/load', {path: file}).pipe(function(xml) {
if (!xml) { return; }
- openerp.web.qweb.add_template(_.str.trim(xml));
+ instance.web.qweb.add_template(_.str.trim(xml));
});
});
});
@@ -1044,10 +1565,10 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
var mod = this.module_list[j];
if(this.module_loaded[mod])
continue;
- openerp[mod] = {};
+ instance[mod] = {};
// init module mod
- if(openerp._openerp[mod] != undefined) {
- openerp._openerp[mod](openerp);
+ if(instance._openerp[mod] != undefined) {
+ instance._openerp[mod](instance);
this.module_loaded[mod] = true;
}
}
@@ -1167,164 +1688,10 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
}
});
-/**
- * Base class for all visual components. Provides a lot of functionalities helpful
- * for the management of a part of the DOM.
- *
- * Widget handles:
- * - Rendering with QWeb.
- * - Life-cycle management and parenting (when a parent is destroyed, all its children are
- * destroyed too).
- * - Insertion in DOM.
- *
- * Guide to create implementations of the Widget class:
- * ==============================================
- *
- * Here is a sample child class:
- *
- * MyWidget = openerp.base.Widget.extend({
- * // the name of the QWeb template to use for rendering
- * template: "MyQWebTemplate",
- *
- * init: function(parent) {
- * this._super(parent);
- * // stuff that you want to init before the rendering
- * },
- * start: function() {
- * // stuff you want to make after the rendering, `this.$element` holds a correct value
- * this.$element.find(".my_button").click(/* an example of event binding * /);
- *
- * // if you have some asynchronous operations, it's a good idea to return
- * // a promise in start()
- * var promise = this.rpc(...);
- * return promise;
- * }
- * });
- *
- * Now this class can simply be used with the following syntax:
- *
- * var my_widget = new MyWidget(this);
- * my_widget.appendTo($(".some-div"));
- *
- * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the
- * DOM inside the ".some-div" div and its events were binded.
- *
- * And of course, when you don't need that widget anymore, just do:
- *
- * my_widget.destroy();
- *
- * That will kill the widget in a clean way and erase its content from the dom.
- */
-openerp.web.Widget = nova.Widget.extend(_.extend({}, openerp.web.CallbackEnabledMixin, {
- /**
- * 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.
- *
- * @type string
- */
- template: null,
- /**
- * Constructs the widget and sets its parent if a parent is given.
- *
- * @constructs openerp.web.Widget
- * @extends openerp.web.CallbackEnabled
- *
- * @param {openerp.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) {
- this._super(parent);
- openerp.web.CallbackEnabledMixin.init.call(this);
- this.session = openerp.connection;
- },
- /**
- * Renders the element. The default implementation renders the widget using QWeb,
- * `this.template` must be defined. The context given to QWeb contains the "widget"
- * key that references `this`.
- */
- renderElement: function() {
- var rendered = null;
- if (this.template)
- rendered = openerp.web.qweb.render(this.template, {widget: this});
- if (_.str.trim(rendered)) {
- var elem = $(rendered);
- this.$element.replaceWith(elem);
- this.$element = elem;
- }
- },
- /**
- * Informs the action manager to do an action. This supposes that
- * the action manager can be found amongst the ancestors of the current widget.
- * If that's not the case this method will simply return `false`.
- */
- do_action: function(action, on_finished) {
- if (this.getParent()) {
- return this.getParent().do_action(action, on_finished);
- }
- return false;
- },
- do_notify: function() {
- if (this.getParent()) {
- return this.getParent().do_notify.apply(this,arguments);
- }
- return false;
- },
- do_warn: function() {
- if (this.getParent()) {
- return this.getParent().do_warn.apply(this,arguments);
- }
- return false;
- },
- rpc: function(url, data, success, error) {
- var def = $.Deferred().then(success, error);
- var self = this;
- openerp.connection.rpc(url, data). then(function() {
- if (!self.isDestroyed())
- def.resolve.apply(def, arguments);
- }, function() {
- if (!self.isDestroyed())
- def.reject.apply(def, arguments);
- });
- return def.promise();
- }
-}));
-
-/**
- * @deprecated use :class:`openerp.web.Widget`
- */
-openerp.web.OldWidget = openerp.web.Widget.extend({
- init: function(parent, element_id) {
- 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));
- },
- renderElement: function() {
- var rendered = this.render();
- if (rendered) {
- var elem = $(rendered);
- this.$element.replaceWith(elem);
- this.$element = elem;
- }
- return this;
- },
- render: function (additional) {
- if (this.template)
- return openerp.web.qweb.render(this.template, _.extend({widget: this}, additional || {}));
- return null;
- }
-});
-
-openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.web.TranslationDataBase# */{
+instance.web.TranslationDataBase = instance.web.Class.extend(/** @lends instance.web.TranslationDataBase# */{
/**
- * @constructs openerp.web.TranslationDataBase
- * @extends openerp.web.Class
+ * @constructs instance.web.TranslationDataBase
+ * @extends instance.web.Class
*/
init: function() {
this.db = {};
@@ -1374,138 +1741,33 @@ openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.we
}
});
-/** Configure blockui */
-if ($.blockUI) {
- $.blockUI.defaults.baseZ = 1100;
- $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">';
-}
-
-/** Configure default qweb */
-openerp.web._t = new openerp.web.TranslationDataBase().build_translation_function();
-/**
- * Lazy translation function, only performs the translation when actually
- * printed (e.g. inserted into a template)
- *
- * Useful when defining translatable strings in code evaluated before the
- * translation database is loaded, as class attributes or at the top-level of
- * an OpenERP Web module
- *
- * @param {String} s string to translate
- * @returns {Object} lazy translation object
- */
-openerp.web._lt = function (s) {
- return {toString: function () { return openerp.web._t(s); }}
-};
-openerp.web.qweb = new QWeb2.Engine();
-openerp.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined);
-openerp.web.qweb.default_dict = {
- '_' : _,
- '_t' : openerp.web._t
-};
-openerp.web.qweb.preprocess_node = function() {
- // Note that 'this' is the Qweb Node
- switch (this.node.nodeType) {
- case 3:
- case 4:
- // Text and CDATAs
- var translation = this.node.parentNode.attributes['t-translation'];
- if (translation && translation.value === 'off') {
- return;
- }
- var ts = _.str.trim(this.node.data);
- if (ts.length === 0) {
- return;
- }
- var tr = openerp.web._t(ts);
- if (tr !== ts) {
- this.node.data = tr;
- }
- break;
- case 1:
- // Element
- var attr, attrs = ['label', 'title', 'alt'];
- while (attr = attrs.pop()) {
- if (this.attributes[attr]) {
- this.attributes[attr] = openerp.web._t(this.attributes[attr]);
- }
- }
- }
-};
-
/**
- * A small utility function to check if a class implements correctly an interface, assuming that
- * interface is simply specified using a dictionary containing methods and attributes with the
- * correct type. It only performs the check when in debug mode and the only effect of an invalid
- * check is messages in the console.
+ * @deprecated use :class:`instance.web.Widget`
*/
-openerp.web.check_interface = function(_class, _interface) {
- if (! openerp.web.check_interface.debug)
- return;
- for (var member in _interface) {
- if ( (typeof _class.prototype[member] != typeof _interface[member]) ) {
- console.error("class failed to implement interface member '" + member + "'");
+instance.web.OldWidget = instance.web.Widget.extend({
+ init: function(parent, element_id) {
+ 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));
+ },
+ renderElement: function() {
+ var rendered = this.render();
+ if (rendered) {
+ var elem = $(rendered);
+ this.$element.replaceWith(elem);
+ this.$element = elem;
}
+ return this;
+ },
+ render: function (additional) {
+ if (this.template)
+ return instance.web.qweb.render(this.template, _.extend({widget: this}, additional || {}));
+ return null;
}
-}
-openerp.web.check_interface.debug = ($.deparam($.param.querystring()).debug != undefined);
-
-/** Jquery extentions */
-$.Mutex = (function() {
- function Mutex() {
- this.def = $.Deferred().resolve();
- }
- Mutex.prototype.exec = function(action) {
- var current = this.def;
- var next = this.def = $.Deferred();
- return current.pipe(function() {
- return $.when(action()).always(function() {
- next.resolve();
- });
- });
- };
- return Mutex;
-})();
-
-/** Setup default connection */
-openerp.connection = new openerp.web.Connection();
-openerp.web.qweb.default_dict['__debug__'] = openerp.connection.debug;
-
-
-$.async_when = function() {
- var async = false;
- var def = $.Deferred();
- $.when.apply($, arguments).then(function() {
- var args = arguments;
- var action = function() {
- def.resolve.apply(def, args);
- };
- if (async)
- action();
- else
- setTimeout(action, 0);
- }, function() {
- var args = arguments;
- var action = function() {
- def.reject.apply(def, args);
- };
- if (async)
- action();
- else
- setTimeout(action, 0);
- });
- async = true;
- return def;
-};
-
-// special tweak for the web client
-var old_async_when = $.async_when;
-$.async_when = function() {
- if (openerp.connection.synch)
- return $.when.apply(this, arguments);
- else
- return old_async_when.apply(this, arguments);
-};
+});
-};
+}
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:
diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js
new file mode 100644
index 00000000000..86040302668
--- /dev/null
+++ b/addons/web/static/src/js/coresetup.js
@@ -0,0 +1,142 @@
+/*---------------------------------------------------------
+ * OpenERP Web core
+ *--------------------------------------------------------*/
+var console;
+if (!console) {
+ console = {log: function () {}};
+}
+if (!console.debug) {
+ console.debug = console.log;
+}
+
+openerp.web.coresetup = function(instance) {
+
+/** Configure default qweb */
+instance.web._t = new instance.web.TranslationDataBase().build_translation_function();
+/**
+ * Lazy translation function, only performs the translation when actually
+ * printed (e.g. inserted into a template)
+ *
+ * Useful when defining translatable strings in code evaluated before the
+ * translation database is loaded, as class attributes or at the top-level of
+ * an OpenERP Web module
+ *
+ * @param {String} s string to translate
+ * @returns {Object} lazy translation object
+ */
+instance.web._lt = function (s) {
+ return {toString: function () { return instance.web._t(s); }}
+};
+instance.web.qweb = new QWeb2.Engine();
+instance.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined);
+instance.web.qweb.default_dict = {
+ '_' : _,
+ '_t' : instance.web._t
+};
+instance.web.qweb.preprocess_node = function() {
+ // Note that 'this' is the Qweb Node
+ switch (this.node.nodeType) {
+ case 3:
+ case 4:
+ // Text and CDATAs
+ var translation = this.node.parentNode.attributes['t-translation'];
+ if (translation && translation.value === 'off') {
+ return;
+ }
+ var ts = _.str.trim(this.node.data);
+ if (ts.length === 0) {
+ return;
+ }
+ var tr = instance.web._t(ts);
+ if (tr !== ts) {
+ this.node.data = tr;
+ }
+ break;
+ case 1:
+ // Element
+ var attr, attrs = ['label', 'title', 'alt'];
+ while (attr = attrs.pop()) {
+ if (this.attributes[attr]) {
+ this.attributes[attr] = instance.web._t(this.attributes[attr]);
+ }
+ }
+ }
+};
+
+/** Configure blockui */
+if ($.blockUI) {
+ $.blockUI.defaults.baseZ = 1100;
+ $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">';
+}
+
+/** Custom jQuery plugins */
+$.fn.getAttributes = function() {
+ var o = {};
+ if (this.length) {
+ for (var attr, i = 0, attrs = this[0].attributes, l = attrs.length; i < l; i++) {
+ attr = attrs.item(i)
+ o[attr.nodeName] = attr.nodeValue;
+ }
+ }
+ return o;
+}
+
+/** Jquery extentions */
+$.Mutex = (function() {
+ function Mutex() {
+ this.def = $.Deferred().resolve();
+ }
+ Mutex.prototype.exec = function(action) {
+ var current = this.def;
+ var next = this.def = $.Deferred();
+ return current.pipe(function() {
+ return $.when(action()).always(function() {
+ next.resolve();
+ });
+ });
+ };
+ return Mutex;
+})();
+
+/** Setup default connection */
+instance.connection = new instance.web.Connection();
+instance.web.qweb.default_dict['__debug__'] = instance.connection.debug;
+
+$.async_when = function() {
+ var async = false;
+ var def = $.Deferred();
+ $.when.apply($, arguments).then(function() {
+ var args = arguments;
+ var action = function() {
+ def.resolve.apply(def, args);
+ };
+ if (async)
+ action();
+ else
+ setTimeout(action, 0);
+ }, function() {
+ var args = arguments;
+ var action = function() {
+ def.reject.apply(def, args);
+ };
+ if (async)
+ action();
+ else
+ setTimeout(action, 0);
+ });
+ async = true;
+ return def;
+};
+
+// 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);
+};
+
+};
+
+// 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 a29a9c5ad36..2dc66aaa135 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -1,5 +1,5 @@
-openerp.web.data = function(openerp) {
+openerp.web.data = function(instance) {
/**
* Serializes the sort criterion array of a dataset into a form which can be
@@ -8,7 +8,7 @@ openerp.web.data = function(openerp) {
* @param {Array} criterion array of fields, from first to last criteria, prefixed with '-' for reverse sorting
* @returns {String} SQL-like sorting string (``ORDER BY``) clause
*/
-openerp.web.serialize_sort = function (criterion) {
+instance.web.serialize_sort = function (criterion) {
return _.map(criterion,
function (criteria) {
if (criteria[0] === '-') {
@@ -18,7 +18,7 @@ openerp.web.serialize_sort = function (criterion) {
}).join(', ');
};
-openerp.web.Query = openerp.web.Class.extend({
+instance.web.Query = instance.web.Class.extend({
init: function (model, fields) {
this._model = model;
this._fields = fields;
@@ -30,7 +30,7 @@ openerp.web.Query = openerp.web.Class.extend({
},
clone: function (to_set) {
to_set = to_set || {};
- var q = new openerp.web.Query(this._model, this._fields);
+ var q = new instance.web.Query(this._model, this._fields);
q._context = this._context;
q._filter = this._filter;
q._limit = this._limit;
@@ -41,11 +41,11 @@ openerp.web.Query = openerp.web.Class.extend({
if (!to_set.hasOwnProperty(key)) { continue; }
switch(key) {
case 'filter':
- q._filter = new openerp.web.CompoundDomain(
+ q._filter = new instance.web.CompoundDomain(
q._filter, to_set.filter);
break;
case 'context':
- q._context = new openerp.web.CompoundContext(
+ q._context = new instance.web.CompoundContext(
q._context, to_set.context);
break;
case 'limit':
@@ -58,14 +58,14 @@ openerp.web.Query = openerp.web.Class.extend({
},
_execute: function () {
var self = this;
- return openerp.connection.rpc('/web/dataset/search_read', {
+ return instance.connection.rpc('/web/dataset/search_read', {
model: this._model.name,
fields: this._fields || false,
domain: this._model.domain(this._filter),
context: this._model.context(this._context),
offset: this._offset,
limit: this._limit,
- sort: openerp.web.serialize_sort(this._order_by)
+ sort: instance.web.serialize_sort(this._order_by)
}).pipe(function (results) {
self._count = results.length;
return results.records;
@@ -127,10 +127,10 @@ openerp.web.Query = openerp.web.Class.extend({
context: this._model.context(this._context),
offset: this._offset,
limit: this._limit,
- orderby: openerp.web.serialize_sort(this._order_by) || false
+ orderby: instance.web.serialize_sort(this._order_by) || false
}).pipe(function (results) {
return _(results).map(function (result) {
- return new openerp.web.data.Group(
+ return new instance.web.data.Group(
self._model.name, grouping[0], result);
});
});
@@ -194,10 +194,10 @@ openerp.web.Query = openerp.web.Class.extend({
}
});
-openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
+instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{
/**
- * @constructs openerp.web.Model
- * @extends openerp.web.Class
+ * @constructs instance.web.Model
+ * @extends instance.web.Class
*
* @param {String} model_name name of the OpenERP model this object is bound to
* @param {Object} [context]
@@ -233,7 +233,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
kwargs = args;
args = [];
}
- return openerp.connection.rpc('/web/dataset/call_kw', {
+ return instance.connection.rpc('/web/dataset/call_kw', {
model: this.name,
method: method,
args: args,
@@ -247,7 +247,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
* @returns {openerp.web.Query}
*/
query: function (fields) {
- return new openerp.web.Query(this, fields);
+ return new instance.web.Query(this, fields);
},
/**
* Executes a signal on the designated workflow, on the bound OpenERP model
@@ -256,7 +256,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
* @param {String} signal signal to trigger on the workflow
*/
exec_workflow: function (id, signal) {
- return openerp.connection.rpc('/web/dataset/exec_workflow', {
+ return instance.connection.rpc('/web/dataset/exec_workflow', {
model: this.name,
id: id,
signal: signal
@@ -270,7 +270,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
*/
domain: function (domain) {
if (!domain) { return this._domain; }
- return new openerp.web.CompoundDomain(
+ return new instance.web.CompoundDomain(
this._domain, domain);
},
/**
@@ -281,8 +281,8 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
* @returns The union of the user's context and the model's internal context, as well as the provided context if any. In that order.
*/
context: function (context) {
- return new openerp.web.CompoundContext(
- openerp.connection.user_context, this._context, context || {});
+ return new instance.web.CompoundContext(
+ instance.connection.user_context, this._context, context || {});
},
/**
* Button action caller, needs to perform cleanup if an action is returned
@@ -292,7 +292,7 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
* FIXME: remove when evaluator integrated
*/
call_button: function (method, args) {
- return openerp.connection.rpc('/web/dataset/call_button', {
+ return instance.connection.rpc('/web/dataset/call_button', {
model: this.name,
method: method,
domain_id: null,
@@ -302,12 +302,12 @@ openerp.web.Model = openerp.web.Class.extend(/** @lends openerp.web.Model# */{
},
});
-openerp.web.Traverser = openerp.web.Class.extend(/** @lends openerp.web.Traverser# */{
+instance.web.Traverser = instance.web.Class.extend(/** @lends openerp.web.Traverser# */{
/**
- * @constructs openerp.web.Traverser
- * @extends openerp.web.Class
+ * @constructs instance.web.Traverser
+ * @extends instance.web.Class
*
- * @param {openerp.web.Model} model instance this traverser is bound to
+ * @param {instance.web.Model} model instance this traverser is bound to
*/
init: function (model) {
this._model = model;
@@ -387,11 +387,11 @@ openerp.web.Traverser = openerp.web.Class.extend(/** @lends openerp.web.Traverse
*
* @namespace
*/
-openerp.web.data = {
- Group: openerp.web.Class.extend(/** @lends openerp.web.data.Group# */{
+instance.web.data = {
+ Group: instance.web.Class.extend(/** @lends openerp.web.data.Group# */{
/**
- * @constructs openerp.web.data.Group
- * @extends openerp.web.Class
+ * @constructs instance.web.data.Group
+ * @extends instance.web.Class
*/
init: function (model, grouping_field, read_group_group) {
// In cases where group_by_no_leaf and no group_by, the result of
@@ -411,7 +411,7 @@ openerp.web.data = {
aggregates[key] = value || 0;
});
- this.model = new openerp.web.Model(
+ this.model = new instance.web.Model(
model, fixed_group.__context, fixed_group.__domain);
var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0;
@@ -439,7 +439,7 @@ openerp.web.data = {
})
};
-openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{
+instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{
/**
* Management interface between views and grouped collections of OpenERP
* records.
@@ -447,13 +447,13 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da
* The root DataGroup is instantiated with the relevant information
* (a session, a model, a domain, a context and a group_by sequence), the
* domain and context may be empty. It is then interacted with via
- * :js:func:`~openerp.web.DataGroup.list`, which is used to read the
+ * :js:func:`~instance.web.DataGroup.list`, which is used to read the
* content of the current grouping level.
*
- * @constructs openerp.web.DataGroup
- * @extends openerp.web.OldWidget
+ * @constructs instance.web.DataGroup
+ * @extends instance.web.OldWidget
*
- * @param {openerp.web.OldWidget} parent widget
+ * @param {instance.web.OldWidget} parent widget
* @param {String} model name of the model managed by this DataGroup
* @param {Array} domain search domain for this DataGroup
* @param {Object} context context of the DataGroup's searches
@@ -462,7 +462,7 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da
*/
init: function(parent, model, domain, context, group_by, level) {
this._super(parent, null);
- this.model = new openerp.web.Model(model, context, domain);
+ this.model = new instance.web.Model(model, context, domain);
this.group_by = group_by;
this.context = context;
this.domain = domain;
@@ -476,7 +476,7 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da
.group_by(this.group_by)).then(function (groups) {
if (!groups) {
ifRecords(_.extend(
- new openerp.web.DataSetSearch(
+ new instance.web.DataSetSearch(
self, self.model.name,
self.model.context(),
self.model.domain()),
@@ -487,7 +487,7 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da
var child_context = _.extend(
{}, self.model.context(), group.model.context());
return _.extend(
- new openerp.web.DataGroup(
+ new instance.web.DataGroup(
self, self.model.name, group.model.domain(),
child_context, group.model._context.group_by,
self.level + 1),
@@ -504,16 +504,16 @@ openerp.web.DataGroup = openerp.web.OldWidget.extend( /** @lends openerp.web.Da
});
}
});
-openerp.web.ContainerDataGroup = openerp.web.DataGroup.extend({ });
-openerp.web.GrouplessDataGroup = openerp.web.DataGroup.extend({ });
+instance.web.ContainerDataGroup = instance.web.DataGroup.extend({ });
+instance.web.GrouplessDataGroup = instance.web.DataGroup.extend({ });
-openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ {
+instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ {
/**
* A specialization of groupless data groups, relying on a single static
* dataset as its records provider.
*
- * @constructs openerp.web.StaticDataGroup
- * @extends openerp.web.GrouplessDataGroup
+ * @constructs instance.web.StaticDataGroup
+ * @extends instance.web.GrouplessDataGroup
* @param {openep.web.DataSetStatic} dataset a static dataset backing the groups
*/
init: function (dataset) {
@@ -524,13 +524,13 @@ openerp.web.StaticDataGroup = openerp.web.GrouplessDataGroup.extend( /** @lends
}
});
-openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{
+instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{
/**
* DateaManagement interface between views and the collection of selected
* OpenERP records (represents the view's state?)
*
- * @constructs openerp.web.DataSet
- * @extends openerp.web.OldWidget
+ * @constructs instance.web.DataSet
+ * @extends instance.web.OldWidget
*
* @param {String} model the OpenERP model this dataset will manage
*/
@@ -540,7 +540,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
this.context = context || {};
this.index = null;
this._sort = [];
- this._model = new openerp.web.Model(model, context);
+ this._model = new instance.web.Model(model, context);
},
previous: function () {
this.index -= 1;
@@ -789,7 +789,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
*/
sort: function (field, force_reverse) {
if (!field) {
- return openerp.web.serialize_sort(this._sort);
+ return instance.web.serialize_sort(this._sort);
}
var reverse = force_reverse || (this._sort[0] === field);
this._sort.splice.apply(
@@ -806,7 +806,7 @@ openerp.web.DataSet = openerp.web.OldWidget.extend( /** @lends openerp.web.Data
this.ids = n_ids;
},
});
-openerp.web.DataSetStatic = openerp.web.DataSet.extend({
+instance.web.DataSetStatic = instance.web.DataSet.extend({
init: function(parent, model, context, ids) {
this._super(parent, model, context);
// all local records
@@ -836,10 +836,10 @@ openerp.web.DataSetStatic = openerp.web.DataSet.extend({
this.set_ids(_.without.apply(null, [this.ids].concat(ids)));
}
});
-openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{
+instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{
/**
- * @constructs openerp.web.DataSetSearch
- * @extends openerp.web.DataSet
+ * @constructs instance.web.DataSetSearch
+ * @extends instance.web.DataSet
*
* @param {Object} parent
* @param {String} model
@@ -851,7 +851,7 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
this.domain = domain || [];
this._length = null;
this.ids = [];
- this._model = new openerp.web.Model(model, context, domain);
+ this._model = new instance.web.Model(model, context, domain);
},
/**
* Read a slice of the records represented by this DataSet, based on its
@@ -906,7 +906,7 @@ openerp.web.DataSetSearch = openerp.web.DataSet.extend(/** @lends openerp.web.D
return this._super();
}
});
-openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
+instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({
virtual_id_prefix: "one2many_v_id_",
debug_mode: true,
init: function() {
@@ -1071,9 +1071,9 @@ openerp.web.BufferedDataSet = openerp.web.DataSetStatic.extend({
this.on_change();
},
});
-openerp.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
+instance.web.BufferedDataSet.virtual_id_regex = /^one2many_v_id_.*$/;
-openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({
+instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({
init: function() {
this._super.apply(this, arguments);
this.create_function = null;
@@ -1119,7 +1119,7 @@ openerp.web.ProxyDataSet = openerp.web.DataSetSearch.extend({
on_unlink: function(ids) {}
});
-openerp.web.CompoundContext = openerp.web.Class.extend({
+instance.web.CompoundContext = instance.web.Class.extend({
init: function () {
this.__ref = "compound_context";
this.__contexts = [];
@@ -1142,7 +1142,7 @@ openerp.web.CompoundContext = openerp.web.Class.extend({
}
});
-openerp.web.CompoundDomain = openerp.web.Class.extend({
+instance.web.CompoundDomain = instance.web.Class.extend({
init: function () {
this.__ref = "compound_domain";
this.__domains = [];
@@ -1165,10 +1165,10 @@ openerp.web.CompoundDomain = openerp.web.Class.extend({
}
});
-openerp.web.DropMisordered = openerp.web.Class.extend(/** @lends openerp.web.DropMisordered# */{
+instance.web.DropMisordered = instance.web.Class.extend(/** @lends openerp.web.DropMisordered# */{
/**
- * @constructs openerp.web.DropMisordered
- * @extends openerp.web.Class
+ * @constructs instance.web.DropMisordered
+ * @extends instance.web.Class
*
* @param {Boolean} [failMisordered=false] whether mis-ordered responses should be failed or just ignored
*/
diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js
index 3d15a4e3607..ebe82ddb4b4 100644
--- a/addons/web/static/src/js/data_export.js
+++ b/addons/web/static/src/js/data_export.js
@@ -1,14 +1,14 @@
-openerp.web.data_export = function(openerp) {
-var QWeb = openerp.web.qweb,
- _t = openerp.web._t;
-openerp.web.DataExport = openerp.web.Dialog.extend({
+openerp.web.data_export = function(instance) {
+var QWeb = instance.web.qweb,
+ _t = instance.web._t;
+instance.web.DataExport = instance.web.Dialog.extend({
template: 'ExportTreeView',
dialog_title: {toString: function () { return _t("Export Data"); }},
init: function(parent, dataset) {
this._super(parent);
this.records = {};
this.dataset = dataset;
- this.exports = new openerp.web.DataSetSearch(
+ this.exports = new instance.web.DataSetSearch(
this, 'ir.exports', this.dataset.get_context());
},
start: function() {
diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js
index 3054a7784c4..30f9323f723 100644
--- a/addons/web/static/src/js/data_import.js
+++ b/addons/web/static/src/js/data_import.js
@@ -1,6 +1,6 @@
-openerp.web.data_import = function(openerp) {
-var QWeb = openerp.web.qweb,
- _t = openerp.web._t;
+openerp.web.data_import = function(instance) {
+var QWeb = instance.web.qweb,
+ _t = instance.web._t;
/**
* Safari does not deal well at all with raw JSON data being returned. As a
* result, we're going to cheat by using a pseudo-jsonp: instead of getting
@@ -29,7 +29,7 @@ function jsonp(form, attributes, callback) {
$(form).ajaxSubmit(attributes);
}
-openerp.web.DataImport = openerp.web.Dialog.extend({
+instance.web.DataImport = instance.web.Dialog.extend({
template: 'ImportDataView',
dialog_title: {toString: function () { return _t("Import Data"); }},
init: function(parent, dataset){
@@ -79,10 +79,10 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
this.$element.delegate('fieldset legend', 'click', function() {
$(this).parent().toggleClass('oe-closed');
});
- this.ready.push(new openerp.web.DataSet(this, this.model).call(
+ this.ready.push(new instance.web.DataSet(this, this.model).call(
'fields_get', [], function (fields) {
self.graft_fields(fields);
- self.ready.push(new openerp.web.DataSet(self, self.model)
+ self.ready.push(new instance.web.DataSet(self, self.model)
.default_get(_.pluck(self.fields, 'id')).then(function (fields) {
_.each(fields, function(val, key) {
if (val) {
@@ -140,7 +140,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
f.fields = [];
// only fetch sub-fields to a depth of 2 levels
if (level < 2) {
- self.ready.push(new openerp.web.DataSet(self, field.relation).call(
+ self.ready.push(new instance.web.DataSet(self, field.relation).call(
'fields_get', [], function (fields) {
self.graft_fields(fields, f, level+1);
}));
@@ -151,7 +151,7 @@ openerp.web.DataImport = openerp.web.Dialog.extend({
});
},
toggle_import_button: function (newstate) {
- openerp.web.dialog(this.$element, 'widget')
+ instance.web.dialog(this.$element, 'widget')
.find('.oe-dialog-import-button')
.button('option', 'disabled', !newstate);
},
diff --git a/addons/web/static/src/js/dates.js b/addons/web/static/src/js/dates.js
index d61223113f8..3db48c8412a 100644
--- a/addons/web/static/src/js/dates.js
+++ b/addons/web/static/src/js/dates.js
@@ -1,5 +1,5 @@
-openerp.web.dates = function(openerp) {
+openerp.web.dates = function(instance) {
/**
* Converts a string to a Date javascript object using OpenERP's
@@ -11,7 +11,7 @@ openerp.web.dates = function(openerp) {
* @param {String} str A string representing a datetime.
* @returns {Date}
*/
-openerp.web.str_to_datetime = function(str) {
+instance.web.str_to_datetime = function(str) {
if(!str) {
return str;
}
@@ -38,7 +38,7 @@ openerp.web.str_to_datetime = function(str) {
* @param {String} str A string representing a date.
* @returns {Date}
*/
-openerp.web.str_to_date = function(str) {
+instance.web.str_to_date = function(str) {
if(!str) {
return str;
}
@@ -65,7 +65,7 @@ openerp.web.str_to_date = function(str) {
* @param {String} str A string representing a time.
* @returns {Date}
*/
-openerp.web.str_to_time = function(str) {
+instance.web.str_to_time = function(str) {
if(!str) {
return str;
}
@@ -104,7 +104,7 @@ var zpad = function(str, size) {
* @param {Date} obj
* @returns {String} A string representing a datetime.
*/
-openerp.web.datetime_to_str = function(obj) {
+instance.web.datetime_to_str = function(obj) {
if (!obj) {
return false;
}
@@ -124,7 +124,7 @@ openerp.web.datetime_to_str = function(obj) {
* @param {Date} obj
* @returns {String} A string representing a date.
*/
-openerp.web.date_to_str = function(obj) {
+instance.web.date_to_str = function(obj) {
if (!obj) {
return false;
}
@@ -143,7 +143,7 @@ openerp.web.date_to_str = function(obj) {
* @param {Date} obj
* @returns {String} A string representing a time.
*/
-openerp.web.time_to_str = function(obj) {
+instance.web.time_to_str = function(obj) {
if (!obj) {
return false;
}
diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js
index 53863e63e97..640ba4d5a93 100644
--- a/addons/web/static/src/js/formats.js
+++ b/addons/web/static/src/js/formats.js
@@ -1,6 +1,6 @@
-openerp.web.formats = function(openerp) {
-var _t = openerp.web._t;
+openerp.web.formats = function(instance) {
+var _t = instance.web._t;
/**
* Intersperses ``separator`` in ``str`` at the positions indicated by
@@ -23,7 +23,7 @@ var _t = openerp.web._t;
* @param {String} separator
* @returns {String}
*/
-openerp.web.intersperse = function (str, indices, separator) {
+instance.web.intersperse = function (str, indices, separator) {
separator = separator || '';
var result = [], last = str.length;
@@ -55,10 +55,10 @@ openerp.web.intersperse = function (str, indices, separator) {
* @param {String} num
* @returns {String}
*/
-openerp.web.insert_thousand_seps = function (num) {
+instance.web.insert_thousand_seps = function (num) {
var negative = num[0] === '-';
num = (negative ? num.slice(1) : num);
- return (negative ? '-' : '') + openerp.web.intersperse(
+ return (negative ? '-' : '') + instance.web.intersperse(
num, _t.database.parameters.grouping, _t.database.parameters.thousands_sep);
};
@@ -69,7 +69,7 @@ openerp.web.insert_thousand_seps = function (num) {
*
* @param {String} value original format
*/
-openerp.web.strip_raw_chars = function (value) {
+instance.web.strip_raw_chars = function (value) {
var isletter = /[a-zA-Z]/, output = [];
for(var index=0; index < value.length; ++index) {
var character = value[index];
@@ -81,7 +81,7 @@ openerp.web.strip_raw_chars = function (value) {
return output.join('');
};
var normalize_format = function (format) {
- return Date.normalizeFormat(openerp.web.strip_raw_chars(format));
+ return Date.normalizeFormat(instance.web.strip_raw_chars(format));
};
/**
* Formats a single atomic value based on a field descriptor
@@ -93,7 +93,7 @@ var normalize_format = function (format) {
* @param {Object} [descriptor.digits] used for the formatting of floats
* @param {String} [value_if_empty=''] returned if the ``value`` argument is considered empty
*/
-openerp.web.format_value = function (value, descriptor, value_if_empty) {
+instance.web.format_value = function (value, descriptor, value_if_empty) {
// If NaN value, display as with a `false` (empty cell)
if (typeof value === 'number' && isNaN(value)) {
value = false;
@@ -111,16 +111,16 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) {
return value_if_empty === undefined ? '' : value_if_empty;
}
var l10n = _t.database.parameters;
- switch (descriptor.widget || descriptor.type) {
+ switch (descriptor.type || (descriptor.field && descriptor.field.type)) {
case 'id':
return value.toString();
case 'integer':
- return openerp.web.insert_thousand_seps(
+ return instance.web.insert_thousand_seps(
_.str.sprintf('%d', value));
case 'float':
var precision = descriptor.digits ? descriptor.digits[1] : 2;
var formatted = _.str.sprintf('%.' + precision + 'f', value).split('.');
- formatted[0] = openerp.web.insert_thousand_seps(formatted[0]);
+ formatted[0] = instance.web.insert_thousand_seps(formatted[0]);
return formatted.join(l10n.decimal_point);
case 'float_time':
var pattern = '%02d:%02d';
@@ -136,17 +136,17 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) {
return value[1];
case 'datetime':
if (typeof(value) == "string")
- value = openerp.web.auto_str_to_date(value);
+ value = instance.web.auto_str_to_date(value);
return value.toString(normalize_format(l10n.date_format)
+ ' ' + normalize_format(l10n.time_format));
case 'date':
if (typeof(value) == "string")
- value = openerp.web.auto_str_to_date(value);
+ value = instance.web.auto_str_to_date(value);
return value.toString(normalize_format(l10n.date_format));
case 'time':
if (typeof(value) == "string")
- value = openerp.web.auto_str_to_date(value);
+ value = instance.web.auto_str_to_date(value);
return value.toString(normalize_format(l10n.time_format));
case 'selection': case 'statusbar':
// Each choice is [value, label]
@@ -163,7 +163,7 @@ openerp.web.format_value = function (value, descriptor, value_if_empty) {
}
};
-openerp.web.parse_value = function (value, descriptor, value_if_empty) {
+instance.web.parse_value = function (value, descriptor, value_if_empty) {
var date_pattern = normalize_format(_t.database.parameters.date_format),
time_pattern = normalize_format(_t.database.parameters.time_format);
switch (value) {
@@ -171,12 +171,12 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
case "":
return value_if_empty === undefined ? false : value_if_empty;
}
- switch (descriptor.widget || descriptor.type) {
+ switch (descriptor.type || (descriptor.field && descriptor.field.type)) {
case 'integer':
var tmp;
do {
tmp = value;
- value = value.replace(openerp.web._t.database.parameters.thousands_sep, "");
+ value = value.replace(instance.web._t.database.parameters.thousands_sep, "");
} while(tmp !== value);
tmp = Number(value);
if (isNaN(tmp))
@@ -190,9 +190,9 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
var tmp2 = value;
do {
tmp = tmp2;
- tmp2 = tmp.replace(openerp.web._t.database.parameters.thousands_sep, "");
+ tmp2 = tmp.replace(instance.web._t.database.parameters.thousands_sep, "");
} while(tmp !== tmp2);
- var reformatted_value = tmp.replace(openerp.web._t.database.parameters.decimal_point, ".");
+ var reformatted_value = tmp.replace(instance.web._t.database.parameters.decimal_point, ".");
var parsed = Number(reformatted_value);
if (isNaN(parsed))
throw new Error(value + " is not a correct float");
@@ -205,62 +205,62 @@ openerp.web.parse_value = function (value, descriptor, value_if_empty) {
}
var float_time_pair = value.split(":");
if (float_time_pair.length != 2)
- return factor * openerp.web.parse_value(value, {type: "float"});
- var hours = openerp.web.parse_value(float_time_pair[0], {type: "integer"});
- var minutes = openerp.web.parse_value(float_time_pair[1], {type: "integer"});
+ return factor * instance.web.parse_value(value, {type: "float"});
+ var hours = instance.web.parse_value(float_time_pair[0], {type: "integer"});
+ var minutes = instance.web.parse_value(float_time_pair[1], {type: "integer"});
return factor * (hours + (minutes / 60));
case 'progressbar':
- return openerp.web.parse_value(value, {type: "float"});
+ return instance.web.parse_value(value, {type: "float"});
case 'datetime':
var datetime = Date.parseExact(
value, (date_pattern + ' ' + time_pattern));
if (datetime !== null)
- return openerp.web.datetime_to_str(datetime);
+ return instance.web.datetime_to_str(datetime);
datetime = Date.parse(value);
if (datetime !== null)
- return openerp.web.datetime_to_str(datetime);
+ return instance.web.datetime_to_str(datetime);
throw new Error(value + " is not a valid datetime");
case 'date':
var date = Date.parseExact(value, date_pattern);
if (date !== null)
- return openerp.web.date_to_str(date);
+ return instance.web.date_to_str(date);
date = Date.parse(value);
if (date !== null)
- return openerp.web.date_to_str(date);
+ return instance.web.date_to_str(date);
throw new Error(value + " is not a valid date");
case 'time':
var time = Date.parseExact(value, time_pattern);
if (time !== null)
- return openerp.web.time_to_str(time);
+ return instance.web.time_to_str(time);
time = Date.parse(value);
if (time !== null)
- return openerp.web.time_to_str(time);
+ return instance.web.time_to_str(time);
throw new Error(value + " is not a valid time");
}
return value;
};
-openerp.web.auto_str_to_date = function(value, type) {
+instance.web.auto_str_to_date = function(value, type) {
try {
- return openerp.web.str_to_datetime(value);
+ return instance.web.str_to_datetime(value);
} catch(e) {}
try {
- return openerp.web.str_to_date(value);
+ return instance.web.str_to_date(value);
} catch(e) {}
try {
- return openerp.web.str_to_time(value);
+ return instance.web.str_to_time(value);
} catch(e) {}
throw new Error("'" + value + "' is not a valid date, datetime nor time");
};
-openerp.web.auto_date_to_str = function(value, type) {
+instance.web.auto_date_to_str = function(value, type) {
switch(type) {
case 'datetime':
- return openerp.web.datetime_to_str(value);
+ return instance.web.datetime_to_str(value);
case 'date':
- return openerp.web.date_to_str(value);
+ return instance.web.date_to_str(value);
case 'time':
- return openerp.web.time_to_str(value);
+ return instance.web.time_to_str(value);
default:
throw new Error(type + " is not convertible to date, datetime nor time");
}
@@ -290,7 +290,7 @@ openerp.web.auto_date_to_str = function(value, type) {
* @param {Number} [options.id] current record's id
*
*/
-openerp.web.format_cell = function (row_data, column, options) {
+instance.web.format_cell = function (row_data, column, options) {
options = options || {};
var attrs = {};
if (options.process_modifiers !== false) {
@@ -303,9 +303,9 @@ openerp.web.format_cell = function (row_data, column, options) {
'<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' +
'</button>', {
title: column.string || '',
- additional_attributes: isNaN(row_data["id"].value) && openerp.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
+ additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ?
'disabled="disabled" class="oe-listview-button-disabled"' : '',
- prefix: openerp.connection.prefix,
+ prefix: instance.connection.prefix,
icon: column.icon,
alt: column.string || ''
});
@@ -320,11 +320,11 @@ openerp.web.format_cell = function (row_data, column, options) {
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', openerp.connection.session_id, options.model, column.id, options.id);
+ 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\""), openerp.web.format_value(
+ text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value(
row_data[column.filename].value, {type: 'char'}));
}
}
@@ -340,7 +340,7 @@ openerp.web.format_cell = function (row_data, column, options) {
});
}
- return _.escape(openerp.web.format_value(
+ 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 773c10382f6..c2df866b5c6 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -1,13 +1,80 @@
-openerp.web.search = function(openerp) {
-var QWeb = openerp.web.qweb,
- _t = openerp.web._t,
- _lt = openerp.web._lt;
+openerp.web.search = function(instance) {
+var QWeb = instance.web.qweb,
+ _t = instance.web._t,
+ _lt = instance.web._lt;
+_.mixin({
+ sum: function (obj) { return _.reduce(obj, function (a, b) { return a + b; }, 0); }
+});
+
+// Have SearchBox optionally use callback function to produce inputs and facets
+// (views) set on callbacks.make_facet and callbacks.make_input keys when
+// initializing VisualSearch
+var SearchBox_renderFacet = function (facet, position) {
+ var view = new (this.app.options.callbacks['make_facet'] || VS.ui.SearchFacet)({
+ app : this.app,
+ model : facet,
+ order : position
+ });
-openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.SearchView# */{
- template: "EmptyComponent",
+ // Input first, facet second.
+ this.renderSearchInput();
+ this.facetViews.push(view);
+ this.$('.VS-search-inner').children().eq(position*2).after(view.render().el);
+
+ view.calculateSize();
+ _.defer(_.bind(view.calculateSize, view));
+
+ return view;
+ }; // warning: will not match
+// Ensure we're replacing the function we think
+if (SearchBox_renderFacet.toString() !== VS.ui.SearchBox.prototype.renderFacet.toString().replace(/(VS\.ui\.SearchFacet)/, "(this.app.options.callbacks['make_facet'] || $1)")) {
+ throw new Error(
+ "Trying to replace wrong version of VS.ui.SearchBox#renderFacet. "
+ + "Please fix replacement.");
+}
+var SearchBox_renderSearchInput = function () {
+ var input = new (this.app.options.callbacks['make_input'] || VS.ui.SearchInput)({position: this.inputViews.length, app: this.app});
+ this.$('.VS-search-inner').append(input.render().el);
+ this.inputViews.push(input);
+ };
+// Ensure we're replacing the function we think
+if (SearchBox_renderSearchInput.toString() !== VS.ui.SearchBox.prototype.renderSearchInput.toString().replace(/(VS\.ui\.SearchInput)/, "(this.app.options.callbacks['make_input'] || $1)")) {
+ throw new Error(
+ "Trying to replace wrong version of VS.ui.SearchBox#renderSearchInput. "
+ + "Please fix replacement.");
+}
+var SearchBox_searchEvent = function (e) {
+ var query = null;
+ this.renderFacets();
+ this.focusSearch(e);
+ this.app.options.callbacks.search(query, this.app.searchQuery);
+ };
+if (SearchBox_searchEvent.toString() !== VS.ui.SearchBox.prototype.searchEvent.toString().replace(
+ /this\.value\(\);\n[ ]{4}this\.focusSearch\(e\);\n[ ]{4}this\.value\(query\)/,
+ 'null;\n this.renderFacets();\n this.focusSearch(e)')) {
+ throw new Error(
+ "Trying to replace wrong version of VS.ui.SearchBox#searchEvent. "
+ + "Please fix replacement.");
+}
+_.extend(VS.ui.SearchBox.prototype, {
+ renderFacet: SearchBox_renderFacet,
+ renderSearchInput: SearchBox_renderSearchInput,
+ searchEvent: SearchBox_searchEvent
+});
+_.extend(VS.model.SearchFacet.prototype, {
+ value: function () {
+ if (this.has('json')) {
+ return this.get('json');
+ }
+ return this.get('value');
+ }
+});
+
+instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.SearchView# */{
+ template: "SearchView",
/**
- * @constructs openerp.web.SearchView
- * @extends openerp.web.OldWidget
+ * @constructs instance.web.SearchView
+ * @extends instance.web.OldWidget
*
* @param parent
* @param element_id
@@ -25,9 +92,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
this.has_defaults = !_.isEmpty(this.defaults);
this.inputs = [];
- this.enabled_filters = [];
-
- this.has_focus = false;
+ this.controls = {};
this.hidden = !!hidden;
this.headless = this.hidden && !this.has_defaults;
@@ -37,20 +102,63 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
this.ready = $.Deferred();
},
start: function() {
- this._super();
+ var self = this;
+ var p = this._super();
+
+ this.setup_global_completion();
+ this.vs = VS.init({
+ container: this.$element,
+ query: '',
+ callbacks: {
+ make_facet: this.proxy('make_visualsearch_facet'),
+ make_input: this.proxy('make_visualsearch_input'),
+ search: function (query, searchCollection) {
+ self.do_search();
+ },
+ facetMatches: function (callback) {
+ },
+ valueMatches : function(facet, searchTerm, callback) {
+ }
+ }
+ });
+
+ var search = function () { self.vs.searchBox.searchEvent({}); };
+ // searchQuery operations
+ this.vs.searchQuery
+ .off('add').on('add', search)
+ .off('change').on('change', search)
+ .off('reset').on('reset', search)
+ .off('remove').on('remove', function (record, collection, options) {
+ if (options['trigger_search']) {
+ search();
+ }
+ });
+
if (this.hidden) {
this.$element.hide();
}
if (this.headless) {
this.ready.resolve();
} else {
- this.rpc("/web/searchview/load", {
+ var load_view = this.rpc("/web/searchview/load", {
model: this.model,
view_id: this.view_id,
- context: this.dataset.get_context()
- }, this.on_loaded);
+ context: this.dataset.get_context() });
+ // FIXME: local eval of domain and context to get rid of special endpoint
+ var filters = this.rpc('/web/searchview/get_filters', {
+ model: this.model
+ }).then(function (filters) { self.custom_filters = filters; });
+
+ $.when(load_view, filters)
+ .pipe(function (load) { return load[0]; })
+ .then(this.on_loaded);
}
- return this.ready.promise();
+
+ this.$element.on('click', '.oe_vs_unfold_drawer', function () {
+ self.$element.toggleClass('oe_searchview_open_drawer');
+ });
+
+ return $.when(p, this.ready);
},
show: function () {
this.$element.show();
@@ -58,61 +166,227 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
hide: function () {
this.$element.hide();
},
+
+ /**
+ * Sets up thingie where all the mess is put?
+ */
+ setup_stuff_drawer: function () {
+ var self = this;
+ $('<div class="oe_vs_unfold_drawer">').appendTo(this.$element.find('.VS-search-box'));
+ var $drawer = $('<div class="oe_searchview_drawer">').appendTo(this.$element);
+ var $filters = $('<div class="oe_searchview_filters">').appendTo($drawer);
+
+ var running_count = 0;
+ // get total filters count
+ var is_group = function (i) { return i instanceof instance.web.search.FilterGroup; };
+ var filters_count = _(this.controls).chain()
+ .flatten()
+ .filter(is_group)
+ .map(function (i) { return i.filters.length; })
+ .sum()
+ .value();
+
+ var col1 = [], col2 = _(this.controls).map(function (inputs, group) {
+ var filters = _(inputs).filter(is_group);
+ return {
+ name: group === 'null' ? _t("Filters") : group,
+ filters: filters,
+ length: _(filters).chain().map(function (i) {
+ return i.filters.length; }).sum().value()
+ };
+ });
+
+ while (col2.length) {
+ // col1 + group should be smaller than col2 + group
+ if ((running_count + col2[0].length) <= (filters_count - running_count)) {
+ running_count += col2[0].length;
+ col1.push(col2.shift());
+ } else {
+ break;
+ }
+ }
+
+ // Create a Custom Filter FilterGroup for each custom filter read from
+ // the db, add all of this as a group in the smallest column
+ [].push.call(col1.length <= col2.length ? col1 : col2, {
+ name: _t("Custom Filters"),
+ filters: _.map(this.custom_filters, function (filter) {
+ // FIXME: handling of ``disabled`` being set
+ var f = new instance.web.search.Filter({attrs: {
+ string: filter.name,
+ context: filter.context,
+ domain: filter.domain
+ }}, self);
+ return new instance.web.search.FilterGroup([f], self);
+ }),
+ length: 3
+ });
+
+ return $.when(
+ this.render_column(col1, $('<div>').appendTo($filters)),
+ this.render_column(col2, $('<div>').appendTo($filters)),
+ (new instance.web.search.Advanced(this).appendTo($drawer)));
+ },
+ render_column: function (column, $el) {
+ return $.when.apply(null, _(column).map(function (group) {
+ $('<h3>').text(group.name).appendTo($el);
+ return $.when.apply(null,
+ _(group.filters).invoke('appendTo', $el));
+ }));
+ },
+ /**
+ * Sets up search view's view-wide auto-completion widget
+ */
+ setup_global_completion: function () {
+ // Prevent keydown from within a facet's input from reaching the
+ // auto-completion widget and opening the completion list
+ this.$element.on('keydown', '.search_facet input', function (e) {
+ e.stopImmediatePropagation();
+ });
+
+ this.$element.autocomplete({
+ source: this.proxy('complete_global_search'),
+ select: this.proxy('select_completion'),
+ focus: function (e) { e.preventDefault(); },
+ html: true,
+ minLength: 0,
+ delay: 0
+ }).data('autocomplete')._renderItem = function (ul, item) {
+ // item of completion list
+ var $item = $( "<li></li>" )
+ .data( "item.autocomplete", item )
+ .appendTo( ul );
+
+ if (item.value !== undefined) {
+ // regular completion item
+ return $item.append(
+ (item.label)
+ ? $('<a>').html(item.label)
+ : $('<a>').text(item.value));
+ }
+ return $item.text(item.category)
+ .css({
+ borderTop: '1px solid #cccccc',
+ margin: 0,
+ padding: 0,
+ zoom: 1,
+ 'float': 'left',
+ clear: 'left',
+ width: '100%'
+ });
+ }
+ },
+ /**
+ * Provide auto-completion result for req.term (an array to `resp`)
+ *
+ * @param {Object} req request to complete
+ * @param {String} req.term searched term to complete
+ * @param {Function} resp response callback
+ */
+ complete_global_search: function (req, resp) {
+ $.when.apply(null, _(this.inputs).chain()
+ .invoke('complete', req.term)
+ .value()).then(function () {
+ resp(_(_(arguments).compact()).flatten(true));
+ });
+ },
+
+ /**
+ * Action to perform in case of selection: create a facet (model)
+ * and add it to the search collection
+ *
+ * @param {Object} e selection event, preventDefault to avoid setting value on object
+ * @param {Object} ui selection information
+ * @param {Object} ui.item selected completion item
+ */
+ select_completion: function (e, ui) {
+ e.preventDefault();
+ this.vs.searchQuery.add(new VS.model.SearchFacet(_.extend(
+ {app: this.vs}, ui.item)));
+ this.vs.searchBox.searchEvent({});
+ },
+
+ /**
+ * Builds the right SearchFacet view based on the facet object to render
+ * (e.g. readonly facets for filters)
+ *
+ * @param {Object} options
+ * @param {VS.model.SearchFacet} options.model facet object to render
+ */
+ make_visualsearch_facet: function (options) {
+ return new instance.web.search.FilterGroupFacet(options);
+
+// if (options.model.get('field') instanceof instance.web.search.FilterGroup) {
+// return new instance.web.search.FilterGroupFacet(options);
+// }
+// return new VS.ui.SearchFacet(options);
+ },
+ /**
+ * Proxies searches on a SearchInput to the search view's global completion
+ *
+ * Also disables SearchInput.autocomplete#_move so search view's
+ * autocomplete can get the corresponding events, or something.
+ *
+ * @param options
+ */
+ make_visualsearch_input: function (options) {
+ var self = this, input = new VS.ui.SearchInput(options);
+ input.setupAutocomplete = function () {
+ _.extend(this.box.autocomplete({
+ minLength: 1,
+ delay: 0,
+ search: function () {
+ self.$element.autocomplete('search', input.box.val());
+ return false;
+ }
+ }).data('autocomplete'), {
+ _move: function () {},
+ close: function () { self.$element.autocomplete('close'); }
+ });
+ };
+ return input;
+ },
+
/**
* Builds a list of widget rows (each row is an array of widgets)
*
* @param {Array} items a list of nodes to convert to widgets
* @param {Object} fields a mapping of field names to (ORM) field attributes
- * @returns Array
+ * @param {String} [group_name] name of the group to put the new controls in
*/
- make_widgets: function (items, fields) {
- var rows = [],
- row = [];
- rows.push(row);
+ make_widgets: function (items, fields, group_name) {
+ group_name = group_name || null;
+ if (!(group_name in this.controls)) {
+ this.controls[group_name] = [];
+ }
+ var self = this, group = this.controls[group_name];
var filters = [];
_.each(items, function (item) {
if (filters.length && item.tag !== 'filter') {
- row.push(
- new openerp.web.search.FilterGroup(
- filters, this));
+ group.push(new instance.web.search.FilterGroup(filters, this));
filters = [];
}
- if (item.tag === 'newline') {
- row = [];
- rows.push(row);
- } else if (item.tag === 'filter') {
- if (!this.has_focus) {
- item.attrs.default_focus = '1';
- this.has_focus = true;
- }
- filters.push(
- new openerp.web.search.Filter(
- item, this));
- } else if (item.tag === 'separator') {
- // a separator is a no-op
- } else {
- if (item.tag === 'group') {
- // TODO: group and field should be fetched from registries, maybe even filters
- row.push(
- new openerp.web.search.Group(
- item, this, fields));
- } else if (item.tag === 'field') {
- if (!this.has_focus) {
- item.attrs.default_focus = '1';
- this.has_focus = true;
- }
- row.push(
- this.make_field(
- item, fields[item['attrs'].name]));
- }
+ switch (item.tag) {
+ case 'separator': case 'newline':
+ break;
+ case 'filter':
+ filters.push(new instance.web.search.Filter(item, this));
+ break;
+ case 'group':
+ self.make_widgets(item.children, fields, item.attrs.string);
+ break;
+ case 'field':
+ group.push(this.make_field(item, fields[item['attrs'].name]));
+ // filters
+ self.make_widgets(item.children, fields, group_name);
+ break;
}
}, this);
+
if (filters.length) {
- row.push(new openerp.web.search.FilterGroup(filters, this));
+ group.push(new instance.web.search.FilterGroup(filters, this));
}
-
- return rows;
},
/**
* Creates a field for the provided field descriptor item (which comes
@@ -120,18 +394,13 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
*
* @param {Object} item fields_view_get node for the field
* @param {Object} field fields_get result for the field
- * @returns openerp.web.search.Field
+ * @returns instance.web.search.Field
*/
make_field: function (item, field) {
- try {
- return new (openerp.web.search.fields.get_any(
- [item.attrs.widget, field.type]))
- (item, field, this);
- } catch (e) {
- if (! e instanceof openerp.web.KeyNotFound) {
- throw e;
- }
- // KeyNotFound means unknown field type
+ var obj = instance.web.search.fields.get_any( [item.attrs.widget, field.type]);
+ if(obj) {
+ return new (obj) (item, field, this);
+ } else {
console.group('Unknown field type ' + field.type);
console.error('View node', item);
console.info('View field', field);
@@ -141,6 +410,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
}
},
on_loaded: function(data) {
+ var self = this;
this.fields_view = data.fields_view;
if (data.fields_view.type !== 'search' ||
data.fields_view.arch.tag !== 'search') {
@@ -148,52 +418,20 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
"Got non-search view after asking for a search view: type %s, arch root %s",
data.fields_view.type, data.fields_view.arch.tag));
}
- var self = this,
- lines = this.make_widgets(
- data.fields_view['arch'].children,
- data.fields_view.fields);
-
- // for extended search view
- var ext = new openerp.web.search.ExtendedSearch(this, this.model);
- lines.push([ext]);
- this.extended_search = ext;
-
- var render = QWeb.render("SearchView", {
- 'view': data.fields_view['arch'],
- 'lines': lines,
- 'defaults': this.defaults
- });
- this.$element.html(render);
+ this.make_widgets(
+ data.fields_view['arch'].children,
+ data.fields_view.fields);
- var f = this.$element.find('form');
- this.$element.find('form')
- .submit(this.do_search)
- .bind('reset', this.do_clear);
- // start() all the widgets
- var widget_starts = _(lines).chain().flatten().map(function (widget) {
- return widget.start();
- }).value();
-
- $.when.apply(null, widget_starts).then(function () {
- self.ready.resolve();
- });
-
- this.reload_managed_filters();
- },
- reload_managed_filters: function() {
- var self = this;
- return this.rpc('/web/searchview/get_filters', {
- model: this.dataset.model
- }).then(function(result) {
- self.managed_filters = result;
- var filters = self.$element.find(".oe_search-view-filters-management");
- filters.html(QWeb.render("SearchView.managed-filters", {
- filters: result,
- disabled_filter_message: _t('Filter disabled due to invalid syntax')
- }));
- filters.change(self.on_filters_management);
- });
+ // load defaults
+ return $.when(
+ this.setup_stuff_drawer(),
+ $.when.apply(null, _(this.inputs).invoke('facet_for_defaults', this.defaults))
+ .then(function () {
+ self.vs.searchQuery.reset(_(arguments).compact(), {silent: true});
+ self.vs.searchBox.renderFacets();
+ }))
+ .then(function () { self.ready.resolve(); })
},
/**
* Handle event when the user make a selection in the filters management select box.
@@ -222,11 +460,11 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
break;
case 'save_filter':
var data = this.build_search_data();
- var context = new openerp.web.CompoundContext();
+ var context = new instance.web.CompoundContext();
_.each(data.contexts, function(x) {
context.add(x);
});
- var domain = new openerp.web.CompoundDomain();
+ var domain = new instance.web.CompoundDomain();
_.each(data.domains, function(x) {
domain.add(x);
});
@@ -234,7 +472,7 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
context.add({"group_by": groupbys});
var dial_html = QWeb.render("SearchView.managed-filters.add");
var $dial = $(dial_html);
- openerp.web.dialog($dial, {
+ instance.web.dialog($dial, {
modal: true,
title: _t("Filter Entry"),
buttons: [
@@ -287,13 +525,13 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
on_add_to_dashboard: function() {
this.$element.find(".oe_search-view-filters-management")[0].selectedIndex = 0;
var self = this,
- menu = openerp.webclient.menu,
+ menu = instance.webclient.menu,
$dialog = $(QWeb.render("SearchView.add_to_dashboard", {
dashboards : menu.data.data.children,
selected_menu_id : menu.$element.find('a.active').data('menu')
}));
$dialog.find('input').val(this.fields_view.name);
- openerp.web.dialog($dialog, {
+ instance.web.dialog($dialog, {
modal: true,
title: _t("Add to Dashboard"),
buttons: [
@@ -305,8 +543,8 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
var menu_id = $(this).find("select").val(),
title = $(this).find("input").val(),
data = self.build_search_data(),
- context = new openerp.web.CompoundContext(),
- domain = new openerp.web.CompoundDomain();
+ context = new instance.web.CompoundContext(),
+ domain = new instance.web.CompoundDomain();
_.each(data.contexts, function(x) {
context.add(x);
});
@@ -335,47 +573,33 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
* Performs the search view collection of widget data.
*
* If the collection went well (all fields are valid), then triggers
- * :js:func:`openerp.web.SearchView.on_search`.
+ * :js:func:`instance.web.SearchView.on_search`.
*
* If at least one field failed its validation, triggers
- * :js:func:`openerp.web.SearchView.on_invalid` instead.
+ * :js:func:`instance.web.SearchView.on_invalid` instead.
*
* @param e jQuery event object coming from the "Search" button
*/
- do_search: function (e) {
- if (this.headless && !this.has_defaults) {
- return this.on_search([], [], []);
- }
-
- if (e && e.preventDefault) { e.preventDefault(); }
-
- var data = this.build_search_data();
+ do_search: function () {
+ var domains = [], contexts = [], groupbys = [], errors = [];
- if (data.errors.length) {
- this.on_invalid(data.errors);
- return;
- }
-
- this.on_search(data.domains, data.contexts, data.groupbys);
- },
- build_search_data: function() {
- var domains = [],
- contexts = [],
- errors = [];
-
- _.each(this.inputs, function (input) {
+ this.vs.searchQuery.each(function (facet) {
+ var field = facet.get('field');
try {
- var domain = input.get_domain();
+ var domain = field.get_domain(facet);
if (domain) {
domains.push(domain);
}
-
- var context = input.get_context();
+ var context = field.get_context(facet);
if (context) {
contexts.push(context);
}
+ var group_by = field.get_groupby(facet);
+ if (group_by) {
+ groupbys.push.apply(groupbys, group_by);
+ }
} catch (e) {
- if (e instanceof openerp.web.search.Invalid) {
+ if (e instanceof instance.web.search.Invalid) {
errors.push(e);
} else {
throw e;
@@ -383,23 +607,11 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
}
});
- // TODO: do we need to handle *fields* with group_by in their context?
- var groupbys = _(this.enabled_filters)
- .chain()
- .map(function (filter) { return filter.get_context();})
- .compact()
- .value();
-
- if (this.filter_data.contexts) {
- contexts = this.filter_data.contexts.concat(contexts)
- }
- if (this.filter_data.domains) {
- domains = this.filter_data.domains.concat(domains);
- }
- if (this.filter_data.groupbys) {
- groupbys = this.filter_data.groupbys.concat(groupbys);
+ if (!_.isEmpty(errors)) {
+ this.on_invalid(errors);
+ return;
}
- return {domains: domains, contexts: contexts, errors: errors, groupbys: groupbys};
+ return this.on_search(domains, contexts, groupbys);
},
/**
* Triggered after the SearchView has collected all relevant domains and
@@ -432,84 +644,68 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
*/
on_invalid: function (errors) {
this.do_notify(_t("Invalid Search"), _t("triggered from search view"));
- },
- /**
- * @param {Boolean} [reload_view=true]
- */
- do_clear: function (reload_view) {
- this.filter_data = {};
- this.$element.find(".oe_search-view-filters-management").val('');
+ }
+});
+
+/** @namespace */
+instance.web.search = {};
- this.$element.find('.filter_label, .filter_icon').removeClass('enabled');
- this.enabled_filters.splice(0);
- var string = $('a.searchview_group_string');
- _.each(string, function(str){
- $(str).closest('div.searchview_group').removeClass("expanded").addClass('folded');
- });
- this.$element.find('table:last').hide();
+instance.web.search.FilterGroupFacet = VS.ui.SearchFacet.extend({
+ events: _.extend({
+ 'click': 'selectFacet'
+ }, VS.ui.SearchFacet.prototype.events),
- $('.searchview_extended_groups_list').empty();
- return $.async_when.apply(
- null, _(this.inputs).invoke('clear')).pipe(
- reload_view !== false ? this.on_clear : null);
+ render: function () {
+ this.setMode('not', 'editing');
+ this.setMode('not', 'selected');
+
+ var value = this.model.get('value');
+ this.$el.html(QWeb.render('SearchView.filters.facet', {
+ facet: this.model
+ }));
+ // virtual input so SearchFacet code has something to play with
+ this.box = $('<input>').val(value);
+
+ return this;
},
- /**
- * Triggered when the search view gets cleared
- *
- * @event
- */
- on_clear: function () {
- this.do_search();
+ enableEdit: function () {
+ this.selectFacet()
},
- /**
- * Called by a filter propagating its state changes
- *
- * @param {openerp.web.search.Filter} filter a filter which got toggled
- * @param {Boolean} default_enabled filter got enabled through the default values, at render time.
- */
- do_toggle_filter: function (filter, default_enabled) {
- if (default_enabled || filter.is_enabled()) {
- this.enabled_filters.push(filter);
- } else {
- this.enabled_filters = _.without(
- this.enabled_filters, filter);
- }
-
- if (!default_enabled) {
- // selecting a filter after initial loading automatically
- // triggers refresh
- this.$element.find('form').submit();
+ keydown: function (e) {
+ var key = VS.app.hotkeys.key(e);
+ if (key !== 'right') {
+ return VS.ui.SearchFacet.prototype.keydown.call(this, e);
}
+ e.preventDefault();
+ this.deselectFacet();
+ this.options.app.searchBox.focusNextFacet(this, 1);
}
});
-
-/** @namespace */
-openerp.web.search = {};
/**
- * Registry of search fields, called by :js:class:`openerp.web.SearchView` to
+ * Registry of search fields, called by :js:class:`instance.web.SearchView` to
* find and instantiate its field widgets.
*/
-openerp.web.search.fields = new openerp.web.Registry({
- 'char': 'openerp.web.search.CharField',
- 'text': 'openerp.web.search.CharField',
- 'boolean': 'openerp.web.search.BooleanField',
- 'integer': 'openerp.web.search.IntegerField',
- 'id': 'openerp.web.search.IntegerField',
- 'float': 'openerp.web.search.FloatField',
- 'selection': 'openerp.web.search.SelectionField',
- 'datetime': 'openerp.web.search.DateTimeField',
- 'date': 'openerp.web.search.DateField',
- 'many2one': 'openerp.web.search.ManyToOneField',
- 'many2many': 'openerp.web.search.CharField',
- 'one2many': 'openerp.web.search.CharField'
+instance.web.search.fields = new instance.web.Registry({
+ 'char': 'instance.web.search.CharField',
+ 'text': 'instance.web.search.CharField',
+ 'boolean': 'instance.web.search.BooleanField',
+ 'integer': 'instance.web.search.IntegerField',
+ 'id': 'instance.web.search.IntegerField',
+ 'float': 'instance.web.search.FloatField',
+ 'selection': 'instance.web.search.SelectionField',
+ 'datetime': 'instance.web.search.DateTimeField',
+ 'date': 'instance.web.search.DateField',
+ 'many2one': 'instance.web.search.ManyToOneField',
+ 'many2many': 'instance.web.search.CharField',
+ 'one2many': 'instance.web.search.CharField'
});
-openerp.web.search.Invalid = openerp.web.Class.extend( /** @lends openerp.web.search.Invalid# */{
+instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web.search.Invalid# */{
/**
* Exception thrown by search widgets when they hold invalid values,
* which they can not return when asked.
*
- * @constructs openerp.web.search.Invalid
- * @extends openerp.web.Class
+ * @constructs instance.web.search.Invalid
+ * @extends instance.web.Class
*
* @param field the name of the field holding an invalid value
* @param value the invalid value
@@ -527,92 +723,42 @@ openerp.web.search.Invalid = openerp.web.Class.extend( /** @lends openerp.web.se
);
}
});
-openerp.web.search.Widget = openerp.web.OldWidget.extend( /** @lends openerp.web.search.Widget# */{
+instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{
template: null,
/**
* Root class of all search widgets
*
- * @constructs openerp.web.search.Widget
- * @extends openerp.web.OldWidget
+ * @constructs instance.web.search.Widget
+ * @extends instance.web.OldWidget
*
* @param view the ancestor view of this widget
*/
init: function (view) {
this._super(view);
this.view = view;
- },
- /**
- * Sets and returns a globally unique identifier for the widget.
- *
- * If a prefix is specified, the identifier will be appended to it.
- *
- * @params prefix prefix sections, empty/falsy sections will be removed
- */
- make_id: function () {
- this.element_id = _.uniqueId(
- ['search'].concat(
- _.compact(_.toArray(arguments)),
- ['']).join('_'));
- return this.element_id;
- },
- /**
- * "Starts" the widgets. Called at the end of the rendering, this allows
- * widgets to hook themselves to their view sections.
- *
- * On widgets, if they kept a reference to a view and have an element_id,
- * will fetch and set their root element on $element.
- */
- start: function () {
- this._super();
- if (this.view && this.element_id) {
- // id is unique, and no getElementById on elements
- this.$element = $(document.getElementById(
- this.element_id));
- }
- },
- /**
- * "Stops" the widgets. Called when the view destroys itself, this
- * lets the widgets clean up after themselves.
- */
- destroy: function () {
- delete this.view;
- this._super();
- },
- render: function (defaults) {
- // FIXME
- return this._super(_.extend(this, {defaults: defaults}));
}
});
-openerp.web.search.add_expand_listener = function($root) {
+instance.web.search.add_expand_listener = function($root) {
$root.find('a.searchview_group_string').click(function (e) {
$root.toggleClass('folded expanded');
e.stopPropagation();
e.preventDefault();
});
};
-openerp.web.search.Group = openerp.web.search.Widget.extend({
+instance.web.search.Group = instance.web.search.Widget.extend({
template: 'SearchView.group',
init: function (view_section, view, fields) {
this._super(view);
this.attrs = view_section.attrs;
this.lines = view.make_widgets(
view_section.children, fields);
- this.make_id('group');
- },
- start: function () {
- this._super();
- openerp.web.search.add_expand_listener(this.$element);
- var widget_starts = _(this.lines).chain().flatten()
- .map(function (widget) { return widget.start(); })
- .value();
- return $.when.apply(null, widget_starts);
}
});
-openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp.web.search.Input# */{
+instance.web.search.Input = instance.web.search.Widget.extend( /** @lends instance.web.search.Input# */{
/**
- * @constructs openerp.web.search.Input
- * @extends openerp.web.search.Widget
+ * @constructs instance.web.search.Input
+ * @extends instance.web.search.Widget
*
* @param view
*/
@@ -621,10 +767,44 @@ openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp.
this.view.inputs.push(this);
this.style = undefined;
},
+ /**
+ * Fetch auto-completion values for the widget.
+ *
+ * The completion values should be an array of objects with keys category,
+ * label, value prefixed with an object with keys type=section and label
+ *
+ * @param {String} value value to complete
+ * @returns {jQuery.Deferred<null|Array>}
+ */
+ complete: function (value) {
+ return $.when(null)
+ },
+ /**
+ * Returns a VS.model.SearchFacet instance for the provided defaults if
+ * they apply to this widget, or null if they don't.
+ *
+ * This default implementation will try calling
+ * :js:func:`instance.web.search.Input#facet_for` if the widget's name
+ * matches the input key
+ *
+ * @param {Object} defaults
+ * @returns {jQuery.Deferred<null|Object>}
+ */
+ facet_for_defaults: function (defaults) {
+ if (!this.attrs ||
+ !(this.attrs.name in defaults && defaults[this.attrs.name])) {
+ return $.when(null);
+ }
+ return this.facet_for(defaults[this.attrs.name]);
+ },
get_context: function () {
throw new Error(
"get_context not implemented for widget " + this.attrs.type);
},
+ get_groupby: function () {
+ throw new Error(
+ "get_groupby not implemented for widget " + this.attrs.type);
+ },
get_domain: function () {
throw new Error(
"get_domain not implemented for widget " + this.attrs.type);
@@ -638,42 +818,80 @@ openerp.web.search.Input = openerp.web.search.Widget.extend( /** @lends openerp.
}
}
this.attrs = attrs;
- },
- /**
- * Specific clearing operations, if any
- */
- clear: function () {}
+ }
});
-openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends openerp.web.search.FilterGroup# */{
+instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends instance.web.search.FilterGroup# */{
template: 'SearchView.filters',
/**
* Inclusive group of filters, creates a continuous "button" with clickable
* sections (the normal display for filters is to be a self-contained button)
*
- * @constructs openerp.web.search.FilterGroup
- * @extends openerp.web.search.Input
+ * @constructs instance.web.search.FilterGroup
+ * @extends instance.web.search.Input
*
- * @param {Array<openerp.web.search.Filter>} filters elements of the group
- * @param {openerp.web.SearchView} view view in which the filters are contained
+ * @param {Array<instance.web.search.Filter>} filters elements of the group
+ * @param {instance.web.SearchView} view view in which the filters are contained
*/
init: function (filters, view) {
this._super(view);
this.filters = filters;
- this.length = filters.length;
},
start: function () {
- this._super();
- _.each(this.filters, function (filter) {
- filter.start();
+ this.$element.on('click', 'li', this.proxy('toggle_filter'));
+ return $.when(null);
+ },
+ facet_for_defaults: function (defaults) {
+ var fs = _(this.filters).filter(function (f) {
+ return f.attrs && f.attrs.name && !!defaults[f.attrs.name];
});
+ if (_.isEmpty(fs)) { return $.when(null); }
+ return $.when(new VS.model.SearchFacet({
+ category: _t("Filter"),
+ value: _(fs).map(function (f) {
+ return f.attrs.string || f.attrs.name }).join(' | '),
+ json: fs,
+ field: this,
+ app: this.view.vs
+ }));
+ },
+ /**
+ * Fetches contexts for all enabled filters in the group
+ *
+ * @param {VS.model.SearchFacet} facet
+ * @return {*} combined contexts of the enabled filters in this group
+ */
+ get_context: function (facet) {
+ var contexts = _(facet.get('json')).chain()
+ .map(function (filter) { return filter.attrs.context; })
+ .reject(_.isEmpty)
+ .value();
+
+ if (!contexts.length) { return; }
+ if (contexts.length === 1) { return contexts[0]; }
+ return _.extend(new instance.web.CompoundContext, {
+ __contexts: contexts
+ });
+ },
+ /**
+ * Fetches group_by sequence for all enabled filters in the group
+ *
+ * @param {VS.model.SearchFacet} facet
+ * @return {Array} enabled filters in this group
+ */
+ get_groupby: function (facet) {
+ return _(facet.get('json')).chain()
+ .map(function (filter) { return filter.attrs.context; })
+ .reject(_.isEmpty)
+ .value();
},
- get_context: function () { },
/**
* Handles domains-fetching for all the filters within it: groups them.
+ *
+ * @param {VS.model.SearchFacet} facet
+ * @return {*} combined domains of the enabled filters in this group
*/
- get_domain: function () {
- var domains = _(this.filters).chain()
- .filter(function (filter) { return filter.is_enabled(); })
+ get_domain: function (facet) {
+ var domains = _(facet.get('json')).chain()
.map(function (filter) { return filter.attrs.domain; })
.reject(_.isEmpty)
.value();
@@ -683,19 +901,62 @@ openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends open
for (var i=domains.length; --i;) {
domains.unshift(['|']);
}
- return _.extend(new openerp.web.CompoundDomain(), {
+ return _.extend(new instance.web.CompoundDomain(), {
__domains: domains
});
+ },
+ toggle_filter: function (e) {
+ this.toggle(this.filters[$(e.target).index()]);
+ },
+ toggle: function (filter) {
+ // FIXME: oh god, my eyes, they hurt
+ var self = this, fs;
+ var facet = this.view.vs.searchQuery.detect(function (f) {
+ return f.get('field') === self; });
+ if (facet) {
+ fs = facet.get('json');
+
+ if (_.include(fs, filter)) {
+ fs = _.without(fs, filter);
+ } else {
+ fs.push(filter);
+ }
+ if (_(fs).isEmpty()) {
+ this.view.vs.searchQuery.remove(facet, {trigger_search: true});
+ } else {
+ facet.set({
+ json: fs,
+ value: _(fs).map(function (f) {
+ return f.attrs.string || f.attrs.name }).join(' | ')
+ });
+ }
+ return;
+ } else {
+ fs = [filter];
+ }
+
+ this.view.vs.searchQuery.add({
+ category: _t("Filter"),
+ value: _(fs).map(function (f) {
+ return f.attrs.string || f.attrs.name }).join(' | '),
+ json: fs,
+ field: this,
+ app: this.view.vs
+ });
}
});
-openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.web.search.Filter# */{
+instance.web.search.Filter = instance.web.search.Input.extend(/** @lends instance.web.search.Filter# */{
template: 'SearchView.filter',
/**
* Implementation of the OpenERP filters (button with a context and/or
* a domain sent as-is to the search view)
*
- * @constructs openerp.web.search.Filter
- * @extends openerp.web.search.Input
+ * Filters are only attributes holder, the actual work (compositing
+ * domains and contexts, converting between facets and filters) is
+ * performed by the filter group.
+ *
+ * @constructs instance.web.search.Filter
+ * @extends instance.web.search.Input
*
* @param node
* @param view
@@ -703,56 +964,17 @@ openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.w
init: function (node, view) {
this._super(view);
this.load_attrs(node.attrs);
- this.classes = [this.attrs.string ? 'filter_label' : 'filter_icon'];
- this.make_id('filter', this.attrs.name);
- },
- start: function () {
- this._super();
- var self = this;
- this.$element.click(function (e) {
- $(this).toggleClass('enabled');
- self.view.do_toggle_filter(self);
- });
- },
- /**
- * Returns whether the filter is currently enabled (in use) or not.
- *
- * @returns a boolean
- */
- is_enabled:function () {
- return this.$element.hasClass('enabled');
- },
- /**
- * If the filter is present in the defaults (and has a truthy value),
- * enable the filter.
- *
- * @param {Object} defaults the search view's default values
- */
- render: function (defaults) {
- if (this.attrs.name && defaults[this.attrs.name]) {
- this.classes.push('enabled');
- this.view.do_toggle_filter(this, true);
- }
- return this._super(defaults);
},
- get_context: function () {
- if (!this.is_enabled()) {
- return;
- }
- return this.attrs.context;
- },
- /**
- * Does not return anything: filter domain is handled at the FilterGroup
- * level
- */
- get_domain: function () { }
+ facet_for: function () { return $.when(null); },
+ get_context: function () { },
+ get_domain: function () { },
});
-openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.web.search.Field# */ {
+instance.web.search.Field = instance.web.search.Input.extend( /** @lends instance.web.search.Field# */ {
template: 'SearchView.field',
default_operator: '=',
/**
- * @constructs openerp.web.search.Field
- * @extends openerp.web.search.Input
+ * @constructs instance.web.search.Field
+ * @extends instance.web.search.Input
*
* @param view_section
* @param field
@@ -761,25 +983,21 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
init: function (view_section, field, view) {
this._super(view);
this.load_attrs(_.extend({}, field, view_section.attrs));
- this.filters = new openerp.web.search.FilterGroup(_.compact(_.map(
- view_section.children, function (filter_node) {
- if (filter_node.attrs.string &&
- typeof console !== 'undefined' && console.debug) {
- console.debug("Filter-in-field with a 'string' attribute "
- + "in view", view);
- }
- delete filter_node.attrs.string;
- return new openerp.web.search.Filter(
- filter_node, view);
- })), view);
- this.make_id('input', field.type, this.attrs.name);
},
- start: function () {
- this._super();
- this.filters.start();
+ facet_for: function (value) {
+ return $.when(new VS.model.SearchFacet({
+ category: this.attrs.string || this.attrs.name,
+ value: String(value),
+ json: value,
+ field: this,
+ app: this.view.vs
+ }));
},
- get_context: function () {
- var val = this.get_value();
+ get_value: function (facet) {
+ return facet.value();
+ },
+ get_context: function (facet) {
+ var val = this.get_value(facet);
// A field needs a value to be "active", and a context to send when
// active
var has_value = (val !== null && val !== '');
@@ -787,10 +1005,10 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
if (!(has_value && context)) {
return;
}
- return _.extend(
- {}, context,
- {own_values: {self: val}});
+ return new instance.web.CompoundContext(context)
+ .set_eval_context({self: val});
},
+ get_groupby: function () { },
/**
* Function creating the returned domain for the field, override this
* methods in children if you only need to customize the field's domain
@@ -802,11 +1020,11 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
* @param {Number|String} value parsed value for the field
* @returns {Array<Array>} domain to include in the resulting search
*/
- make_domain: function (name, operator, value) {
- return [[name, operator, value]];
+ make_domain: function (name, operator, facet) {
+ return [[name, operator, this.get_value(facet)]];
},
- get_domain: function () {
- var val = this.get_value();
+ get_domain: function (facet) {
+ var val = this.get_value(facet);
if (val === null || val === '') {
return;
}
@@ -816,9 +1034,10 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
return this.make_domain(
this.attrs.name,
this.attrs.operator || this.default_operator,
- val);
+ facet);
}
- return _.extend({}, domain, {own_values: {self: val}});
+ return new instance.web.CompoundDomain(domain)
+ .set_eval_context({self: val});
}
});
/**
@@ -829,15 +1048,25 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
* * The Javascript and the HTML values are identical (strings)
*
* @class
- * @extends openerp.web.search.Field
+ * @extends instance.web.search.Field
*/
-openerp.web.search.CharField = openerp.web.search.Field.extend( /** @lends openerp.web.search.CharField# */ {
+instance.web.search.CharField = instance.web.search.Field.extend( /** @lends instance.web.search.CharField# */ {
default_operator: 'ilike',
- get_value: function () {
- return this.$element.val();
+ complete: function (value) {
+ if (_.isEmpty(value)) { return $.when(null); }
+ var label = _.str.sprintf(_.str.escapeHTML(
+ _t("Search %(field)s for: %(value)s")), {
+ field: '<em>' + this.attrs.string + '</em>',
+ value: '<strong>' + _.str.escapeHTML(value) + '</strong>'});
+ return $.when([{
+ category: this.attrs.string,
+ label: label,
+ value: value,
+ field: this
+ }]);
}
});
-openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends openerp.web.search.NumberField# */{
+instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{
get_value: function () {
if (!this.$element.val()) {
return null;
@@ -846,7 +1075,7 @@ openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends open
check = Number(this.$element.val());
if (isNaN(val) || val !== check) {
this.$element.addClass('error');
- throw new openerp.web.search.Invalid(
+ throw new instance.web.search.Invalid(
this.attrs.name, this.$element.val(), this.error_message);
}
this.$element.removeClass('error');
@@ -855,13 +1084,13 @@ openerp.web.search.NumberField = openerp.web.search.Field.extend(/** @lends open
});
/**
* @class
- * @extends openerp.web.search.NumberField
+ * @extends instance.web.search.NumberField
*/
-openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.IntegerField# */{
+instance.web.search.IntegerField = instance.web.search.NumberField.extend(/** @lends instance.web.search.IntegerField# */{
error_message: _t("not a valid integer"),
parse: function (value) {
try {
- return openerp.web.parse_value(value, {'widget': 'integer'});
+ return instance.web.parse_value(value, {'widget': 'integer'});
} catch (e) {
return NaN;
}
@@ -869,13 +1098,13 @@ openerp.web.search.IntegerField = openerp.web.search.NumberField.extend(/** @len
});
/**
* @class
- * @extends openerp.web.search.NumberField
+ * @extends instance.web.search.NumberField
*/
-openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends openerp.web.search.FloatField# */{
+instance.web.search.FloatField = instance.web.search.NumberField.extend(/** @lends instance.web.search.FloatField# */{
error_message: _t("not a valid number"),
parse: function (value) {
try {
- return openerp.web.parse_value(value, {'widget': 'float'});
+ return instance.web.parse_value(value, {'widget': 'float'});
} catch (e) {
return NaN;
}
@@ -883,9 +1112,9 @@ openerp.web.search.FloatField = openerp.web.search.NumberField.extend(/** @lends
});
/**
* @class
- * @extends openerp.web.search.Field
+ * @extends instance.web.search.Field
*/
-openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends openerp.web.search.SelectionField# */{
+instance.web.search.SelectionField = instance.web.search.Field.extend(/** @lends instance.web.search.SelectionField# */{
// This implementation is a basic <select> field, but it may have to be
// altered to be more in line with the GTK client, which uses a combo box
// (~ jquery.autocomplete):
@@ -904,48 +1133,48 @@ openerp.web.search.SelectionField = openerp.web.search.Field.extend(/** @lends o
return !item[1];
});
},
- get_value: function () {
- var index = parseInt(this.$element.val(), 10);
- if (isNaN(index)) { return null; }
- var value = this.attrs.selection[index][0];
- if (value === false) { return null; }
- return value;
- },
- /**
- * The selection field needs a default ``false`` value in case none is
- * provided, so that selector options with a ``false`` value (convention
- * for explicitly empty options) get selected by default rather than the
- * first (value-holding) option in the selection.
- *
- * @param {Object} defaults search default values
- */
- render: function (defaults) {
- if (!defaults[this.attrs.name]) {
- defaults[this.attrs.name] = false;
- }
- return this._super(defaults);
- },
- clear: function () {
- var self = this, d = $.Deferred(), selection = this.attrs.selection;
- for(var index=0; index<selection.length; ++index) {
- var item = selection[index];
- if (!item[1]) {
- setTimeout(function () {
- // won't override mutable, because we immediately bail out
- //noinspection JSReferencingMutableVariableFromClosure
- self.$element.val(index);
- d.resolve();
- }, 0);
- return d.promise();
- }
- }
- return d.resolve().promise();
+ complete: function (needle) {
+ var self = this;
+ var results = _(this.attrs.selection).chain()
+ .filter(function (sel) {
+ var value = sel[0], label = sel[1];
+ if (!value) { return false; }
+ return label.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
+ })
+ .map(function (sel) {
+ return {
+ category: self.attrs.string,
+ field: self,
+ value: sel[1],
+ json: sel[0]
+ };
+ }).value();
+ if (_.isEmpty(results)) { return $.when(null); }
+ return $.when.apply(null, [{
+ category: this.attrs.string
+ }].concat(results));
+ },
+ facet_for: function (value) {
+ var match = _(this.attrs.selection).detect(function (sel) {
+ return sel[0] === value;
+ });
+ if (!match) { return $.when(null); }
+ return $.when(new VS.model.SearchFacet({
+ category: this.attrs.string,
+ value: match[1],
+ json: match[0],
+ field: this,
+ app: this.view.app
+ }));
+ },
+ get_value: function (facet) {
+ return facet.get('json');
}
});
-openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @lends openerp.web.search.BooleanField# */{
+instance.web.search.BooleanField = instance.web.search.SelectionField.extend(/** @lends instance.web.search.BooleanField# */{
/**
- * @constructs openerp.web.search.BooleanField
- * @extends openerp.web.search.BooleanField
+ * @constructs instance.web.search.BooleanField
+ * @extends instance.web.search.BooleanField
*/
init: function () {
this._super.apply(this, arguments);
@@ -954,25 +1183,8 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @
['false', _t("No")]
];
},
- /**
- * Search defaults likely to be boolean values (for a boolean field).
- *
- * In the HTML, we only want/get strings, and our strings here are ``true``
- * and ``false``, so ensure we use precisely those by truth-testing the
- * default value (iif there is one in the view's defaults).
- *
- * @param {Object} defaults default values for this search view
- * @returns {String} rendered boolean field
- */
- render: function (defaults) {
- var name = this.attrs.name;
- if (name in defaults) {
- defaults[name] = defaults[name] ? "true" : "false";
- }
- return this._super(defaults);
- },
- get_value: function () {
- switch (this._super()) {
+ get_value: function (facet) {
+ switch (this._super(facet)) {
case 'false': return false;
case 'true': return true;
default: return null;
@@ -981,26 +1193,27 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @
});
/**
* @class
- * @extends openerp.web.search.DateField
+ * @extends instance.web.search.DateField
*/
-openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends openerp.web.search.DateField# */{
- template: "SearchView.date",
- start: function () {
- this._super();
- // FIXME: this insanity puts a div inside a span
- this.datewidget = new openerp.web.DateWidget(this);
- this.datewidget.prependTo(this.$element);
- this.datewidget.$element.find("input")
- .attr("size", 15)
- .attr("autofocus", this.attrs.default_focus === '1' ? 'autofocus' : null)
- .removeAttr('style');
- this.datewidget.set_value(this.defaults[this.attrs.name] || false);
- },
- get_value: function () {
- return this.datewidget.get_value() || null;
- },
- clear: function () {
- this.datewidget.set_value(false);
+instance.web.search.DateField = instance.web.search.Field.extend(/** @lends instance.web.search.DateField# */{
+ get_value: function (facet) {
+ return instance.web.date_to_str(facet.get('json'));
+ },
+ complete: function (needle) {
+ var d = Date.parse(needle);
+ if (!d) { return $.when(null); }
+ var value = instance.web.format_value(d, this.attrs);
+ var label = _.str.sprintf(_.str.escapeHTML(
+ _t("Search %(field)s at: %(value)s")), {
+ field: '<em>' + this.attrs.string + '</em>',
+ value: '<strong>' + value + '</strong>'});
+ return $.when([{
+ category: this.attrs.string,
+ label: label,
+ value: value,
+ json: d,
+ field: this
+ }]);
}
});
/**
@@ -1012,205 +1225,135 @@ openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends opener
* spanning the whole day selected by the date widget
*
* @class
- * @extends openerp.web.DateField
+ * @extends instance.web.DateField
*/
-openerp.web.search.DateTimeField = openerp.web.search.DateField.extend(/** @lends openerp.web.search.DateTimeField# */{
- make_domain: function (name, operator, value) {
- return ['&', [name, '>=', value + ' 00:00:00'],
- [name, '<=', value + ' 23:59:59']];
+instance.web.search.DateTimeField = instance.web.search.DateField.extend(/** @lends instance.web.search.DateTimeField# */{
+ get_value: function (facet) {
+ return instance.web.datetime_to_str(facet.get('json'));
}
});
-openerp.web.search.ManyToOneField = openerp.web.search.CharField.extend({
+instance.web.search.ManyToOneField = instance.web.search.CharField.extend({
init: function (view_section, field, view) {
this._super(view_section, field, view);
+ this.model = new instance.web.Model(this.attrs.relation);
+ },
+ complete: function (needle) {
var self = this;
- this.got_name = $.Deferred().then(function () {
- self.$element.val(self.name);
+ // TODO: context
+ // FIXME: "concurrent" searches (multiple requests, mis-ordered responses)
+ return this.model.call('name_search', [], {
+ name: needle,
+ limit: 8,
+ context: {}
+ }).pipe(function (results) {
+ if (_.isEmpty(results)) { return null; }
+ return [{category: self.attrs.string}].concat(
+ _(results).map(function (result) {
+ return {
+ category: self.attrs.string,
+ value: result[1],
+ json: result[0],
+ field: self
+ };
+ }));
});
- this.model = new openerp.web.Model(this.attrs['relation']);
-
- this.orderer = new openerp.web.DropMisordered();
- },
- start: function () {
- this._super();
- this.setup_autocomplete();
- var started = $.Deferred();
- this.got_name.then(function () { started.resolve();},
- function () { started.resolve(); });
- return started.promise();
},
- setup_autocomplete: function () {
+ facet_for: function (value) {
var self = this;
- this.$element.autocomplete({
- source: function (req, resp) {
- self.orderer.add(self.model.call('name_search', null, {
- name: req.term,
- args: self.attrs.domain,
- limit: 8
- })).then(function (data) {
- resp(_.map(data, function (result) {
- return {id: result[0], label: result[1]}
- }));
- });
- },
- select: function (event, ui) {
- self.id = ui.item.id;
- self.name = ui.item.label;
- },
- delay: 100
- })
- },
- on_name_get: function (name_get) {
- if (!name_get.length) {
- delete this.id;
- this.got_name.reject();
- return;
- }
- this.name = name_get[0][1];
- this.got_name.resolve();
- },
- render: function (defaults) {
- if (defaults[this.attrs.name]) {
- this.id = defaults[this.attrs.name];
- if (this.id instanceof Array)
- this.id = this.id[0];
- // TODO: maybe this should not be completely removed
- delete defaults[this.attrs.name];
- this.model.call('name_get', [[this.id]]).then(
- this.proxy('on_name_get'));
- } else {
- this.got_name.reject();
+ if (value instanceof Array) {
+ return $.when(new VS.model.SearchFacet({
+ category: this.attrs.string,
+ value: value[1],
+ json: value[0],
+ field: this,
+ app: this.view.vs
+ }));
}
- return this._super(defaults);
+ return this.model.call('name_get', [value], {}).pipe(function (names) {
+ return new VS.model.SearchFacet({
+ category: self.attrs.string,
+ value: names[0][1],
+ json: names[0][0],
+ field: self,
+ app: self.view.vs
+ });
+ })
},
- make_domain: function (name, operator, value) {
- if (this.id && this.name) {
- if (value === this.name) {
- return [[name, '=', this.id]];
- } else {
- delete this.id;
- delete this.name;
- }
+ make_domain: function (name, operator, facet) {
+ // ``json`` -> actual auto-completed id
+ if (facet.get('json')) {
+ return [[name, '=', facet.get('json')]];
}
- return this._super(name, operator, value);
+
+ return this._super(name, operator, facet);
}
});
-openerp.web.search.ExtendedSearch = openerp.web.search.Input.extend({
- template: 'SearchView.extended_search',
- init: function (parent, model) {
- this._super(parent);
- this.model = model;
- },
- add_group: function() {
- var group = new openerp.web.search.ExtendedSearchGroup(this, this.fields);
- group.appendTo(this.$element.find('.searchview_extended_groups_list'));
- this.check_last_element();
- },
+instance.web.search.Advanced = instance.web.search.Input.extend({
+ template: 'SearchView.advanced',
start: function () {
- this._super();
- this.$element.closest("table.oe-searchview-render-line").css("display", "none");
var self = this;
- this.rpc("/web/searchview/fields_get",
- {"model": this.model}, function(data) {
- self.fields = data.fields;
- if (!('id' in self.fields)) {
- self.fields.id = {
- string: 'ID',
- type: 'id'
- }
- }
- openerp.web.search.add_expand_listener(self.$element);
- self.$element.find('.searchview_extended_add_group').click(function (e) {
- self.add_group();
+ this.$element
+ .on('keypress keydown keyup', function (e) { e.stopPropagation(); })
+ .on('click', 'h4', function () {
+ self.$element.toggleClass('oe_opened');
+ }).on('click', 'button.oe_add_condition', function () {
+ self.append_proposition();
+ }).on('submit', 'form', function (e) {
+ e.preventDefault();
+ self.commit_search();
});
+ return $.when(
+ this._super(),
+ this.rpc("/web/searchview/fields_get", {model: this.view.model}, function(data) {
+ self.fields = _.extend({
+ id: { string: 'ID', type: 'id' }
+ }, data.fields);
+ })).then(function () {
+ self.append_proposition();
});
},
- get_context: function() {
- return null;
- },
- get_domain: function() {
- if (!this.$element) {
- return null; // not a logical state but sometimes it happens
- }
- if(this.$element.closest("table.oe-searchview-render-line").css("display") == "none") {
- return null;
- }
- return _.reduce(this.getChildren(),
- function(mem, x) { return mem.concat(x.get_domain());}, []);
- },
- on_activate: function() {
- this.add_group();
- var table = this.$element.closest("table.oe-searchview-render-line");
- table.css("display", "");
- if(this.$element.hasClass("folded")) {
- this.$element.toggleClass("folded expanded");
- }
- },
- hide: function() {
- var table = this.$element.closest("table.oe-searchview-render-line");
- table.css("display", "none");
- if(this.$element.hasClass("expanded")) {
- this.$element.toggleClass("folded expanded");
- }
- },
- check_last_element: function() {
- _.each(this.getChildren(), function(x) {x.set_last_group(false);});
- if (this.getChildren().length >= 1) {
- this.getChildren()[this.getChildren().length - 1].set_last_group(true);
- }
- }
-});
-
-openerp.web.search.ExtendedSearchGroup = openerp.web.OldWidget.extend({
- template: 'SearchView.extended_search.group',
- init: function (parent, fields) {
- this._super(parent);
- this.fields = fields;
- },
- add_prop: function() {
- var prop = new openerp.web.search.ExtendedSearchProposition(this, this.fields);
- var render = prop.render({'index': this.getChildren().length - 1});
- this.$element.find('.searchview_extended_propositions_list').append(render);
- prop.start();
+ append_proposition: function () {
+ return (new instance.web.search.ExtendedSearchProposition(this, this.fields))
+ .appendTo(this.$element.find('ul'));
},
- start: function () {
- var _this = this;
- this.add_prop();
- this.$element.find('.searchview_extended_add_proposition').click(function () {
- _this.add_prop();
+ commit_search: function () {
+ var self = this;
+ // Get domain sections from all propositions
+ var children = this.getChildren(),
+ domain = _.invoke(children, 'get_proposition');
+ var filters = _(domain).map(function (section) {
+ return new instance.web.search.Filter({attrs: {
+ string: _.str.sprintf('%s(%s)%s',
+ section[0], section[1], section[2]),
+ domain: [section]
+ }}, self.view);
});
- this.$element.find('.searchview_extended_delete_group').click(function () {
- _this.destroy();
+ // Create Filter (& FilterGroup around it) with that domain
+ var f = new instance.web.search.FilterGroup(filters, this.view);
+ // add group to query
+ this.view.vs.searchQuery.add({
+ category: _t("Advanced"),
+ value: _(filters).map(function (f) {
+ return f.attrs.string || f.attrs.name }).join(' | '),
+ json: filters,
+ field: f,
+ app: this.view.vs
});
- },
- get_domain: function() {
- var props = _(this.getChildren()).chain().map(function(x) {
- return x.get_proposition();
- }).compact().value();
- var choice = this.$element.find(".searchview_extended_group_choice").val();
- var op = choice == "all" ? "&" : "|";
- return choice == "none" ? ['!'] : [].concat(
- _.map(_.range(_.max([0,props.length - 1])), function() { return op; }),
- props);
- },
- destroy: function() {
- var parent = this.getParent();
- if (this.getParent().getChildren().length == 1)
- this.getParent().hide();
- this._super();
- parent.check_last_element();
- },
- set_last_group: function(is_last) {
- this.$element.toggleClass('last_group', is_last);
+ // remove all propositions
+ _.invoke(children, 'destroy');
+ // add new empty proposition
+ this.append_proposition();
+ // TODO: API on searchview
+ this.view.$element.removeClass('oe_searchview_open_drawer');
}
});
-openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/** @lends openerp.web.search.ExtendedSearchProposition# */{
+instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{
template: 'SearchView.extended_search.proposition',
/**
- * @constructs openerp.web.search.ExtendedSearchProposition
- * @extends openerp.web.OldWidget
+ * @constructs instance.web.search.ExtendedSearchProposition
+ * @extends instance.web.OldWidget
*
* @param parent
* @param fields
@@ -1225,8 +1368,6 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/**
this.value = null;
},
start: function () {
- this.$element = $("#" + this.element_id);
- this.select_field(this.fields.length > 0 ? this.fields[0] : null);
var _this = this;
this.$element.find(".searchview_extended_prop_field").change(function() {
_this.changed();
@@ -1234,14 +1375,7 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/**
this.$element.find('.searchview_extended_delete_prop').click(function () {
_this.destroy();
});
- },
- destroy: function() {
- var parent;
- if (this.getParent().getChildren().length == 1)
- parent = this.getParent();
- this._super();
- if (parent)
- parent.destroy();
+ this.changed();
},
changed: function() {
var nval = this.$element.find(".searchview_extended_prop_field").val();
@@ -1267,17 +1401,11 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/**
}
var type = field.type;
- try {
- openerp.web.search.custom_filters.get_object(type);
- } catch (e) {
- if (! e instanceof openerp.web.KeyNotFound) {
- throw e;
- }
- type = "char";
- console.log('Unknow field type ' + e.key);
+ var obj = instance.web.search.custom_filters.get_object(type);
+ if(obj === null) {
+ obj = instance.web.search.custom_filters.get_object("char");
}
- this.value = new (openerp.web.search.custom_filters.get_object(type))
- (this);
+ this.value = new (obj) (this);
if(this.value.set_field) {
this.value.set_field(field);
}
@@ -1301,28 +1429,24 @@ openerp.web.search.ExtendedSearchProposition = openerp.web.OldWidget.extend(/**
}
});
-openerp.web.search.ExtendedSearchProposition.Field = openerp.web.OldWidget.extend({
+instance.web.search.ExtendedSearchProposition.Field = instance.web.OldWidget.extend({
start: function () {
this.$element = $("#" + this.element_id);
}
});
-openerp.web.search.ExtendedSearchProposition.Char = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.Char = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.char',
operators: [
{value: "ilike", text: _lt("contains")},
{value: "not ilike", text: _lt("doesn't contain")},
{value: "=", text: _lt("is equal to")},
- {value: "!=", text: _lt("is not equal to")},
- {value: ">", text: _lt("greater than")},
- {value: "<", text: _lt("less than")},
- {value: ">=", text: _lt("greater or equal than")},
- {value: "<=", text: _lt("less or equal than")}
+ {value: "!=", text: _lt("is not equal to")}
],
get_value: function() {
return this.$element.val();
}
});
-openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.empty',
operators: [
{value: "=", text: _lt("is equal to")},
@@ -1337,11 +1461,11 @@ openerp.web.search.ExtendedSearchProposition.DateTime = openerp.web.search.Exten
},
start: function() {
this._super();
- this.datewidget = new openerp.web.DateTimeWidget(this);
+ this.datewidget = new instance.web.DateTimeWidget(this);
this.datewidget.prependTo(this.$element);
}
});
-openerp.web.search.ExtendedSearchProposition.Date = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.Date = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.empty',
operators: [
{value: "=", text: _lt("is equal to")},
@@ -1356,11 +1480,11 @@ openerp.web.search.ExtendedSearchProposition.Date = openerp.web.search.ExtendedS
},
start: function() {
this._super();
- this.datewidget = new openerp.web.DateWidget(this);
+ this.datewidget = new instance.web.DateWidget(this);
this.datewidget.prependTo(this.$element);
}
});
-openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.integer',
operators: [
{value: "=", text: _lt("is equal to")},
@@ -1372,16 +1496,16 @@ openerp.web.search.ExtendedSearchProposition.Integer = openerp.web.search.Extend
],
get_value: function() {
try {
- return openerp.web.parse_value(this.$element.val(), {'widget': 'integer'});
+ return instance.web.parse_value(this.$element.val(), {'widget': 'integer'});
} catch (e) {
return "";
}
}
});
-openerp.web.search.ExtendedSearchProposition.Id = openerp.web.search.ExtendedSearchProposition.Integer.extend({
+instance.web.search.ExtendedSearchProposition.Id = instance.web.search.ExtendedSearchProposition.Integer.extend({
operators: [{value: "=", text: _lt("is")}]
});
-openerp.web.search.ExtendedSearchProposition.Float = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.Float = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.float',
operators: [
{value: "=", text: _lt("is equal to")},
@@ -1393,13 +1517,13 @@ openerp.web.search.ExtendedSearchProposition.Float = openerp.web.search.Extended
],
get_value: function() {
try {
- return openerp.web.parse_value(this.$element.val(), {'widget': 'float'});
+ return instance.web.parse_value(this.$element.val(), {'widget': 'float'});
} catch (e) {
return "";
}
}
});
-openerp.web.search.ExtendedSearchProposition.Selection = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.selection',
operators: [
{value: "=", text: _lt("is")},
@@ -1412,7 +1536,7 @@ openerp.web.search.ExtendedSearchProposition.Selection = openerp.web.search.Exte
return this.$element.val();
}
});
-openerp.web.search.ExtendedSearchProposition.Boolean = openerp.web.search.ExtendedSearchProposition.Field.extend({
+instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.ExtendedSearchProposition.Field.extend({
template: 'SearchView.extended_search.proposition.boolean',
operators: [
{value: "=", text: _lt("is true")},
@@ -1423,21 +1547,21 @@ openerp.web.search.ExtendedSearchProposition.Boolean = openerp.web.search.Extend
}
});
-openerp.web.search.custom_filters = new openerp.web.Registry({
- 'char': 'openerp.web.search.ExtendedSearchProposition.Char',
- 'text': 'openerp.web.search.ExtendedSearchProposition.Char',
- 'one2many': 'openerp.web.search.ExtendedSearchProposition.Char',
- 'many2one': 'openerp.web.search.ExtendedSearchProposition.Char',
- 'many2many': 'openerp.web.search.ExtendedSearchProposition.Char',
+instance.web.search.custom_filters = new instance.web.Registry({
+ 'char': 'instance.web.search.ExtendedSearchProposition.Char',
+ 'text': 'instance.web.search.ExtendedSearchProposition.Char',
+ 'one2many': 'instance.web.search.ExtendedSearchProposition.Char',
+ 'many2one': 'instance.web.search.ExtendedSearchProposition.Char',
+ 'many2many': 'instance.web.search.ExtendedSearchProposition.Char',
- 'datetime': 'openerp.web.search.ExtendedSearchProposition.DateTime',
- 'date': 'openerp.web.search.ExtendedSearchProposition.Date',
- 'integer': 'openerp.web.search.ExtendedSearchProposition.Integer',
- 'float': 'openerp.web.search.ExtendedSearchProposition.Float',
- 'boolean': 'openerp.web.search.ExtendedSearchProposition.Boolean',
- 'selection': 'openerp.web.search.ExtendedSearchProposition.Selection',
+ 'datetime': 'instance.web.search.ExtendedSearchProposition.DateTime',
+ 'date': 'instance.web.search.ExtendedSearchProposition.Date',
+ 'integer': 'instance.web.search.ExtendedSearchProposition.Integer',
+ 'float': 'instance.web.search.ExtendedSearchProposition.Float',
+ 'boolean': 'instance.web.search.ExtendedSearchProposition.Boolean',
+ 'selection': 'instance.web.search.ExtendedSearchProposition.Selection',
- 'id': 'openerp.web.search.ExtendedSearchProposition.Id'
+ 'id': 'instance.web.search.ExtendedSearchProposition.Id'
});
};
diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js
index 2c0dc3d70b4..59d1fed9345 100644
--- a/addons/web/static/src/js/view_editor.js
+++ b/addons/web/static/src/js/view_editor.js
@@ -1,15 +1,15 @@
-openerp.web.view_editor = function(openerp) {
-var _t = openerp.web._t;
-var QWeb = openerp.web.qweb;
-openerp.web.ViewEditor = openerp.web.OldWidget.extend({
+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 openerp.web.DataSetSearch(this, 'ir.ui.view', null, null),
+ this.dataset = new instance.web.DataSetSearch(this, 'ir.ui.view', null, null),
this.model = dataset.model;
this.xml_element_id = 0;
- this.property = openerp.web.ViewEditor.property_widget;
+ this.property = instance.web.ViewEditor.property_widget;
this.one_object = false;
},
start: function() {
@@ -39,7 +39,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
select_view_id: self.parent.fields_view.view_id
}
};
- this.view_edit_dialog = new openerp.web.Dialog(this, {
+ this.view_edit_dialog = new instance.web.Dialog(this, {
title: action_title,
width: 850,
buttons: [
@@ -51,7 +51,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
}).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 openerp.web.ActionManager(this);
+ this.action_manager = new instance.web.ActionManager(this);
this.action_manager.appendTo(this.view_edit_dialog);
$.when(this.action_manager.do_action(action)).then(function() {
var viewmanager = self.action_manager.inner_viewmanager,
@@ -67,7 +67,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
},
on_create_view: function() {
var self = this;
- this.create_view_dialog = new openerp.web.Dialog(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 () {
@@ -115,8 +115,8 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
},
do_save_view: function(values) {
def = $.Deferred();
- var field_dataset = new openerp.web.DataSetSearch(this, this.model, null, null);
- var model_dataset = new openerp.web.DataSetSearch(this, 'ir.model', null, null);
+ 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) {
@@ -360,7 +360,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
edit_view: function(one_object) {
var self = this;
this.one_object = one_object;
- this.edit_xml_dialog = new openerp.web.Dialog(this, {
+ 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: [
@@ -392,7 +392,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
action_buttons: false
}
};
- var action_manager = new openerp.web.ActionManager(self);
+ var action_manager = new instance.web.ActionManager(self);
action_manager.do_action(action);
}},
{text: _t("Close"), click: function(){
@@ -517,7 +517,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
var self = this;
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 openerp.web.DataSetSearch(this, this.model, null, null);
+ var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null);
parent_tr = $(parent_tr).find('a').text();
field_dataset.call( 'fields_get', [], function(result) {
var fields = _.keys(result);
@@ -811,7 +811,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
},
on_edit_node: function(properties){
var self = this;
- this.edit_node_dialog = new openerp.web.Dialog(this,{
+ this.edit_node_dialog = new instance.web.Dialog(this,{
title: _t("Properties"),
width: 450,
buttons: [
@@ -880,7 +880,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
this.edit_widget = [];
self.ready = $.when(self.on_groups(properties)).then(function () {
_PROPERTIES_ATTRIBUTES['groups']['selection'] = self.groups;
- var values = _.keys( openerp.web.form.widgets.map);
+ var values = _.keys( instance.web.form.widgets.map);
values.push('');
values.sort();
_PROPERTIES_ATTRIBUTES['widget']['selection'] = values;
@@ -908,8 +908,8 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
def.resolve();
}
var group_ids = [], group_names = {}, groups = [];
- var res_groups = new openerp.web.DataSetSearch(this,'res.groups', null, null),
- model_data = new openerp.web.DataSetSearch(self,'ir.model.data', null, null);
+ 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;
@@ -935,7 +935,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
{'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 openerp.web.Dialog(this,{
+ this.add_node_dialog = new instance.web.Dialog(this,{
title: _t("Properties"),
width: 450,
buttons: [
@@ -980,7 +980,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
});
table_selector.find("td[id^=]").attr("width","100px");
self.add_node_dialog.$element.find('#new_field').click(function() {
- model_data = new openerp.web.DataSetSearch(self,'ir.model', null, null);
+ 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].id);
});
@@ -998,7 +998,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
action_buttons: true
}
};
- var action_manager = new openerp.web.ActionManager(self);
+ var action_manager = new instance.web.ActionManager(self);
$.when(action_manager.do_action(action)).then(function() {
var controller = action_manager.dialog_viewmanager.views['form'].controller;
controller.on_button_cancel.add_last(function(){
@@ -1015,7 +1015,7 @@ openerp.web.ViewEditor = openerp.web.OldWidget.extend({
});
}
});
-openerp.web.ViewEditor.Field = openerp.web.Class.extend({
+instance.web.ViewEditor.Field = instance.web.Class.extend({
init: function(view, widget) {
this.$element = view.$element;
this.dirty = false;
@@ -1041,7 +1041,7 @@ openerp.web.ViewEditor.Field = openerp.web.Class.extend({
validate: function() {
this.is_invalid = false;
try {
- var value = openerp.web.parse_value(this.get_value(), this, '');
+ var value = instance.web.parse_value(this.get_value(), this, '');
this.is_invalid = this.required && value === '';
} catch(e) {
this.is_invalid = true;
@@ -1051,7 +1051,7 @@ openerp.web.ViewEditor.Field = openerp.web.Class.extend({
return _.str.sprintf("<td id = %s>%s</td>", this.name, QWeb.render(this.template, {widget: this}))
}
});
-openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({
+instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({
template : "vieweditor_boolean",
start: function() {
var self = this;
@@ -1069,7 +1069,7 @@ openerp.web.ViewEditor.FieldBoolean = openerp.web.ViewEditor.Field.extend({
return this.$element.find("input[id=" + this.name + "]").is(':checked')? "1" : null;
}
});
-openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({
+instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({
template : "vieweditor_char",
start: function () {
var self = this;
@@ -1085,7 +1085,7 @@ openerp.web.ViewEditor.FieldChar = openerp.web.ViewEditor.Field.extend({
return this.$element.find("input[id=" + this.name + "]").val();
}
});
-openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({
+instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({
template : "vieweditor_selection",
start: function () {
var self = this;
@@ -1115,7 +1115,7 @@ openerp.web.ViewEditor.FieldSelect = openerp.web.ViewEditor.Field.extend({
return this.$element.find("select[id=" + this.name + "]").val();
}
});
-openerp.web.ViewEditor.FieldSelectMulti = openerp.web.ViewEditor.FieldSelect.extend({
+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);
@@ -1131,7 +1131,7 @@ openerp.web.ViewEditor.FieldSelectMulti = openerp.web.ViewEditor.FieldSelect.ext
});
}
});
-openerp.web.ViewEditor.FieldFloat = openerp.web.ViewEditor.FieldChar.extend({
+instance.web.ViewEditor.FieldFloat = instance.web.ViewEditor.FieldChar.extend({
});
var _PROPERTIES = {
@@ -1196,11 +1196,11 @@ var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD',
'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner',
'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph'
];
-openerp.web.ViewEditor.property_widget = new openerp.web.Registry({
- 'boolean' : 'openerp.web.ViewEditor.FieldBoolean',
- 'selection_multi' : 'openerp.web.ViewEditor.FieldSelectMulti',
- 'selection' : 'openerp.web.ViewEditor.FieldSelect',
- 'char' : 'openerp.web.ViewEditor.FieldChar',
- 'float' : 'openerp.web.ViewEditor.FieldFloat'
+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 50c5dcb976a..7e5b173fd37 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -1,31 +1,32 @@
-openerp.web.form = function (openerp) {
+openerp.web.form = function (instance) {
+var _t = instance.web._t,
+ _lt = instance.web._lt;
+var QWeb = instance.web.qweb;
-var _t = openerp.web._t,
- _lt = openerp.web._lt;
-var QWeb = openerp.web.qweb;
+/** @namespace */
+instance.web.form = {};
-openerp.web.views.add('form', 'openerp.web.FormView');
-openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */{
+instance.web.views.add('form', 'instance.web.FormView');
+instance.web.FormView = instance.web.View.extend({
/**
* Indicates that this view is not searchable, and thus that no search
* view should be displayed (if there is one active).
*/
searchable: false,
- readonly : false,
- form_template: "FormView",
+ template: "FormView",
display_name: _lt('Form'),
+ view_type: "form",
/**
- * @constructs openerp.web.FormView
- * @extends openerp.web.View
+ * @constructs instance.web.FormView
+ * @extends instance.web.View
*
- * @param {openerp.web.Session} session the current openerp session
- * @param {openerp.web.DataSet} dataset the dataset this view will work with
+ * @param {instance.web.Session} session the current openerp session
+ * @param {instance.web.DataSet} dataset the dataset this view will work with
* @param {String} view_id the identifier of the OpenERP view object
* @param {Object} options
- * - sidebar : [true|false]
* - resize_textareas : [true|false|max_height]
*
- * @property {openerp.web.Registry} registry=openerp.web.form.widgets widgets registry for this form view instance
+ * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance
*/
init: function(parent, dataset, view_id, options) {
this._super(parent);
@@ -34,98 +35,86 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.model = dataset.model;
this.view_id = view_id || false;
this.fields_view = {};
- this.widgets = {};
- this.widgets_counter = 0;
this.fields = {};
this.fields_order = [];
this.datarecord = {};
this.default_focus_field = null;
this.default_focus_button = null;
- this.registry = openerp.web.form.widgets;
+ this.fields_registry = instance.web.form.widgets;
+ this.tags_registry = instance.web.form.tags;
this.has_been_loaded = $.Deferred();
- this.$form_header = null;
this.translatable_fields = [];
_.defaults(this.options, {
- "not_interactible_on_create": false
+ "not_interactible_on_create": false,
+ "initial_mode": "view",
});
this.is_initialized = $.Deferred();
this.mutating_mutex = new $.Mutex();
this.on_change_mutex = new $.Mutex();
this.reload_mutex = new $.Mutex();
- },
- start: function() {
- this._super();
- return this.init_view();
- },
- init_view: function() {
- if (this.embedded_view) {
- var def = $.Deferred().then(this.on_loaded);
- var self = this;
- $.async_when().then(function() {def.resolve(self.embedded_view);});
- return def.promise();
- } else {
- var context = new openerp.web.CompoundContext(this.dataset.get_context());
- return this.rpc("/web/view/load", {
- "model": this.model,
- "view_id": this.view_id,
- "view_type": "form",
- toolbar: this.options.sidebar,
- context: context
- }, this.on_loaded);
- }
+ this.mode = null;
+ this.rendering_engine = new instance.web.form.FormRenderingEngineReadonly(this);
+ this.qweb = null; // A QWeb instance will be created if the view is a QWeb template
},
destroy: function() {
- if (this.sidebar) {
- this.sidebar.attachments.destroy();
- this.sidebar.destroy();
- }
- _.each(this.widgets, function(w) {
+ _.each(this.get_widgets(), function(w) {
w.destroy();
});
this._super();
},
- reposition: function ($e) {
- this.$element = $e;
- this.on_loaded();
- },
on_loaded: function(data) {
var self = this;
if (!data) {
- throw "No data provided.";
+ throw new Error("No data provided.");
}
- if (this.root_frame) {
- throw "Form view does not support multiple calls to on_loaded";
+ if (this.arch) {
+ throw "Form view does not support multiple calls to on_loaded";
}
this.fields_order = [];
this.fields_view = data;
- this.rendered = QWeb.render(this.form_template, {'widget': this});
- this.$element.html(this.rendered);
-
- this.root_frame = instanciate_widget(this.registry.get_object('frame'), this, this.fields_view.arch);
- var to_append = $(".oe_form_header", this.$element);
- this.root_frame.appendTo(to_append.length > 0 ? to_append : this.$element);
- this.root_frame.$element.children().unwrap();
-
- this.$form_header = this.$element.find('.oe_form_header:first');
- this.$form_header.find('div.oe_form_pager button[data-pager-action]').click(function() {
+ this.rendering_engine.set_fields_registry(this.fields_registry);
+ this.rendering_engine.set_tags_registry(this.tags_registry);
+ if (!this.extract_qweb_template(data)) {
+ this.rendering_engine.set_fields_view(data);
+ var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container');
+ this.rendering_engine.render_to($dest);
+ }
+
+ this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar');
+
+ this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self}));
+ if (this.options.$buttons) {
+ this.$buttons.appendTo(this.options.$buttons);
+ } else {
+ this.$element.find('.oe_form_buttons').replaceWith(this.$buttons);
+ }
+ this.$buttons
+ .on('click','button.oe_form_button_save',this.on_button_save)
+ .on('click','button.oe_form_button_cancel',this.on_button_cancel)
+ .on('click','button.oe_form_button_edit',this.on_button_edit)
+ .on('click','button.oe_form_button_create',this.on_button_create)
+ .on('click','button.oe_form_button_duplicate',this.on_button_duplicate)
+ .on('click','button.oe_form_button_delete',this.on_button_delete);
+
+ 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.$form_header.find('button.oe_form_button_save').click(this.on_button_save);
- this.$form_header.find('button.oe_form_button_cancel').click(this.on_button_cancel);
-
- if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) {
- this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
- this.sidebar.start();
- this.sidebar.do_unfold();
- this.sidebar.attachments = new openerp.web.form.SidebarAttachments(this.sidebar, this);
- this.sidebar.add_toolbar(this.fields_view.toolbar);
- this.set_common_sidebar_sections(this.sidebar);
-
- this.sidebar.add_section(_t('Customize'), 'customize');
- this.sidebar.add_items('customize', [{
+ if (!this.sidebar && this.options.$sidebar) {
+ this.sidebar = new instance.web.Sidebar(this);
+ this.sidebar.appendTo(this.$sidebar);
+ if(this.fields_view.toolbar) {
+ this.sidebar.add_toolbar(this.fields_view.toolbar);
+ }
+ this.sidebar.add_items('other', [{
label: _t('Set Default'),
form: this,
callback: function (item) {
@@ -133,9 +122,61 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
}]);
}
+ this.on("change:mode", this, this.switch_mode);
+ this.set({mode: this.options.initial_mode});
this.has_been_loaded.resolve();
+ return $.when();
+ },
+ extract_qweb_template: function(fvg) {
+ for (var i=0, ii=fvg.arch.children.length; i < ii; i++) {
+ var child = fvg.arch.children[i];
+ if (child.tag === "templates") {
+ this.qweb = new QWeb2.Engine();
+ this.qweb.add_template(instance.web.json_node_to_xml(child));
+ if (!this.qweb.has_template('form')) {
+ throw new Error("No QWeb template found for form view");
+ }
+ return true;
+ }
+ }
+ this.qweb = null;
+ return false;
+ },
+ get_fvg_from_qweb: function(record) {
+ var view = this.qweb.render('form', this.get_qweb_context(record));
+ var fvg = _.clone(this.fields_view);
+ fvg.arch = instance.web.xml_to_json(instance.web.str_to_xml(view).firstChild);
+ return fvg;
+ },
+ get_qweb_context: function(record) {
+ var self = this,
+ new_record = {};
+ _.each(record, function(value_, name) {
+ var r = _.clone(self.fields_view.fields[name] || {});
+ if ((r.type === 'date' || r.type === 'datetime') && value_) {
+ r.raw_value = instance.web.auto_str_to_date(value_);
+ } else {
+ r.raw_value = value_;
+ }
+ r.value = instance.web.format_value(value_, r);
+ new_record[name] = r;
+ });
+ return {
+ record : new_record,
+ new_record : !record.id
+ };
+ },
+ kill_current_form: function() {
+ _.each(this.getChildren(), function(el) {
+ el.destroy();
+ });
+ this.fields = {};
+ this.fields_order = [];
+ this.default_focus_field = null;
+ this.default_focus_button = null;
+ this.translatable_fields = [];
+ this.$element.find('.oe_form_container').empty();
},
-
do_load_state: function(state, warm) {
if (state.id && this.datarecord.id != state.id) {
if (!this.dataset.get_id_index(state.id)) {
@@ -147,9 +188,18 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
}
},
-
do_show: function () {
var self = this;
+ if (this.sidebar) {
+ this.sidebar.$element.show();
+ }
+ if (this.$buttons) {
+ this.$buttons.show();
+ this.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty');
+ }
+ if (this.$pager) {
+ this.$pager.show();
+ }
this.$element.show().css('visibility', 'hidden');
this.$element.removeClass('oe_form_dirty');
return this.has_been_loaded.pipe(function() {
@@ -165,17 +215,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
result.pipe(function() {
self.$element.css('visibility', 'visible');
});
- if (self.sidebar) {
- self.sidebar.$element.show();
- }
return result;
});
},
do_hide: function () {
- this._super();
if (this.sidebar) {
this.sidebar.$element.hide();
}
+ if (this.$buttons) {
+ this.$buttons.hide();
+ }
+ if (this.$pager) {
+ this.$pager.hide();
+ }
+ this._super();
},
on_record_loaded: function(record) {
var self = this, set_values = [];
@@ -185,13 +238,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
this.datarecord = record;
+ if (this.qweb) {
+ this.kill_current_form();
+ this.rendering_engine.set_fields_view(this.get_fvg_from_qweb(record));
+ var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container');
+ this.rendering_engine.render_to($dest);
+ }
+
_(this.fields).each(function (field, f) {
- field.reset();
+ field._dirty_flag = false;
var result = field.set_value(self.datarecord[f] || false);
set_values.push(result);
- $.when(result).then(function() {
- field.validate();
- });
});
return $.when.apply(null, set_values).pipe(function() {
if (!record.id) {
@@ -200,7 +257,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
_.each(self.fields_order, function(field_name) {
if (record[field_name] !== undefined) {
var field = self.fields[field_name];
- field.dirty = true;
+ field._dirty_flag = true;
self.do_onchange(field);
}
});
@@ -209,7 +266,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
self.is_initialized.resolve();
self.do_update_pager(record.id == null);
if (self.sidebar) {
- self.sidebar.attachments.do_update();
+ self.sidebar.do_attachement_update(self.dataset, self.datarecord.id);
}
if (self.default_focus_field) {
self.default_focus_field.focus();
@@ -218,20 +275,15 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
self.do_push_state({id:record.id});
}
self.$element.removeClass('oe_form_dirty');
+ self.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty');
});
},
on_form_changed: function() {
- for (var w in this.widgets) {
- w = this.widgets[w];
- w.process_modifiers();
- if (w.field) {
- w.validate();
- }
- w.update_dom();
- }
+ this.trigger("view_content_has_changed");
},
do_notify_change: function() {
this.$element.addClass('oe_form_dirty');
+ this.$buttons.find('.oe_form_button_save').addClass('oe_form_button_save_dirty');
},
on_pager_action: function(action) {
if (this.can_be_discarded()) {
@@ -253,11 +305,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
},
do_update_pager: function(hide_index) {
- var $pager = this.$form_header.find('div.oe_form_pager');
var index = hide_index ? '-' : this.dataset.index + 1;
- $pager.find('button').prop('disabled', this.dataset.ids.length < 2);
- $pager.find('span.oe_pager_index').html(index);
- $pager.find('span.oe_pager_count').html(this.dataset.ids.length);
+ this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end()
+ .find('span.oe_pager_index').html(index).end()
+ .find('span.oe_pager_count').html(this.dataset.ids.length);
},
parse_on_change: function (on_change, widget) {
var self = this;
@@ -278,7 +329,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
'None': function () {return null;},
'context': function (i) {
context_index = i;
- var ctx = new openerp.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {});
+ var ctx = new instance.web.CompoundContext(self.dataset.get_context(), widget.build_context() ? widget.build_context() : {});
return ctx;
}
};
@@ -296,8 +347,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
// form field
if (self.fields[field]) {
- var value = self.fields[field].get_on_change_value();
- return value == null ? false : value;
+ var value_ = self.fields[field].get_value();
+ return value_ == null ? false : value_;
}
// parent field
var splitted = field.split('.');
@@ -360,17 +411,17 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
if (widget.field['change_default']) {
- var fieldname = widget.name, value;
+ var fieldname = widget.name, value_;
if (response.value && (fieldname in response.value)) {
// Use value from onchange if onchange executed
- value = response.value[fieldname];
+ value_ = response.value[fieldname];
} else {
// otherwise get form value for field
- value = self.fields[fieldname].get_on_change_value();
+ value_ = self.fields[fieldname].get_value();
}
- var condition = fieldname + '=' + value;
+ var condition = fieldname + '=' + value_;
- if (value) {
+ if (value_) {
can_process_onchange = self.rpc({
url: '/web/dataset/call',
async: false
@@ -411,10 +462,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var field = this.fields[f];
// If field is not defined in the view, just ignore it
if (field) {
- var value = result.value[f];
- if (field.get_value() != value) {
- field.set_value(value);
- field.dirty = true;
+ var value_ = result.value[f];
+ if (field.get_value() != value_) {
+ field.set_value(value_);
+ field._dirty_flag = true;
if (!_.contains(processed, field.name)) {
this.do_onchange(field, processed);
}
@@ -424,7 +475,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.on_form_changed();
}
if (!_.isEmpty(result.warning)) {
- openerp.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), {
+ instance.web.dialog($(QWeb.render("CrashManagerWarning", result.warning)), {
modal: true,
buttons: [
{text: _t("Ok"), click: function() { $(this).dialog("close"); }}
@@ -447,19 +498,38 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
return $.Deferred().reject();
}
},
+ switch_mode: function() {
+ var mode = this.get("mode");
+ var self = this;
+ if(mode=="view") {
+ self.$buttons.find('.oe_form_buttons_edit').hide();
+ self.$buttons.find('.oe_form_buttons_view').show();
+ _.each(this.fields,function(field){
+ field.set({"force_readonly": true});
+ });
+ } else {
+ self.$buttons.find('.oe_form_buttons_edit').show();
+ self.$buttons.find('.oe_form_buttons_view').hide();
+ _.each(this.fields,function(field){
+ field.set({"force_readonly": false});
+ });
+ }
+ self.mode = mode;
+ },
on_button_save: function() {
var self = this;
return this.do_save().then(function(result) {
- self.do_prev_view({'created': result.created, 'default': 'page'});
+ self.set({mode: "view"});
});
},
on_button_cancel: function() {
if (this.can_be_discarded()) {
- return this.do_prev_view({'default': 'page'});
+ this.set({mode: "view"});
}
},
on_button_new: function() {
var self = this;
+ this.set({mode: "edit"});
var def = $.Deferred();
$.when(this.has_been_loaded).then(function() {
if (self.can_be_discarded()) {
@@ -477,6 +547,44 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
});
return def.promise();
},
+ on_button_edit: function() {
+ return this.set({mode: "edit"});
+ },
+ on_button_create: function() {
+ this.dataset.index = null;
+ this.do_show();
+ },
+ on_button_duplicate: function() {
+ var self = this;
+ var def = $.Deferred();
+ $.when(this.has_been_loaded).then(function() {
+ 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"});
+ }).then(function() {
+ def.resolve();
+ });
+ });
+ return def.promise();
+ },
+ on_button_delete: function() {
+ var self = this;
+ var def = $.Deferred();
+ $.when(this.has_been_loaded).then(function() {
+ if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
+ self.dataset.unlink([self.datarecord.id]).then(function() {
+ self.on_pager_action('next');
+ def.resolve();
+ });
+ } else {
+ $.async_when().then(function () {
+ def.reject();
+ })
+ }
+ });
+ return def.promise();
+ },
can_be_discarded: function() {
return !this.$element.is('.oe_form_dirty') || confirm(_t("Warning, the record has been modified, your changes will be discarded."));
},
@@ -499,11 +607,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
f = self.fields[f];
if (!f.is_valid()) {
form_invalid = true;
- f.update_dom(true);
if (!first_invalid_field) {
first_invalid_field = f;
}
- } else if (f.name !== 'id' && !f.readonly && (!self.datarecord.id || f.is_dirty())) {
+ } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || 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
@@ -511,20 +618,23 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
}
if (form_invalid) {
+ self.set({'display_invalid_fields': true});
first_invalid_field.focus();
self.on_invalid();
return $.Deferred().reject();
} else {
+ self.set({'display_invalid_fields': false});
var save_deferral;
if (!self.datarecord.id) {
//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)) {
+ } else if (_.isEmpty(values) && ! self.force_dirty) {
//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);
save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) {
return self.on_saved(r);
@@ -542,7 +652,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var msg = "<ul>";
_.each(this.fields, function(f) {
if (!f.is_valid()) {
- msg += "<li>" + f.string + "</li>";
+ msg += "<li>" + f.node.attrs.string + "</li>";
}
});
msg += "</ul>";
@@ -580,14 +690,14 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
this.dataset.alter_ids(this.dataset.ids.concat([this.datarecord.id]));
this.dataset.index = this.dataset.ids.length - 1;
} else {
- this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids));
+ this.dataset.alter_ids([this.datarecord.id].concat(this.dataset.ids));
this.dataset.index = 0;
}
this.do_update_pager();
if (this.sidebar) {
- this.sidebar.attachments.do_update();
+ this.sidebar.do_attachement_update(this.dataset, this.datarecord.id);
}
- //openerp.log("The record has been created with id #" + this.datarecord.id);
+ //instance.log("The record has been created with id #" + this.datarecord.id);
this.reload();
return $.when(_.extend(r, {created: true})).then(success);
}
@@ -598,6 +708,10 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
reload: function() {
var self = this;
return this.reload_mutex.exec(function() {
+ if (self.dataset.index == null) {
+ self.do_prev_view();
+ return $.Deferred().reject().promise();
+ }
if (self.dataset.index == null || self.dataset.index < 0) {
return $.when(self.on_button_new());
} else {
@@ -607,15 +721,20 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
});
},
+ get_widgets: function() {
+ return _.filter(this.getChildren(), function(obj) {
+ return obj instanceof instance.web.form.Widget;
+ });
+ },
get_fields_values: function(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) {
+ _.each(this.fields, function(value_, key) {
if (_.include(blacklist, key))
return;
- var val = value.get_value();
+ var val = value_.get_value();
values[key] = val;
});
return values;
@@ -632,8 +751,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
});
},
is_dirty: function() {
- return _.any(this.fields, function (value) {
- return value.is_dirty();
+ return _.any(this.fields, function (value_) {
+ return value_._dirty_flag;
});
},
is_interactible_record: function() {
@@ -642,7 +761,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
if (this.options.not_interactible_on_create)
return false;
} else if (typeof(id) === "string") {
- if(openerp.web.BufferedDataSet.virtual_id_regex.test(id))
+ if(instance.web.BufferedDataSet.virtual_id_regex.test(id))
return false;
}
return true;
@@ -654,12 +773,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var self = this;
var fields = _.chain(this.fields)
.map(function (field, name) {
- var value = field.get_value();
+ var value_ = field.get_value();
// ignore fields which are empty, invisible, readonly, o2m
// or m2m
- if (!value
- || field.invisible
- || field.readonly
+ if (!value_
+ || field.get('invisible')
+ || field.get("readonly")
|| field.field.type === 'one2many'
|| field.field.type === 'many2many') {
return false;
@@ -668,33 +787,33 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
switch(field.field.type) {
case 'selection':
displayed = _(field.values).find(function (option) {
- return option[0] === value;
+ return option[0] === value_;
})[1];
break;
case 'many2one':
- displayed = field.value[1] || value;
+ displayed = value_;
break;
default:
- displayed = value;
+ displayed = value_;
}
return {
name: name,
- string: field.string,
- value: value,
+ string: field.node_atts.string,
+ value: value_,
displayed: displayed,
// convert undefined to false
change_default: !!field.field.change_default
}
})
.compact()
- .sortBy(function (field) { return field.string; })
+ .sortBy(function (field) { return field.node_atts.string; })
.value();
var conditions = _.chain(fields)
.filter(function (field) { return field.change_default; })
.value();
- var d = new openerp.web.Dialog(this, {
+ var d = new instance.web.Dialog(this, {
title: _t("Set Default"),
args: {
fields: fields,
@@ -706,12 +825,12 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
var $defaults = d.$element.find('#formview_default_fields');
var field_to_set = $defaults.val();
if (!field_to_set) {
- $defaults.parent().addClass('invalid');
+ $defaults.parent().addClass('oe_form_invalid');
return;
}
var condition = d.$element.find('#formview_default_conditions').val(),
all_users = d.$element.find('#formview_default_all').is(':checked');
- new openerp.web.DataSet(self, 'ir.values').call(
+ new instance.web.DataSet(self, 'ir.values').call(
'set_default', [
self.dataset.model,
field_to_set,
@@ -725,9 +844,368 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
});
d.template = 'FormView.set_default';
d.open();
- }
+ },
+ register_field: function(field, name) {
+ this.fields[name] = field;
+ this.fields_order.push(name);
+ if (this.get_field(name).translate) {
+ this.translatable_fields.push(field);
+ }
+ field.on('changed_value', this, function() {
+ field._dirty_flag = true;
+ if (field.is_syntax_valid()) {
+ this.do_onchange(field);
+ this.on_form_changed(true);
+ this.do_notify_change();
+ }
+ });
+ },
+ get_field: function(field_name) {
+ return this.fields_view.fields[field_name];
+ },
+ is_create_mode: function() {
+ return !this.datarecord.id;
+ },
});
-openerp.web.FormDialog = openerp.web.Dialog.extend({
+
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+instance.web.form.FormRenderingEngineInterface = {
+ set_fields_view: function(fields_view) {},
+ set_fields_registry: function(fields_registry) {},
+ render_to: function($element) {},
+};
+
+/**
+ * Default rendering engine for the form view.
+ *
+ * It is necessary to set the view using set_view() before usage.
+ */
+instance.web.form.FormRenderingEngine = instance.web.Class.extend({
+ init: function(view) {
+ this.view = view;
+ },
+ set_fields_view: function(fvg) {
+ this.fvg = fvg;
+ },
+ set_tags_registry: function(tags_registry) {
+ this.tags_registry = tags_registry;
+ },
+ set_fields_registry: function(fields_registry) {
+ this.fields_registry = fields_registry;
+ },
+ render_to: function($target) {
+ var self = this;
+ this.$target = $target;
+
+ // TODO: I know this will save the world and all the kitten for a moment,
+ // but one day, we will have to get rid of xml2json
+ var xml = instance.web.json_node_to_xml(this.fvg.arch);
+ this.$form = $('<div class="oe_form">' + xml + '</div>');
+ if (this.fvg.arch.attrs && this.fvg.arch.attrs['layout'] !== 'manual') {
+ this.$form.attr('layout', 'auto');
+ }
+
+ this.fields_to_init = [];
+ this.tags_to_init = [];
+ this.labels = {};
+ this.process(this.$form);
+
+ this.$form.appendTo(this.$target);
+
+ _.each(this.fields_to_init, function($elem) {
+ var name = $elem.attr("name");
+ if (!self.fvg.fields[name]) {
+ throw new Error("Field '" + name + "' specified in view could not be found.");
+ }
+ var obj = self.fields_registry.get_any([$elem.attr('widget'), self.fvg.fields[name].type]);
+ if (!obj) {
+ throw new Error("Widget type '"+ key + "' is not implemented");
+ }
+ var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
+ var $label = self.labels[$elem.attr("name")];
+ if ($label) {
+ w.set_input_id($label.attr("for"));
+ }
+ self.alter_field(w);
+ self.view.register_field(w, $elem.attr("name"));
+ w.replace($elem);
+ });
+ _.each(this.tags_to_init, function($elem) {
+ var tag_name = $elem[0].tagName.toLowerCase();
+ var obj = self.tags_registry.get_object(tag_name);
+ var w = new (obj)(self.view, instance.web.xml_to_json($elem[0]));
+ w.replace($elem);
+ })
+ // TODO: return a deferred
+ },
+ render_element: function(template, layout/* dictionaries */) {
+ var dicts = [].slice.call(arguments).slice(2);
+ dicts.unshift({ 'layout' : layout });
+ var dict = _.extend.apply(_, dicts);
+ dict['classnames'] = dict['class'] || ''; // class is a reserved word and might caused problem to Safari when used from QWeb
+ var alt_template = template + '.' + layout;
+ template = QWeb.has_template(alt_template) ? alt_template : template;
+ return $(QWeb.render(template, dict));
+ },
+ alter_field: function(field) {
+ },
+ toggle_layout_debugging: function() {
+ if (!this.$target.has('.oe_layout_debug_cell:first').length) {
+ this.$target.find('[title]').removeAttr('title');
+ this.$target.find('.oe_form_group_cell').each(function() {
+ var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan');
+ $(this).attr('title', text);
+ });
+ }
+ this.$target.toggleClass('oe_layout_debugging');
+ },
+ process: function($tag, layout) {
+ var self = this;
+ if ($tag.attr('layout') === 'auto') {
+ $tag.addClass('oe_form_autolayout');
+ }
+ layout = $tag.attr('layout') || layout || 'auto';
+ $tag.removeAttr('layout');
+ var tagname = $tag[0].nodeName.toLowerCase();
+ if (this.tags_registry.contains(tagname)) {
+ this.tags_to_init.push($tag);
+ return $tag;
+ }
+ var fn = self['process_' + tagname];
+ if (fn) {
+ var args = [].slice.call(arguments);
+ args[0] = $tag;
+ args[1] = layout;
+ return fn.apply(self, args);
+ } else {
+ // generic tag handling, just process children
+ $tag.children().each(function() {
+ self.process($(this), layout);
+ });
+ self.handle_common_properties($tag, $tag);
+ $tag.removeAttr("modifiers");
+ return $tag;
+ }
+ },
+ process_sheet: function() {
+ this.process_form.apply(this, arguments);
+ },
+ process_form: function($form, layout) {
+ var $new_form = this.render_element('FormRenderingForm', layout, $form.getAttributes());
+ var $dst = (layout === 'auto') ? $new_form.find('group:first') : $new_form;
+ $new_form.attr("modifiers", $form.attr("modifiers"));
+ $form.children().appendTo($dst);
+ if ($form[0] === this.$form[0]) {
+ // If root element, replace it
+ this.$form = $new_form;
+ } else {
+ $form.before($new_form).remove();
+ }
+ this.process($new_form);
+ },
+ preprocess_field: function($field) {
+ var self = this;
+ var name = $field.attr('name'),
+ field_colspan = parseInt($field.attr('colspan'), 10),
+ field_modifiers = JSON.parse($field.attr('modifiers') || '{}');
+
+ if ($field.attr('nolabel') === '1')
+ return;
+ $field.attr('nolabel', '1');
+ var found = false;
+ this.$form.find('label[for="' + name + '"]').each(function(i ,el) {
+ $(el).parents().each(function(unused, tag) {
+ var name = tag.tagName.toLowerCase();
+ if (name === "field" || name in self.tags_registry.map)
+ found = true;
+ });
+ });
+ if (found)
+ return;
+
+ $label = $('<label/>').attr({
+ 'for' : name,
+ "modifiers": JSON.stringify({invisible: field_modifiers.invisible}),
+ "string": $field.attr('string'),
+ "help": $field.attr('help'),
+ });
+ $label.insertBefore($field);
+ if (field_colspan > 1) {
+ $field.attr('colspan', field_colspan - 1);
+ }
+ return $label;
+ },
+ process_field: function($field, layout) {
+ var $label = this.preprocess_field($field);
+ if ($label)
+ this.process($label, layout);
+ this.fields_to_init.push($field);
+ return $field;
+ },
+ process_group: function($group, layout) {
+ var self = this;
+ if ($group.parent().is('.oe_form_group_cell')) {
+ $group.parent().addClass('oe_form_group_nested');
+ }
+ $group.children('field').each(function() {
+ self.preprocess_field($(this));
+ });
+ var $new_group = this.render_element('FormRenderingGroup', layout, $group.getAttributes()),
+ $table;
+ if ($new_group.is('table')) {
+ $table = $new_group;
+ } else {
+ $table = $new_group.find('table:first');
+ }
+ $table.addClass('oe_form_group');
+ var $tr, $td,
+ cols = parseInt($group.attr('col') || 4, 10),
+ row_cols = cols;
+
+ var children = [];
+ $group.children().each(function(a,b,c) {
+ var $child = $(this),
+ colspan = parseInt($child.attr('colspan') || 1, 10),
+ tagName = $child[0].tagName.toLowerCase();
+ if (tagName === 'newline') {
+ $tr = null;
+ return;
+ }
+ if (!$tr || row_cols < colspan) {
+ $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table);
+ row_cols = cols;
+ }
+ row_cols -= colspan;
+
+ $td = $('<td/>').addClass('oe_form_group_cell').attr('colspan', colspan);
+ // invisibility transfer
+ var field_modifiers = JSON.parse($child.attr('modifiers') || '{}');
+ var invisible = field_modifiers.invisible;
+ field_modifiers.invisible = undefined;
+ $child.attr('modifiers', JSON.stringify(field_modifiers));
+ self.handle_common_properties($td, $("<dummy>").attr("modifiers", JSON.stringify({invisible: invisible})));
+
+ $tr.append($td.append($child));
+ children.push($child[0]);
+ });
+ if (row_cols && $td) {
+ $td.attr('colspan', parseInt($td.attr('colspan'), 10) + row_cols);
+ }
+ $group.before($new_group).remove();
+
+ // Now compute width of cells
+ $table.find('> tbody > tr').each(function() {
+ var to_compute = [],
+ row_cols = cols,
+ total = 100;
+ $(this).children().each(function() {
+ var $td = $(this),
+ $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--;
+ }
+ break;
+ case 'label':
+ if ($child.attr('for')) {
+ $td.attr('width', '1%');
+ row_cols--;
+ total--;
+ }
+ break;
+ default:
+ to_compute.push($td);
+ }
+ });
+ var unit = Math.floor(total / row_cols);
+ _.each(to_compute, function($td, i) {
+ var width = parseInt($td.attr('colspan'), 10) * unit;
+ $td.attr('width', ((i == to_compute.length - 1) ? total : width) + '%');
+ total -= width;
+ });
+ });
+ _.each(children, function(el) {
+ self.process($(el));
+ });
+ this.handle_common_properties($new_group, $group);
+ return $new_group;
+ },
+ process_notebook: function($notebook, layout) {
+ var self = this;
+ var pages = [];
+ $notebook.find('> page').each(function() {
+ var $page = $(this);
+ var page_attrs = $page.getAttributes();
+ page_attrs.id = _.uniqueId('notebook_page_');
+ pages.push(page_attrs);
+ var $new_page = self.render_element('FormRenderingNotebookPage', layout, page_attrs);
+ var $dst = (layout === 'auto') ? $new_page.find('group:first') : $new_page;
+ $page.children().appendTo($dst);
+ $page.before($new_page).remove();
+ self.handle_common_properties($new_page, $page);
+ });
+ var $new_notebook = this.render_element('FormRenderingNotebook', layout, { pages : pages });
+ $notebook.children().appendTo($new_notebook);
+ $notebook.before($new_notebook).remove();
+ $new_notebook.children().each(function() {
+ self.process($(this));
+ });
+ $new_notebook.tabs();
+ this.handle_common_properties($new_notebook, $notebook);
+ return $new_notebook;
+ },
+ process_separator: function($separator, layout) {
+ var $new_separator = this.render_element('FormRenderingSeparator', layout, $separator.getAttributes());
+ $separator.before($new_separator).remove();
+ this.handle_common_properties($new_separator, $separator);
+ return $new_separator;
+ },
+ process_label: function($label, layout) {
+ var name = $label.attr("for"),
+ field_orm = this.fvg.fields[name];
+ var dict = {
+ string: $label.attr('string') || (field_orm || {}).string || '',
+ help: $label.attr('help') || (field_orm || {}).help || '',
+ _for: name ? _.uniqueId('oe-field-input-') : undefined,
+ };
+ var align = parseFloat(dict.align);
+ if (isNaN(align) || align === 1) {
+ align = 'right';
+ } else if (align === 0) {
+ align = 'left';
+ } else {
+ align = 'center';
+ }
+ dict.align = align;
+ var $new_label = this.render_element('FormRenderingLabel', layout, dict);
+ $label.before($new_label).remove();
+ this.handle_common_properties($new_label, $label);
+ if (name) {
+ this.labels[name] = $new_label;
+ }
+ return $new_label;
+ },
+ handle_common_properties: function($new_element, $node) {
+ var str_modifiers = $node.attr("modifiers") || "{}"
+ var modifiers = JSON.parse(str_modifiers);
+ if (modifiers.invisible !== undefined)
+ new instance.web.form.InvisibilityChanger(this.view, this.view, modifiers.invisible, $new_element);
+ $new_element.addClass($node.attr("class") || "");
+ },
+});
+
+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);
this.dataset = dataset;
@@ -736,8 +1214,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({
},
start: function() {
this._super();
- this.form = new openerp.web.FormView(this, this.dataset, this.view_id, {
- sidebar: false,
+ this.form = new instance.web.FormView(this, this.dataset, this.view_id, {
pager: false
});
this.form.appendTo(this.$element);
@@ -758,62 +1235,7 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({
}
});
-/** @namespace */
-openerp.web.form = {};
-
-openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({
- init: function(parent, form_view) {
- var $section = parent.add_section(_t('Attachments'), 'attachments');
- this.$div = $('<div class="oe-sidebar-attachments"></div>');
- $section.append(this.$div);
-
- this._super(parent, $section.attr('id'));
- this.view = form_view;
- },
- do_update: function() {
- if (!this.view.datarecord.id) {
- this.on_attachments_loaded([]);
- } else {
- (new openerp.web.DataSetSearch(
- this, 'ir.attachment', this.view.dataset.get_context(),
- [
- ['res_model', '=', this.view.dataset.model],
- ['res_id', '=', this.view.datarecord.id],
- ['type', 'in', ['binary', 'url']]
- ])).read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded);
- }
- },
- on_attachments_loaded: function(attachments) {
- this.attachments = attachments;
- this.$div.html(QWeb.render('FormView.sidebar.attachments', this));
- this.$element.find('.oe-binary-file').change(this.on_attachment_changed);
- this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
- },
- on_attachment_changed: function(e) {
- window[this.element_id + '_iframe'] = this.do_update;
- var $e = $(e.target);
- if ($e.val() != '') {
- this.$element.find('form.oe-binary-form').submit();
- $e.parent().find('input[type=file]').prop('disabled', true);
- $e.parent().find('button').prop('disabled', true).find('img, span').toggle();
- }
- },
- on_attachment_delete: function(e) {
- var self = this, $e = $(e.currentTarget);
- var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
- if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) {
- this.rpc('/web/dataset/unlink', {
- model: 'ir.attachment',
- ids: [parseInt($e.attr('data-id'))]
- }, function(r) {
- $e.parent().remove();
- self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
- });
- }
- }
-});
-
-openerp.web.form.compute_domain = function(expr, fields) {
+instance.web.form.compute_domain = function(expr, fields) {
var stack = [];
for (var i = expr.length - 1; i >= 0; i--) {
var ex = expr[i];
@@ -842,7 +1264,7 @@ openerp.web.form.compute_domain = function(expr, fields) {
_t("Unknown field %s in domain %s"),
ex[0], JSON.stringify(expr)));
}
- var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value;
+ var field_value = field.get_value ? field.get_value() : field.value;
var op = ex[1];
var val = ex[2];
@@ -884,60 +1306,84 @@ openerp.web.form.compute_domain = function(expr, fields) {
return _.all(stack, _.identity);
};
-openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.form.Widget# */{
- form_template: 'Widget',
+/**
+ * Must be applied over an class already possessing the GetterSetterMixin.
+ *
+ * Apply the result of the "invisible" domain to this.$element.
+ */
+instance.web.form.InvisibilityChangerMixin = {
+ init: function(field_manager, invisible_domain) {
+ 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});
+ });
+ this.set({invisible: this._ic_invisible_modifier === true});
+ },
+ start: function() {
+ var check_visibility = function() {
+ if (this.get("invisible")) {
+ this.$element.hide();
+ } else {
+ this.$element.show();
+ }
+ };
+ this.on("change:invisible", this, check_visibility);
+ _.bind(check_visibility, this)();
+ },
+};
+
+instance.web.form.InvisibilityChanger = instance.web.Class.extend(_.extend({}, instance.web.GetterSetterMixin, instance.web.form.InvisibilityChangerMixin, {
+ init: function(parent, field_manager, invisible_domain, $element) {
+ this.setParent(parent);
+ instance.web.GetterSetterMixin.init.call(this);
+ instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain);
+ this.$element = $element;
+ this.start();
+ },
+}));
+
+instance.web.form.Widget = instance.web.Widget.extend(_.extend({}, instance.web.form.InvisibilityChangerMixin, {
/**
- * @constructs openerp.web.form.Widget
- * @extends openerp.web.OldWidget
+ * @constructs instance.web.form.Widget
+ * @extends instance.web.Widget
*
* @param view
* @param node
*/
init: function(view, node) {
+ this._super(view);
this.view = view;
this.node = node;
this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}');
- this.always_invisible = (this.modifiers.invisible && this.modifiers.invisible === true);
- this.type = this.type || node.tag;
- this.element_name = this.element_name || this.type;
-
- this._super(view);
-
- this.children = node.children;
- this.colspan = parseInt(node.attrs.colspan || 1, 10);
- this.decrease_max_width = 0;
-
- this.string = this.string || node.attrs.string;
- this.help = this.help || node.attrs.help;
- this.invisible = this.modifiers['invisible'] === true;
- this.classname = 'oe_form_' + this.type;
-
- this.align = parseFloat(this.node.attrs.align);
- if (isNaN(this.align) || this.align === 1) {
- this.align = 'right';
- } else if (this.align === 0) {
- this.align = 'left';
- } else {
- this.align = 'center';
- }
+ instance.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible);
- this.width = this.node.attrs.width;
+ this.view.on("view_content_has_changed", this, this.process_modifiers);
+ },
+ renderElement: function() {
+ this._super();
+ this.$element.addClass(this.node.attrs["class"] || "");
+ },
+ start: function() {
+ this._super();
+ instance.web.form.InvisibilityChangerMixin.start.call(this);
},
destroy: function() {
- this._super.apply(this, arguments);
$.fn.tipsy.clear();
+ this._super.apply(this, arguments);
},
process_modifiers: function() {
- var compute_domain = openerp.web.form.compute_domain;
+ var compute_domain = instance.web.form.compute_domain;
+ var to_set = {};
for (var a in this.modifiers) {
- this[a] = compute_domain(this.modifiers[a], this.view.fields);
+ if (!_.include(["invisible"], a)) {
+ var val = compute_domain(this.modifiers[a], this.view.fields);
+ to_set[a] = val;
+ }
}
- },
- update_dom: function() {
- this.$element.toggle(!this.invisible);
- },
- renderElement: function() {
- this.$element.html(QWeb.render(this.form_template, { "widget": this }));
+ this.set(to_set);
},
do_attach_tooltip: function(widget, trigger, options) {
widget = widget || this;
@@ -947,12 +1393,12 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
delayOut: 0,
fade: true,
title: function() {
- var template = widget.form_template + '.tooltip';
+ var template = widget.template + '.tooltip';
if (!QWeb.has_template(template)) {
template = 'WidgetLabel.tooltip';
}
return QWeb.render(template, {
- debug: openerp.connection.debug,
+ debug: instance.connection.debug,
widget: widget
})},
gravity: $.fn.tipsy.autoBounds(50, 'nw'),
@@ -979,7 +1425,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
},
_build_eval_context: function(blacklist) {
var a_dataset = this.view.dataset;
- return new openerp.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist));
+ return new instance.web.CompoundContext(a_dataset.get_context(), this._build_view_fields_values(blacklist));
},
/**
* Builds a new context usable for operations related to fields by merging
@@ -994,7 +1440,7 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
if (v_context.__ref || true) { //TODO: remove true
var fields_values = this._build_eval_context(blacklist);
- v_context = new openerp.web.CompoundContext(v_context).set_eval_context(fields_values);
+ v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values);
}
return v_context;
},
@@ -1005,235 +1451,28 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
var final_domain = n_domain !== null ? n_domain : f_domain;
if (!(final_domain instanceof Array) || true) { //TODO: remove true
var fields_values = this._build_eval_context();
- final_domain = new openerp.web.CompoundDomain(final_domain).set_eval_context(fields_values);
+ final_domain = new instance.web.CompoundDomain(final_domain).set_eval_context(fields_values);
}
return final_domain;
}
-});
+}));
-openerp.web.form.WidgetFrame = openerp.web.form.Widget.extend({
- form_template: 'WidgetFrame',
- init: function(view, node) {
- this._super(view, node);
- this.columns = parseInt(node.attrs.col || 4, 10);
- this.x = 0;
- this.y = 0;
- this.table = [];
- this.add_row();
- for (var i = 0; i < node.children.length; i++) {
- var n = node.children[i];
- if (n.tag == "newline") {
- this.add_row();
- } else {
- this.handle_node(n);
- }
- }
- this.set_row_cells_with(this.table[this.table.length - 1]);
- },
- renderElement: function() {
- this._super();
- var self = this;
- _.each(this.table, function(row) {
- _.each(row, function(td) {
- td.$element = self.$element.find('.' + td.element_class);
- td.renderElement();
- });
- });
- },
- start: function() {
- _.each(this.table, function(row) {
- _.each(row, function(td) {
- td.start();
- });
- });
- },
- add_row: function(){
- if (this.table.length) {
- this.set_row_cells_with(this.table[this.table.length - 1]);
- }
- var row = [];
- this.table.push(row);
- this.x = 0;
- this.y += 1;
- return row;
- },
- set_row_cells_with: function(row) {
- var bypass = 0,
- max_width = 100,
- row_length = row.length;
- for (var i = 0; i < row.length; i++) {
- if (row[i].always_invisible) {
- row_length--;
- } else {
- bypass += row[i].width === undefined ? 0 : 1;
- max_width -= row[i].decrease_max_width;
- }
- }
- var size_unit = Math.round(max_width / (this.columns - bypass)),
- colspan_sum = 0;
- for (var i = 0; i < row.length; i++) {
- var w = row[i];
- if (w.always_invisible) {
- continue;
- }
- colspan_sum += w.colspan;
- if (w.width === undefined) {
- var width = (i === row_length - 1 && colspan_sum === this.columns) ? max_width : Math.round(size_unit * w.colspan);
- max_width -= width;
- w.width = width + '%';
- }
- }
- },
- handle_node: function(node) {
- var type = {};
- if (node.tag == 'field') {
- type = this.view.fields_view.fields[node.attrs.name] || {};
- if (node.attrs.widget == 'statusbar' && node.attrs.nolabel !== '1') {
- // This way we can retain backward compatibility between addons and old clients
- node.attrs.colspan = (parseInt(node.attrs.colspan, 10) || 1) + 1;
- node.attrs.nolabel = '1';
- }
- }
- var widget = instanciate_widget(this.view.registry.get_any(
- [node.attrs.widget, type.type, node.tag]), this.view, node);
- if (node.tag == 'field') {
- if (!this.view.default_focus_field || node.attrs.default_focus == '1') {
- this.view.default_focus_field = widget;
- }
- if (node.attrs.nolabel != '1') {
- var label = instanciate_widget(this.view.registry.get_object('label'), this.view, node);
- label["for"] = widget;
- this.add_widget(label, widget.colspan + 1);
- }
- }
- this.add_widget(widget);
- },
- add_widget: function(widget, colspan) {
- var current_row = this.table[this.table.length - 1];
- if (!widget.always_invisible) {
- colspan = colspan || widget.colspan;
- if (current_row.length && (this.x + colspan) > this.columns) {
- current_row = this.add_row();
- }
- this.x += widget.colspan;
- }
- current_row.push(widget);
- return widget;
- }
-});
-
-openerp.web.form.WidgetGroup = openerp.web.form.WidgetFrame.extend({
- form_template: 'WidgetGroup'
-}),
-
-openerp.web.form.WidgetNotebook = openerp.web.form.Widget.extend({
- form_template: 'WidgetNotebook',
- init: function(view, node) {
- this._super(view, node);
- this.pages = [];
- for (var i = 0; i < node.children.length; i++) {
- var n = node.children[i];
- if (n.tag == "page") {
- var page = instanciate_widget(this.view.registry.get_object('notebookpage'),
- this.view, n, this, this.pages.length);
- this.pages.push(page);
- }
- }
- },
- renderElement: function() {
- this._super();
- var self = this;
- _.each(this.pages, function(page) {
- page.$element = self.$element.find('.' + page.element_class);
- page.renderElement();
- });
- },
- start: function() {
- var self = this;
- this._super.apply(this, arguments);
- this.$element.find('> ul > li').each(function (index, tab_li) {
- var page = self.pages[index],
- id = _.uniqueId(self.element_name + '-');
- page.element_id = id;
- $(tab_li).find('a').attr('href', '#' + id);
- });
- this.$element.find('> div').each(function (index, page) {
- page.id = self.pages[index].element_id;
- });
- this.$element.tabs();
- this.view.on_button_new.add_first(this.do_select_first_visible_tab);
- if (openerp.connection.debug) {
- this.do_attach_tooltip(this, this.$element.find('ul:first'), {
- gravity: 's'
- });
- }
- _.each(this.pages, function(page) {
- page.start();
- });
- },
- do_select_first_visible_tab: function() {
- for (var i = 0; i < this.pages.length; i++) {
- var page = this.pages[i];
- if (page.invisible === false) {
- this.$element.tabs('select', page.index);
- break;
- }
- }
- }
-});
-
-openerp.web.form.WidgetNotebookPage = openerp.web.form.WidgetFrame.extend({
- form_template: 'WidgetNotebookPage',
- init: function(view, node, notebook, index) {
- this.notebook = notebook;
- this.index = index;
- this.element_name = 'page_' + index;
- this._super(view, node);
- },
- start: function() {
- this._super.apply(this, arguments);
- this.$element_tab = this.notebook.$element.find(
- '> ul > li:eq(' + this.index + ')');
- },
- update_dom: function() {
- if (this.invisible && this.index === this.notebook.$element.tabs('option', 'selected')) {
- this.notebook.do_select_first_visible_tab();
- }
- this.$element_tab.toggle(!this.invisible);
- this.$element.toggle(!this.invisible);
- }
-});
-
-openerp.web.form.WidgetSeparator = openerp.web.form.Widget.extend({
- form_template: 'WidgetSeparator',
- init: function(view, node) {
- this._super(view, node);
- this.orientation = node.attrs.orientation || 'horizontal';
- if (this.orientation === 'vertical') {
- this.width = '1';
- }
- this.classname += '_' + this.orientation;
- }
-});
-
-openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
- form_template: 'WidgetButton',
+instance.web.form.WidgetButton = instance.web.form.Widget.extend({
+ template: 'WidgetButton',
init: function(view, node) {
this._super(view, node);
this.force_disabled = false;
- if (this.string) {
- // We don't have button key bindings in the webclient
- this.string = this.string.replace(/_/g, '');
- }
- if (node.attrs.default_focus == '1') {
+ this.string = (this.node.attrs.string || '').replace(/_/g, '');
+ if (this.node.attrs.default_focus == '1') {
// TODO fme: provide enter key binding to widgets
this.view.default_focus_button = this;
}
+ this.view.on('view_content_has_changed', this, this.check_disable);
},
start: function() {
this._super.apply(this, arguments);
- this.$element.find("button").click(this.on_click);
- if (this.help || openerp.connection.debug) {
+ this.$element.click(this.on_click);
+ if (this.node.attrs.help || instance.connection.debug) {
this.do_attach_tooltip();
}
},
@@ -1251,7 +1490,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
var exec_action = function() {
if (self.node.attrs.confirm) {
var def = $.Deferred();
- var dialog = openerp.web.dialog($('<div>' + self.node.attrs.confirm + '</div>'), {
+ var dialog = instance.web.dialog($('<div/>').text(self.node.attrs.confirm), {
title: _t('Confirm'),
modal: true,
buttons: [
@@ -1275,6 +1514,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
}
};
if (!this.node.attrs.special) {
+ this.view.force_dirty = true;
return this.view.recursive_save().pipe(exec_action);
} else {
return exec_action();
@@ -1285,7 +1525,7 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
var context = this.node.attrs.context;
if (context && context.__ref) {
- context = new openerp.web.CompoundContext(context);
+ context = new instance.web.CompoundContext(context);
context.set_eval_context(this._build_eval_context());
}
@@ -1295,81 +1535,57 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
self.view.reload();
});
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.check_disable();
- },
check_disable: function() {
- var disabled = (this.readonly || this.force_disabled || !this.view.is_interactible_record());
- this.$element.find('button').prop('disabled', disabled);
- this.$element.find("button").css('color', disabled ? 'grey' : '');
+ var disabled = (this.force_disabled || !this.view.is_interactible_record());
+ this.$element.prop('disabled', disabled);
+ this.$element.css('color', disabled ? 'grey' : '');
}
});
-openerp.web.form.WidgetLabel = openerp.web.form.Widget.extend({
- form_template: 'WidgetLabel',
- init: function(view, node) {
- this.element_name = 'label_' + node.attrs.name;
-
- this._super(view, node);
-
- if (this.node.tag == 'label' && !this.string && this.node.children.length) {
- this.string = this.node.children[0];
- this.align = 'left';
- }
-
- if (this.node.tag == 'label' && (this.align === 'left' || this.node.attrs.colspan || (this.string && this.string.length > 32))) {
- this.form_template = "WidgetParagraph";
- this.colspan = parseInt(this.node.attrs.colspan || 1, 10);
- // Widgets default to right-aligned, but paragraph defaults to
- // left-aligned
- if (isNaN(parseFloat(this.node.attrs.align))) {
- this.align = 'left';
- }
-
- this.multilines = this.string && _.str.lines(this.string).length > 1;
- } else {
- this.colspan = 1;
- this.width = '1%';
- this.decrease_max_width = 1;
- this.nowrap = true;
- }
- },
- renderElement: function() {
- var rendered;
- if (this['for'] && this.type !== 'label') {
- rendered = QWeb.render(this.form_template, {widget: this['for']});
- } else {
- // Actual label widgets should not have a false and have type label
- rendered = QWeb.render(this.form_template, {widget: this});
- }
- this.$element.html(rendered);
- },
- start: function() {
- this._super();
- var self = this;
- if (this['for'] && (this['for'].help || openerp.connection.debug)) {
- this.do_attach_tooltip(self['for']);
- }
- this.$element.find("label").dblclick(function() {
- var widget = self['for'] || self;
- openerp.log(widget.element_class , widget);
- window.w = widget;
- });
- }
-});
+/**
+ * Interface implemented by the form view or any other object
+ * able to provide the features necessary for the fields to work.
+ *
+ * Properties:
+ * - display_invalid_fields : if true, all fields where is_valid() return true should
+ * be displayed as invalid.
+ * Events:
+ * - view_content_has_changed : when the values of the fields have changed. When
+ * this event is triggered all fields should reprocess their modifiers.
+ */
+instance.web.form.FieldManagerInterface = {
+ /**
+ * Must return the asked field as in fields_get.
+ */
+ get_field: function(field_name) {},
+ /**
+ * Called by the field when the translate button is clicked.
+ */
+ open_translate_dialog: function(field) {},
+ /**
+ * Returns true when the view is in create mode.
+ */
+ is_create_mode: function() {},
+};
/**
* Interface to be implemented by fields.
*
- * Novajs Attributes:
- * - ...
- *
- * Novajs Events:
- * - ...
+ * Properties:
+ * - readonly: boolean. If set to true the field should appear in readonly mode.
+ * - force_readonly: boolean, When it is true, the field should always appear
+ * in read only mode, no matter what the value of the "readonly" property can be.
+ * Events:
+ * - changed_value: triggered to inform the view to check on_changes
*
*/
-openerp.web.form.FieldInterface = {
+instance.web.form.FieldInterface = {
+ /**
+ * Constructor takes 2 arguments:
+ * - field_manager: Implements FieldManagerInterface
+ * - node: the "<field>" node in json form
+ */
+ init: function(field_manager, node) {},
/**
* Called by the form view to indicate the value of the field.
*
@@ -1386,7 +1602,7 @@ openerp.web.form.FieldInterface = {
* as well as any format commonly returned in a on_change. It must be able to autodetect those formats as
* no information is ever given to know which format is used.
*/
- set_value: function(value) {},
+ set_value: function(value_) {},
/**
* Get the current value of the widget.
*
@@ -1405,221 +1621,307 @@ openerp.web.form.FieldInterface = {
* syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0).
*/
get_value: function() {},
+ /**
+ * Inform the current object of the id it should use to match a html <label> that exists somewhere in the
+ * view.
+ */
+ set_input_id: function(id) {},
+ /**
+ * Returns true if is_syntax_valid() returns true and the value is semantically
+ * valid too according to the semantic restrictions applied to the field.
+ */
+ is_valid: function() {},
+ /**
+ * Returns true if the field holds a value which is syntaxically correct, ignoring
+ * the potential semantic restrictions applied to the field.
+ */
+ is_syntax_valid: function() {},
+ /**
+ * Must set the focus on the field.
+ */
+ focus: function() {},
};
/**
- * Abstract class for classes implementing FieldInterface. Should be renamed to AbstractField some
- * day.
+ * Abstract class for classes implementing FieldInterface.
+ *
+ * Properties:
+ * - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending
+ * the values of the "readonly" property and the "force_readonly" property on the field manager.
+ * - value: useful property to hold the value of the field. By default, set_value() and get_value()
+ * set and retrieve the value property. Changing the value property also triggers automatically
+ * a 'changed_value' event that inform the view to trigger on_changes.
+ *
*/
-openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{
+instance.web.form.AbstractField = instance.web.form.Widget.extend(/** @lends instance.web.form.AbstractField# */{
/**
- * @constructs openerp.web.form.Field
- * @extends openerp.web.form.Widget
+ * @constructs instance.web.form.AbstractField
+ * @extends instance.web.form.Widget
*
- * @param view
+ * @param field_manager
* @param node
*/
- init: function(view, node) {
- this.name = node.attrs.name;
- this.value = undefined;
- view.fields[this.name] = this;
- view.fields_order.push(this.name);
- this.type = node.attrs.widget || view.fields_view.fields[node.attrs.name].type;
- this.element_name = "field_" + this.name + "_" + this.type;
-
- this._super(view, node);
-
- if (node.attrs.nolabel != '1' && this.colspan > 1) {
- this.colspan--;
- }
- this.field = view.fields_view.fields[node.attrs.name] || {};
- this.string = node.attrs.string || this.field.string;
- this.help = node.attrs.help || this.field.help;
- this.nolabel = (this.field.nolabel || node.attrs.nolabel) === '1';
- this.readonly = this.modifiers['readonly'] === true;
- this.required = this.modifiers['required'] === true;
- this.invalid = this.dirty = false;
-
- this.classname = 'oe_form_field_' + this.type;
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
+ this.field_manager = field_manager;
+ this.name = this.node.attrs.name;
+ this.set({'value': false});
+ this.field = this.field_manager.get_field(this.name);
+ this.set({required: this.modifiers['required'] === true});
+
+ // some events to make the property "effective_readonly" sync automatically with "readonly" and
+ // "force_readonly"
+ this.set({"readonly": this.modifiers['readonly'] === true});
+ var test_effective_readonly = function() {
+ this.set({"effective_readonly": this.get("readonly") || !!this.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)();
+
+ this.on("change:value", this, function() {
+ if (! this._inhibit_on_change)
+ this.trigger('changed_value');
+ this._check_css_flags();
+ });
},
start: function() {
this._super.apply(this, arguments);
if (this.field.translate) {
- this.view.translatable_fields.push(this);
this.$element.addClass('oe_form_field_translatable');
- this.$element.find('.oe_field_translate').click(this.on_translate);
+ this.$element.find('.oe_field_translate').click(_.bind(function() {
+ this.field_manager.open_translate_dialog(this);
+ }, this));
}
- if (this.nolabel && openerp.connection.debug) {
+ if (instance.connection.debug) {
this.do_attach_tooltip(this, this.$element);
}
+ if (!this.disable_utility_classes) {
+ var set_required = function() {
+ this.$element.toggleClass('oe_form_required', this.get("required"));
+ };
+ this.on("change:required", this, set_required);
+ _.bind(set_required, this)();
+ }
},
- set_value: function(value) {
- this.value = value;
- this.invalid = false;
- this.update_dom();
- this.on_value_changed();
- },
- set_value_from_ui: function() {
- this.on_value_changed();
- },
- on_value_changed: function() {
- },
- on_translate: function() {
- this.view.open_translate_dialog(this);
+ set_value: function(value_) {
+ this._inhibit_on_change = true;
+ this.set({'value': value_});
+ this._inhibit_on_change = false;
},
get_value: function() {
- return this.value;
+ return this.get('value');
},
is_valid: function() {
- return !this.invalid;
+ return this.is_syntax_valid() && (! this.get('required') || ! this.is_false());
},
- is_dirty: function() {
- return this.dirty && !this.readonly;
+ is_syntax_valid: function() {
+ return true;
},
- get_on_change_value: function() {
- return this.get_value();
+ /**
+ * Method useful to implement to ease validity testing. Must return true if the current
+ * value is similar to false in OpenERP.
+ */
+ is_false: function() {
+ return this.get('value') === false;
},
- update_dom: function(show_invalid) {
- this._super.apply(this, arguments);
+ _check_css_flags: function(show_invalid) {
if (this.field.translate) {
- this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
+ this.$element.find('.oe_field_translate').toggle(!this.field_manager.is_create_mode());
}
if (!this.disable_utility_classes) {
- this.$element.toggleClass('disabled', this.readonly);
- this.$element.toggleClass('required', this.required);
- if (show_invalid) {
- this.$element.toggleClass('invalid', !this.is_valid());
+ if (this.field_manager.get('display_invalid_fields')) {
+ this.$element.toggleClass('oe_form_invalid', !this.is_valid());
}
}
},
- on_ui_change: function() {
- this.dirty = true;
- this.validate();
- if (this.is_valid()) {
- this.set_value_from_ui();
- this.view.do_onchange(this);
- this.view.on_form_changed(true);
- this.view.do_notify_change();
- } else {
- this.update_dom(true);
- }
- },
- validate: function() {
- this.invalid = false;
+ focus: function() {
},
- focus: function($element) {
- if ($element) {
- setTimeout(function() {
- $element.focus();
- }, 50);
- }
- },
- reset: function() {
- this.dirty = false;
+ /**
+ * Utility method to focus an element, but only after a small amount of time.
+ */
+ delay_focus: function($elem) {
+ setTimeout(function() {
+ $elem.focus();
+ }, 50);
},
+ /**
+ * Utility method to get the widget options defined in the field xml description.
+ */
get_definition_options: function() {
if (!this.definition_options) {
var str = this.node.attrs.options || '{}';
this.definition_options = JSON.parse(str);
}
return this.definition_options;
- }
+ },
+ set_input_id: function(id) {
+ this.id_for_label = id;
+ },
});
-openerp.web.form.FieldChar = openerp.web.form.Field.extend({
- form_template: 'FieldChar',
- init: function (view, node) {
- this._super(view, node);
- this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
- },
+/**
+ * A mixin to apply on any field that has to completely re-render when its readonly state
+ * switch.
+ */
+instance.web.form.ReinitializeFieldMixin = {
+ /**
+ * Default implementation of start(), use it or call explicitly initialize_field().
+ */
start: function() {
- this._super.apply(this, arguments);
- this.$element.find('input').change(this.on_ui_change);
+ this._super();
+ this.initialize_field();
},
- set_value: function(value) {
- this._super.apply(this, arguments);
- var show_value = openerp.web.format_value(value, this, '');
- this.$element.find('input').val(show_value);
- return show_value;
+ initialize_field: function() {
+ this.on("change:effective_readonly", this, function() {
+ this.destroy_content();
+ this.renderElement();
+ this.initialize_content();
+ this.render_value();
+ });
+ this.initialize_content();
+ this.render_value();
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('input').prop('readonly', this.readonly);
+ /**
+ * Called to destroy anything that could have been created previously, called before a
+ * re-initialization.
+ */
+ destroy_content: function() {},
+ /**
+ * Called to initialize the content.
+ */
+ initialize_content: function() {},
+ /**
+ * Called to render the value. Should also be explicitly called at the end of a set_value().
+ */
+ render_value: function() {},
+};
+
+instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ template: 'FieldChar',
+ init: function (field_manager, node) {
+ this._super(field_manager, node);
+ this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
},
- set_value_from_ui: function() {
- this.value = openerp.web.parse_value(this.$element.find('input').val(), this);
- this._super();
+ initialize_content: function() {
+ var self = this;
+ this.$element.find('input').change(function() {
+ self.set({'value': instance.web.parse_value(self.$element.find('input').val(), self)});
+ });
},
- validate: function() {
- this.invalid = false;
- try {
- var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
- this.invalid = this.required && value === '';
- } catch(e) {
- this.invalid = true;
+ set_value: function(value_) {
+ this._super(value_);
+ this.render_value();
+ },
+ render_value: function() {
+ var show_value = instance.web.format_value(this.get('value'), this, '');
+ if (!this.get("effective_readonly")) {
+ this.$element.find('input').val(show_value);
+ } else {
+ if (this.password) {
+ show_value = new Array(show_value.length + 1).join('*');
+ }
+ this.$element.text(show_value);
}
},
- focus: function($element) {
- this._super($element || this.$element.find('input:first'));
+ is_syntax_valid: function() {
+ if (!this.get("effective_readonly")) {
+ try {
+ var value_ = instance.web.parse_value(this.$element.find('input').val(), this, '');
+ return true;
+ } catch(e) {
+ return false;
+ }
+ }
+ return true;
+ },
+ is_false: function() {
+ return this.get('value') === '';
+ },
+ focus: function() {
+ this.delay_focus(this.$element.find('input:first'));
}
-});
+}));
-openerp.web.form.FieldID = openerp.web.form.FieldChar.extend({
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('input').prop('readonly', true);
- }
+instance.web.form.FieldID = instance.web.form.FieldChar.extend({
+
});
-openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
- form_template: 'FieldEmail',
- start: function() {
- this._super.apply(this, arguments);
+instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({
+ template: 'FieldEmail',
+ initialize_content: function() {
+ this._super();
this.$element.find('button').click(this.on_button_clicked);
},
+ render_value: function() {
+ if (!this.get("effective_readonly")) {
+ this._super();
+ } else {
+ this.$element.find('a')
+ .attr('href', 'mailto:' + this.get('value'))
+ .text(this.get('value'));
+ }
+ },
on_button_clicked: function() {
- if (!this.value || !this.is_valid()) {
+ if (!this.get('value') || !this.is_syntax_valid()) {
this.do_warn("E-mail error", "Can't send email to invalid e-mail address");
} else {
- location.href = 'mailto:' + this.value;
+ location.href = 'mailto:' + this.get('value');
}
}
});
-openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
- form_template: 'FieldUrl',
- start: function() {
- this._super.apply(this, arguments);
+instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({
+ template: 'FieldUrl',
+ initialize_content: function() {
+ this._super();
this.$element.find('button').click(this.on_button_clicked);
},
+ render_value: function() {
+ if (!this.get("effective_readonly")) {
+ this._super();
+ } else {
+ var tmp = this.get('value');
+ var s = /(\w+):(.+)/.exec(tmp);
+ if (!s) {
+ tmp = "http://" + this.get('value');
+ }
+ this.$element.find('a').attr('href', tmp).text(tmp);
+ }
+ },
on_button_clicked: function() {
- if (!this.value) {
+ if (!this.get('value')) {
this.do_warn("Resource error", "This resource is empty");
} else {
- window.open(this.value);
+ var url = $.trim(this.get('value'));
+ if(/^www\./i.test(url))
+ url = 'http://'+url;
+ window.open(url);
}
}
});
-openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
- init: function (view, node) {
- this._super(view, node);
- if (node.attrs.digits) {
+instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({
+ is_field_number: true,
+ init: function (field_manager, node) {
+ this._super(field_manager, node);
+ this.set({'value': 0});
+ if (this.node.attrs.digits) {
this.digits = py.eval(node.attrs.digits);
} else {
- this.digits = view.fields_view.fields[node.attrs.name].digits;
+ this.digits = this.field.digits;
}
},
- set_value: function(value) {
- if (value === false || value === undefined) {
+ set_value: function(value_) {
+ if (value_ === false || value_ === undefined) {
// As in GTK client, floats default to 0
- value = 0;
+ value_ = 0;
}
- this._super.apply(this, [value]);
+ this._super.apply(this, [value_]);
}
});
-openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
+instance.web.DateTimeWidget = instance.web.OldWidget.extend({
template: "web.datetimepicker",
jqueryui_object: 'datetimepicker',
type_of_date: "datetime",
@@ -1640,46 +1942,46 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
showButtonPanel: true
});
this.$element.find('img.oe_datepicker_trigger').click(function() {
- if (!self.readonly && !self.picker('widget').is(':visible')) {
- self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
+ if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) {
+ self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date());
self.$input_picker.show();
self.picker('show');
self.$input_picker.hide();
}
});
this.set_readonly(false);
- this.value = false;
+ this.set({'value': false});
},
picker: function() {
return $.fn[this.jqueryui_object].apply(this.$input_picker, arguments);
},
- on_picker_select: function(text, instance) {
+ on_picker_select: function(text, instance_) {
var date = this.picker('getDate');
this.$input.val(date ? this.format_client(date) : '').change();
},
- set_value: function(value) {
- this.value = value;
- this.$input.val(value ? this.format_client(value) : '');
+ set_value: function(value_) {
+ this.set({'value': value_});
+ this.$input.val(value_ ? this.format_client(value_) : '');
},
get_value: function() {
- return this.value;
+ return this.get('value');
},
- set_value_from_ui: function() {
- var value = this.$input.val() || false;
- this.value = this.parse_client(value);
+ set_value_from_ui_: function() {
+ var value_ = this.$input.val() || false;
+ this.set({'value': this.parse_client(value_)});
},
set_readonly: function(readonly) {
this.readonly = readonly;
this.$input.prop('readonly', this.readonly);
this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
},
- is_valid: function(required) {
- var value = this.$input.val();
- if (value === "") {
- return !required;
+ is_valid_: function() {
+ var value_ = this.$input.val();
+ if (value_ === "") {
+ return true;
} else {
try {
- this.parse_client(value);
+ this.parse_client(value_);
return true;
} catch(e) {
return false;
@@ -1687,99 +1989,120 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
}
},
parse_client: function(v) {
- return openerp.web.parse_value(v, {"widget": this.type_of_date});
+ return instance.web.parse_value(v, {"widget": this.type_of_date});
},
format_client: function(v) {
- return openerp.web.format_value(v, {"widget": this.type_of_date});
+ return instance.web.format_value(v, {"widget": this.type_of_date});
},
on_change: function() {
- if (this.is_valid()) {
- this.set_value_from_ui();
+ if (this.is_valid_()) {
+ this.set_value_from_ui_();
}
}
});
-openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
+instance.web.DateWidget = instance.web.DateTimeWidget.extend({
jqueryui_object: 'datepicker',
type_of_date: "date"
});
-openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
- form_template: "EmptyComponent",
+instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ template: "EmptyComponent",
build_widget: function() {
- return new openerp.web.DateTimeWidget(this);
+ return new instance.web.DateTimeWidget(this);
},
- start: function() {
- var self = this;
- this._super.apply(this, arguments);
- this.datewidget = this.build_widget();
- this.datewidget.on_change.add_last(this.on_ui_change);
- this.datewidget.appendTo(this.$element);
+ destroy_content: function() {
+ if (this.datewidget) {
+ this.datewidget.destroy();
+ this.datewidget = undefined;
+ }
+ },
+ initialize_content: function() {
+ if (!this.get("effective_readonly")) {
+ this.datewidget = this.build_widget();
+ this.datewidget.on_change.add_last(_.bind(function() {
+ this.set({'value': this.datewidget.get_value()});
+ }, this));
+ this.datewidget.appendTo(this.$element);
+ }
},
- set_value: function(value) {
- this._super(value);
- this.datewidget.set_value(value);
+ set_value: function(value_) {
+ this._super(value_);
+ this.render_value();
},
- get_value: function() {
- return this.datewidget.get_value();
+ render_value: function() {
+ if (!this.get("effective_readonly")) {
+ this.datewidget.set_value(this.get('value'));
+ } else {
+ this.$element.text(instance.web.format_value(this.get('value'), this, ''));
+ }
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.datewidget.set_readonly(this.readonly);
+ is_syntax_valid: function() {
+ if (!this.get("effective_readonly")) {
+ return this.datewidget.is_valid_();
+ }
+ return true;
},
- validate: function() {
- this.invalid = !this.datewidget.is_valid(this.required);
+ is_false: function() {
+ return this.get('value') === '';
},
- focus: function($element) {
- this._super($element || this.datewidget.$input);
+ focus: function() {
+ if (this.datewidget && this.datewidget.$input)
+ this.delay_focus(this.datewidget.$input);
}
-});
+}));
-openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
+instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({
build_widget: function() {
- return new openerp.web.DateWidget(this);
+ return new instance.web.DateWidget(this);
}
});
-openerp.web.form.FieldText = openerp.web.form.Field.extend({
- form_template: 'FieldText',
- start: function() {
- this._super.apply(this, arguments);
- this.$element.find('textarea').change(this.on_ui_change);
+instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ template: 'FieldText',
+ initialize_content: function() {
+ this.$textarea = this.$element.find('textarea');
this.resized = false;
+ if (!this.get("effective_readonly")) {
+ this.$textarea.change(_.bind(function() {
+ this.set({'value': instance.web.parse_value(this.$textarea.val(), this)});
+ }, this));
+ } else {
+ this.$textarea.attr('disabled', 'disabled');
+ }
},
- set_value: function(value) {
+ set_value: function(value_) {
this._super.apply(this, arguments);
- var show_value = openerp.web.format_value(value, this, '');
- this.$element.find('textarea').val(show_value);
+ this.render_value();
+ },
+ render_value: function() {
+ var show_value = instance.web.format_value(this.get('value'), this, '');
+ this.$textarea.val(show_value);
if (!this.resized && this.view.options.resize_textareas) {
this.do_resize(this.view.options.resize_textareas);
this.resized = true;
}
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('textarea').prop('readonly', this.readonly);
- },
- set_value_from_ui: function() {
- this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this);
- this._super();
- },
- validate: function() {
- this.invalid = false;
- try {
- var value = openerp.web.parse_value(this.$element.find('textarea').val(), this, '');
- this.invalid = this.required && value === '';
- } catch(e) {
- this.invalid = true;
+ is_syntax_valid: function() {
+ if (!this.get("effective_readonly")) {
+ try {
+ var value_ = instance.web.parse_value(this.$textarea.val(), this, '');
+ return true;
+ } catch(e) {
+ return false;
+ }
}
+ return true;
+ },
+ is_false: function() {
+ return this.get('value') === '';
},
focus: function($element) {
- this._super($element || this.$element.find('textarea:first'));
+ this.delay_focus(this.$textarea);
},
do_resize: function(max_height) {
max_height = parseInt(max_height, 10);
- var $input = this.$element.find('textarea'),
+ var $input = this.$textarea,
$div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()),
new_height;
$div.text($input.val());
@@ -1797,64 +2120,60 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
$div.remove();
$input.height(new_height);
},
- reset: function() {
- this.resized = false;
- }
-});
+}));
-openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
- form_template: 'FieldBoolean',
+instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({
+ template: 'FieldBoolean',
start: function() {
- var self = this;
this._super.apply(this, arguments);
- this.$element.find('input').click(self.on_ui_change);
- },
- set_value: function(value) {
- this._super.apply(this, arguments);
- this.$element.find('input')[0].checked = value;
- },
- set_value_from_ui: function() {
- this.value = this.$element.find('input').is(':checked');
- this._super();
+ this.$checkbox = $("input", this.$element);
+ this.$element.click(_.bind(function() {
+ this.set({'value': this.$checkbox.is(':checked')});
+ }, this));
+ var check_readonly = function() {
+ this.$checkbox.prop('disabled', this.get("effective_readonly"));
+ };
+ this.on("change:effective_readonly", this, check_readonly);
+ _.bind(check_readonly, this)();
},
- update_dom: function() {
+ set_value: function(value_) {
this._super.apply(this, arguments);
- this.$element.find('input').prop('disabled', this.readonly);
+ this.$checkbox[0].checked = value_;
},
- focus: function($element) {
- this._super($element || this.$element.find('input:first'));
+ focus: function() {
+ this.delay_focus(this.$checkbox);
}
});
-openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
- form_template: 'FieldProgressBar',
+instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({
+ template: 'FieldProgressBar',
start: function() {
this._super.apply(this, arguments);
- this.$element.find('div').progressbar({
- value: this.value,
- disabled: this.readonly
+ this.$element.progressbar({
+ value: this.get('value'),
+ disabled: this.get("effective_readonly")
});
},
- set_value: function(value) {
+ set_value: function(value_) {
this._super.apply(this, arguments);
- var show_value = Number(value);
+ var show_value = Number(value_);
if (isNaN(show_value)) {
show_value = 0;
}
- var formatted_value = openerp.web.format_value(show_value, { type : 'float' }, '0');
- this.$element.find('div').progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
+ var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0');
+ this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
}
});
-openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
+instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({
// to replace view editor
});
-openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
- form_template: 'FieldSelection',
- init: function(view, node) {
+instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ template: 'FieldSelection',
+ init: function(field_manager, node) {
var self = this;
- this._super(view, node);
+ this._super(field_manager, node);
this.values = _.clone(this.field.selection);
_.each(this.values, function(v, i) {
if (v[0] === false && v[1] === '') {
@@ -1863,7 +2182,7 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
});
this.values.unshift([false, '']);
},
- start: function() {
+ initialize_content: function() {
// Flag indicating whether we're in an event chain containing a change
// event on the select, in order to know what to do on keyup[RETURN]:
// * If the user presses [RETURN] as part of changing the value of a
@@ -1876,9 +2195,10 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
// changing the selected value), takes the action as validating the
// row
var ischanging = false;
- this._super.apply(this, arguments);
this.$element.find('select')
- .change(this.on_ui_change)
+ .change(_.bind(function() {
+ this.set({'value': this.values[this.$element.find('select')[0].selectedIndex][0]});
+ }, this))
.change(function () { ischanging = true; })
.click(function () { ischanging = false; })
.keyup(function (e) {
@@ -1887,32 +2207,37 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
ischanging = false;
});
},
- set_value: function(value) {
- value = value === null ? false : value;
- value = value instanceof Array ? value[0] : value;
- this._super(value);
- var index = 0;
- for (var i = 0, ii = this.values.length; i < ii; i++) {
- if (this.values[i][0] === value) index = i;
- }
- this.$element.find('select')[0].selectedIndex = index;
- },
- set_value_from_ui: function() {
- this.value = this.values[this.$element.find('select')[0].selectedIndex][0];
- this._super();
+ set_value: function(value_) {
+ value_ = value_ === null ? false : value_;
+ value_ = value_ instanceof Array ? value_[0] : value_;
+ this._super(value_);
+ this.render_value();
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('select').prop('disabled', this.readonly);
+ render_value: function() {
+ if (!this.get("effective_readonly")) {
+ var index = 0;
+ for (var i = 0, ii = this.values.length; i < ii; i++) {
+ if (this.values[i][0] === this.get('value')) index = i;
+ }
+ this.$element.find('select')[0].selectedIndex = index;
+ } else {
+ var self = this;
+ var option = _(this.values)
+ .detect(function (record) { return record[0] === self.get('value'); });
+ this.$element.text(option ? option[1] : this.values[0][1]);
+ }
},
- validate: function() {
- var value = this.values[this.$element.find('select')[0].selectedIndex];
- this.invalid = !(value && !(this.required && value[0] === false));
+ is_syntax_valid: function() {
+ if (this.get("effective_readonly")) {
+ return true;
+ }
+ var value_ = this.values[this.$element.find('select')[0].selectedIndex];
+ return !! value_;
},
- focus: function($element) {
- this._super($element || this.$element.find('select:first'));
+ focus: function() {
+ this.delay_focus(this.$element.find('select:first'));
}
-});
+}));
// jquery autocomplete tweak to allow html
(function() {
@@ -1921,8 +2246,8 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
function filter( array, term ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
- return $.grep( array, function(value) {
- return matcher.test( $( "<div>" ).html( value.label || value.value || value ).text() );
+ return $.grep( array, function(value_) {
+ return matcher.test( $( "<div>" ).html( value_.label || value_.value || value_ ).text() );
});
}
@@ -1946,114 +2271,88 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
});
})();
-openerp.web.form.dialog = function(content, options) {
+instance.web.form.dialog = function(content, options) {
options = _.extend({
width: '90%',
height: 'auto',
min_width: '800px'
}, options || {});
- var dialog = new openerp.web.Dialog(null, options, content).open();
+ var dialog = new instance.web.Dialog(null, options, content).open();
return dialog.$element;
};
-openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
- form_template: 'FieldMany2One',
- init: function(view, node) {
- this._super(view, node);
+instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ template: "FieldMany2One",
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
this.limit = 7;
- this.value = null;
- this.cm_id = _.uniqueId('m2o_cm_');
+ this.set({'value': false});
+ this.display_value = {};
this.last_search = [];
- this.tmp_value = undefined;
+ this.floating = false;
+ this.inhibit_on_change = false;
this.orderer = new openerp.web.DropMisordered();
},
start: function() {
this._super();
+ instance.web.form.ReinitializeFieldMixin.start.call(this);
+ this.on("change:value", this, function() {
+ this.floating = false;
+ this.render_value();
+ });
+ },
+ initialize_content: function() {
+ if (!this.get("effective_readonly"))
+ this.render_editable();
+ this.render_value();
+ },
+ render_editable: function() {
var self = this;
this.$input = this.$element.find("input");
+
+ self.$input.tipsy({
+ title: function() {
+ return "No element was selected, you should create or select one from the dropdown list.";
+ },
+ trigger:'manual',
+ fade: true,
+ });
+
this.$drop_down = this.$element.find(".oe-m2o-drop-down-button");
- this.$menu_btn = this.$element.find(".oe-m2o-cm-button");
-
- // context menu
- var init_context_menu_def = $.Deferred().then(function(e) {
- var rdataset = new openerp.web.DataSet(self, "ir.values", self.build_context());
- rdataset.call("get", ['action', 'client_action_relate',
- [[self.field.relation, false]], false, rdataset.get_context()], false, 0)
- .then(function(result) {
- self.related_entries = result;
-
- var $cmenu = $("#" + self.cm_id);
- $cmenu.append(QWeb.render("FieldMany2One.context_menu", {widget: self}));
- var bindings = {};
- bindings[self.cm_id + "_search"] = function() {
- if (self.readonly)
- return;
- self._search_create_popup("search");
- };
- bindings[self.cm_id + "_create"] = function() {
- if (self.readonly)
- return;
- self._search_create_popup("form");
- };
- bindings[self.cm_id + "_open"] = function() {
- if (!self.value) {
- return;
- }
- var pop = new openerp.web.form.FormOpenPopup(self.view);
- pop.show_element(
- self.field.relation,
- self.value[0],
- self.build_context(),
- {
- title: _t("Open: ") + (self.string || self.name)
- }
- );
- pop.on_write_completed.add_last(function() {
- self.set_value(self.value[0]);
- });
- };
- _.each(_.range(self.related_entries.length), function(i) {
- bindings[self.cm_id + "_related_" + i] = function() {
- self.open_related(self.related_entries[i]);
- };
- });
- var cmenu = self.$menu_btn.contextMenu(self.cm_id, {'noRightClick': true,
- bindings: bindings, itemStyle: {"color": ""},
- onContextMenu: function() {
- if(self.value) {
- $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").removeClass("oe-m2o-disabled-cm");
- } else {
- $("#" + self.cm_id + " .oe_m2o_menu_item_mandatory").addClass("oe-m2o-disabled-cm");
- }
- if (!self.readonly) {
- $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").removeClass("oe-m2o-disabled-cm");
- } else {
- $("#" + self.cm_id + " .oe_m2o_menu_item_noreadonly").addClass("oe-m2o-disabled-cm");
- }
- return true;
- }, menuStyle: {width: "200px"}
- });
- $.async_when().then(function() {self.$menu_btn.trigger(e);});
+ this.$follow_button = $(".oe-m2o-cm-button", this.$element);
+
+ this.$follow_button.click(function() {
+ if (!self.get('value')) {
+ return;
+ }
+ var pop = new instance.web.form.FormOpenPopup(self.view);
+ pop.show_element(
+ self.field.relation,
+ self.get("value"),
+ self.build_context(),
+ {
+ title: _t("Open: ") + (self.string || self.name)
+ }
+ );
+ pop.on_write_completed.add_last(function() {
+ self.display_value = {};
+ self.render_value();
});
});
- var ctx_callback = function(e) {init_context_menu_def.resolve(e); e.preventDefault()};
- this.$menu_btn.click(ctx_callback);
// some behavior for input
this.$input.keyup(function() {
if (self.$input.val() === "") {
- self._change_int_value(null);
- } else if (self.value === null || (self.value && self.$input.val() !== self.value[1])) {
- self._change_int_value(undefined);
+ self.set({value: false});
+ } else {
+ self.floating = true;
}
});
this.$drop_down.click(function() {
- if (self.readonly)
- return;
if (self.$input.autocomplete("widget").is(":visible")) {
self.$input.autocomplete("close");
} else {
- if (self.value) {
+ if (self.get("value") && ! self.floating) {
self.$input.autocomplete("search", "");
} else {
self.$input.autocomplete("search");
@@ -2061,16 +2360,43 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
self.$input.focus();
}
});
+ var tip_def = $.Deferred();
+ var untip_def = $.Deferred();
+ var tip_delay = 200;
+ var tip_duration = 3000;
var anyoneLoosesFocus = function() {
- if (!self.$input.is(":focus") &&
- !self.$input.autocomplete("widget").is(":visible") &&
- !self.value) {
- if (self.value === undefined && self.last_search.length > 0) {
- self._change_int_ext_value(self.last_search[0]);
+ if (self.floating) {
+ if (self.last_search.length > 0) {
+ if (self.last_search[0][0] != self.get("value")) {
+ self.display_value = {};
+ self.display_value["" + self.last_search[0][0]] = self.last_search[0][1];
+ self.set({value: self.last_search[0][0]});
+ } else {
+ self.render_value();
+ }
} else {
- self._change_int_ext_value(null);
+ self.set({value: false});
}
}
+ if (! self.get("value")) {
+ tip_def.reject();
+ untip_def.reject();
+ tip_def = $.Deferred();
+ tip_def.then(function() {
+ self.$input.tipsy("show");
+ });
+ setTimeout(function() {
+ tip_def.resolve();
+ untip_def.reject();
+ untip_def = $.Deferred();
+ untip_def.then(function() {
+ self.$input.tipsy("hide");
+ });
+ setTimeout(function() {untip_def.resolve();}, tip_duration);
+ }, tip_delay);
+ } else {
+ tip_def.reject();
+ }
};
this.$input.focusout(anyoneLoosesFocus);
@@ -2082,9 +2408,11 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
isSelecting = true;
var item = ui.item;
if (item.id) {
- self._change_int_value([item.id, item.name]);
+ self.display_value = {};
+ self.display_value["" + item.id] = item.name;
+ self.set({value: item.id});
} else if (item.action) {
- self._change_int_value(undefined);
+ self.floating = true;
item.action();
return false;
}
@@ -2112,7 +2440,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
var search_val = request.term;
var self = this;
- var dataset = new openerp.web.DataSet(this, this.field.relation, self.build_context());
+ var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context());
this.orderer.add(dataset.name_search(
search_val, self.build_domain(), 'ilike', this.limit + 1)).then(function(data) {
@@ -2133,7 +2461,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
values.push({label: _t("<em>   Search More...</em>"), action: function() {
dataset.name_search(search_val, self.build_domain(), 'ilike'
, false, function(data) {
- self._change_int_value(null);
self._search_create_popup("search", data);
});
}});
@@ -2142,7 +2469,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
var raw_result = _(data.result).map(function(x) {return x[1];});
if (search_val.length > 0 &&
!_.include(raw_result, search_val) &&
- (!self.value || search_val !== self.value[1])) {
+ (!self.get("value") || self.floating)) {
values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'),
$('<span />').text(search_val).html()), action: function() {
self._quick_create(search_val);
@@ -2150,7 +2477,6 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
}
// create...
values.push({label: _t("<em>   Create and Edit...</em>"), action: function() {
- self._change_int_value(null);
self._search_create_popup("form", undefined, {"default_name": search_val});
}});
@@ -2160,13 +2486,14 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
_quick_create: function(name) {
var self = this;
var slow_create = function () {
- self._change_int_value(null);
self._search_create_popup("form", undefined, {"default_name": name});
};
if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) {
- new openerp.web.DataSet(this, this.field.relation, self.build_context())
+ new instance.web.DataSet(this, this.field.relation, self.build_context())
.name_create(name, function(data) {
- self._change_int_ext_value(data);
+ self.display_value = {};
+ self.display_value["" + data[0]] = data[1];
+ self.set({value: data[0]});
}).fail(function(error, event) {
event.preventDefault();
slow_create();
@@ -2177,7 +2504,7 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
// all search/create popup handling
_search_create_popup: function(view, ids, context) {
var self = this;
- var pop = new openerp.web.form.SelectCreatePopup(this);
+ var pop = new instance.web.form.SelectCreatePopup(this);
pop.select_element(
self.field.relation,
{
@@ -2187,100 +2514,71 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
disable_multiple_selection: true
},
self.build_domain(),
- new openerp.web.CompoundContext(self.build_context(), context || {})
+ new instance.web.CompoundContext(self.build_context(), context || {})
);
pop.on_select_elements.add(function(element_ids) {
- new openerp.web.DataSet(self, self.field.relation, self.build_context())
- .name_get([element_ids[0]], function(data) {
- self._change_int_ext_value(data[0]);
- });
+ self.set({value: element_ids[0]});
});
},
- _change_int_ext_value: function(value) {
- this._change_int_value(value);
- this.$input.val(this.value ? this.value[1] : "");
- },
- _change_int_value: function(value) {
- this.value = value;
- var back_orig_value = this.original_value;
- if (this.value === null || this.value) {
- this.original_value = this.value;
+ render_value: function(no_recurse) {
+ var self = this;
+ if (! this.get("value")) {
+ this.display_string("");
+ return;
}
- if (back_orig_value === undefined) { // first use after a set_value()
+ var display = this.display_value["" + this.get("value")];
+ if (display) {
+ this.display_string(display);
return;
}
- if (this.value !== undefined && ((back_orig_value ? back_orig_value[0] : null)
- !== (this.value ? this.value[0] : null))) {
- this.on_ui_change();
+ if (! no_recurse) {
+ var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
+ dataset.name_get([self.get("value")], function(data) {
+ self.display_value["" + self.get("value")] = data[0][1];
+ self.render_value(true);
+ });
}
},
- set_value: function(value) {
- value = value || null;
- this.invalid = false;
+ display_string: function(str) {
var self = this;
- this.tmp_value = value;
- self.update_dom();
- self.on_value_changed();
- var real_set_value = function(rval) {
- self.tmp_value = undefined;
- self.value = rval;
- self.original_value = undefined;
- self._change_int_ext_value(rval);
- };
- if (value && !(value instanceof Array)) {
- // name_get in a m2o does not use the context of the field
- new openerp.web.DataSet(this, this.field.relation, self.view.dataset.get_context())
- .name_get([value], function(data) {
- real_set_value(data[0]);
- }).fail(function() {self.tmp_value = undefined;});
+ if (!this.get("effective_readonly")) {
+ this.$input.val(str);
} else {
- $.async_when().then(function() {real_set_value(value);});
- }
- },
- get_value: function() {
- if (this.tmp_value !== undefined) {
- if (this.tmp_value instanceof Array) {
- return this.tmp_value[0];
- }
- return this.tmp_value ? this.tmp_value : false;
- }
- if (this.value === undefined)
- return this.original_value ? this.original_value[0] : false;
- return this.value ? this.value[0] : false;
- },
- validate: function() {
- this.invalid = false;
- var val = this.tmp_value !== undefined ? this.tmp_value : this.value;
- if (val === null) {
- this.invalid = this.required;
+ this.$element.find('a')
+ .unbind('click')
+ .text(str)
+ .click(function () {
+ self.do_action({
+ type: 'ir.actions.act_window',
+ res_model: self.field.relation,
+ res_id: self.get("value"),
+ context: self.build_context(),
+ views: [[false, 'form']],
+ target: 'current'
+ });
+ return false;
+ });
}
},
- open_related: function(related) {
+ set_value: function(value_) {
var self = this;
- if (!self.value)
- return;
- var additional_context = {
- active_id: self.value[0],
- active_ids: [self.value[0]],
- active_model: self.field.relation
- };
- self.rpc("/web/action/load", {
- action_id: related[2].id,
- context: additional_context
- }, function(result) {
- result.result.context = _.extend(result.result.context || {}, additional_context);
- self.do_action(result.result);
- });
+ if (value_ instanceof Array) {
+ this.display_value = {};
+ this.display_value["" + value_[0]] = value_[1];
+ value_ = value_[0];
+ }
+ value_ = value_ || false;
+ this.inhibit_on_change = true;
+ this._super(value_);
+ this.inhibit_on_change = false;
},
- focus: function ($element) {
- this._super($element || this.$input);
+ is_false: function() {
+ return ! this.get("value");
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$input.prop('readonly', this.readonly);
+ focus: function () {
+ this.delay_focus(this.$input);
}
-});
-openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface);
+}));
/*
# Values: (0, 0, { fields }) create
@@ -2327,24 +2625,24 @@ var commands = {
return [6, false, ids];
}
};
-openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
- form_template: 'FieldOne2Many',
+instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({
multi_selection: false,
- init: function(view, node) {
- this._super(view, node);
+ disable_utility_classes: true,
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
+ lazy_build_o2m_kanban_view();
this.is_loaded = $.Deferred();
this.initial_is_loaded = this.is_loaded;
this.is_setted = $.Deferred();
this.form_last_update = $.Deferred();
this.init_form_last_update = this.form_last_update;
- this.disable_utility_classes = true;
},
start: function() {
this._super.apply(this, arguments);
var self = this;
- this.dataset = new openerp.web.form.One2ManyDataSet(this, this.field.relation);
+ this.dataset = new instance.web.form.One2ManyDataSet(this, this.field.relation);
this.dataset.o2m = this;
this.dataset.parent_view = this.view;
this.dataset.child_name = this.name;
@@ -2356,16 +2654,23 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
this.is_setted.then(function() {
self.load_views();
});
+ this.is_loaded.then(function() {
+ self.on("change:effective_readonly", self, function() {
+ self.is_loaded = self.is_loaded.pipe(function() {
+ self.viewmanager.destroy();
+ return $.when(self.load_views()).then(function() {
+ self.reload_current_view();
+ });
+ });
+ });
+ });
},
trigger_on_change: function() {
var tmp = this.doing_on_change;
this.doing_on_change = true;
- this.on_ui_change();
+ this.trigger('changed_value');
this.doing_on_change = tmp;
},
- is_readonly: function() {
- return this.readonly || this.force_readonly;
- },
load_views: function() {
var self = this;
@@ -2376,21 +2681,21 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
var view = {
view_id: false,
view_type: mode == "tree" ? "list" : mode,
- options: { sidebar : false }
+ options: {}
};
if (self.field.views && self.field.views[mode]) {
view.embedded_view = self.field.views[mode];
}
if(view.view_type === "list") {
view.options.selectable = self.multi_selection;
- if (self.is_readonly()) {
+ if (self.get("effective_readonly")) {
view.options.addable = null;
view.options.deletable = null;
view.options.isClarkGable = false;
}
} else if (view.view_type === "form") {
- if (self.is_readonly()) {
- view.view_type = 'page';
+ if (self.get("effective_readonly")) {
+ view.view_type = 'form';
}
view.options.not_interactible_on_create = true;
}
@@ -2398,12 +2703,14 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
});
this.views = views;
- this.viewmanager = new openerp.web.ViewManager(this, this.dataset, views, {});
+ this.viewmanager = new instance.web.ViewManager(this, this.dataset, views, {
+ $sidebar: false,
+ });
this.viewmanager.template = 'One2Many.viewmanager';
- this.viewmanager.registry = openerp.web.views.extend({
- list: 'openerp.web.form.One2ManyListView',
- form: 'openerp.web.form.One2ManyFormView',
- page: 'openerp.web.PageView'
+ this.viewmanager.registry = instance.web.views.extend({
+ list: 'instance.web.form.One2ManyListView',
+ form: 'instance.web.form.One2ManyFormView',
+ kanban: 'instance.web.form.One2ManyKanbanView',
});
var once = $.Deferred().then(function() {
self.init_form_last_update.resolve();
@@ -2412,12 +2719,12 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
self.initial_is_loaded.resolve();
});
this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
+ controller.o2m = self;
if (view_type == "list") {
- controller.o2m = self;
- if (self.is_readonly())
+ if (self.get("effective_readonly"))
controller.set_editable(false);
- } else if (view_type == "form" || view_type == 'page') {
- if (view_type == 'page' || self.is_readonly()) {
+ } else if (view_type === "form") {
+ if (self.get("effective_readonly")) {
$(".oe_form_buttons", controller.$element).children().remove();
}
controller.on_record_loaded.add_last(function() {
@@ -2451,7 +2758,7 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
var view = self.viewmanager.views[active_view].controller;
if(active_view === "list") {
return view.reload_content();
- } else if (active_view === "form" || active_view === 'page') {
+ } else if (active_view === "form") {
if (self.dataset.index === null && self.dataset.ids.length >= 1) {
self.dataset.index = 0;
}
@@ -2460,18 +2767,18 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
};
self.form_last_update = self.form_last_update.pipe(act, act);
return self.form_last_update;
- } else if (active_view === "graph") {
+ } else if (view.do_search) {
return view.do_search(self.build_domain(), self.dataset.get_context(), []);
}
}, undefined);
},
- set_value: function(value) {
- value = value || [];
+ set_value: function(value_) {
+ value_ = value_ || [];
var self = this;
this.dataset.reset_ids([]);
- if(value.length >= 1 && value[0] instanceof Array) {
+ if(value_.length >= 1 && value_[0] instanceof Array) {
var ids = [];
- _.each(value, function(command) {
+ _.each(value_, function(command) {
var obj = {values: command[2]};
switch (command[0]) {
case commands.CREATE:
@@ -2500,10 +2807,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
});
this._super(ids);
this.dataset.set_ids(ids);
- } else if (value.length >= 1 && typeof(value[0]) === "object") {
+ } else if (value_.length >= 1 && typeof(value_[0]) === "object") {
var ids = [];
this.dataset.delete_all = true;
- _.each(value, function(command) {
+ _.each(value_, function(command) {
var obj = {values: command};
obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix);
obj.defaults = {};
@@ -2514,8 +2821,8 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
this._super(ids);
this.dataset.set_ids(ids);
} else {
- this._super(value);
- this.dataset.reset_ids(value);
+ this._super(value_);
+ this.dataset.reset_ids(value_);
}
if (this.dataset.index === null && this.dataset.ids.length > 0) {
this.dataset.index = 0;
@@ -2572,61 +2879,37 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
return false;
}, this));
},
- is_valid: function() {
- this.validate();
- return this._super();
- },
- validate: function() {
- this.invalid = false;
+ is_syntax_valid: function() {
if (!this.viewmanager.views[this.viewmanager.active_view])
- return;
+ return true;
var view = this.viewmanager.views[this.viewmanager.active_view].controller;
if (this.viewmanager.active_view === "form") {
for (var f in view.fields) {
f = view.fields[f];
if (!f.is_valid()) {
- this.invalid = true;
- return;
+ return false;
}
}
}
+ return true;
},
- is_dirty: function() {
- this.save_any_view();
- return this._super();
- },
- update_dom: function() {
- this._super.apply(this, arguments);
- var self = this;
- if (this.previous_readonly !== this.readonly) {
- this.previous_readonly = this.readonly;
- if (this.viewmanager) {
- this.is_loaded = this.is_loaded.pipe(function() {
- self.viewmanager.destroy();
- return $.when(self.load_views()).then(function() {
- self.reload_current_view();
- });
- });
- }
- }
- }
});
-openerp.web.form.One2ManyDataSet = openerp.web.BufferedDataSet.extend({
+instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({
get_context: function() {
this.context = this.o2m.build_context([this.o2m.name]);
return this.context;
}
});
-openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
+instance.web.form.One2ManyListView = instance.web.ListView.extend({
_template: 'One2Many.listview',
do_add_record: function () {
if (this.options.editable) {
this._super.apply(this, arguments);
} else {
var self = this;
- var pop = new openerp.web.form.SelectCreatePopup(this);
+ var pop = new instance.web.form.SelectCreatePopup(this);
pop.on_default_get.add(self.dataset.on_default_get);
pop.select_element(
self.o2m.field.relation,
@@ -2657,7 +2940,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
},
do_activate_record: function(index, id) {
var self = this;
- var pop = new openerp.web.form.FormOpenPopup(self.o2m.view);
+ var pop = new instance.web.form.FormOpenPopup(self.o2m.view);
pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
title: _t("Open: ") + self.name,
auto_write: false,
@@ -2668,7 +2951,7 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
},
form_view_options: {'not_interactible_on_create':true},
- readonly: self.o2m.is_readonly()
+ readonly: self.o2m.get("effective_readonly")
});
pop.on_write.add(function(id, data) {
self.o2m.dataset.write(id, data, {}, function(r) {
@@ -2683,12 +2966,12 @@ openerp.web.form.One2ManyListView = openerp.web.ListView.extend({
}
});
-openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
+instance.web.form.One2ManyFormView = instance.web.FormView.extend({
form_template: 'One2Many.formview',
on_loaded: function(data) {
this._super(data);
var self = this;
- this.$form_header.find('button.oe_form_button_create').click(function() {
+ this.$buttons.find('button.oe_form_button_create').click(function() {
self.do_save().then(self.on_button_new);
});
},
@@ -2701,12 +2984,43 @@ openerp.web.form.One2ManyFormView = openerp.web.FormView.extend({
}
});
-openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
- form_template: 'FieldMany2Many',
+var lazy_build_o2m_kanban_view = function() {
+if (! instance.web_kanban || instance.web.form.One2ManyKanbanView)
+ return;
+instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({
+ open_record: function(id) {
+ var self = this;
+ var pop = new instance.web.form.FormOpenPopup(self.o2m.view);
+ pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), {
+ title: _t("Open: ") + self.name,
+ auto_write: false,
+ alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined,
+ parent_view: self.o2m.view,
+ child_name: self.o2m.name,
+ read_function: function() {
+ return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments);
+ },
+ form_view_options: {'not_interactible_on_create':true},
+ readonly: self.o2m.get("effective_readonly"),
+ });
+ pop.on_write.add(function(id, data) {
+ self.o2m.dataset.write(id, data, {}, function(r) {
+ self.o2m.reload_current_view();
+ });
+ });
+
+ },
+});
+}
+
+/*
+ * TODO niv: clean those deferred stuff, it could be better
+ */
+instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({
multi_selection: false,
- init: function(view, node) {
- this._super(view, node);
- this.list_id = _.uniqueId("many2many");
+ disable_utility_classes: true,
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
this.is_loaded = $.Deferred();
this.initial_is_loaded = this.is_loaded;
this.is_setted = $.Deferred();
@@ -2716,43 +3030,44 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
var self = this;
- this.dataset = new openerp.web.form.Many2ManyDataSet(this, this.field.relation);
+ this.dataset = new instance.web.form.Many2ManyDataSet(this, this.field.relation);
this.dataset.m2m = this;
this.dataset.on_unlink.add_last(function(ids) {
- self.on_ui_change();
+ self.dataset_changed();
});
this.is_setted.then(function() {
self.load_view();
});
+ this.is_loaded.then(function() {
+ self.on("change:effective_readonly", self, function() {
+ self.is_loaded = self.is_loaded.pipe(function() {
+ self.list_view.destroy();
+ return $.when(self.load_view()).then(function() {
+ self.reload_content();
+ });
+ });
+ });
+ })
},
- set_value: function(value) {
- value = value || [];
- if (value.length >= 1 && value[0] instanceof Array) {
- value = value[0][2];
+ set_value: function(value_) {
+ value_ = value_ || [];
+ if (value_.length >= 1 && value_[0] instanceof Array) {
+ value_ = value_[0][2];
}
- this._super(value);
- this.dataset.set_ids(value);
+ this._super(value_);
+ this.dataset.set_ids(value_);
var self = this;
self.reload_content();
this.is_setted.resolve();
},
- get_value: function() {
- return [commands.replace_with(this.dataset.ids)];
- },
- validate: function() {
- this.invalid = false;
- },
- is_readonly: function() {
- return this.readonly || this.force_readonly;
- },
load_view: function() {
var self = this;
- this.list_view = new openerp.web.form.Many2ManyListView(this, this.dataset, false, {
- 'addable': self.is_readonly() ? null : _t("Add"),
- 'deletable': self.is_readonly() ? false : true,
+ this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, {
+ 'addable': self.get("effective_readonly") ? null : _t("Add"),
+ 'deletable': self.get("effective_readonly") ? false : true,
'selectable': self.multi_selection,
- 'isClarkGable': self.is_readonly() ? false : true
+ 'isClarkGable': self.get("effective_readonly") ? false : true
});
var embedded = (this.field.views || {}).tree;
if (embedded) {
@@ -2765,7 +3080,7 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
loaded.resolve();
});
$.async_when().then(function () {
- self.list_view.appendTo($("#" + self.list_id));
+ self.list_view.appendTo(self.$element);
});
return loaded;
},
@@ -2775,24 +3090,12 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
return self.list_view.reload_content();
});
},
- update_dom: function() {
- this._super.apply(this, arguments);
- var self = this;
- if (this.previous_readonly !== this.readonly) {
- this.previous_readonly = this.readonly;
- if (this.list_view) {
- this.is_loaded = this.is_loaded.pipe(function() {
- self.list_view.destroy();
- return $.when(self.load_view()).then(function() {
- self.reload_content();
- });
- });
- }
- }
- }
+ dataset_changed: function() {
+ this.set({'value': [commands.replace_with(this.dataset.ids)]});
+ },
});
-openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
+instance.web.form.Many2ManyDataSet = instance.web.DataSetStatic.extend({
get_context: function() {
this.context = this.m2m.build_context();
return this.context;
@@ -2801,25 +3104,25 @@ openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
/**
* @class
- * @extends openerp.web.ListView
+ * @extends instance.web.ListView
*/
-openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends openerp.web.form.Many2ManyListView# */{
+instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends instance.web.form.Many2ManyListView# */{
do_add_record: function () {
- var pop = new openerp.web.form.SelectCreatePopup(this);
+ var pop = new instance.web.form.SelectCreatePopup(this);
pop.select_element(
this.model,
{
title: _t("Add: ") + this.name
},
- new openerp.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
+ new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]),
this.m2m_field.build_context()
);
var self = this;
pop.on_select_elements.add(function(element_ids) {
- _.each(element_ids, function(element_id) {
- if(! _.detect(self.dataset.ids, function(x) {return x == element_id;})) {
- self.dataset.set_ids([].concat(self.dataset.ids, [element_id]));
- self.m2m_field.on_ui_change();
+ _.each(element_ids, function(one_id) {
+ if(! _.detect(self.dataset.ids, function(x) {return x == one_id;})) {
+ self.dataset.set_ids([].concat(self.dataset.ids, [one_id]));
+ self.m2m_field.dataset_changed();
self.reload_content();
}
});
@@ -2827,10 +3130,10 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
},
do_activate_record: function(index, id) {
var self = this;
- var pop = new openerp.web.form.FormOpenPopup(this);
+ var pop = new instance.web.form.FormOpenPopup(this);
pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), {
title: _t("Open: ") + this.name,
- readonly: this.getParent().is_readonly()
+ readonly: this.getParent().get("effective_readonly")
});
pop.on_write_completed.add_last(function() {
self.reload_content();
@@ -2840,9 +3143,9 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
/**
* @class
- * @extends openerp.web.OldWidget
+ * @extends instance.web.OldWidget
*/
-openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.SelectCreatePopup# */{
+instance.web.form.SelectCreatePopup = instance.web.OldWidget.extend(/** @lends instance.web.form.SelectCreatePopup# */{
template: "SelectCreatePopup",
/**
* options:
@@ -2868,7 +3171,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
this.initial_ids = this.options.initial_ids;
this.created_elements = [];
this.renderElement();
- openerp.web.form.dialog(this.$element, {
+ instance.web.form.dialog(this.$element, {
close: function() {
self.check_exit();
},
@@ -2879,7 +3182,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
start: function() {
this._super();
var self = this;
- this.dataset = new openerp.web.ProxyDataSet(this, this.model,
+ this.dataset = new instance.web.ProxyDataSet(this, this.model,
this.context);
this.dataset.create_function = function() {
return self.options.create_function.apply(null, arguments).then(function(r) {
@@ -2899,10 +3202,10 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
contexts: [this.context]
}, function (results) {
var search_defaults = {};
- _.each(results.context, function (value, key) {
+ _.each(results.context, function (value_, key) {
var match = /^search_default_(.*)$/.exec(key);
if (match) {
- search_defaults[match[1]] = value;
+ search_defaults[match[1]] = value_;
}
});
self.setup_search_view(search_defaults);
@@ -2920,7 +3223,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
if (this.searchview) {
this.searchview.destroy();
}
- this.searchview = new openerp.web.SearchView(this,
+ this.searchview = new instance.web.SearchView(this,
this.dataset, false, search_defaults);
this.searchview.on_search.add(function(domains, contexts, groupbys) {
if (self.initial_ids) {
@@ -2932,13 +3235,13 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
}
});
this.searchview.on_loaded.add_last(function () {
- self.view_list = new openerp.web.form.SelectCreateListView(self,
+ self.view_list = new instance.web.form.SelectCreateListView(self,
self.dataset, false,
_.extend({'deletable': false,
'selectable': !self.options.disable_multiple_selection
}, self.options.list_view_options || {}));
self.view_list.popup = self;
- self.view_list.appendTo($("#" + self.element_id + "_view_list")).pipe(function() {
+ self.view_list.appendTo($(".oe-select-create-popup-view-list", self.$element)).pipe(function() {
self.view_list.do_show();
}).pipe(function() {
self.searchview.do_search();
@@ -2960,7 +3263,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
});
});
});
- this.searchview.appendTo($("#" + this.element_id + "_search"));
+ this.searchview.appendTo($(".oe-select-create-popup-view-list", self.$element));
},
do_search: function(domains, contexts, groupbys) {
var self = this;
@@ -2974,14 +3277,14 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
},
create_row: function() {
var self = this;
- var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
+ var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain);
wdataset.parent_view = this.options.parent_view;
wdataset.child_name = this.options.child_name;
return wdataset.create.apply(wdataset, arguments);
},
write_row: function() {
var self = this;
- var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
+ var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain);
wdataset.parent_view = this.options.parent_view;
wdataset.child_name = this.options.child_name;
return wdataset.write.apply(wdataset, arguments);
@@ -3005,11 +3308,11 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
this.view_list.$element.hide();
}
this.dataset.index = null;
- this.view_form = new openerp.web.FormView(this, this.dataset, false, self.options.form_view_options);
+ this.view_form = new instance.web.FormView(this, this.dataset, false, self.options.form_view_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("#" + this.element_id + "_view_form"));
+ this.view_form.appendTo(this.$element.find(".oe-select-create-popup-view-form"));
this.view_form.on_loaded.add_last(function() {
var $buttons = self.view_form.$element.find(".oe_form_buttons");
$buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self}));
@@ -3045,7 +3348,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
on_default_get: function(res) {}
});
-openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
+instance.web.form.SelectCreateListView = instance.web.ListView.extend({
do_add_record: function () {
this.popup.new_object();
},
@@ -3061,9 +3364,9 @@ openerp.web.form.SelectCreateListView = openerp.web.ListView.extend({
/**
* @class
- * @extends openerp.web.OldWidget
+ * @extends instance.web.OldWidget
*/
-openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp.web.form.FormOpenPopup# */{
+instance.web.form.FormOpenPopup = instance.web.OldWidget.extend(/** @lends instance.web.form.FormOpenPopup# */{
template: "FormOpenPopup",
/**
* options:
@@ -3081,7 +3384,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
this.context = context || {};
this.options = _.defaults(options || {}, {"auto_write": true});
this.renderElement();
- openerp.web.dialog(this.$element, {
+ instance.web.dialog(this.$element, {
title: options.title || '',
modal: true,
width: 960,
@@ -3091,7 +3394,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
},
start: function() {
this._super();
- this.dataset = new openerp.web.form.FormOpenDataset(this, this.model, this.context);
+ this.dataset = new instance.web.form.FormOpenDataset(this, this.model, this.context);
this.dataset.fop = this;
this.dataset.ids = [this.row_id];
this.dataset.index = 0;
@@ -3103,7 +3406,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
if (!this.options.auto_write)
return;
var self = this;
- var wdataset = new openerp.web.DataSetSearch(this, this.model, this.context, this.domain);
+ var wdataset = new instance.web.DataSetSearch(this, this.model, this.context, this.domain);
wdataset.parent_view = this.options.parent_view;
wdataset.child_name = this.options.child_name;
wdataset.write(id, data, {}, function(r) {
@@ -3113,14 +3416,14 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
on_write_completed: function() {},
setup_form_view: function() {
var self = this;
- var FormClass = this.options.readonly
- ? openerp.web.views.get_object('page')
- : openerp.web.views.get_object('form');
- this.view_form = new FormClass(this, this.dataset, false, self.options.form_view_options);
+ var FormClass = instance.web.views.get_object('form');
+ var options = _.clone(self.options.form_view_options) || {};
+ options.initial_mode = this.options.readonly ? "view" : "edit";
+ this.view_form = new FormClass(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("#" + this.element_id + "_view_form"));
+ this.view_form.appendTo(this.$element.find(".oe-form-open-popup-form-view"));
this.view_form.on_loaded.add_last(function() {
var $buttons = self.view_form.$element.find(".oe_form_buttons");
$buttons.html(QWeb.render("FormOpenPopup.form.buttons"));
@@ -3144,7 +3447,7 @@ openerp.web.form.FormOpenPopup = openerp.web.OldWidget.extend(/** @lends openerp
}
});
-openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
+instance.web.form.FormOpenDataset = instance.web.ProxyDataSet.extend({
read_ids: function() {
if (this.fop.options.read_function) {
return this.fop.options.read_function.apply(null, arguments);
@@ -3154,40 +3457,28 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
}
});
-openerp.web.form.FieldReference = openerp.web.form.Field.extend({
- form_template: 'FieldReference',
- init: function(view, node) {
- this._super(view, node);
+instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ template: 'FieldReference',
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
this.fields_view = {
fields: {
selection: {
- selection: view.fields_view.fields[this.name].selection
+ selection: this.view.fields_view.fields[this.name].selection
},
m2o: {
relation: null
}
}
};
- this.get_fields_values = view.get_fields_values;
- this.get_selected_ids = view.get_selected_ids;
+ this.get_fields_values = this.view.get_fields_values;
+ this.get_selected_ids = this.view.get_selected_ids;
this.do_onchange = this.on_form_changed = this.do_notify_change = this.on_nop;
this.dataset = this.view.dataset;
- this.widgets_counter = 0;
this.view_id = 'reference_' + _.uniqueId();
- this.widgets = {};
this.fields = {};
this.fields_order = [];
- this.selection = new openerp.web.form.FieldSelection(this, { attrs: {
- name: 'selection',
- widget: 'selection'
- }});
this.reference_ready = true;
- this.selection.on_value_changed.add_last(this.on_selection_changed);
- this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
- name: 'm2o',
- widget: 'many2one'
- }});
- this.m2o.on_ui_change.add_last(this.on_ui_change);
},
on_nop: function() {
},
@@ -3195,59 +3486,82 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
if (this.reference_ready) {
var sel = this.selection.get_value();
this.m2o.field.relation = sel;
- this.m2o.set_value(null);
+ this.m2o.set_value(false);
this.m2o.$element.toggle(sel !== false);
}
},
- start: function() {
- this._super();
+ destroy_content: function() {
+ if (this.selection) {
+ this.selection.destroy();
+ this.selection = undefined;
+ }
+ if (this.m2o) {
+ this.m2o.destroy();
+ this.m2o = undefined;
+ }
+ },
+ initialize_content: function() {
+ this.selection = new instance.web.form.FieldSelection(this, { attrs: {
+ name: 'selection',
+ widget: 'selection'
+ }});
+ this.selection.set({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.renderElement();
this.selection.start();
+
+ this.m2o = new instance.web.form.FieldMany2One(this, { attrs: {
+ name: 'm2o',
+ widget: 'many2one'
+ }});
+ this.m2o.set({"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.renderElement();
this.m2o.start();
},
- is_valid: function() {
- return this.required === false || typeof(this.get_value()) === 'string';
+ is_false: function() {
+ return typeof(this.get_value()) !== 'string';
},
- is_dirty: function() {
- return this.selection.is_dirty() || this.m2o.is_dirty();
+ set_value: function(value_) {
+ this._super(value_);
+ this.render_value();
},
- set_value: function(value) {
- this._super(value);
+ render_value: function() {
this.reference_ready = false;
var vals = [], sel_val, m2o_val;
- if (typeof(value) === 'string') {
- vals = value.split(',');
+ if (typeof(this.get('value')) === 'string') {
+ vals = this.get('value').split(',');
}
sel_val = vals[0] || false;
m2o_val = vals[1] ? parseInt(vals[1], 10) : false;
- this.selection.set_value(sel_val);
+ if (!this.get("effective_readonly")) {
+ this.selection.set_value(sel_val);
+ }
this.m2o.field.relation = sel_val;
this.m2o.set_value(m2o_val);
this.m2o.$element.toggle(sel_val !== false);
this.reference_ready = true;
},
- get_value: function() {
+ data_changed: function() {
var model = this.selection.get_value(),
id = this.m2o.get_value();
if (typeof(model) === 'string' && typeof(id) === 'number') {
- return model + ',' + id;
+ this.set({'value': model + ',' + id});
} else {
- return false;
+ this.set({'value': false});
}
- }
-});
+ },
+}));
-openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
- init: function(view, node) {
- this._super(view, node);
+instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, {
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
this.iframe = this.element_id + '_iframe';
this.binary_value = false;
},
- start: function() {
- this._super.apply(this, arguments);
+ initialize_content: function() {
this.$element.find('input.oe-binary-file').change(this.on_file_change);
this.$element.find('button.oe-binary-file-save').click(this.on_save_as);
this.$element.find('.oe-binary-file-clear').click(this.on_clear);
@@ -3281,7 +3595,6 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
console.warn("Error while uploading file : ", name);
} else {
this.on_file_uploaded_and_valid.apply(this, arguments);
- this.on_ui_change();
}
this.$element.find('.oe-binary-progress').hide();
this.$element.find('.oe-binary').show();
@@ -3300,48 +3613,64 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
context: this.view.dataset.get_context()
})},
complete: $.unblockUI,
- error: openerp.webclient.crashmanager.on_rpc_error
+ error: instance.webclient.crashmanager.on_rpc_error
});
},
on_clear: function() {
- if (this.value !== false) {
- this.value = false;
+ if (this.get('value') !== false) {
this.binary_value = false;
- this.on_ui_change();
+ this.set({'value': false});
}
return false;
}
-});
+}));
-openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
- form_template: 'FieldBinaryFile',
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('.oe-binary-file-set, .oe-binary-file-clear').toggle(!this.readonly);
- this.$element.find('input[type=text]').prop('readonly', this.readonly);
+instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({
+ template: 'FieldBinaryFile',
+ initialize_content: function() {
+ this._super();
+ if (this.get("effective_readonly")) {
+ var self = this;
+ this.$element.find('a').click(function() {
+ if (self.get('value')) {
+ self.on_save_as();
+ }
+ return false;
+ });
+ }
},
- set_value: function(value) {
+ set_value: function(value_) {
this._super.apply(this, arguments);
- var show_value;
- if (this.node.attrs.filename) {
- show_value = this.view.datarecord[this.node.attrs.filename] || '';
+ this.render_value();
+ },
+ render_value: function() {
+ if (!this.get("effective_readonly")) {
+ var show_value;
+ if (this.node.attrs.filename) {
+ show_value = this.view.datarecord[this.node.attrs.filename] || '';
+ } else {
+ show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : '';
+ }
+ this.$element.find('input').eq(0).val(show_value);
} else {
- show_value = (value != null && value !== false) ? value : '';
+ this.$element.find('a').show(!!this.get('value'));
+ if (this.get('value')) {
+ var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || '');
+ this.$element.find('a').text(show_value);
+ }
}
- this.$element.find('input').eq(0).val(show_value);
},
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- this.value = file_base64;
this.binary_value = true;
+ this.set({'value': file_base64});
var show_value = name + " (" + this.human_filesize(size) + ")";
this.$element.find('input').eq(0).val(show_value);
this.set_filename(name);
},
- set_filename: function(value) {
+ set_filename: function(value_) {
var filename = this.node.attrs.filename;
if (this.view.fields[filename]) {
- this.view.fields[filename].set_value(value);
- this.view.fields[filename].on_ui_change();
+ this.view.fields[filename].set({value: value_});
}
},
on_clear: function() {
@@ -3351,58 +3680,59 @@ openerp.web.form.FieldBinaryFile = openerp.web.form.FieldBinary.extend({
}
});
-openerp.web.form.FieldBinaryImage = openerp.web.form.FieldBinary.extend({
- form_template: 'FieldBinaryImage',
- start: function() {
- this._super.apply(this, arguments);
- this.$image = this.$element.find('img.oe-binary-image');
+instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({
+ template: 'FieldBinaryImage',
+ initialize_content: function() {
+ this._super();
+ this.$placeholder = $(".oe_form_field-binary-image-placeholder", this.$element);
+ if (!this.get("effective_readonly"))
+ this.$element.find('.oe-binary').show();
+ else
+ this.$element.find('.oe-binary').hide();
},
- update_dom: function() {
+ set_value: function(value_) {
this._super.apply(this, arguments);
- this.$element.find('.oe-binary').toggle(!this.readonly);
+ this.render_value();
},
- set_value: function(value) {
- this._super.apply(this, arguments);
- this.set_image_maxwidth();
-
+ render_value: function() {
var url;
- if (value && value.substr(0, 10).indexOf(' ') == -1) {
- url = 'data:image/png;base64,' + this.value;
- } else {
+ if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) {
+ url = 'data:image/png;base64,' + this.get('value');
+ } else if (this.get('value')) {
url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' +
this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime());
+ } else {
+ url = "/web/static/src/img/placeholder.png";
}
- this.$image.attr('src', url);
- },
- set_image_maxwidth: function() {
- this.$image.css('max-width', this.$element.width());
+ var rendered = QWeb.render("FieldBinaryImage-img", {widget: this, url: url});;
+ this.$placeholder.html(rendered);
},
on_file_change: function() {
- this.set_image_maxwidth();
+ this.render_value();
this._super.apply(this, arguments);
},
on_file_uploaded_and_valid: function(size, name, content_type, file_base64) {
- this.value = file_base64;
+ this.set({'value': file_base64});
this.binary_value = true;
- this.$image.attr('src', 'data:' + (content_type || 'image/png') + ';base64,' + file_base64);
+ this.render_value();
},
on_clear: function() {
this._super.apply(this, arguments);
- this.$image.attr('src', '/web/static/src/img/placeholder.png');
+ this.render_value();
}
});
-openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
- form_template: "EmptyComponent",
+instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({
+ template: "EmptyComponent",
start: function() {
this._super();
this.selected_value = null;
this.render_list();
},
- set_value: function(value) {
- this._super(value);
- this.selected_value = value;
+ set_value: function(value_) {
+ this._super(value_);
+ this.selected_value = value_;
this.render_list();
},
@@ -3420,7 +3750,7 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
});
}
- var content = openerp.web.qweb.render("FieldStatus.content", {widget: this, _:_});
+ var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_});
this.$element.html(content);
var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}");
@@ -3457,77 +3787,36 @@ openerp.web.form.FieldStatus = openerp.web.form.Field.extend({
}
});
-openerp.web.form.WidgetHtml = openerp.web.form.Widget.extend({
- renderElement: function () {
- var $root = $('<div class="oe_form_html_view">');
- this.render_children(this, $root);
- var rendered = $root.html();
- this.$element.html(rendered);
- },
- render_children: function (object, $into) {
- var self = this,
- fields = this.view.fields_view.fields;
- _(object.children).each(function (child) {
- if (typeof child === 'string') {
- $into.text(child);
- } else if (child.tag === 'field') {
- var widget = instanciate_widget(self.view.registry.get_object('frame'),
- self.view, {tag: 'ueule', attrs: {}, children: [child] });
- widget.appendTo($into);
- } else {
- var $child = $(document.createElement(child.tag))
- .attr(child.attrs)
- .appendTo($into);
- self.render_children(child, $child);
- }
- });
- }
-});
-
-
/**
- * Registry of form widgets, called by :js:`openerp.web.FormView`
+ * Registry of form widgets, called by :js:`instance.web.FormView`
*/
-openerp.web.form.widgets = new openerp.web.Registry({
- 'frame' : 'openerp.web.form.WidgetFrame',
- 'group' : 'openerp.web.form.WidgetGroup',
- 'notebook' : 'openerp.web.form.WidgetNotebook',
- 'notebookpage' : 'openerp.web.form.WidgetNotebookPage',
- 'separator' : 'openerp.web.form.WidgetSeparator',
- 'label' : 'openerp.web.form.WidgetLabel',
- 'button' : 'openerp.web.form.WidgetButton',
- 'char' : 'openerp.web.form.FieldChar',
- 'id' : 'openerp.web.form.FieldID',
- 'email' : 'openerp.web.form.FieldEmail',
- 'url' : 'openerp.web.form.FieldUrl',
- 'text' : 'openerp.web.form.FieldText',
- 'date' : 'openerp.web.form.FieldDate',
- 'datetime' : 'openerp.web.form.FieldDatetime',
- 'selection' : 'openerp.web.form.FieldSelection',
- 'many2one' : 'openerp.web.form.FieldMany2One',
- 'many2many' : 'openerp.web.form.FieldMany2Many',
- 'one2many' : 'openerp.web.form.FieldOne2Many',
- 'one2many_list' : 'openerp.web.form.FieldOne2Many',
- 'reference' : 'openerp.web.form.FieldReference',
- 'boolean' : 'openerp.web.form.FieldBoolean',
- 'float' : 'openerp.web.form.FieldFloat',
- 'integer': 'openerp.web.form.FieldFloat',
- 'float_time': 'openerp.web.form.FieldFloat',
- 'progressbar': 'openerp.web.form.FieldProgressBar',
- 'image': 'openerp.web.form.FieldBinaryImage',
- 'binary': 'openerp.web.form.FieldBinaryFile',
- 'statusbar': 'openerp.web.form.FieldStatus',
- 'html': 'openerp.web.form.WidgetHtml'
+instance.web.form.widgets = new instance.web.Registry({
+ 'char' : 'instance.web.form.FieldChar',
+ 'id' : 'instance.web.form.FieldID',
+ 'email' : 'instance.web.form.FieldEmail',
+ 'url' : 'instance.web.form.FieldUrl',
+ 'text' : 'instance.web.form.FieldText',
+ 'date' : 'instance.web.form.FieldDate',
+ 'datetime' : 'instance.web.form.FieldDatetime',
+ 'selection' : 'instance.web.form.FieldSelection',
+ 'many2one' : 'instance.web.form.FieldMany2One',
+ 'many2many' : 'instance.web.form.FieldMany2Many',
+ 'one2many' : 'instance.web.form.FieldOne2Many',
+ 'one2many_list' : 'instance.web.form.FieldOne2Many',
+ 'reference' : 'instance.web.form.FieldReference',
+ 'boolean' : 'instance.web.form.FieldBoolean',
+ 'float' : 'instance.web.form.FieldFloat',
+ 'integer': 'instance.web.form.FieldFloat',
+ 'float_time': 'instance.web.form.FieldFloat',
+ 'progressbar': 'instance.web.form.FieldProgressBar',
+ 'image': 'instance.web.form.FieldBinaryImage',
+ 'binary': 'instance.web.form.FieldBinaryFile',
+ 'statusbar': 'instance.web.form.FieldStatus'
});
-var instanciate_widget = function(claz, view, node, o1, o2) {
- var widget = new (claz)(view, node, o1, o2);
- widget.element_class = (['formview', view.view_id, widget.element_name,
- view.widgets_counter++].join("_")).replace(/[^\r\n\f0-9A-Za-z_-]/g, "_");
- view.widgets[widget.element_class] = widget;
- return widget;
-}
-
+instance.web.form.tags = new instance.web.Registry({
+ 'button' : 'instance.web.form.WidgetButton',
+});
};
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index e8fff450c6e..c29957cac2d 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -1,9 +1,9 @@
-openerp.web.list = function (openerp) {
-var _t = openerp.web._t,
- _lt = openerp.web._lt;
-var QWeb = openerp.web.qweb;
-openerp.web.views.add('list', 'openerp.web.ListView');
-openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView# */ {
+openerp.web.list = function (instance) {
+var _t = instance.web._t,
+ _lt = instance.web._lt;
+var QWeb = instance.web.qweb;
+instance.web.views.add('list', 'instance.web.ListView');
+instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListView# */ {
_template: 'ListView',
display_name: _lt('List'),
defaults: {
@@ -33,11 +33,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
* See constructor parameters and method documentations for information on
* the default behaviors and possible options for the list view.
*
- * @constructs openerp.web.ListView
- * @extends openerp.web.View
+ * @constructs instance.web.ListView
+ * @extends instance.web.View
*
* @param parent parent object
- * @param {openerp.web.DataSet} dataset the dataset the view should work with
+ * @param {instance.web.DataSet} dataset the dataset the view should work with
* @param {String} view_id the listview's identifier, if any
* @param {Object} options A set of options used to configure the view
* @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox)
@@ -61,12 +61,12 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.records = new Collection();
- this.set_groups(new openerp.web.ListView.Groups(this));
+ this.set_groups(new instance.web.ListView.Groups(this));
- if (this.dataset instanceof openerp.web.DataSetStatic) {
- this.groups.datagroup = new openerp.web.StaticDataGroup(this.dataset);
+ if (this.dataset instanceof instance.web.DataSetStatic) {
+ this.groups.datagroup = new instance.web.StaticDataGroup(this.dataset);
} else {
- this.groups.datagroup = new openerp.web.DataGroup(
+ this.groups.datagroup = new instance.web.DataGroup(
this, this.model,
dataset.get_domain(),
dataset.get_context(),
@@ -104,7 +104,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
/**
* Set a custom Group construct as the root of the List View.
*
- * @param {openerp.web.ListView.Groups} groups
+ * @param {instance.web.ListView.Groups} groups
*/
set_groups: function (groups) {
var self = this;
@@ -136,7 +136,6 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
* @returns {$.Deferred} loading promise
*/
start: function() {
- this._super();
this.$element.addClass('oe-listview');
return this.reload_view(null, null, true);
},
@@ -208,6 +207,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.$element.html(QWeb.render(this._template, this));
// Head hook
+ // Selecting records
this.$element.find('.all-record-selector').click(function(){
self.$element.find('.oe-record-selector input').prop('checked',
self.$element.find('.all-record-selector').prop('checked') || false);
@@ -216,70 +216,95 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
'selected', [selection.ids, selection.records]);
});
- this.$element.find('.oe-list-add')
- .click(this.proxy('do_add_record'))
- .attr('disabled', grouped && this.options.editable);
- this.$element.find('.oe-list-delete')
- .attr('disabled', true)
- .click(this.proxy('do_delete_selected'));
+ // Sorting columns
this.$element.find('thead').delegate('th.oe-sortable[data-id]', 'click', function (e) {
e.stopPropagation();
-
var $this = $(this);
self.dataset.sort($this.data('id'));
if ($this.find('span').length) {
- $this.find('span').toggleClass(
- 'ui-icon-triangle-1-s ui-icon-triangle-1-n');
+ $this.find('span').toggleClass( 'ui-icon-triangle-1-s ui-icon-triangle-1-n');
} else {
- $this.append('<span class="ui-icon ui-icon-triangle-1-n">')
- .siblings('.oe-sortable').find('span').remove();
+ $this.append('<span class="ui-icon ui-icon-triangle-1-n">') .siblings('.oe-sortable').find('span').remove();
}
self.reload_content();
});
- this.$element.find('.oe-list-pager')
- .delegate('button', 'click', function () {
- var $this = $(this);
- switch ($this.data('pager-action')) {
- case 'first':
- self.page = 0; break;
- case 'last':
- self.page = Math.floor(
- self.dataset.size() / self.limit());
- break;
- case 'next':
- self.page += 1; break;
- case 'previous':
- self.page -= 1; break;
- }
- self.reload_content();
- }).find('.oe-pager-state')
- .click(function (e) {
- e.stopPropagation();
+ // Add and delete
+ if (!this.$buttons) {
+ this.$buttons = $(QWeb.render("ListView.buttons", {'widget':self}));
+ if (this.options.$buttons) {
+ this.$buttons.appendTo(this.options.$buttons);
+ } else {
+ this.$element.find('.oe_list_buttons').replaceWith(this.$buttons);
+ }
+ this.$buttons.find('.oe_list_add')
+ .click(this.proxy('do_add_record'))
+ .prop('disabled', grouped && this.options.editable)
+ .end()
+ .find('.oe_list_delete')
+ .click(this.proxy('do_delete_selected'))
+ .prop('disabled', true);
+ }
+
+ // Pager
+ if (!this.$pager) {
+ this.$pager = $(QWeb.render("ListView.pager", {'widget':self}));
+ if (this.options.$buttons) {
+ this.$pager.appendTo(this.options.$pager);
+ } else {
+ this.$element.find('.oe_list_pager').replaceWith(this.$pager);
+ }
+
+ this.$pager
+ .on('click', 'a[data-pager-action]', function () {
var $this = $(this);
+ var max_page = Math.floor(self.dataset.size() / self.limit());
+ switch ($this.data('pager-action')) {
+ case 'first':
+ self.page = 0; break;
+ case 'last':
+ self.page = max_page - 1;
+ break;
+ case 'next':
+ self.page += 1; break;
+ case 'previous':
+ self.page -= 1; break;
+ }
+ if (self.page < 0) {
+ self.page = max_page;
+ } else if (self.page > max_page) {
+ self.page = 0;
+ }
+ self.reload_content();
+ }).find('.oe-pager-state')
+ .click(function (e) {
+ e.stopPropagation();
+ var $this = $(this);
+
+ var $select = $('<select>')
+ .appendTo($this.empty())
+ .click(function (e) {e.stopPropagation();})
+ .append('<option value="80">80</option>' +
+ '<option value="100">100</option>' +
+ '<option value="200">200</option>' +
+ '<option value="500">500</option>' +
+ '<option value="NaN">' + _t("Unlimited") + '</option>')
+ .change(function () {
+ var val = parseInt($select.val(), 10);
+ self._limit = (isNaN(val) ? null : val);
+ self.page = 0;
+ self.reload_content();
+ })
+ .val(self._limit || 'NaN');
+ });
+ }
- var $select = $('<select>')
- .appendTo($this.empty())
- .click(function (e) {e.stopPropagation();})
- .append('<option value="80">80</option>' +
- '<option value="100">100</option>' +
- '<option value="200">200</option>' +
- '<option value="500">500</option>' +
- '<option value="NaN">' + _t("Unlimited") + '</option>')
- .change(function () {
- var val = parseInt($select.val(), 10);
- self._limit = (isNaN(val) ? null : val);
- self.page = 0;
- self.reload_content();
- })
- .val(self._limit || 'NaN');
- });
- if (!this.sidebar && this.options.sidebar && this.options.sidebar_id) {
- this.sidebar = new openerp.web.Sidebar(this, this.options.sidebar_id);
- this.sidebar.start();
+ // Sidebar
+ if (!this.sidebar && this.options.$sidebar) {
+ this.sidebar = new instance.web.Sidebar(this);
+ this.sidebar.appendTo(this.options.$sidebar);
this.sidebar.add_toolbar(this.fields_view.toolbar);
- this.set_common_sidebar_sections(this.sidebar);
}
},
/**
@@ -287,7 +312,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
*
* Horrifying side-effect: sets the dataset's data on this.dataset?
*
- * @param {openerp.web.DataSet} dataset
+ * @param {instance.web.DataSet} dataset
*/
configure_pager: function (dataset) {
this.dataset.ids = dataset.ids;
@@ -296,28 +321,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
this.dataset._length = dataset._length;
}
- var limit = this.limit(),
- total = dataset.size(),
- first = (this.page * limit),
- last;
- if (!limit || (total - first) < limit) {
- last = total;
- } else {
- last = first + limit;
- }
- this.$element.find('span.oe-pager-state').empty().text(_.str.sprintf(
- _t("[%(first_record)d to %(last_record)d] of %(records_count)d"), {
- first_record: first + 1,
- last_record: last,
- records_count: total
- }));
+ var page = this.page + 1,
+ total = Math.floor(dataset.size() / this.limit()) + 1;
- this.$element
- .find('button[data-pager-action=first], button[data-pager-action=previous]')
- .attr('disabled', this.page === 0)
- .end()
- .find('button[data-pager-action=last], button[data-pager-action=next]')
- .attr('disabled', last === total);
+ this.$pager.find('.oe-pager-state').text(isNaN(total)
+ ? '-' : _.str.sprintf('%d / %d', page, total));
},
/**
* Sets up the listview's columns: merges view and fields data, move
@@ -328,7 +336,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
* @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed
*/
setup_columns: function (fields, grouped) {
- var domain_computer = openerp.web.form.compute_domain;
+ var domain_computer = instance.web.form.compute_domain;
var noop = function () { return {}; };
var field_to_column = function (field) {
@@ -397,7 +405,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
*
* The default implementation asks the list view's view manager to switch
* to a different view (by calling
- * :js:func:`~openerp.web.ViewManager.on_mode_switch`), using the
+ * :js:func:`~instance.web.ViewManager.on_mode_switch`), using the
* provided record index (within the current list view's dataset).
*
* If the index is null, ``switch_to_record`` asks for the creation of a
@@ -407,7 +415,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
* @param {String} [view="page"] the view type to switch to
*/
select_record:function (index, view) {
- view = view || index == null ? 'form' : 'page';
+ view = view || index == null ? 'form' : 'form';
this.dataset.index = index;
_.delay(_.bind(function () {
this.do_switch_view(view);
@@ -418,12 +426,24 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
if (this.sidebar) {
this.sidebar.$element.show();
}
+ if (this.$buttons) {
+ this.$buttons.show();
+ }
+ if (this.$pager) {
+ this.$pager.show();
+ }
},
do_hide: function () {
- this._super();
if (this.sidebar) {
this.sidebar.$element.hide();
}
+ if (this.$buttons) {
+ this.$buttons.hide();
+ }
+ if (this.$pager) {
+ this.$pager.hide();
+ }
+ this._super();
},
/**
* Reloads the list view based on the current settings (dataset & al)
@@ -444,7 +464,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
view_id: this.view_id,
view_type: "tree",
context: this.dataset.get_context(context),
- toolbar: this.options.sidebar
+ toolbar: !!this.options.$sidebar
}, callback);
}
},
@@ -479,7 +499,6 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
reload: function () {
return this.reload_content();
},
-
do_load_state: function(state, warm) {
var reload = false;
if (state.page && this.page !== state.page) {
@@ -507,7 +526,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
*/
do_search: function (domain, context, group_by) {
this.page = 0;
- this.groups.datagroup = new openerp.web.DataGroup(
+ this.groups.datagroup = new instance.web.DataGroup(
this, this.model, domain, context, group_by);
this.groups.datagroup.sort = this.dataset._sort;
@@ -544,16 +563,20 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
* @param {Array} records selected record values
*/
do_select: function (ids, records) {
- this.$element.find('.oe-list-delete').attr('disabled', !ids.length);
+ this.$buttons.find('.oe_list_delete').attr('disabled', !ids.length);
if (!ids.length) {
this.dataset.index = 0;
- if (this.sidebar) { this.sidebar.do_fold(); }
+ if (this.sidebar) {
+ this.sidebar.$element.hide();
+ }
this.compute_aggregates();
return;
}
this.dataset.index = _(this.dataset.ids).indexOf(ids[0]);
- if (this.sidebar) { this.sidebar.do_unfold(); }
+ if (this.sidebar) {
+ this.sidebar.$element.show();
+ }
this.compute_aggregates(_(records).map(function (record) {
return {count: 1, values: record};
@@ -575,7 +598,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
return;
}
- var c = new openerp.web.CompoundContext();
+ var c = new instance.web.CompoundContext();
c.set_eval_context(_.extend({
active_id: id,
active_ids: [id],
@@ -592,7 +615,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
*
* @param {Number} index index of the record in the dataset
* @param {Object} id identifier of the activated record
- * @param {openerp.web.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups)
+ * @param {instance.web.DataSet} dataset dataset in which the record is available (may not be the listview's dataset in case of nested groups)
*/
do_activate_record: function (index, id, dataset, view) {
this.dataset.ids = dataset.ids;
@@ -616,11 +639,11 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
/**
* Computes the aggregates for the current list view, either on the
* records provided or on the records of the internal
- * :js:class:`~openerp.web.ListView.Group`, by calling
- * :js:func:`~openerp.web.ListView.group.get_records`.
+ * :js:class:`~instance.web.ListView.Group`, by calling
+ * :js:func:`~instance.web.ListView.group.get_records`.
*
* Then displays the aggregates in the table through
- * :js:method:`~openerp.web.ListView.display_aggregates`.
+ * :js:method:`~instance.web.ListView.display_aggregates`.
*
* @param {Array} [records]
*/
@@ -696,7 +719,7 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
}
$footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id))
- .html(openerp.web.format_cell(aggregation, column, {
+ .html(instance.web.format_cell(aggregation, column, {
process_modifiers: false
}));
});
@@ -747,9 +770,20 @@ openerp.web.ListView = openerp.web.View.extend( /** @lends openerp.web.ListView#
.attr('colspan', this.previous_colspan);
this.previous_colspan = null;
}
+ },
+ no_result: function () {
+ if (this.groups.group_by
+ || !this.options.action
+ || !this.options.action.help) {
+ return;
+ }
+ this.$element.children('table').replaceWith(
+ $('<div class="oe_listview_nocontent">')
+ .append($('<img>', { src: '/web/static/src/img/list_empty_arrow.png' }))
+ .append($('<div>').html(this.options.action.help)));
}
});
-openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.ListView.List# */{
+instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.ListView.List# */{
/**
* List display for the ListView, handles basic DOM events and transforms
* them in the relevant higher-level events, to which the list view (or
@@ -774,10 +808,10 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
* Triggered when a row of the table is clicked, provides the index (in
* the rows array) and id of the selected record to the handle function.
*
- * @constructs openerp.web.ListView.List
- * @extends openerp.web.Class
+ * @constructs instance.web.ListView.List
+ * @extends instance.web.Class
*
- * @param {Object} opts display options, identical to those of :js:class:`openerp.web.ListView`
+ * @param {Object} opts display options, identical to those of :js:class:`instance.web.ListView`
*/
init: function (group, opts) {
var self = this;
@@ -824,7 +858,7 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
this.records.bind(event, callback);
}, this);
- this.$_element = $('<tbody class="ui-widget-content">')
+ this.$_element = $('<tbody>')
.appendTo(document.body)
.delegate('th.oe-record-selector', 'click', function (e) {
e.stopPropagation();
@@ -892,7 +926,7 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
// to get a correctly displayable value in the field
var model = ref_match[1],
id = parseInt(ref_match[2], 10);
- new openerp.web.DataSet(this.view, model).name_get([id], function(names) {
+ new instance.web.DataSet(this.view, model).name_get([id], function(names) {
if (!names.length) { return; }
record.set(column.id, names[0][1]);
});
@@ -907,14 +941,14 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
// fetch the name, set it on the record (in the right field)
// and let the various registered events handle refreshing the
// row
- new openerp.web.DataSet(this.view, column.relation)
+ new instance.web.DataSet(this.view, column.relation)
.name_get([value], function (names) {
if (!names.length) { return; }
record.set(column.id, names[0]);
});
}
}
- return openerp.web.format_cell(record.toForm().data, column, {
+ return instance.web.format_cell(record.toForm().data, column, {
model: this.dataset.model,
id: record.get('id')
});
@@ -1068,19 +1102,19 @@ openerp.web.ListView.List = openerp.web.Class.extend( /** @lends openerp.web.Lis
});
}
});
-openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.ListView.Groups# */{
+instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.web.ListView.Groups# */{
passtrough_events: 'action deleted row_link',
/**
* Grouped display for the ListView. Handles basic DOM events and interacts
- * with the :js:class:`~openerp.web.DataGroup` bound to it.
+ * with the :js:class:`~instance.web.DataGroup` bound to it.
*
* Provides events similar to those of
- * :js:class:`~openerp.web.ListView.List`
+ * :js:class:`~instance.web.ListView.List`
*
- * @constructs openerp.web.ListView.Groups
- * @extends openerp.web.Class
+ * @constructs instance.web.ListView.Groups
+ * @extends instance.web.Class
*
- * @param {openerp.web.ListView} view
+ * @param {instance.web.ListView} view
* @param {Object} [options]
* @param {Collection} [options.records]
* @param {Object} [options.options]
@@ -1191,7 +1225,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
self.records.proxy(group.value).reset();
delete self.children[group.value];
}
- var child = self.children[group.value] = new openerp.web.ListView.Groups(self.view, {
+ var child = self.children[group.value] = new instance.web.ListView.Groups(self.view, {
records: self.records.proxy(group.value),
options: self.options,
columns: self.columns
@@ -1227,7 +1261,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
var group_column = _(self.columns).detect(function (column) {
return column.id === group.grouped_on; });
try {
- $group_column.html(openerp.web.format_cell(
+ $group_column.html(instance.web.format_cell(
row_data, group_column, {
value_if_empty: _t("Undefined"),
process_modifiers: false
@@ -1264,7 +1298,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
var r = {};
r[column.id] = {value: group.aggregates[column.id]};
$('<td class="oe-number">')
- .html(openerp.web.format_cell(
+ .html(instance.web.format_cell(
r, column, {process_modifiers: false}))
.appendTo($row);
} else {
@@ -1297,7 +1331,7 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
},
render_dataset: function (dataset) {
var self = this,
- list = new openerp.web.ListView.List(this, {
+ list = new instance.web.ListView.List(this, {
options: this.options,
columns: this.columns,
dataset: dataset,
@@ -1344,6 +1378,9 @@ openerp.web.ListView.Groups = openerp.web.Class.extend( /** @lends openerp.web.L
self.records.add(records, {silent: true});
list.render();
d.resolve(list);
+ if (_.isEmpty(records)) {
+ view.no_result();
+ }
});});
return d.promise();
},
@@ -1510,10 +1547,10 @@ var Events = /** @lends Events# */{
return this;
}
};
-var Record = openerp.web.Class.extend(/** @lends Record# */{
+var Record = instance.web.Class.extend(/** @lends Record# */{
/**
* @constructs Record
- * @extends openerp.web.Class
+ * @extends instance.web.Class
*
* @mixes Events
* @param {Object} [data]
@@ -1591,7 +1628,7 @@ var Record = openerp.web.Class.extend(/** @lends Record# */{
}
});
Record.include(Events);
-var Collection = openerp.web.Class.extend(/** @lends Collection# */{
+var Collection = instance.web.Class.extend(/** @lends Collection# */{
/**
* Smarter collections, with events, very strongly inspired by Backbone's.
*
@@ -1599,7 +1636,7 @@ var Collection = openerp.web.Class.extend(/** @lends Collection# */{
* various serious
*
* @constructs Collection
- * @extends openerp.web.Class
+ * @extends instance.web.Class
*
* @mixes Events
* @param {Array} [records] records to initialize the collection with
@@ -1631,19 +1668,19 @@ var Collection = openerp.web.Class.extend(/** @lends Collection# */{
var records = record instanceof Array ? record : [record];
for(var i=0, length=records.length; i<length; ++i) {
- var instance = (records[i] instanceof Record) ? records[i] : new Record(records[i]);
- instance.bind(null, this._onRecordEvent);
- this._byId[instance.get('id')] = instance;
+ var instance_ = (records[i] instanceof Record) ? records[i] : new Record(records[i]);
+ instance_.bind(null, this._onRecordEvent);
+ this._byId[instance_.get('id')] = instance_;
if (options.at == undefined) {
- this.records.push(instance);
+ this.records.push(instance_);
if (!options.silent) {
- this.trigger('add', this, instance, this.records.length-1);
+ this.trigger('add', this, instance_, this.records.length-1);
}
} else {
var insertion_index = options.at + i;
- this.records.splice(insertion_index, 0, instance);
+ this.records.splice(insertion_index, 0, instance_);
if (!options.silent) {
- this.trigger('add', this, instance, insertion_index);
+ this.trigger('add', this, instance_, insertion_index);
}
}
this.length++;
@@ -1774,7 +1811,7 @@ var Collection = openerp.web.Class.extend(/** @lends Collection# */{
}
});
Collection.include(Events);
-openerp.web.list = {
+instance.web.list = {
Events: Events,
Record: Record,
Collection: Collection
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index b8699bcc006..ac19e3f341d 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -2,16 +2,16 @@
* handles editability case for lists, because it depends on form and forms already depends on lists it had to be split out
* @namespace
*/
-openerp.web.list_editable = function (openerp) {
+openerp.web.list_editable = function (instance) {
var KEY_RETURN = 13,
KEY_ESCAPE = 27;
- var QWeb = openerp.web.qweb;
+ var QWeb = instance.web.qweb;
// editability status of list rows
- openerp.web.ListView.prototype.defaults.editable = null;
+ instance.web.ListView.prototype.defaults.editable = null;
// TODO: not sure second @lends on existing item is correct, to check
- openerp.web.ListView.include(/** @lends openerp.web.ListView# */{
+ instance.web.ListView.include(/** @lends instance.web.ListView# */{
init: function () {
var self = this;
this._super.apply(this, arguments);
@@ -37,7 +37,7 @@ openerp.web.list_editable = function (openerp) {
*
* @param {Number} index index of the record in the dataset
* @param {Object} id identifier of the record being edited
- * @param {openerp.web.DataSet} dataset dataset in which the record is available
+ * @param {instance.web.DataSet} dataset dataset in which the record is available
*/
do_edit: function (index, id, dataset) {
_.extend(this.dataset, dataset);
@@ -93,8 +93,8 @@ openerp.web.list_editable = function (openerp) {
}
});
- openerp.web.ListView.Groups.include(/** @lends openerp.web.ListView.Groups# */{
- passtrough_events: openerp.web.ListView.Groups.prototype.passtrough_events + " edit 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();
@@ -113,7 +113,7 @@ openerp.web.list_editable = function (openerp) {
}
});
- openerp.web.ListView.List.include(/** @lends openerp.web.ListView.List# */{
+ instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{
row_clicked: function (event) {
if (!this.options.editable) {
return this._super.apply(this, arguments);
@@ -235,7 +235,8 @@ openerp.web.list_editable = function (openerp) {
}
self.edition = true;
self.edition_id = record_id;
- self.edition_form = new openerp.web.ListEditableFormView(self.view, self.dataset, false);
+ $new_row.addClass("oe_form_container");
+ self.edition_form = new instance.web.ListEditableFormView(self.view, self.dataset, false);
self.edition_form.$element = $new_row;
self.edition_form.editable_list = self;
// HO HO
@@ -273,7 +274,7 @@ openerp.web.list_editable = function (openerp) {
// insert after the source record
var index = self.records.indexOf(
self.records.get(source_record_id)) + 1;
- record = new openerp.web.list.Record({id: id});
+ record = new instance.web.list.Record({id: id});
self.records.add(record, {at: index});
self.dataset.ids.splice(index, 0, id);
}
@@ -366,52 +367,57 @@ openerp.web.list_editable = function (openerp) {
});
}
});
- if (!openerp.web.list) {
- openerp.web.list = {};
- }
- if (!openerp.web.list.form) {
- openerp.web.list.form = {};
- }
- openerp.web.list.form.WidgetFrame = openerp.web.form.WidgetFrame.extend({
- form_template: 'ListView.row.frame'
- });
- var form_widgets = openerp.web.form.widgets;
- openerp.web.list.form.widgets = form_widgets.extend({
- 'frame': 'openerp.web.list.form.WidgetFrame'
- });
- // All form widgets inherit a problematic behavior from
- // openerp.web.form.WidgetFrame: the cell itself is removed when invisible
- // whether it's @invisible or @attrs[invisible]. In list view, only the
- // former should completely remove the cell. We need to override update_dom
- // on all widgets since we can't just hit on widget itself (I think)
- var list_form_widgets = openerp.web.list.form.widgets;
- _(form_widgets.map).each(function (widget_path, key) {
- if (key === 'frame') { return; }
- var new_path = 'openerp.web.list.form.' + key;
-
- openerp.web.list.form[key] = (form_widgets.get_object(key)).extend({
- update_dom: function () {
- this.$element.children().css('visibility', '');
- if (this.modifiers.tree_invisible) {
- var old_invisible = this.invisible;
- this.invisible = true;
- this._super();
- this.invisible = old_invisible;
- } else if (this.invisible) {
- this.$element.children().css('visibility', 'hidden');
- } else {
- this._super();
- }
- }
- });
- list_form_widgets.add(key, new_path);
- });
- openerp.web.ListEditableFormView = openerp.web.FormView.extend({
- form_template: 'ListView.row.form',
+ instance.web.ListEditableFormView = instance.web.FormView.extend({
init: function() {
- this._super.apply(this, arguments);
- this.registry = openerp.web.list.form.widgets;
+ this._super.apply(this, arguments);
+ this.rendering_engine = new instance.web.ListEditableRenderingEngine(this);
+ },
+ renderElement: function() {}
+ });
+
+ instance.web.ListEditableRenderingEngine = instance.web.Class.extend({
+ init: function(view) {
+ this.view = view;
+ },
+ set_fields_view: function(fields_view) {
+ this.fvg = fields_view;
+ },
+ set_tags_registry: function(tags_registry) {
+ this.tags_registry = tags_registry;
+ },
+ set_fields_registry: function(fields_registry) {
+ this.fields_registry = fields_registry;
+ },
+ render_to: function($element) {
+ 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);
+ if (this.view.editable_list.options.isClarkGable)
+ $("<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 key = tag_name;
+ if (tag_name === "field") {
+ var name = $(el).attr("name");
+ key = $(el).attr('widget') || self.fvg.fields[name].type;
+ }
+ var obj = self.view.fields_registry.get_object(key);
+ var w = new (obj)(self.view, instance.web.xml_to_json(el));
+ self.view.register_field(w, $(el).attr("name"));
+ w.appendTo($td);
+ $td.appendTo($element);
+ });
+ $("<td><button class='oe-edit-row-save' type='button'></button></td>").appendTo($element);
},
});
};
diff --git a/addons/web/static/src/js/view_page.js b/addons/web/static/src/js/view_page.js
deleted file mode 100644
index 8949169838a..00000000000
--- a/addons/web/static/src/js/view_page.js
+++ /dev/null
@@ -1,288 +0,0 @@
-openerp.web.page = function (openerp) {
- var _t = openerp.web._t,
- _lt = openerp.web._lt;
-
- openerp.web.views.add('page', 'openerp.web.PageView');
- openerp.web.PageView = openerp.web.FormView.extend({
- form_template: "PageView",
- display_name: _lt('Page'),
- init: function () {
- this._super.apply(this, arguments);
- this.registry = openerp.web.page.readonly;
- },
- reload: function () {
- if (this.dataset.index == null) {
- this.do_prev_view();
- return $.Deferred().reject().promise();
- }
- return this._super();
- },
- on_loaded: function(data) {
- this._super(data);
- this.$form_header.find('button.oe_form_button_edit').click(this.on_button_edit);
- this.$form_header.find('button.oe_form_button_create').click(this.on_button_create);
- this.$form_header.find('button.oe_form_button_duplicate').click(this.on_button_duplicate);
- this.$form_header.find('button.oe_form_button_delete').click(this.on_button_delete);
- },
- on_button_edit: function() {
- return this.do_switch_view('form');
- },
- on_button_create: function() {
- this.dataset.index = null;
- return this.do_switch_view('form');
- },
- on_button_duplicate: function() {
- var self = this;
- var def = $.Deferred();
- $.when(this.has_been_loaded).then(function() {
- 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.do_switch_view('form');
- }).then(function() {
- def.resolve();
- });
- });
- return def.promise();
- },
- on_button_delete: function() {
- var self = this;
- var def = $.Deferred();
- $.when(this.has_been_loaded).then(function() {
- if (self.datarecord.id && confirm(_t("Do you really want to delete this record?"))) {
- self.dataset.unlink([self.datarecord.id]).then(function() {
- self.on_pager_action('next');
- def.resolve();
- });
- } else {
- $.async_when().then(function () {
- def.reject();
- })
- }
- });
- return def.promise();
- }
- });
-
- /** @namespace */
- openerp.web.page = {};
-
- openerp.web.page.WidgetFrameReadonly = openerp.web.form.WidgetFrame.extend({
- form_template: 'WidgetFrame.readonly'
- });
- openerp.web.page.FieldReadonly = openerp.web.form.Field.extend({
-
- });
- openerp.web.page.FieldCharReadonly = openerp.web.page.FieldReadonly.extend({
- form_template: 'FieldChar.readonly',
- init: function(view, node) {
- this._super(view, node);
- this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
- },
- set_value: function (value) {
- this._super.apply(this, arguments);
- var show_value = openerp.web.format_value(value, this, '');
- if (this.password) {
- show_value = new Array(show_value.length + 1).join('*');
- }
- this.$element.find('div').text(show_value);
- return show_value;
- }
- });
- openerp.web.page.FieldFloatReadonly = openerp.web.page.FieldCharReadonly.extend({
- init: function (view, node) {
- this._super(view, node);
- if (node.attrs.digits) {
- this.digits = py.eval(node.attrs.digits);
- } else {
- this.digits = view.fields_view.fields[node.attrs.name].digits;
- }
- }
- });
- openerp.web.page.FieldURIReadonly = openerp.web.page.FieldCharReadonly.extend({
- form_template: 'FieldURI.readonly',
- scheme: null,
- format_value: function (value) {
- return value;
- },
- set_value: function (value) {
- if (!value) {
- this.$element.find('a').text('').attr('href', '#');
- return;
- }
- this.$element.find('a')
- .attr('href', this.scheme + ':' + value)
- .text(this.format_value(value));
- }
- });
- openerp.web.page.FieldEmailReadonly = openerp.web.page.FieldURIReadonly.extend({
- scheme: 'mailto'
- });
- openerp.web.page.FieldUrlReadonly = openerp.web.page.FieldURIReadonly.extend({
- set_value: function (value) {
- if (!value) {
- this.$element.find('a').text('').attr('href', '#');
- return;
- }
- var s = /(\w+):(.+)/.exec(value);
- if (!s) {
- value = "http://" + value;
- }
- this.$element.find('a').attr('href', value).text(value);
- }
- });
- openerp.web.page.FieldBooleanReadonly = openerp.web.form.FieldBoolean.extend({
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('input').prop('disabled', true);
- }
- });
- openerp.web.page.FieldSelectionReadonly = openerp.web.page.FieldReadonly.extend({
- form_template: 'FieldChar.readonly',
- init: function(view, node) {
- // lifted straight from r/w version
- var self = this;
- this._super(view, node);
- this.values = _.clone(this.field.selection);
- _.each(this.values, function(v, i) {
- if (v[0] === false && v[1] === '') {
- self.values.splice(i, 1);
- }
- });
- this.values.unshift([false, '']);
- },
- set_value: function (value) {
- value = value === null ? false : value;
- value = value instanceof Array ? value[0] : value;
- var option = _(this.values)
- .detect(function (record) { return record[0] === value; });
- this._super(value);
- this.$element.find('div').text(option ? option[1] : this.values[0][1]);
- }
- });
- openerp.web.page.FieldMany2OneReadonly = openerp.web.page.FieldURIReadonly.extend({
- set_value: function (value) {
- value = value || null;
- this.invalid = false;
- var self = this;
- this.value = value;
- self.update_dom();
- self.on_value_changed();
- var real_set_value = function(rval) {
- self.value = rval;
- self.$element.find('a')
- .unbind('click')
- .text(rval ? rval[1] : '')
- .click(function () {
- self.do_action({
- type: 'ir.actions.act_window',
- res_model: self.field.relation,
- res_id: self.value[0],
- context: self.build_context(),
- views: [[false, 'page'], [false, 'form']],
- target: 'current'
- });
- return false;
- });
- };
- if (value && !(value instanceof Array)) {
- new openerp.web.DataSet(
- this, this.field.relation, self.build_context())
- .name_get([value], function(data) {
- real_set_value(data[0]);
- });
- } else {
- $.async_when().then(function() {real_set_value(value);});
- }
- },
- get_value: function() {
- if (!this.value) {
- return false;
- } else if (this.value instanceof Array) {
- return this.value[0];
- } else {
- return this.value;
- }
- }
- });
- openerp.web.page.FieldReferenceReadonly = openerp.web.page.FieldMany2OneReadonly.extend({
- set_value: function (value) {
- if (!value) {
- return this._super(null);
- }
- var reference = value.split(',');
- this.field.relation = reference[0];
- var id = parseInt(reference[1], 10);
- return this._super(id);
- },
- get_value: function () {
- if (!this.value) {
- return null;
- }
- var id;
- if (typeof this.value === 'number') {
- // name_get has not run yet
- id = this.value;
- } else {
- id = this.value[0];
- }
- return _.str.sprintf('%s,%d', this.field.relation, id);
- }
- });
-
- openerp.web.page.FieldMany2ManyReadonly = openerp.web.form.FieldMany2Many.extend({
- force_readonly: true
- });
- openerp.web.page.FieldOne2ManyReadonly = openerp.web.form.FieldOne2Many.extend({
- force_readonly: true
- });
- openerp.web.page.FieldBinaryImageReaonly = openerp.web.form.FieldBinaryImage.extend({
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('.oe-binary').hide();
- }
- });
- openerp.web.page.FieldBinaryFileReadonly = openerp.web.form.FieldBinary.extend({
- form_template: 'FieldURI.readonly',
- start: function() {
- this._super.apply(this, arguments);
- var self = this;
- this.$element.find('a').click(function() {
- if (self.value) {
- self.on_save_as();
- }
- return false;
- });
- },
- set_value: function(value) {
- this._super.apply(this, arguments);
- this.$element.find('a').show(!!value);
- if (value) {
- var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || '');
- this.$element.find('a').text(show_value);
- }
- }
- });
- openerp.web.page.readonly = openerp.web.form.widgets.extend({
- 'frame': 'openerp.web.page.WidgetFrameReadonly',
- 'char': 'openerp.web.page.FieldCharReadonly',
- 'id': 'openerp.web.page.FieldCharReadonly',
- 'email': 'openerp.web.page.FieldEmailReadonly',
- 'url': 'openerp.web.page.FieldUrlReadonly',
- 'text': 'openerp.web.page.FieldCharReadonly',
- 'date': 'openerp.web.page.FieldCharReadonly',
- 'datetime': 'openerp.web.page.FieldCharReadonly',
- 'selection' : 'openerp.web.page.FieldSelectionReadonly',
- 'many2one': 'openerp.web.page.FieldMany2OneReadonly',
- 'many2many' : 'openerp.web.page.FieldMany2ManyReadonly',
- 'one2many' : 'openerp.web.page.FieldOne2ManyReadonly',
- 'one2many_list' : 'openerp.web.page.FieldOne2ManyReadonly',
- 'reference': 'openerp.web.page.FieldReferenceReadonly',
- 'boolean': 'openerp.web.page.FieldBooleanReadonly',
- 'float': 'openerp.web.page.FieldFloatReadonly',
- 'integer': 'openerp.web.page.FieldCharReadonly',
- 'float_time': 'openerp.web.page.FieldCharReadonly',
- 'binary': 'openerp.web.page.FieldBinaryFileReadonly',
- 'image': 'openerp.web.page.FieldBinaryImageReaonly'
- });
-};
diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js
index 6039e4a0822..9e938fe293a 100644
--- a/addons/web/static/src/js/view_tree.js
+++ b/addons/web/static/src/js/view_tree.js
@@ -2,12 +2,12 @@
* OpenERP web library
*---------------------------------------------------------*/
-openerp.web.view_tree = function(openerp) {
-var QWeb = openerp.web.qweb,
- _lt = openerp.web._lt;
+openerp.web.view_tree = function(instance) {
+var QWeb = instance.web.qweb,
+ _lt = instance.web._lt;
-openerp.web.views.add('tree', 'openerp.web.TreeView');
-openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView# */{
+instance.web.views.add('tree', 'instance.web.TreeView');
+instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeView# */{
display_name: _lt('Tree'),
/**
* Indicates that this view is not searchable, and thus that no search
@@ -17,8 +17,8 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
/**
* Genuine tree view (the one displayed as a tree, not the list)
*
- * @constructs openerp.web.TreeView
- * @extends openerp.web.View
+ * @constructs instance.web.TreeView
+ * @extends instance.web.View
*
* @param parent
* @param dataset
@@ -39,7 +39,6 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
},
start: function () {
- this._super();
return this.rpc("/web/treeview/load", {
model: this.model,
view_id: this.view_id,
@@ -204,7 +203,7 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
'fields_view': self.fields_view.arch.children,
'fields': self.fields,
'level': $curr_node.data('level') || 0,
- 'render': openerp.web.format_value,
+ 'render': instance.web.format_value,
'color_for': self.color_for
});
@@ -227,12 +226,12 @@ openerp.web.TreeView = openerp.web.View.extend(/** @lends openerp.web.TreeView#
return this.rpc('/web/treeview/action', {
id: id,
model: this.dataset.model,
- context: new openerp.web.CompoundContext(
+ context: new instance.web.CompoundContext(
this.dataset.get_context(), local_context)
}).pipe(function (actions) {
if (!actions.length) { return; }
var action = actions[0][2];
- var c = new openerp.web.CompoundContext(local_context);
+ var c = new instance.web.CompoundContext(local_context);
if (action.context) {
c.add(action.context);
}
diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js
index 43ecc8444fc..36e3c2b802b 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -2,21 +2,11 @@
* OpenERP web library
*---------------------------------------------------------*/
-openerp.web.views = function(session) {
-var QWeb = session.web.qweb,
- _t = session.web._t;
+openerp.web.views = function(instance) {
+var QWeb = instance.web.qweb,
+ _t = instance.web._t;
-/**
- * Registry for all the client actions key: tag value: widget
- */
-session.web.client_actions = new session.web.Registry();
-
-/**
- * Registry for all the main views
- */
-session.web.views = new session.web.Registry();
-
-session.web.ActionManager = session.web.OldWidget.extend({
+instance.web.ActionManager = instance.web.Widget.extend({
init: function(parent) {
this._super(parent);
this.inner_action = null;
@@ -25,9 +15,6 @@ session.web.ActionManager = session.web.OldWidget.extend({
this.dialog_viewmanager = null;
this.client_widget = null;
},
- render: function() {
- return '<div id="' + this.element_id + '" style="height: 100%;"></div>';
- },
dialog_stop: function () {
if (this.dialog) {
this.dialog_viewmanager.destroy();
@@ -65,8 +52,8 @@ session.web.ActionManager = session.web.OldWidget.extend({
if (run_action) {
this.null_action();
action_loaded = this.do_action(state.action_id);
- session.webclient.menu.has_been_loaded.then(function() {
- session.webclient.menu.open_action(state.action_id);
+ instance.webclient.menu.has_been_loaded.then(function() {
+ instance.webclient.menu.open_action(state.action_id);
});
}
} else if (state.model && state.id) {
@@ -76,7 +63,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
res_model: state.model,
res_id: state.id,
type: 'ir.actions.act_window',
- views: [[false, 'page'], [false, 'form']]
+ views: [[false, 'form']]
};
action_loaded = this.do_action(action);
} else if (state.sa) {
@@ -112,12 +99,13 @@ session.web.ActionManager = session.web.OldWidget.extend({
}
var type = action.type.replace(/\./g,'_');
var popup = action.target === 'new';
+ var inline = action.target === 'inline';
action.flags = _.extend({
- views_switcher : !popup,
- search_view : !popup,
- action_buttons : !popup,
- sidebar : !popup,
- pager : !popup,
+ views_switcher : !popup && !inline,
+ search_view : !popup && !inline,
+ action_buttons : !popup && !inline,
+ sidebar : !popup && !inline,
+ pager : !popup && !inline,
display_title : !popup
}, action.flags || {});
if (!(type in this)) {
@@ -136,31 +124,31 @@ session.web.ActionManager = session.web.OldWidget.extend({
.contains(action.res_model)) {
var old_close = on_close;
on_close = function () {
- session.webclient.do_reload().then(old_close);
+ instance.webclient.do_reload().then(old_close);
};
}
if (action.target === 'new') {
if (this.dialog == null) {
- this.dialog = new session.web.Dialog(this, { width: '80%' });
+ this.dialog = new instance.web.Dialog(this, { width: '80%' });
if(on_close)
this.dialog.on_close.add(on_close);
} else {
this.dialog_viewmanager.destroy();
}
this.dialog.dialog_title = action.name;
- this.dialog_viewmanager = new session.web.ViewManagerAction(this, action);
+ this.dialog_viewmanager = new instance.web.ViewManagerAction(this, action);
this.dialog_viewmanager.appendTo(this.dialog.$element);
this.dialog.open();
} else {
if(action.menu_id) {
return this.getParent().do_action(action, function () {
- session.webclient.menu.open_menu(action.menu_id);
+ instance.webclient.menu.open_menu(action.menu_id);
});
}
this.dialog_stop();
this.content_stop();
this.inner_action = action;
- this.inner_viewmanager = new session.web.ViewManagerAction(this, action);
+ this.inner_viewmanager = new instance.web.ViewManagerAction(this, action);
this.inner_viewmanager.appendTo(this.$element);
}
},
@@ -182,7 +170,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
ir_actions_client: function (action) {
this.content_stop();
this.dialog_stop();
- var ClientWidget = session.web.client_actions.get_object(action.tag);
+ var ClientWidget = instance.web.client_actions.get_object(action.tag);
(this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element);
},
ir_actions_report_xml: function(action, on_closed) {
@@ -204,7 +192,7 @@ session.web.ActionManager = session.web.OldWidget.extend({
}
self.dialog_stop();
},
- error: session.webclient.crashmanager.on_rpc_error
+ error: instance.webclient.crashmanager.on_rpc_error
})
});
},
@@ -216,16 +204,8 @@ session.web.ActionManager = session.web.OldWidget.extend({
}
});
-session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.ViewManager# */{
+instance.web.ViewManager = instance.web.Widget.extend({
template: "ViewManager",
- /**
- * @constructs session.web.ViewManager
- * @extends session.web.OldWidget
- *
- * @param parent
- * @param dataset
- * @param views
- */
init: function(parent, dataset, views, flags) {
this._super(parent);
this.model = dataset ? dataset.model : undefined;
@@ -234,7 +214,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
this.active_view = null;
this.views_src = _.map(views, function(x) {
if (x instanceof Array) {
- var View = session.web.views.get_object(x[1], true);
+ var View = instance.web.views.get_object(x[1], true);
return {
view_id: x[0],
view_type: x[1],
@@ -246,22 +226,16 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
});
this.views = {};
this.flags = flags || {};
- this.registry = session.web.views;
+ this.registry = instance.web.views;
this.views_history = [];
},
- render: function() {
- return session.web.qweb.render(this.template, {
- self: this,
- prefix: this.element_id,
- views: this.views_src});
- },
/**
* @returns {jQuery.Deferred} initial view loading promise
*/
start: function() {
this._super();
var self = this;
- this.$element.find('.oe_vm_switch button').click(function() {
+ this.$element.find('.oe_view_manager_switch a').click(function() {
self.on_mode_switch($(this).data('view-type'));
});
var views_ids = {};
@@ -270,7 +244,9 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
deferred : $.Deferred(),
controller : null,
options : _.extend({
- sidebar_id : self.element_id + '_sidebar_' + view.view_type,
+ $buttons : self.$element.find('.oe_view_manager_buttons'),
+ $sidebar : self.$element.find('.oe_view_manager_sidebar'),
+ $pager : self.$element.find('.oe_view_manager_pager'),
action : self.action,
action_views_ids : views_ids
}, self.flags, self.flags[view.view_type] || {}, view.options || {})
@@ -278,7 +254,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
views_ids[view.view_type] = view.view_id;
});
if (this.flags.views_switcher === false) {
- this.$element.find('.oe_vm_switch').hide();
+ this.$element.find('.oe_view_manager_switch').hide();
}
// If no default view defined, switch to the first one in sequence
var default_view = this.flags.default_view || this.views_src[0].view_type;
@@ -292,9 +268,9 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
* @returns {jQuery.Deferred} new view loading promise
*/
on_mode_switch: function(view_type, no_store) {
- var self = this,
- view = this.views[view_type],
- view_promise;
+ var self = this;
+ var view = this.views[view_type];
+ var view_promise;
if(!view)
return $.Deferred().reject();
@@ -306,13 +282,22 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
if (!view.controller) {
// Lazy loading of views
var controllerclass = this.registry.get_object(view_type);
- var controller = new controllerclass(this, this.dataset, view.view_id, view.options);
+ var options = _.clone(view.options);
+ if (view_type === "form" && this.action) {
+ switch (this.action.target) {
+ case 'new':
+ case 'inline':
+ options.initial_mode = 'edit';
+ break;
+ }
+ }
+ var controller = new controllerclass(this, this.dataset, view.view_id, options);
if (view.embedded_view) {
controller.set_embedded_view(view.embedded_view);
}
controller.do_switch_view.add_last(this.on_mode_switch);
controller.do_prev_view.add_last(this.on_prev_view);
- var container = $("#" + this.element_id + '_view_' + view_type);
+ var container = this.$element.find(".oe_view_manager_view_" + view_type);
view_promise = controller.appendTo(container);
this.views[view_type].controller = controller;
this.views[view_type].deferred.resolve(view_type);
@@ -335,9 +320,10 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
}
this.$element
- .find('.oe_vm_switch button').removeAttr('disabled')
- .filter('[data-view-type="' + view_type + '"]')
- .attr('disabled', true);
+ .find('.oe_view_manager_switch a').parent().removeClass('active')
+ this.$element
+ .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]')
+ .parent().addClass('active');
$.when(view_promise).then(function () {
_.each(_.keys(self.views), function(view_name) {
@@ -367,15 +353,16 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
* @returns {$.Deferred} switching end signal
*/
on_prev_view: function (options) {
+ options = options || {};
var current_view = this.views_history.pop();
var previous_view = this.views_history[this.views_history.length - 1] || options['default'];
if (options.created && current_view === 'form' && previous_view === 'list') {
// APR special case: "If creation mode from list (and only from a list),
// after saving, go to page view (don't come back in list)"
- return this.on_mode_switch('page');
+ return this.on_mode_switch('form');
} else if (options.created && !previous_view && this.action && this.action.flags.default_view === 'form') {
// APR special case: "If creation from dashboard, we have no previous view
- return this.on_mode_switch('page');
+ return this.on_mode_switch('form');
}
return this.on_mode_switch(previous_view, true);
},
@@ -390,12 +377,10 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
if (this.searchview) {
this.searchview.destroy();
}
- this.searchview = new session.web.SearchView(
- this, this.dataset,
- view_id, search_defaults, this.flags.search_view === false);
+ this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, this.flags.search_view === false);
this.searchview.on_search.add(this.do_searchview_search);
- return this.searchview.appendTo($("#" + this.element_id + "_search"));
+ return this.searchview.appendTo(this.$element.find(".oe_view_manager_view_search"));
},
do_searchview_search: function(domains, contexts, groupbys) {
var self = this,
@@ -451,13 +436,13 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
}
});
-session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{
+instance.web.ViewManagerAction = instance.web.ViewManager.extend({
template:"ViewManagerAction",
/**
- * @constructs session.web.ViewManagerAction
- * @extends session.web.ViewManager
+ * @constructs instance.web.ViewManagerAction
+ * @extends instance.web.ViewManager
*
- * @param {session.web.ActionManager} parent parent object/widget
+ * @param {instance.web.ActionManager} parent parent object/widget
* @param {Object} action descriptor for the action this viewmanager needs to manage its views.
*/
init: function(parent, action) {
@@ -482,7 +467,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
this._super(parent, null, action.views, flags);
this.session = parent.session;
this.action = action;
- var dataset = new session.web.DataSetSearch(this, action.res_model, action.context, action.domain);
+ var dataset = new instance.web.DataSetSearch(this, action.res_model, action.context, action.domain);
if (action.res_id) {
dataset.ids.push(action.res_id);
dataset.index = 0;
@@ -523,7 +508,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
this.$element.find('.oe_debug_view').change(this.on_debug_changed);
if (this.action.help && !this.flags.low_profile) {
- var Users = new session.web.DataSet(self, 'res.users'),
+ 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);
@@ -547,17 +532,6 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
}
}
- var $res_logs = this.$element.find('.oe-view-manager-logs:first');
- $res_logs.delegate('a.oe-more-logs', 'click', function () {
- $res_logs.removeClass('oe-folded');
- return false;
- }).delegate('a.oe-remove-everything', 'click', function () {
- $res_logs.removeClass('oe-has-more').find('ul').empty();
- $res_logs.css('display','none');
- return false;
- });
- $res_logs.css('display','none');
-
return manager_ready;
},
on_debug_changed: function (evt) {
@@ -568,23 +542,26 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
current_view = this.views[this.active_view].controller;
switch (val) {
case 'fvg':
- var dialog = new session.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open();
- $('<pre>').text(session.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element);
+ var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open();
+ $('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element);
break;
case 'perm_read':
var ids = current_view.get_selected_ids();
if (ids.length === 1) {
this.dataset.call('perm_read', [ids]).then(function(result) {
- var dialog = new session.web.Dialog(this, {
+ var dialog = new instance.web.Dialog(this, {
title: _.str.sprintf(_t("View Log (%s)"), self.dataset.model),
width: 400
}, QWeb.render('ViewManagerDebugViewLog', {
perm : result[0],
- format : session.web.format_value
+ format : instance.web.format_value
})).open();
});
}
break;
+ case 'toggle_layout_outline':
+ current_view.rendering_engine.toggle_layout_debugging();
+ break;
case 'fields':
this.dataset.call_and_eval(
'fields_get', [false, {}], null, 1).then(function (fields) {
@@ -602,7 +579,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
.append($('<dd style="white-space: pre-wrap;">').text(def));
});
});
- new session.web.Dialog(self, {
+ new instance.web.Dialog(self, {
title: _.str.sprintf(_t("Model %s fields"),
self.dataset.model),
width: '95%'}, $root).open();
@@ -610,7 +587,7 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
break;
case 'manage_views':
if (current_view.fields_view && current_view.fields_view.arch) {
- var view_editor = new session.web.ViewEditor(current_view, current_view.$element, this.dataset, 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"),
@@ -661,8 +638,6 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
return $.when(this._super(view_type, no_store)).then(function () {
self.shortcut_check(self.views[view_type]);
- self.$element.find('.oe-view-manager-logs:first').addClass('oe-folded').removeClass('oe-has-more').css('display','none').find('ul').empty();
-
var controller = self.views[self.active_view].controller,
fvg = controller.fields_view,
view_id = (fvg && fvg.view_id) || '--';
@@ -744,127 +719,88 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
}
});
},
- /**
- * Intercept do_action resolution from children views
- */
- on_action_executed: function () {
- return new session.web.DataSet(this, 'res.log')
- .call('get', [], this.do_display_log);
- },
- /**
- * @param {Array<Object>} log_records
- */
- do_display_log: function (log_records) {
- var self = this;
- var cutoff = 3;
- var $logs = this.$element.find('.oe-view-manager-logs:first').addClass('oe-folded').css('display', 'block');
- var $logs_list = $logs.find('ul').empty();
- $logs.toggleClass('oe-has-more', log_records.length > cutoff);
- _(log_records.reverse()).each(function (record) {
- var context = {};
- if (record.context) {
- try { context = py.eval(record.context); }
- catch (e) { /* TODO: what do I do now? */ }
- }
- $(_.str.sprintf('<li><a href="#">%s</a></li>', record.name))
- .appendTo($logs_list)
- .delegate('a', 'click', function () {
- self.do_action({
- type: 'ir.actions.act_window',
- res_model: record.res_model,
- res_id: record.res_id,
- // TODO: need to have an evaluated context here somehow
- context: context,
- views: [[context.view_id || false, 'form']]
- });
- return false;
- });
- });
- },
display_title: function () {
return this.action.name;
}
});
-session.web.Sidebar = session.web.OldWidget.extend({
- init: function(parent, element_id) {
- this._super(parent, element_id);
- this.items = {};
- this.sections = {};
+instance.web.Sidebar = instance.web.Widget.extend({
+ init: function(parent) {
+ this._super(parent);
+ var view = this.getParent();
+ this.sections = [
+ { 'name' : 'print', 'label' : _t('Print'), },
+ { 'name' : 'files', 'label' : _t('Attachement'), },
+ { 'name' : 'other', 'label' : _t('More'), }
+ ];
+ this.items = {
+ 'print' : [],
+ 'files' : [],
+ 'other' : [
+ { label: _t("Import"), callback: view.on_sidebar_import },
+ { label: _t("Export"), callback: view.on_sidebar_export }
+ ]
+ }
+ if (this.session.uid === 1) {
+ var item = { label: _t("Translate"), callback: view.on_sidebar_translate, title: _t("Technical translation") };
+ this.items.other.push(item);
+ }
},
start: function() {
- this._super(this);
var self = this;
- this.$element.html(session.web.qweb.render('Sidebar'));
- this.$element.find(".toggle-sidebar").click(function(e) {
- self.do_toggle();
+ this._super(this);
+ this.redraw();
+ this.$element.on('click','.oe_dropdown_toggle',function(event) {
+ $(this).parent().find('ul').toggle();
+ return false;
});
- },
- add_default_sections: function() {
- var self = this,
- view = this.getParent(),
- view_manager = view.getParent(),
- action = view_manager.action;
- if (this.session.uid === 1) {
- this.add_section(_t('Customize'), 'customize');
- this.add_items('customize', [{
- label: _t("Translate"),
- callback: view.on_sidebar_translate,
- title: _t("Technical translation")
- }]);
- }
-
- this.add_section(_t('Other Options'), 'other');
- this.add_items('other', [
- {
- label: _t("Import"),
- callback: view.on_sidebar_import
- }, {
- label: _t("Export"),
- callback: view.on_sidebar_export
+ this.$element.on('click','.oe_dropdown_menu li a', function(event) {
+ var section = $(this).data('section');
+ var index = $(this).data('index');
+ $(this).closest('ul').hide();
+ var item = self.items[section][index];
+ if (item.callback) {
+ item.callback.apply(self, [item]);
+ } else if (item.action) {
+ self.on_item_action_clicked(item);
+ } else if (item.url) {
+ return true;
}
- ]);
+ return false;
+ });
+ //this.$div.html(QWeb.render('FormView.sidebar.attachments', this));
+ //this.$element.find('.oe-binary-file').change(this.on_attachment_changed);
+ //this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete);
+ },
+ redraw: function() {
+ var self = this;
+ self.$element.html(QWeb.render('Sidebar', {widget: self}));
+ this.$element.find('ul').hide();
+ },
+ add_section: function() {
+ var self = this;
},
-
add_toolbar: function(toolbar) {
var self = this;
- _.each([['print', _t("Reports")], ['action', _t("Actions")], ['relate', _t("Links")]], function(type) {
- var items = toolbar[type[0]];
- if (items.length) {
+ _.each(['print','action','relate'], function(type) {
+ var items = toolbar[type];
+ if (items) {
for (var i = 0; i < items.length; i++) {
items[i] = {
label: items[i]['name'],
action: items[i],
- classname: 'oe_sidebar_' + type[0]
+ classname: 'oe_sidebar_' + type
}
}
- self.add_section(type[1], type[0]);
- self.add_items(type[0], items);
+ self.add_items(type=='print' ? 'print' : 'other', items);
}
});
},
-
- add_section: function(name, code) {
- if(!code) code = _.str.underscored(name);
- var $section = this.sections[code];
-
- if(!$section) {
- var section_id = _.uniqueId(this.element_id + '_section_' + code + '_');
- $section = $(session.web.qweb.render("Sidebar.section", {
- section_id: section_id,
- name: name,
- classname: 'oe_sidebar_' + code
- }));
- $section.appendTo(this.$element.find('div.sidebar-actions'));
- this.sections[code] = $section;
- }
- return $section;
- },
/**
* For each item added to the section:
*
* ``label``
- * will be used as the item's name in the sidebar
+ * will be used as the item's name in the sidebar, can be html
*
* ``action``
* descriptor for the action which will be executed, ``action`` and
@@ -886,34 +822,10 @@ session.web.Sidebar = session.web.OldWidget.extend({
* @param {Array<{label, action | callback[, classname][, title]}>} items
*/
add_items: function(section_code, items) {
- var self = this,
- $section = this.add_section(_.str.titleize(section_code.replace('_', ' ')), section_code),
- section_id = $section.attr('id');
-
+ var self = this;
if (items) {
- for (var i = 0; i < items.length; i++) {
- items[i].element_id = _.uniqueId(section_id + '_item_');
- this.items[items[i].element_id] = items[i];
- }
-
- var $items = $(session.web.qweb.render("Sidebar.section.items", {items: items}));
-
- $items.find('a.oe_sidebar_action_a').click(function() {
- var item = self.items[$(this).attr('id')];
- if (item.callback) {
- item.callback.apply(self, [item]);
- }
- if (item.action) {
- self.on_item_action_clicked(item);
- }
- return false;
- });
-
- var $ul = $section.find('ul');
- if(!$ul.length) {
- $ul = $('<ul/>').appendTo($section);
- }
- $items.appendTo($ul);
+ this.items[section_code].push.apply(this.items[section_code],items);
+ this.redraw();
}
},
on_item_action_clicked: function(item) {
@@ -921,11 +833,7 @@ session.web.Sidebar = session.web.OldWidget.extend({
self.getParent().sidebar_context().then(function (context) {
var ids = self.getParent().get_selected_ids();
if (ids.length == 0) {
- //TODO: make prettier warning?
- openerp.web.dialog($("<div />").text(_t("You must choose at least one record.")), {
- title: _t("Warning"),
- modal: true
- });
+ instance.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true });
return false;
}
var additional_context = _.extend({
@@ -948,18 +856,60 @@ session.web.Sidebar = session.web.OldWidget.extend({
});
});
},
- do_fold: function() {
- this.$element.addClass('closed-sidebar').removeClass('open-sidebar');
+ do_attachement_update: function(dataset, model_id) {
+ if (!model_id) {
+ this.on_attachments_loaded([]);
+ } else {
+ var dom = [ ['res_model', '=', dataset.model], ['res_id', '=', model_id], ['type', 'in', ['binary', 'url']] ];
+ var ds = new instance.web.DataSetSearch(this, 'ir.attachment', dataset.get_context(), dom);
+ ds.read_slice(['name', 'url', 'type'], {}).then(this.on_attachments_loaded);
+ }
},
- do_unfold: function() {
- this.$element.addClass('open-sidebar').removeClass('closed-sidebar');
+ on_attachments_loaded: function(attachments) {
+ var self = this;
+ var items = [];
+ // TODO: preprend: _s +
+ var prefix = '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id=';
+ _.each(attachments,function(a) {
+ a.label = a.name;
+ if(a.type === "binary") {
+ a.url = prefix + a.id + '&t=' + (new Date().getTime());
+ }
+ });
+ attachments.push( { label: _t("Add..."), callback: self.on_attachment_add } );
+ self.items['files'] = attachments;
+ self.redraw();
+ },
+ on_attachment_add: function(e) {
+ this.$element.find('.oe_sidebar_add').show();
+ },
+ on_attachment_changed: function(e) {
+ return;
+ window[this.element_id + '_iframe'] = this.do_update;
+ var $e = $(e.target);
+ if ($e.val() != '') {
+ this.$element.find('form.oe-binary-form').submit();
+ $e.parent().find('input[type=file]').prop('disabled', true);
+ $e.parent().find('button').prop('disabled', true).find('img, span').toggle();
+ }
},
- do_toggle: function() {
- this.$element.toggleClass('open-sidebar closed-sidebar');
+ on_attachment_delete: function(e) {
+ return;
+ var self = this, $e = $(e.currentTarget);
+ var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text());
+ if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) {
+ this.rpc('/web/dataset/unlink', {
+ model: 'ir.attachment',
+ ids: [parseInt($e.attr('data-id'))]
+ }, function(r) {
+ $e.parent().remove();
+ self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted");
+ });
+ }
}
});
-session.web.TranslateDialog = session.web.Dialog.extend({
+instance.web.TranslateDialog = instance.web.Dialog.extend({
dialog_title: {toString: function () { return _t("Translations"); }},
init: function(view) {
// TODO fme: should add the language to fields_view_get because between the fields view get
@@ -979,14 +929,14 @@ session.web.TranslateDialog = session.web.Dialog.extend({
this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name });
this.languages = null;
this.languages_loaded = $.Deferred();
- (new session.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
+ (new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(),
[['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }).then(this.on_languages_loaded);
},
start: function() {
var self = this;
this._super();
$.when(this.languages_loaded).then(function() {
- self.$element.html(session.web.qweb.render('TranslateDialog', { widget: self }));
+ self.$element.html(instance.web.qweb.render('TranslateDialog', { widget: self }));
self.$fields_form = self.$element.find('.oe_translation_form');
self.$fields_form.find('.oe_trad_field').change(function() {
$(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value')));
@@ -1075,29 +1025,61 @@ session.web.TranslateDialog = session.web.Dialog.extend({
}
});
-session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
+instance.web.View = instance.web.Widget.extend({
template: "EmptyComponent",
// name displayed in view switchers
display_name: '',
+ /**
+ * Define a view type for each view to allow automatic call to fields_view_get.
+ */
+ view_type: undefined,
init: function(parent, dataset, view_id, options) {
this._super(parent);
this.dataset = dataset;
this.view_id = view_id;
this.set_default_options(options);
},
+ start: function() {
+ return this.load_view();
+ },
+ load_view: function() {
+ if (this.embedded_view) {
+ var def = $.Deferred();
+ var self = this;
+ $.async_when().then(function() {def.resolve(self.embedded_view);});
+ return def.pipe(this.on_loaded);
+ } else {
+ var context = new instance.web.CompoundContext(this.dataset.get_context());
+ if (! this.view_type)
+ console.warn("view_type is not defined", this);
+ return this.rpc("/web/view/load", {
+ "model": this.dataset.model,
+ "view_id": this.view_id,
+ "view_type": this.view_type,
+ toolbar: !!this.options.$sidebar,
+ context: context
+ }).pipe(this.on_loaded);
+ }
+ },
+ /**
+ * Called after a successful call to fields_view_get.
+ * Must return a promise.
+ */
+ on_loaded: function(fields_view_get) {
+ },
set_default_options: function(options) {
this.options = options || {};
_.defaults(this.options, {
// All possible views options should be defaulted here
+ $sidebar: null,
sidebar_id: null,
- sidebar: true,
action: null,
action_views_ids: {}
});
},
open_translate_dialog: function(field) {
if (!this.translate_dialog) {
- this.translate_dialog = new session.web.TranslateDialog(this).start();
+ this.translate_dialog = new instance.web.TranslateDialog(this).start();
}
this.translate_dialog.open(field);
},
@@ -1109,7 +1091,7 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
* @param {String} [action_data.special=null] special action handlers (currently: only ``'cancel'``)
* @param {String} [action_data.type='workflow'] the action type, if present, one of ``'object'``, ``'action'`` or ``'workflow'``
* @param {Object} [action_data.context=null] additional action context, to add to the current context
- * @param {session.web.DataSet} dataset a dataset object used to communicate with the server
+ * @param {instance.web.DataSet} dataset a dataset object used to communicate with the server
* @param {Object} [record_id] the identifier of the object on which the action is to be applied
* @param {Function} on_closed callback to execute when dialog is closed or when the action does not generate any result (no new action)
*/
@@ -1121,12 +1103,12 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
return self.getParent().on_action_executed.apply(null, arguments);
}
};
- var context = new session.web.CompoundContext(dataset.get_context(), action_data.context || {});
+ var context = new instance.web.CompoundContext(dataset.get_context(), action_data.context || {});
var handler = function (r) {
var action = r.result;
if (action && action.constructor == Object) {
- var ncontext = new session.web.CompoundContext(context);
+ var ncontext = new instance.web.CompoundContext(context);
if (record_id) {
ncontext.add({
active_id: record_id,
@@ -1175,13 +1157,12 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
/**
* Directly set a view to use instead of calling fields_view_get. This method must
* be called before start(). When an embedded view is set, underlying implementations
- * of session.web.View must use the provided view instead of any other one.
+ * of instance.web.View must use the provided view instead of any other one.
*
* @param embedded_view A view.
*/
set_embedded_view: function(embedded_view) {
this.embedded_view = embedded_view;
- this.options.sidebar = false;
},
do_show: function () {
this.$element.show();
@@ -1214,15 +1195,12 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
},
do_search: function(view) {
},
- set_common_sidebar_sections: function(sidebar) {
- sidebar.add_default_sections();
- },
on_sidebar_import: function() {
- var import_view = new session.web.DataImport(this, this.dataset);
+ var import_view = new instance.web.DataImport(this, this.dataset);
import_view.start();
},
on_sidebar_export: function() {
- var export_view = new session.web.DataExport(this, this.dataset);
+ var export_view = new instance.web.DataExport(this, this.dataset);
export_view.start();
},
on_sidebar_translate: function() {
@@ -1247,7 +1225,29 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
}
});
-session.web.json_node_to_xml = function(node, human_readable, indent) {
+instance.web.xml_to_json = function(node) {
+ switch (node.nodeType) {
+ case 3:
+ case 4:
+ return node.data;
+ break;
+ case 1:
+ var attrs = $(node).getAttributes();
+ _.each(['domain', 'filter_domain', 'context', 'default_get'], function(key) {
+ if (attrs[key]) {
+ try {
+ attrs[key] = JSON.parse(attrs[key]);
+ } catch(e) { }
+ }
+ });
+ return {
+ tag: node.tagName.toLowerCase(),
+ attrs: attrs,
+ children: _.map(node.childNodes, instance.web.xml_to_json)
+ }
+ }
+}
+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;
@@ -1276,7 +1276,7 @@ session.web.json_node_to_xml = function(node, human_readable, indent) {
r += '>' + cr;
var childs = [];
for (var i = 0, ii = node.children.length; i < ii; i++) {
- childs.push(session.web.json_node_to_xml(node.children[i], human_readable, indent + 1));
+ childs.push(instance.web.json_node_to_xml(node.children[i], human_readable, indent + 1));
}
r += childs.join(cr);
r += cr + sindent + '</' + node.tag + '>';
@@ -1285,6 +1285,43 @@ session.web.json_node_to_xml = function(node, human_readable, indent) {
return r + '/>';
}
}
+instance.web.xml_to_str = function(node) {
+ if (window.ActiveXObject) {
+ return node.xml;
+ } else {
+ return (new XMLSerializer()).serializeToString(node);
+ }
+}
+instance.web.str_to_xml = function(s) {
+ if (window.DOMParser) {
+ var dp = new DOMParser();
+ var r = dp.parseFromString(s, "text/xml");
+ if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
+ throw new Error("Could not parse string to xml");
+ }
+ return r;
+ }
+ var xDoc;
+ try {
+ xDoc = new ActiveXObject("MSXML2.DOMDocument");
+ } catch (e) {
+ throw new Error("Could not find a DOM Parser: " + e.message);
+ }
+ xDoc.async = false;
+ xDoc.preserveWhiteSpace = true;
+ xDoc.loadXML(s);
+ return xDoc;
+}
+
+/**
+ * Registry for all the client actions key: tag value: widget
+ */
+instance.web.client_actions = new instance.web.Registry();
+
+/**
+ * Registry for all the main views
+ */
+instance.web.views = new instance.web.Registry();
};