summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js
diff options
context:
space:
mode:
authorniv-openerp <nicolas.vanhoren@openerp.com>2012-04-16 12:25:03 +0200
committerniv-openerp <nicolas.vanhoren@openerp.com>2012-04-16 12:25:03 +0200
commitf7418cc61ac3f5445f50bae2c60aab38fdca2a2a (patch)
tree10b7efd371bd89139352aca3695aca0f0ec4df86 /addons/web/static/src/js
parentb7fa2c39b4f16c1d47762d993fbc5fcf122da436 (diff)
parent3687148b501d1d2c114188c797c81054725d944b (diff)
[merge] lot and lot of improvements, mainly css + new form views
bzr revid: nicolas.vanhoren@openerp.com-20120416102503-latyewf3ksvc6mvv
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.js393
-rw-r--r--addons/web/static/src/js/corelib.js (renamed from addons/web/static/src/js/core.js)1191
-rw-r--r--addons/web/static/src/js/coresetup.js142
-rw-r--r--addons/web/static/src/js/search.js1281
-rw-r--r--addons/web/static/src/js/view_form.js1981
-rw-r--r--addons/web/static/src/js/view_list.js180
-rw-r--r--addons/web/static/src/js/view_list_editable.js91
-rw-r--r--addons/web/static/src/js/view_page.js288
-rw-r--r--addons/web/static/src/js/views.js286
10 files changed, 3125 insertions, 2729 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..892c86f2a92 100644
--- a/addons/web/static/src/js/chrome.js
+++ b/addons/web/static/src/js/chrome.js
@@ -5,14 +5,13 @@ openerp.web.chrome = function(openerp) {
var QWeb = openerp.web.qweb,
_t = openerp.web._t;
-openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web.Notification# */{
+openerp.web.Notification = openerp.web.Widget.extend({
template: 'Notification',
-
init: function() {
this._super.apply(this, arguments);
+ // move to openerp.web.notification
openerp.notification = this;
},
-
start: function() {
this._super.apply(this, arguments);
this.$element.notify({
@@ -42,7 +41,6 @@ openerp.web.Notification = openerp.web.OldWidget.extend(/** @lends openerp.web.
text: text
}, opts);
}
-
});
openerp.web.dialog = function(element) {
@@ -51,15 +49,8 @@ openerp.web.dialog = function(element) {
return result;
}
-openerp.web.Dialog = openerp.web.OldWidget.extend(/** @lends openerp.web.Dialog# */{
+openerp.web.Dialog = openerp.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);
@@ -128,7 +119,7 @@ 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');
@@ -149,7 +140,6 @@ 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();
@@ -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>'), {
+ openerp.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"); }}
@@ -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: openerp.connection, error: error}));
}
});
-openerp.web.Loading = openerp.web.OldWidget.extend(/** @lends openerp.web.Loading# */{
+openerp.web.Loading = openerp.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);
+openerp.web.DatabaseManager = openerp.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.
@@ -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: openerp.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,
-
+openerp.web.Login = openerp.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 openerp.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(openerp.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
- */
+openerp.web.Menu = openerp.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# */{
+openerp.web.UserMenu = openerp.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();
@@ -910,6 +812,7 @@ openerp.web.UserMenu = openerp.web.Widget.extend(/** @lends openerp.web.UserMen
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: {
@@ -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;
@@ -968,11 +871,7 @@ 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
- */
+openerp.web.WebClient = openerp.web.Widget.extend({
init: function(parent) {
var self = this;
this._super(parent);
@@ -982,7 +881,7 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
},
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) {
@@ -1126,16 +1025,15 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
}
});
-openerp.web.EmbeddedClient = openerp.web.OldWidget.extend({
+openerp.web.EmbeddedClient = openerp.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);
},
-
start: function() {
var self = this;
this.am.appendTo(this.$element.addClass('openerp'));
@@ -1152,7 +1050,6 @@ openerp.web.EmbeddedClient = openerp.web.OldWidget.extend({
self.am.do_action(action);
});
}
-
});
openerp.web.embed = function (origin, dbname, login, key, action, options) {
@@ -1168,7 +1065,7 @@ openerp.web.embed = function (origin, dbname, login, key, action, options) {
}
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);
+ var client = new openerp.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 ffdabb75c90..2da804c3d2c 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(openerp) {
-// a function to override the "extend()" method of JR's inheritance, allowing
-// the usage of "include()"
-var 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 = openerp.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)
+ openerp.web.Class = function(){};
+
+ /**
+ * Subclass an existing class
+ *
+ * @param {Object} prop class-level properties (class attributes and instance methods) to set on the new class
+ */
+ openerp.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 @@ var 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.
+ */
+openerp.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) };
+ this.__parentedParent = parent;
+ if (parent && parent.__parentedMixin) {
+ parent.__parentedChildren.push(this);
}
- 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"
+ },
+ /**
+ * 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# */ {
+openerp.web.Events = openerp.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;
+ }
+});
+
+openerp.web.EventDispatcherMixin = _.extend({}, openerp.web.ParentedMixin, {
+ __eventDispatcherMixin: true,
+ init: function() {
+ openerp.web.ParentedMixin.init.call(this);
+ this.__edispatcherEvents = new openerp.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();
+ openerp.web.ParentedMixin.destroy.call(this);
+ }
+});
+
+openerp.web.GetterSetterMixin = _.extend({}, openerp.web.EventDispatcherMixin, {
+ init: function() {
+ openerp.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];
+ }
+});
+
+openerp.web.CallbackEnabledMixin = _.extend({}, openerp.web.GetterSetterMixin, {
+ init: function() {
+ openerp.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
+ // 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);
+ }
+ 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# */ {
+
+openerp.web.WidgetMixin = _.extend({},openerp.web.CallbackEnabledMixin, {
+ /**
+ * Tag name when creating a default $element.
+ * @type string
+ */
+ tagName: 'div',
+ /**
+ * Constructs the widget and sets its parent if a parent is given.
+ *
+ * @constructs 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) {
+ openerp.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();
+ }
+ openerp.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);
+ },
/**
- * Thrown when a key could not be found in a mapping
+ * Renders the current widget and replaces the given jQuery object.
*
- * @constructs openerp.web.KeyNotFound
- * @extends openerp.web.NotFound
- * @param {String} key the key which could not be found
+ * @param target A jQuery object or a Widget instance.
*/
- init: function (key) {
- this.key = key;
+ replace: function(target) {
+ return this.__widgetRenderAndInsert(_.bind(function(t) {
+ this.$element.replaceAll(t);
+ }, this), target);
+ },
+ __widgetRenderAndInsert: function(insertion, target) {
+ this.renderElement();
+ insertion(target);
+ return this.start();
},
- toString: function () {
- return "The key " + this.key + " was not found";
+ /**
+ * 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() {
}
});
-openerp.web.ObjectNotFound = openerp.web.NotFound.extend( /** @lends openerp.web.ObjectNotFound# */ {
+
+// Classes
+
+openerp.web.CallbackEnabled = openerp.web.Class.extend(openerp.web.CallbackEnabledMixin, {
+ init: function() {
+ openerp.web.CallbackEnabledMixin.init.call(this);
+ }
+});
+
+/**
+ * Base class for all visual components. Provides a lot of functionalities helpful
+ * for the management of a part of the DOM.
+ *
+ * Widget handles:
+ * - Rendering with QWeb.
+ * - Life-cycle management and parenting (when a parent is destroyed, all its children are
+ * destroyed too).
+ * - Insertion in DOM.
+ *
+ * Guide to create implementations of the Widget class:
+ * ==============================================
+ *
+ * Here is a sample child class:
+ *
+ * MyWidget = 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 = openerp.web.Class.extend(openerp.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.
*
- * @constructs openerp.web.ObjectNotFound
- * @extends openerp.web.NotFound
- * @param {String} path the invalid object path
+ * @type string
*/
- init: function (path) {
- this.path = path;
+ 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) {
+ openerp.web.WidgetMixin.init.call(this,parent);
+ this.session = openerp.connection;
},
- toString: function () {
- return "Could not find any object of path " + this.path;
+ /**
+ * 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();
}
});
-openerp.web.Registry = openerp.web.Class.extend( /** @lends openerp.web.Registry# */ {
+
+openerp.web.Registry = openerp.web.Class.extend({
/**
* Stores a mapping of arbitrary key (strings) to object paths (as strings
* as well).
@@ -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,7 +782,7 @@ 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;
@@ -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.
@@ -320,53 +867,6 @@ 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# */{
/**
* @constructs openerp.web.Connection
@@ -1187,160 +1687,6 @@ 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# */{
/**
* @constructs openerp.web.TranslationDataBase
@@ -1394,138 +1740,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:`openerp.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 + "'");
+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.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..a68a7bb3d7e
--- /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(openerp) {
+
+/** 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]);
+ }
+ }
+ }
+};
+
+/** 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 */
+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/search.js b/addons/web/static/src/js/search.js
index 0136d41ada0..6e1026b48a1 100644
--- a/addons/web/static/src/js/search.js
+++ b/addons/web/static/src/js/search.js
@@ -2,9 +2,76 @@ openerp.web.search = function(openerp) {
var QWeb = openerp.web.qweb,
_t = openerp.web._t,
_lt = openerp.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');
+ }
+});
+
+openerp.web.SearchView = openerp.web.Widget.extend(/** @lends openerp.web.SearchView# */{
+ template: "SearchView",
/**
* @constructs openerp.web.SearchView
* @extends openerp.web.OldWidget
@@ -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 openerp.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 openerp.web.search.Filter({attrs: {
+ string: filter.name,
+ context: filter.context,
+ domain: filter.domain
+ }}, self);
+ return new openerp.web.search.FilterGroup([f], self);
+ }),
+ length: 3
+ });
+
+ return $.when(
+ this.render_column(col1, $('<div>').appendTo($filters)),
+ this.render_column(col2, $('<div>').appendTo($filters)),
+ (new openerp.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 openerp.web.search.FilterGroupFacet(options);
+
+// if (options.model.get('field') instanceof openerp.web.search.FilterGroup) {
+// return new openerp.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 openerp.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 openerp.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 openerp.web.search.FilterGroup(filters, this));
}
-
- return rows;
},
/**
* Creates a field for the provided field descriptor item (which comes
@@ -123,15 +397,10 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
* @returns openerp.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 = openerp.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);
- 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();
+ this.make_widgets(
+ data.fields_view['arch'].children,
+ data.fields_view.fields);
- $.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.
@@ -342,38 +580,24 @@ openerp.web.SearchView = openerp.web.OldWidget.extend(/** @lends openerp.web.Sea
*
* @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(); }
+ do_search: function () {
+ var domains = [], contexts = [], groupbys = [], errors = [];
- var data = this.build_search_data();
-
- 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) {
errors.push(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,59 +644,43 @@ 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 */
+openerp.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();
+openerp.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
* find and instantiate its field widgets.
@@ -540,47 +736,6 @@ openerp.web.search.Widget = openerp.web.OldWidget.extend( /** @lends openerp.web
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) {
@@ -597,15 +752,6 @@ openerp.web.search.Group = openerp.web.search.Widget.extend({
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);
}
});
@@ -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:`openerp.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,11 +818,7 @@ 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# */{
template: 'SearchView.filters',
@@ -659,21 +835,63 @@ openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends open
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 openerp.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();
@@ -686,6 +904,45 @@ openerp.web.search.FilterGroup = openerp.web.search.Input.extend(/** @lends open
return _.extend(new openerp.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# */{
@@ -694,6 +951,10 @@ openerp.web.search.Filter = openerp.web.search.Input.extend(/** @lends openerp.w
* Implementation of the OpenERP filters (button with a context and/or
* a domain sent as-is to the search view)
*
+ * Filters are only attributes holder, the actual work (compositing
+ * domains and contexts, converting between facets and filters) is
+ * performed by the filter group.
+ *
* @constructs openerp.web.search.Filter
* @extends openerp.web.search.Input
*
@@ -703,49 +964,10 @@ 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# */ {
template: 'SearchView.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 !== '');
@@ -790,6 +1008,7 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
return new openerp.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
@@ -801,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;
}
@@ -815,7 +1034,7 @@ 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 new openerp.web.CompoundDomain(domain)
.set_eval_context({self: val});
@@ -833,8 +1052,18 @@ openerp.web.search.Field = openerp.web.search.Input.extend( /** @lends openerp.w
*/
openerp.web.search.CharField = openerp.web.search.Field.extend( /** @lends openerp.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# */{
@@ -904,42 +1133,42 @@ 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# */{
@@ -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;
@@ -984,23 +1196,24 @@ openerp.web.search.BooleanField = openerp.web.search.SelectionField.extend(/** @
* @extends openerp.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);
+ get_value: function (facet) {
+ return openerp.web.date_to_str(facet.get('json'));
+ },
+ complete: function (needle) {
+ var d = Date.parse(needle);
+ if (!d) { return $.when(null); }
+ var value = openerp.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
+ }]);
}
});
/**
@@ -1015,194 +1228,124 @@ openerp.web.search.DateField = openerp.web.search.Field.extend(/** @lends opener
* @extends openerp.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']];
+ get_value: function (facet) {
+ return openerp.web.datetime_to_str(facet.get('json'));
}
});
openerp.web.search.ManyToOneField = openerp.web.search.CharField.extend({
init: function (view_section, field, view) {
this._super(view_section, field, view);
+ this.model = new openerp.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.dataset = new openerp.web.DataSet(
- this.view, this.attrs['relation']);
},
- 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) {
- if (self.abort_last) {
- self.abort_last();
- delete self.abort_last;
- }
- self.dataset.name_search(
- req.term, self.attrs.domain, 'ilike', 8, function (data) {
- resp(_.map(data, function (result) {
- return {id: result[0], label: result[1]}
- }));
- });
- self.abort_last = self.dataset.abort_last;
- },
- select: function (event, ui) {
- self.id = ui.item.id;
- self.name = ui.item.label;
- },
- delay: 0
- })
- },
- 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.dataset.name_get([this.id], $.proxy(this, '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();
- },
+openerp.web.search.Advanced = openerp.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 openerp.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 openerp.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 openerp.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');
}
});
@@ -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,12 @@ 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";
+ var obj = openerp.web.search.custom_filters.get_object(type);
+ if(obj === null) {
console.log('Unknow field type ' + e.key);
+ obj = openerp.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);
}
@@ -1312,11 +1441,7 @@ openerp.web.search.ExtendedSearchProposition.Char = openerp.web.search.ExtendedS
{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();
diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js
index 9abf8fdfbf9..e5824d64f59 100644
--- a/addons/web/static/src/js/view_form.js
+++ b/addons/web/static/src/js/view_form.js
@@ -1,18 +1,19 @@
openerp.web.form = function (openerp) {
-
var _t = openerp.web._t,
_lt = openerp.web._lt;
var QWeb = openerp.web.qweb;
+/** @namespace */
+openerp.web.form = {};
+
openerp.web.views.add('form', 'openerp.web.FormView');
-openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView# */{
+openerp.web.FormView = openerp.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",
/**
@@ -35,77 +36,82 @@ 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 = openerp.web.form.widgets;
+ this.tags_registry = openerp.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();
+ this.mode = null;
+ this.rendering_engine = new openerp.web.form.FormRenderingEngineReadonly(this);
},
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_view(data);
+ this.rendering_engine.set_fields_registry(this.fields_registry);
+ this.rendering_engine.set_tags_registry(this.tags_registry);
+ 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);
+ if (!this.sidebar && this.options.sidebar) {
+ this.sidebar = new openerp.web.Sidebar(this);
+ this.sidebar.appendTo(this.$sidebar);
+ //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', [{
+ this.sidebar.add_items('other', [{
label: _t('Set Default'),
form: this,
callback: function (item) {
@@ -113,10 +119,11 @@ 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();
},
-
do_load_state: function(state, warm) {
if (state.id && this.datarecord.id != state.id) {
if (!this.dataset.get_id_index(state.id)) {
@@ -128,9 +135,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() {
@@ -146,17 +162,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 = [];
@@ -190,7 +209,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.attachments.do_update();
}
if (self.default_focus_field) {
self.default_focus_field.focus();
@@ -199,20 +218,21 @@ 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();
+ this.trigger("view_content_has_changed");
+ _.each(this.get_widgets(), function(w) {
if (w.field) {
w.validate();
}
w.update_dom();
- }
+ });
},
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()) {
@@ -234,11 +254,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;
@@ -277,7 +296,7 @@ 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();
+ var value = self.fields[field].get_value();
return value == null ? false : value;
}
// parent field
@@ -347,7 +366,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
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;
@@ -428,19 +447,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()) {
@@ -458,6 +496,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."));
},
@@ -484,7 +560,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
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.is_dirty())) {
// Special case 'id' field, do not save this field
// on 'create' : save all non readonly fields
// on 'edit' : save non readonly modified fields
@@ -523,7 +599,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>";
@@ -566,7 +642,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
this.do_update_pager();
if (this.sidebar) {
- this.sidebar.attachments.do_update();
+ // this.sidebar.attachments.do_update();
}
//openerp.log("The record has been created with id #" + this.datarecord.id);
this.reload();
@@ -579,6 +655,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 {
@@ -588,6 +668,11 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
});
},
+ get_widgets: function() {
+ return _.filter(this.getChildren(), function(obj) {
+ return obj instanceof openerp.web.form.Widget;
+ });
+ },
get_fields_values: function(blacklist) {
blacklist = blacklist || [];
var values = {};
@@ -639,8 +724,8 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
// ignore fields which are empty, invisible, readonly, o2m
// or m2m
if (!value
- || field.invisible
- || field.readonly
+ || field.get('invisible')
+ || field.get("readonly")
|| field.field.type === 'one2many'
|| field.field.type === 'many2many') {
return false;
@@ -661,7 +746,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
return {
name: name,
- string: field.string,
+ string: field.node_atts.string,
value: value,
displayed: displayed,
// convert undefined to false
@@ -669,7 +754,7 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
}
})
.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; })
@@ -687,7 +772,7 @@ 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(),
@@ -708,7 +793,343 @@ openerp.web.FormView = openerp.web.View.extend( /** @lends openerp.web.FormView#
d.open();
}
});
-openerp.web.FormDialog = openerp.web.Dialog.extend({
+
+/**
+ * Interface to be implemented by rendering engines for the form view.
+ */
+openerp.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.
+ */
+openerp.web.form.FormRenderingEngine = openerp.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 = openerp.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, openerp.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);
+ 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, openerp.web.xml_to_json($elem[0]));
+ w.replace($elem);
+ })
+ },
+ 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('.oe_form_group_cell').each(function() {
+ var text = 'W:' + ($(this).attr('width') || '') + ' - C:' + $(this).attr('colspan'),
+ $span = $('<span class="oe_layout_debug_cell"/>').text(text);
+ $span.prependTo($(this));
+ });
+ }
+ 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 openerp.web.form.InvisibilityChanger(this.view, this.view, modifiers.invisible, $new_element);
+ $new_element.addClass($node.attr("class") || "");
+ },
+});
+
+openerp.web.form.FormRenderingEngineReadonly = openerp.web.form.FormRenderingEngine.extend({
+ alter_field: function(field) {
+ field.set({"force_readonly": true});
+ },
+});
+
+openerp.web.form.FormDialog = openerp.web.Dialog.extend({
init: function(parent, options, view_id, dataset) {
this._super(parent, options);
this.dataset = dataset;
@@ -739,19 +1160,17 @@ openerp.web.FormDialog = openerp.web.Dialog.extend({
}
});
-/** @namespace */
-openerp.web.form = {};
-openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({
+openerp.web.form.SidebarAttachments = openerp.web.Widget.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'));
+ //var $section = parent.add_section(_t('Attachments'), 'attachments');
+ //this.$div = $('<div class="oe-sidebar-attachments"></div>');
+ //$section.append(this.$div);
+ this._super(parent);
this.view = form_view;
},
do_update: function() {
+ return;
if (!this.view.datarecord.id) {
this.on_attachments_loaded([]);
} else {
@@ -765,12 +1184,14 @@ openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({
}
},
on_attachments_loaded: function(attachments) {
+ return;
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) {
+ return;
window[this.element_id + '_iframe'] = this.do_update;
var $e = $(e.target);
if ($e.val() != '') {
@@ -780,6 +1201,7 @@ openerp.web.form.SidebarAttachments = openerp.web.OldWidget.extend({
}
},
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))) {
@@ -865,60 +1287,86 @@ 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.
+ */
+openerp.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 :
+ openerp.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)();
+ },
+};
+
+openerp.web.form.InvisibilityChanger = openerp.web.Class.extend(_.extend({}, openerp.web.GetterSetterMixin, openerp.web.form.InvisibilityChangerMixin, {
+ init: function(parent, field_manager, invisible_domain, $element) {
+ this.setParent(parent);
+ openerp.web.GetterSetterMixin.init.call(this);
+ openerp.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain);
+ this.$element = $element;
+ this.start();
+ },
+}));
+
+openerp.web.form.Widget = openerp.web.Widget.extend(_.extend({}, openerp.web.form.InvisibilityChangerMixin, {
/**
* @constructs openerp.web.form.Widget
- * @extends openerp.web.OldWidget
+ * @extends openerp.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;
+ openerp.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible);
- 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';
- }
-
- 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();
+ openerp.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 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;
+ }
}
+ this.set(to_set);
},
update_dom: function() {
- this.$element.toggle(!this.invisible);
- },
- renderElement: function() {
- this.$element.html(QWeb.render(this.form_template, { "widget": this }));
},
do_attach_tooltip: function(widget, trigger, options) {
widget = widget || this;
@@ -928,7 +1376,7 @@ 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';
}
@@ -990,231 +1438,23 @@ openerp.web.form.Widget = openerp.web.OldWidget.extend(/** @lends openerp.web.fo
}
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',
+ 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;
}
},
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 || openerp.connection.debug) {
this.do_attach_tooltip();
}
},
@@ -1232,7 +1472,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 = openerp.web.dialog($('<div/>').text(self.node.attrs.confirm), {
title: _t('Confirm'),
modal: true,
buttons: [
@@ -1281,77 +1521,45 @@ openerp.web.form.WidgetButton = openerp.web.form.Widget.extend({
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';
- }
+/**
+ * Interface implemented by the form view or any other object
+ * able to provide the features necessary for the fields to work.
+ *
+ * Properties:
+ * - ...
+ * Events:
+ * - view_content_has_changed : when the values of the fields have changed. When
+ * this event is triggered all fields should reprocess their modifiers.
+ */
+openerp.web.form.FieldManagerInterface = {
- 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 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:
* - ...
*
*/
openerp.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.
*
* set_value() may return an object that can be passed to $.when() that represents the moment when
@@ -1386,42 +1594,48 @@ 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) {}
};
/**
- * 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.
+ *
*/
-openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.form.Field# */{
+openerp.web.form.AbstractField = openerp.web.form.Widget.extend(/** @lends openerp.web.form.AbstractField# */{
/**
- * @constructs openerp.web.form.Field
+ * @constructs openerp.web.form.AbstractField
* @extends openerp.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;
+ init: function(field_manager, node) {
+ this._super(field_manager, node);
+ this.name = this.node.attrs.name;
+ this.value = false;
+ this.view.fields[this.name] = this;
+ this.view.fields_order.push(this.name);
+ this.field = this.view.fields_view.fields[this.name] || {};
+ this.set({required: this.modifiers['required'] === true});
this.invalid = this.dirty = false;
-
- this.classname = 'oe_form_field_' + this.type;
+
+ // 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)();
},
start: function() {
this._super.apply(this, arguments);
@@ -1430,8 +1644,15 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
this.$element.addClass('oe_form_field_translatable');
this.$element.find('.oe_field_translate').click(this.on_translate);
}
- if (this.nolabel && openerp.connection.debug) {
- this.do_attach_tooltip(this, this.$element);
+ if (this.node.attrs.nolabel && openerp.connection.debug) {
+ this.do_attach_tooltip(this, this.$label || 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) {
@@ -1455,10 +1676,7 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
return !this.invalid;
},
is_dirty: function() {
- return this.dirty && !this.readonly;
- },
- get_on_change_value: function() {
- return this.get_value();
+ return this.dirty && !this.get("effective_readonly");
},
update_dom: function(show_invalid) {
this._super.apply(this, arguments);
@@ -1466,10 +1684,8 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
this.$element.find('.oe_field_translate').toggle(!!this.view.datarecord.id);
}
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());
+ this.$element.toggleClass('oe_form_invalid', !this.is_valid());
}
}
},
@@ -1504,28 +1720,72 @@ openerp.web.form.Field = openerp.web.form.Widget.extend(/** @lends openerp.web.f
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',
+/**
+ * A mixin to apply on any field that has to completely re-render when its readonly state
+ * switch.
+ */
+openerp.web.form.ReinitializeFieldMixin = {
+ /**
+ * Default implementation of start(), use it or call explicitly initialize_field().
+ */
+ start: function() {
+ this._super();
+ this.initialize_field();
+ },
+ 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();
+ },
+ /**
+ * 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() {},
+};
+
+openerp.web.form.FieldChar = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+ template: 'FieldChar',
init: function (view, node) {
this._super(view, node);
this.password = this.node.attrs.password === 'True' || this.node.attrs.password === '1';
},
- start: function() {
- this._super.apply(this, arguments);
+ initialize_content: function() {
this.$element.find('input').change(this.on_ui_change);
},
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;
+ this.render_value();
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('input').prop('readonly', this.readonly);
+ render_value: function() {
+ var show_value = openerp.web.format_value(this.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);
+ }
},
set_value_from_ui: function() {
this.value = openerp.web.parse_value(this.$element.find('input').val(), this);
@@ -1533,31 +1793,39 @@ openerp.web.form.FieldChar = openerp.web.form.Field.extend({
},
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;
+ if (!this.get("effective_readonly")) {
+ try {
+ var value = openerp.web.parse_value(this.$element.find('input').val(), this, '');
+ this.invalid = this.get("required") && value === '';
+ } catch(e) {
+ this.invalid = true;
+ }
}
},
focus: function($element) {
this._super($element || 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);
- }
+
});
openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
- form_template: 'FieldEmail',
- start: function() {
- this._super.apply(this, arguments);
+ 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.value)
+ .text(this.value);
+ }
+ },
on_button_clicked: function() {
if (!this.value || !this.is_valid()) {
this.do_warn("E-mail error", "Can't send email to invalid e-mail address");
@@ -1568,11 +1836,23 @@ openerp.web.form.FieldEmail = openerp.web.form.FieldChar.extend({
});
openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
- form_template: 'FieldUrl',
- start: function() {
- this._super.apply(this, arguments);
+ 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.value;
+ var s = /(\w+):(.+)/.exec(tmp);
+ if (!s) {
+ tmp = "http://" + this.value;
+ }
+ this.$element.find('a').attr('href', tmp).text(tmp);
+ }
+ },
on_button_clicked: function() {
if (!this.value) {
this.do_warn("Resource error", "This resource is empty");
@@ -1583,12 +1863,14 @@ openerp.web.form.FieldUrl = openerp.web.form.FieldChar.extend({
});
openerp.web.form.FieldFloat = openerp.web.form.FieldChar.extend({
+ is_field_number: true,
init: function (view, node) {
this._super(view, node);
- if (node.attrs.digits) {
+ this.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) {
@@ -1621,7 +1903,7 @@ 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')) {
+ if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) {
self.picker('setDate', self.value ? openerp.web.auto_str_to_date(self.value) : new Date());
self.$input_picker.show();
self.picker('show');
@@ -1654,10 +1936,10 @@ openerp.web.DateTimeWidget = openerp.web.OldWidget.extend({
this.$input.prop('readonly', this.readonly);
this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly);
},
- is_valid: function(required) {
+ is_valid: function() {
var value = this.$input.val();
if (value === "") {
- return !required;
+ return true;
} else {
try {
this.parse_client(value);
@@ -1685,36 +1967,52 @@ openerp.web.DateWidget = openerp.web.DateTimeWidget.extend({
type_of_date: "date"
});
-openerp.web.form.FieldDatetime = openerp.web.form.Field.extend({
- form_template: "EmptyComponent",
+openerp.web.form.FieldDatetime = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+ template: "EmptyComponent",
build_widget: function() {
return new openerp.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(this.on_ui_change);
+ this.datewidget.appendTo(this.$element);
+ }
},
set_value: function(value) {
this._super(value);
- this.datewidget.set_value(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.value);
+ } else {
+ this.$element.text(openerp.web.format_value(this.value, this, ''));
+ }
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.datewidget.set_readonly(this.readonly);
+ get_value: function() {
+ if (!this.get("effective_readonly")) {
+ return this.datewidget.get_value();
+ } else {
+ return this.value;
+ }
},
validate: function() {
- this.invalid = !this.datewidget.is_valid(this.required);
+ this.invalid = false;
+ if (!this.get("effective_readonly")) {
+ this.invalid = !this.datewidget.is_valid() || (this.get("required") && !this.datewidget.get_value());
+ }
},
focus: function($element) {
- this._super($element || this.datewidget.$input);
+ this._super($element || (this.datewidget && this.datewidget.$input));
}
-});
+}));
openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
build_widget: function() {
@@ -1722,45 +2020,52 @@ openerp.web.form.FieldDate = openerp.web.form.FieldDatetime.extend({
}
});
-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);
- this.resized = false;
+openerp.web.form.FieldText = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+ template: 'FieldText',
+ initialize_content: function() {
+ this.$textarea = undefined;
+ if (!this.get("effective_readonly")) {
+ this.$textarea = this.$element.find('textarea').change(this.on_ui_change);
+ this.resized = false;
+ }
},
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);
- if (!this.resized && this.view.options.resize_textareas) {
- this.do_resize(this.view.options.resize_textareas);
- this.resized = true;
- }
+ this.render_value();
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('textarea').prop('readonly', this.readonly);
+ render_value: function() {
+ var show_value = openerp.web.format_value(this.value, this, '');
+ if (!this.get("effective_readonly")) {
+ this.$textarea.val(show_value);
+ if (!this.resized && this.view.options.resize_textareas) {
+ this.do_resize(this.view.options.resize_textareas);
+ this.resized = true;
+ }
+ } else {
+ this.$element.text(show_value);
+ }
},
set_value_from_ui: function() {
- this.value = openerp.web.parse_value(this.$element.find('textarea').val(), this);
+ this.value = openerp.web.parse_value(this.$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;
+ if (!this.get("effective_readonly")) {
+ try {
+ var value = openerp.web.parse_value(this.$textarea.val(), this, '');
+ this.invalid = this.get("required") && value === '';
+ } catch(e) {
+ this.invalid = true;
+ }
}
},
focus: function($element) {
- this._super($element || this.$element.find('textarea:first'));
+ this._super($element || 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());
@@ -1781,39 +2086,40 @@ openerp.web.form.FieldText = openerp.web.form.Field.extend({
reset: function() {
this.resized = false;
}
-});
+}));
-openerp.web.form.FieldBoolean = openerp.web.form.Field.extend({
- form_template: 'FieldBoolean',
+openerp.web.form.FieldBoolean = openerp.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);
+ this.$checkbox = $("input", this.$element);
+ this.$element.click(this.on_ui_change);
+ var check_readonly = function() {
+ this.$checkbox.prop('disabled', this.get("effective_readonly"));
+ };
+ this.on("change:effective_readonly", this, check_readonly);
+ _.bind(check_readonly, this)();
},
set_value: function(value) {
this._super.apply(this, arguments);
- this.$element.find('input')[0].checked = value;
+ this.$checkbox[0].checked = value;
},
set_value_from_ui: function() {
- this.value = this.$element.find('input').is(':checked');
- this._super();
- },
- update_dom: function() {
+ this.value = this.$checkbox.is(':checked');
this._super.apply(this, arguments);
- this.$element.find('input').prop('disabled', this.readonly);
},
focus: function($element) {
- this._super($element || this.$element.find('input:first'));
+ this._super($element || this.$checkbox);
}
});
-openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
- form_template: 'FieldProgressBar',
+openerp.web.form.FieldProgressBar = openerp.web.form.AbstractField.extend({
+ template: 'FieldProgressBar',
start: function() {
this._super.apply(this, arguments);
- this.$element.find('div').progressbar({
+ this.$element.progressbar({
value: this.value,
- disabled: this.readonly
+ disabled: this.get("effective_readonly")
});
},
set_value: function(value) {
@@ -1823,16 +2129,16 @@ openerp.web.form.FieldProgressBar = openerp.web.form.Field.extend({
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 + '%');
+ this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%');
}
});
-openerp.web.form.FieldTextXml = openerp.web.form.Field.extend({
+openerp.web.form.FieldTextXml = openerp.web.form.AbstractField.extend({
// to replace view editor
});
-openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
- form_template: 'FieldSelection',
+openerp.web.form.FieldSelection = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+ template: 'FieldSelection',
init: function(view, node) {
var self = this;
this._super(view, node);
@@ -1844,7 +2150,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
@@ -1857,7 +2163,6 @@ 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(function () { ischanging = true; })
@@ -1872,28 +2177,38 @@ openerp.web.form.FieldSelection = openerp.web.form.Field.extend({
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.render_value();
+ },
+ 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.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.value; });
+ this.$element.text(option ? option[1] : this.values[0][1]);
}
- 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();
},
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('select').prop('disabled', this.readonly);
- },
validate: function() {
+ if (this.get("effective_readonly")) {
+ this.invalid = false;
+ return;
+ }
var value = this.values[this.$element.find('select')[0].selectedIndex];
- this.invalid = !(value && !(this.required && value[0] === false));
+ this.invalid = !(value && !(this.get("required") && value[0] === false));
},
focus: function($element) {
this._super($element || this.$element.find('select:first'));
}
-});
+}));
// jquery autocomplete tweak to allow html
(function() {
@@ -1937,103 +2252,80 @@ openerp.web.form.dialog = function(content, options) {
return dialog.$element;
};
-openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
- form_template: 'FieldMany2One',
+openerp.web.form.FieldMany2One = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+ template: "FieldMany2One",
init: function(view, node) {
this._super(view, 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;
},
start: function() {
this._super();
+ openerp.web.form.ReinitializeFieldMixin.start.call(this);
+ this.on("change:value", this, function() {
+ this.floating = false;
+ this.render_value();
+ if (! this.inhibit_on_change) {
+ this.on_ui_change();
+ }
+ });
+ },
+ 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.DataSetStatic(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 openerp.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");
@@ -2041,16 +2333,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);
@@ -2062,9 +2381,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;
}
@@ -2117,7 +2438,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);
});
}});
@@ -2126,7 +2446,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);
@@ -2134,7 +2454,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});
}});
@@ -2145,13 +2464,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) {
var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.build_context());
dataset.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();
@@ -2175,97 +2495,72 @@ openerp.web.form.FieldMany2One = openerp.web.form.Field.extend({
new openerp.web.CompoundContext(self.build_context(), context || {})
);
pop.on_select_elements.add(function(element_ids) {
- var dataset = new openerp.web.DataSetStatic(self, self.field.relation, self.build_context());
- dataset.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 openerp.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
- var dataset = new openerp.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context());
- dataset.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);});
+ 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;
+ });
}
},
+ set_value: function(value) {
+ var self = this;
+ 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.set({value: value});
+ this.inhibit_on_change = false;
+ this._super(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;
+ return this.get("value");
},
validate: function() {
- this.invalid = false;
- var val = this.tmp_value !== undefined ? this.tmp_value : this.value;
- if (val === null) {
- this.invalid = this.required;
- }
- },
- open_related: function(related) {
- 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);
- });
+ this.invalid = this.get("required") && ! this.get("value");
},
focus: function ($element) {
this._super($element || this.$input);
- },
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$input.prop('readonly', this.readonly);
}
-});
-openerp.web.check_interface(openerp.web.form.FieldMany2One, openerp.web.form.FieldInterface);
+}));
/*
# Values: (0, 0, { fields }) create
@@ -2312,9 +2607,9 @@ var commands = {
return [6, false, ids];
}
};
-openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
- form_template: 'FieldOne2Many',
+openerp.web.form.FieldOne2Many = openerp.web.form.AbstractField.extend({
multi_selection: false,
+ disable_utility_classes: true,
init: function(view, node) {
this._super(view, node);
lazy_build_o2m_kanban_view();
@@ -2323,7 +2618,6 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
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);
@@ -2342,6 +2636,16 @@ 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;
@@ -2349,9 +2653,6 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
this.on_ui_change();
this.doing_on_change = tmp;
},
- is_readonly: function() {
- return this.readonly || this.force_readonly;
- },
load_views: function() {
var self = this;
@@ -2369,14 +2670,14 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
}
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;
}
@@ -2389,7 +2690,6 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
this.viewmanager.registry = openerp.web.views.extend({
list: 'openerp.web.form.One2ManyListView',
form: 'openerp.web.form.One2ManyFormView',
- page: 'openerp.web.PageView',
kanban: 'openerp.web.form.One2ManyKanbanView',
});
var once = $.Deferred().then(function() {
@@ -2401,10 +2701,10 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
this.viewmanager.on_controller_inited.add_last(function(view_type, controller) {
controller.o2m = self;
if (view_type == "list") {
- 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() {
@@ -2438,7 +2738,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;
}
@@ -2581,21 +2881,6 @@ openerp.web.form.FieldOne2Many = openerp.web.form.Field.extend({
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();
- });
- });
- }
- }
}
});
@@ -2655,7 +2940,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) {
@@ -2705,7 +2990,7 @@ openerp.web.form.One2ManyKanbanView = openerp.web_kanban.KanbanView.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) {
@@ -2717,12 +3002,14 @@ openerp.web.form.One2ManyKanbanView = openerp.web_kanban.KanbanView.extend({
});
}
-openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
- form_template: 'FieldMany2Many',
+/*
+ * TODO niv: clean those deferred stuff, it could be better
+ */
+openerp.web.form.FieldMany2Many = openerp.web.form.AbstractField.extend({
multi_selection: false,
+ disable_utility_classes: true,
init: function(view, node) {
this._super(view, node);
- this.list_id = _.uniqueId("many2many");
this.is_loaded = $.Deferred();
this.initial_is_loaded = this.is_loaded;
this.is_setted = $.Deferred();
@@ -2741,6 +3028,16 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
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 || [];
@@ -2759,16 +3056,13 @@ openerp.web.form.FieldMany2Many = openerp.web.form.Field.extend({
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,
+ '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) {
@@ -2781,7 +3075,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;
},
@@ -2791,21 +3085,6 @@ 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();
- });
- });
- }
- }
- }
});
openerp.web.form.Many2ManyDataSet = openerp.web.DataSetStatic.extend({
@@ -2832,9 +3111,9 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
);
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]));
+ _.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.on_ui_change();
self.reload_content();
}
@@ -2846,7 +3125,7 @@ openerp.web.form.Many2ManyListView = openerp.web.ListView.extend(/** @lends open
var pop = new openerp.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();
@@ -2954,7 +3233,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
'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();
@@ -2976,7 +3255,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;
@@ -3025,7 +3304,7 @@ openerp.web.form.SelectCreatePopup = openerp.web.OldWidget.extend(/** @lends ope
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}));
@@ -3129,14 +3408,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 = openerp.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"));
@@ -3170,8 +3449,8 @@ openerp.web.form.FormOpenDataset = openerp.web.ProxyDataSet.extend({
}
});
-openerp.web.form.FieldReference = openerp.web.form.Field.extend({
- form_template: 'FieldReference',
+openerp.web.form.FieldReference = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
+ template: 'FieldReference',
init: function(view, node) {
this._super(view, node);
this.fields_view = {
@@ -3188,22 +3467,10 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
this.get_selected_ids = 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() {
},
@@ -3215,31 +3482,58 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
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 openerp.web.form.FieldSelection(this, { attrs: {
+ name: 'selection',
+ widget: 'selection'
+ }});
+ this.selection.set({readonly: this.get('effective_readonly')});
+ this.selection.on_value_changed.add_last(this.on_selection_changed);
this.selection.$element = $(".oe_form_view_reference_selection", this.$element);
this.selection.renderElement();
this.selection.start();
+
+ this.m2o = new openerp.web.form.FieldMany2One(this, { attrs: {
+ name: 'm2o',
+ widget: 'many2one'
+ }});
+ this.m2o.set({"readonly": this.get("effective_readonly")});
+ this.m2o.on_ui_change.add_last(this.on_ui_change);
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';
+ return this.get("required") === false || 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();
+ },
+ render_value: function() {
this.reference_ready = false;
var vals = [], sel_val, m2o_val;
- if (typeof(value) === 'string') {
- vals = value.split(',');
+ if (typeof(this.value) === 'string') {
+ vals = this.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);
@@ -3254,16 +3548,15 @@ openerp.web.form.FieldReference = openerp.web.form.Field.extend({
return false;
}
}
-});
+}));
-openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
+openerp.web.form.FieldBinary = openerp.web.form.AbstractField.extend(_.extend({}, openerp.web.form.ReinitializeFieldMixin, {
init: function(view, node) {
this._super(view, 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);
@@ -3327,24 +3620,42 @@ openerp.web.form.FieldBinary = openerp.web.form.Field.extend({
}
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);
+ template: 'FieldBinaryFile',
+ initialize_content: function() {
+ this._super();
+ if (this.get("effective_readonly")) {
+ 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);
- 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.value != null && this.value !== false) ? this.value : '';
+ }
+ this.$element.find('input').eq(0).val(show_value);
} else {
- show_value = (value != null && value !== false) ? value : '';
+ this.$element.find('a').show(!!this.value);
+ if (this.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;
@@ -3368,48 +3679,49 @@ 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');
- },
- update_dom: function() {
- this._super.apply(this, arguments);
- this.$element.find('.oe-binary').toggle(!this.readonly);
+ 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();
},
set_value: function(value) {
this._super.apply(this, arguments);
- this.set_image_maxwidth();
-
+ this.render_value();
+ },
+ render_value: function() {
var url;
- if (value && value.substr(0, 10).indexOf(' ') == -1) {
+ if (this.value && this.value.substr(0, 10).indexOf(' ') == -1) {
url = 'data:image/png;base64,' + this.value;
- } else {
+ } else if (this.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.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",
+openerp.web.form.FieldStatus = openerp.web.form.AbstractField.extend({
+ template: "EmptyComponent",
start: function() {
this._super();
this.selected_value = null;
@@ -3473,45 +3785,10 @@ 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`
*/
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',
@@ -3532,18 +3809,12 @@ openerp.web.form.widgets = new openerp.web.Registry({
'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'
+ 'statusbar': 'openerp.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;
-}
-
+openerp.web.form.tags = new openerp.web.Registry({
+ 'button' : 'openerp.web.form.WidgetButton',
+});
};
diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js
index 06e1e23e1db..f137bd6c211 100644
--- a/addons/web/static/src/js/view_list.js
+++ b/addons/web/static/src/js/view_list.js
@@ -207,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);
@@ -215,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.options.$sidebar) {
+ this.sidebar = new openerp.web.Sidebar(this);
+ this.sidebar.appendTo(this.options.$sidebar);
this.sidebar.add_toolbar(this.fields_view.toolbar);
- this.set_common_sidebar_sections(this.sidebar);
}
},
/**
@@ -295,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
@@ -406,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);
@@ -417,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)
@@ -478,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) {
@@ -543,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};
@@ -823,7 +847,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();
diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js
index b8699bcc006..aea7f6837c1 100644
--- a/addons/web/static/src/js/view_list_editable.js
+++ b/addons/web/static/src/js/view_list_editable.js
@@ -235,6 +235,7 @@ openerp.web.list_editable = function (openerp) {
}
self.edition = true;
self.edition_id = record_id;
+ $new_row.addClass("oe_form_container");
self.edition_form = new openerp.web.ListEditableFormView(self.view, self.dataset, false);
self.edition_form.$element = $new_row;
self.edition_form.editable_list = self;
@@ -366,52 +367,56 @@ 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',
init: function() {
- this._super.apply(this, arguments);
- this.registry = openerp.web.list.form.widgets;
+ this._super.apply(this, arguments);
+ this.rendering_engine = new openerp.web.ListEditableRenderingEngine(this);
+ },
+ renderElement: function() {}
+ });
+
+ openerp.web.ListEditableRenderingEngine = openerp.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 = openerp.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, openerp.web.xml_to_json(el));
+ 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 1e7c570abdf..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.DataSetStatic(
- 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/views.js b/addons/web/static/src/js/views.js
index 052998f4264..de9ff55758c 100644
--- a/addons/web/static/src/js/views.js
+++ b/addons/web/static/src/js/views.js
@@ -6,17 +6,7 @@ openerp.web.views = function(session) {
var QWeb = session.web.qweb,
_t = session.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({
+session.web.ActionManager = session.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();
@@ -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) {
@@ -217,16 +204,8 @@ session.web.ActionManager = session.web.OldWidget.extend({
}
});
-session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.ViewManager# */{
+session.web.ViewManager = session.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;
@@ -250,19 +229,13 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
this.registry = session.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 = {};
@@ -271,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 || {})
@@ -279,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;
@@ -293,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();
@@ -307,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") {
+ 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);
@@ -336,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) {
@@ -373,10 +358,10 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
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);
},
@@ -391,12 +376,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 session.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,
@@ -452,7 +435,7 @@ session.web.ViewManager = session.web.OldWidget.extend(/** @lends session.web.V
}
});
-session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepnerp.web.ViewManagerAction# */{
+session.web.ViewManagerAction = session.web.ViewManager.extend({
template:"ViewManagerAction",
/**
* @constructs session.web.ViewManagerAction
@@ -575,6 +558,9 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
});
}
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) {
@@ -737,85 +723,85 @@ session.web.ViewManagerAction = session.web.ViewManager.extend(/** @lends oepner
}
});
-session.web.Sidebar = session.web.OldWidget.extend({
- init: function(parent, element_id) {
- this._super(parent, element_id);
- this.items = {};
- this.sections = {};
+session.web.Sidebar = session.web.Widget.extend({
+ init: function(parent) {
+ this._super(parent);
+ var view = this.getParent();
+ var view_manager = view.getParent();
+ var action = view_manager.action;
+ 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;
+ });
+ 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();
+ console.log('click item',section,index);
+ var item = self.items[section][index];
+ if (item.callback) {
+ item.callback.apply(self, [item]);
+ }
+ if (item.action) {
+ self.on_item_action_clicked(item);
+ }
+ return false;
});
},
+ redraw: function() {
+ var self = this;
+ self.$element.html(QWeb.render('Sidebar', {widget: self}));
+ this.$element.find('ul').hide();
+ },
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
- }
- ]);
+ var self = this;
+ },
+ 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
@@ -837,34 +823,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) {
@@ -872,11 +834,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
- });
+ session.web.dialog($("<div />").text(_t("You must choose at least one record.")), { title: _t("Warning"), modal: true });
return false;
}
var additional_context = _.extend({
@@ -899,15 +857,6 @@ session.web.Sidebar = session.web.OldWidget.extend({
});
});
},
- do_fold: function() {
- this.$element.addClass('closed-sidebar').removeClass('open-sidebar');
- },
- do_unfold: function() {
- this.$element.addClass('open-sidebar').removeClass('closed-sidebar');
- },
- do_toggle: function() {
- this.$element.toggleClass('open-sidebar closed-sidebar');
- }
});
session.web.TranslateDialog = session.web.Dialog.extend({
@@ -1026,7 +975,7 @@ session.web.TranslateDialog = session.web.Dialog.extend({
}
});
-session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
+session.web.View = session.web.Widget.extend({
template: "EmptyComponent",
// name displayed in view switchers
display_name: '',
@@ -1066,11 +1015,13 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
* Called after a successful call to fields_view_get.
* Must return a promise.
*/
- on_loaded: function(fields_view_get) {},
+ 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,
@@ -1229,6 +1180,28 @@ session.web.View = session.web.Widget.extend(/** @lends session.web.View# */{
}
});
+session.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, session.web.xml_to_json)
+ }
+ }
+}
session.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
@@ -1267,6 +1240,23 @@ session.web.json_node_to_xml = function(node, human_readable, indent) {
return r + '/>';
}
}
+session.web.xml_to_str = function(node) {
+ if (window.ActiveXObject) {
+ return node.xml;
+ } else {
+ return (new XMLSerializer()).serializeToString(node);
+ }
+}
+
+/**
+ * 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();
};