summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/widgets
diff options
context:
space:
mode:
authorGéry Debongnie <ged@odoo.com>2015-03-09 16:34:27 +0100
committerGéry Debongnie <ged@odoo.com>2015-03-18 09:01:49 +0100
commite8a00bc50da435d3859118ca127d20a2eb9a0cab (patch)
tree039f6ec40b37392e76c8830a8e518ac40f7e7fec /addons/web/static/src/js/widgets
parent9036084f065c7aca3b2199903f4da67db3501ba7 (diff)
[REF] web: add module system to the web client
This commit introduces a new module system for the javascript code. Instead of using global (namespaced) variables everywhere (like openerp.web.whatever, ...), each file declare one (or more) module with its dependencies. Each module will be loaded after its dependencies, and will be given a reference to the return values. This change is a massive departure from the way the client worked previously. It will sadly break some/most history, but I am sure that the future git ecosystem will allow you to easily browse the history past this revision (one can dream). This commit feature: * main namespace is now odoo instead of openerp * a way to define modules and dependencies (inspired by requirejs) * many files have been splitted in simpler subfiles * basic support for debugging in odoo.__DEBUG__ * no global variable (i.e. nothing in window.openerp) * many bug fixes, many new bugs Future vision: this modularization should simplify future code development, simplify debugging and will allow a serious testing effort
Diffstat (limited to 'addons/web/static/src/js/widgets')
-rw-r--r--addons/web/static/src/js/widgets/change_password.js47
-rw-r--r--addons/web/static/src/js/widgets/data_export.js442
-rw-r--r--addons/web/static/src/js/widgets/database_manager.js217
-rw-r--r--addons/web/static/src/js/widgets/date_picker.js114
-rw-r--r--addons/web/static/src/js/widgets/loading.js66
-rw-r--r--addons/web/static/src/js/widgets/notification.js44
-rw-r--r--addons/web/static/src/js/widgets/sidebar.js215
-rw-r--r--addons/web/static/src/js/widgets/systray_menu.js38
-rw-r--r--addons/web/static/src/js/widgets/user_menu.js112
9 files changed, 1295 insertions, 0 deletions
diff --git a/addons/web/static/src/js/widgets/change_password.js b/addons/web/static/src/js/widgets/change_password.js
new file mode 100644
index 00000000000..d1b7e98c74e
--- /dev/null
+++ b/addons/web/static/src/js/widgets/change_password.js
@@ -0,0 +1,47 @@
+odoo.define('web.ChangePassword', ['web.core', 'web.Dialog', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+
+var ChangePassword = Widget.extend({
+ template: "ChangePassword",
+ start: function() {
+ var self = this;
+ this.getParent().dialog_title = _t("Change Password");
+ var $button = self.$el.find('.oe_form_button');
+ $button.appendTo(this.getParent().$buttons);
+ $button.eq(2).click(function(){
+ self.$el.parents('.modal').modal('hide');
+ });
+ $button.eq(0).click(function(){
+ self.rpc("/web/session/change_password",{
+ 'fields': $("form[name=change_password_form]").serializeArray()
+ }).done(function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ } else {
+ self.do_action('logout');
+ }
+ });
+ });
+ },
+ display_error: function (error) {
+ return new Dialog(this, {
+ size: 'medium',
+ title: error.title,
+ buttons: [
+ {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
+ ]
+ }, $('<div>').html(error.error)).open();
+ },
+});
+
+core.action_registry.add("change_password", ChangePassword);
+
+return ChangePassword;
+});
diff --git a/addons/web/static/src/js/widgets/data_export.js b/addons/web/static/src/js/widgets/data_export.js
new file mode 100644
index 00000000000..73ec37cdd17
--- /dev/null
+++ b/addons/web/static/src/js/widgets/data_export.js
@@ -0,0 +1,442 @@
+odoo.define('web.DataExport', ['web.core', 'web.crash_manager', 'web.data', 'web.Dialog', 'web.framework'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var crash_manager = require('web.crash_manager');
+var data = require('web.data');
+var Dialog = require('web.Dialog');
+var framework = require('web.framework');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var DataExport = Dialog.extend({
+ template: 'ExportTreeView',
+ dialog_title: {toString: function () { return _t("Export Data"); }},
+ events: {
+ 'click #add_field': function () {
+ var self = this;
+ this.$('#field-tree-structure tr.ui-selected')
+ .removeClass('ui-selected')
+ .find('a').each(function () {
+ var id = $(this).attr('id').split('-')[1];
+ var string = $(this).attr('string');
+ self.add_field(id, string);
+ });
+ },
+ 'click #remove_field': function () {
+ this.$('#fields_list option:selected').remove();
+ },
+ 'click #remove_all_field': function () {
+ this.$('#fields_list').empty();
+ },
+ 'click #export_new_list': 'on_show_save_list',
+ 'click #move_up':'on_click_move_up',
+ 'click #move_down':'on_click_move_down',
+ },
+ init: function(parent, dataset) {
+ var self = this;
+ var options = {
+ buttons: [
+ {text: _t("Close"), click: function () { self.$el.parents('.modal').modal('hide'); }},
+ {text: _t("Export To File"), click: function () { self.on_click_export_data(); }}
+ ],
+ close: function () { self.close();}
+ };
+ this._super(parent, options);
+ this.records = {};
+ this.dataset = dataset;
+ this.exports = new data.DataSetSearch(
+ this, 'ir.exports', this.dataset.get_context());
+ },
+ start: function() {
+ var self = this;
+ this._super.apply(this, arguments);
+
+ var got_fields = new $.Deferred();
+ this.$el.find('#import_compat').change(function() {
+ self.$el.find('#fields_list').empty();
+ self.$el.find('#field-tree-structure').remove();
+ var import_comp = self.$el.find("#import_compat").val();
+ self.rpc("/web/export/get_fields", {
+ model: self.dataset.model,
+ import_compat: !!import_comp,
+ }).done(function (records) {
+ got_fields.resolve();
+ self.on_show_data(records);
+ });
+ }).change();
+
+ var got_domain = this.getParent().get_active_domain().then(function (domain) {
+ if (domain === undefined) {
+ self.ids_to_export = self.getParent().get_selected_ids();
+ self.domain = self.dataset.domain;
+ }
+ else {
+ self.ids_to_export = false;
+ self.domain = domain;
+ }
+ self.on_show_domain();
+ });
+
+ return $.when(
+ got_fields,
+ got_domain,
+ this.rpc('/web/export/formats', {}).done(this.do_setup_export_formats),
+ this.show_exports_list());
+ },
+ on_click_move_up: function () {
+ var prev_row = this.$('#fields_list option:selected').first().prev();
+ if(prev_row.length){
+ var selected_rows = this.$('#fields_list option:selected').detach();
+ prev_row.before(selected_rows);
+ }
+ },
+ on_click_move_down: function () {
+ var next_row = this.$('#fields_list option:selected').last().next();
+ if(next_row.length){
+ var selected_rows = this.$('#fields_list option:selected').detach();
+ next_row.after(selected_rows);
+ }
+ },
+ do_setup_export_formats: function (formats) {
+ var $fmts = this.$el.find('#export_format');
+ _(formats).each(function (format) {
+ var opt = new Option(format.label, format.tag);
+ if (format.error) {
+ opt.disabled = true;
+ opt.replaceChild(
+ document.createTextNode(
+ _.str.sprintf("%s — %s", format.label, format.error)),
+ opt.childNodes[0]);
+ }
+ $fmts.append(opt);
+ });
+ },
+ show_exports_list: function() {
+ var self = this;
+ if (self.$el.find('#saved_export_list').is(':hidden')) {
+ self.$el.find('#ExistsExportList').show();
+ return $.when();
+ }
+ return this.exports.read_slice(['name'], {
+ domain: [['resource', '=', this.dataset.model]]
+ }).done(function (export_list) {
+ if (!export_list.length) {
+ return;
+ }
+ self.$el.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list}));
+ self.$el.find('#saved_export_list').change(function() {
+ self.$el.find('#fields_list option').remove();
+ var export_id = self.$el.find('#saved_export_list option:selected').val();
+ if (export_id) {
+ self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id, 10)}).done(self.do_load_export_field);
+ }
+ });
+ self.$el.find('#delete_export_list').click(function() {
+ var select_exp = self.$el.find('#saved_export_list option:selected');
+ if (select_exp.val()) {
+ self.exports.unlink([parseInt(select_exp.val(), 10)]);
+ select_exp.remove();
+ self.$el.find("#fields_list option").remove();
+ if (self.$el.find('#saved_export_list option').length <= 1) {
+ self.$el.find('#ExistsExportList').hide();
+ }
+ }
+ });
+ });
+ },
+ do_load_export_field: function(field_list) {
+ var export_node = this.$el.find("#fields_list");
+ _(field_list).each(function (field) {
+ export_node.append(new Option(field.label, field.name));
+ });
+ },
+ on_show_save_list: function() {
+ var self = this;
+ var current_node = self.$el.find("#savenewlist");
+ if (!(current_node.find("label")).length) {
+ current_node.append(QWeb.render('ExportNewList'));
+ current_node.find("#add_export_list").click(function() {
+ var value = current_node.find("#savelist_name").val();
+ if (value) {
+ self.do_save_export_list(value);
+ } else {
+ alert(_t("Please enter save field list name"));
+ }
+ });
+ } else {
+ if (current_node.is(':hidden')) {
+ current_node.show();
+ current_node.find("#savelist_name").val("");
+ } else {
+ current_node.hide();
+ }
+ }
+ },
+ do_save_export_list: function(value) {
+ var self = this;
+ var fields = self.get_fields();
+ if (!fields.length) {
+ return;
+ }
+ this.exports.create({
+ name: value,
+ resource: this.dataset.model,
+ export_fields: _(fields).map(function (field) {
+ return [0, 0, {name: field}];
+ })
+ }).then(function (export_list_id) {
+ if (!export_list_id) {
+ return;
+ }
+ if (!self.$el.find("#saved_export_list").length || self.$el.find("#saved_export_list").is(":hidden")) {
+ self.show_exports_list();
+ }
+ self.$el.find("#saved_export_list").append( new Option(value, export_list_id) );
+ });
+ this.on_show_save_list();
+ },
+ on_click: function(id, record) {
+ var self = this;
+ if (!record['children']) {
+ return;
+ }
+ var model = record['params']['model'],
+ prefix = record['params']['prefix'],
+ name = record['params']['name'],
+ exclude_fields = [];
+ if (record['relation_field']) {
+ exclude_fields.push(record['relation_field']);
+ }
+
+ if (!record.loaded) {
+ var import_comp = self.$el.find("#import_compat").val();
+ self.rpc("/web/export/get_fields", {
+ model: model,
+ prefix: prefix,
+ parent_name: name,
+ import_compat: Boolean(import_comp),
+ parent_field_type : record['field_type'],
+ exclude: exclude_fields
+ }).done(function(results) {
+ record.loaded = true;
+ self.on_show_data(results, record.id);
+ });
+ } else {
+ self.showcontent(record.id);
+ }
+ },
+ on_show_domain: function() {
+ this.$el.find('tr').first().find('td').append(QWeb.render('ExportTreeView-Domain', {'record': this}));
+ },
+ on_show_data: function(result, after) {
+ var self = this;
+
+ if (after) {
+ var current_tr = self.$el.find("tr[id='treerow-" + after + "']");
+ current_tr.addClass('open');
+ current_tr.find('img').attr('src','/web/static/src/img/collapse.gif');
+ current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result}));
+ } else {
+ self.$el.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result}));
+ }
+ _.each(result, function(record) {
+ self.records[record.id] = record.value;
+ if (record.required) {
+ var required_fld = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ required_fld.addClass("oe_export_requiredfield");
+ }
+ self.$el.find("img[id='parentimg-" + record.id +"']").click(function() {
+ self.on_click(this.id, record);
+ });
+
+ self.$el.find("tr[id='treerow-" + record.id + "']").click(function(e) {
+ if (e.shiftKey) {
+ var frst_click, scnd_click = '';
+ if (self.row_index === 0) {
+ self.row_index = this.rowIndex;
+ frst_click = self.$el.find("tr[id^='treerow-']")[self.row_index-1];
+ $(frst_click).addClass("ui-selected");
+ } else {
+ var i;
+ if (this.rowIndex >=self.row_index) {
+ for (i = (self.row_index-1); i < this.rowIndex; i++) {
+ scnd_click = self.$el.find("tr[id^='treerow-']")[i];
+ if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(scnd_click).addClass("ui-selected");
+ }
+ }
+ } else {
+ for (i = (self.row_index-1); i >= (this.rowIndex-1); i--) {
+ scnd_click = self.$el.find("tr[id^='treerow-']")[i];
+ if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(scnd_click).addClass("ui-selected");
+ }
+ }
+ }
+ }
+ }
+ self.row_index = this.rowIndex;
+
+ self.$el.find("tr[id='treerow-" + record.id + "']").keyup(function() {
+ self.row_index = 0;
+ });
+ var o2m_selection = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column');
+ if ($(o2m_selection).hasClass("oe_export_readonlyfield")) {
+ return false;
+ }
+ if (e.ctrlKey) {
+ if ($(this).hasClass('ui-selected')) {
+ $(this).removeClass('ui-selected').find('a').blur();
+ } else {
+ $(this).addClass('ui-selected').find('a').focus();
+ }
+ } else if (!e.shiftKey) {
+ self.$el.find("tr.ui-selected")
+ .removeClass("ui-selected").find('a').blur();
+ $(this).addClass("ui-selected").find('a').focus();
+ }
+ return false;
+ });
+
+ self.$el.find("tr[id='treerow-" + record.id + "']").keydown(function(e) {
+ var keyCode = e.keyCode || e.which;
+ var arrow = {left: 37, up: 38, right: 39, down: 40 };
+ var elem;
+ switch (keyCode) {
+ case arrow.left:
+ if ($(this).hasClass('open')) {
+ self.on_click(this.id, record);
+ }
+ break;
+ case arrow.right:
+ if (!$(this).hasClass('open')) {
+ self.on_click(this.id, record);
+ }
+ break;
+ case arrow.up:
+ elem = this;
+ $(elem).removeClass("ui-selected");
+ while (!$(elem).prev().is(":visible")) {
+ elem = $(elem).prev();
+ }
+ if (!$(elem).prev().find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(elem).prev().addClass("ui-selected");
+ }
+ $(elem).prev().find('a').focus();
+ break;
+ case arrow.down:
+ elem = this;
+ $(elem).removeClass("ui-selected");
+ while(!$(elem).next().is(":visible")) {
+ elem = $(elem).next();
+ }
+ if (!$(elem).next().find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ $(elem).next().addClass("ui-selected");
+ }
+ $(elem).next().find('a').focus();
+ break;
+ }
+ });
+ self.$el.find("tr[id='treerow-" + record.id + "']").dblclick(function() {
+ var $o2m_selection = self.$el.find("tr[id^='treerow-" + record.id + "']").find('#tree-column');
+ if (!$o2m_selection.hasClass("oe_export_readonlyfield")) {
+ self.add_field(record.id, $(this).find("a").attr("string"));
+ }
+ });
+ });
+ self.$el.find('#fields_list').mouseover(function(event) {
+ if (event.relatedTarget) {
+ if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) {
+ var field_id = event.relatedTarget.attributes["id"]["value"];
+ if (field_id && field_id.split("-")[0] === 'export') {
+ if (!self.$el.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) {
+ self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]);
+ }
+ }
+ }
+ }
+ });
+ },
+ showcontent: function(id) {
+ // show & hide the contents
+ var $this = this.$el.find("tr[id='treerow-" + id + "']");
+ var is_open = $this.hasClass('open');
+ $this.toggleClass('open');
+
+ var first_child = $this.find('img');
+ if (is_open) {
+ first_child.attr('src', '/web/static/src/img/expand.gif');
+ } else {
+ first_child.attr('src', '/web/static/src/img/collapse.gif');
+ }
+ var child_field = this.$el.find("tr[id^='treerow-" + id +"/']");
+ var child_len = (id.split("/")).length + 1;
+ for (var i = 0; i < child_field.length; i++) {
+ var $child = $(child_field[i]);
+ if (is_open) {
+ $child.hide();
+ } else if (child_len == (child_field[i].id.split("/")).length) {
+ if ($child.hasClass('open')) {
+ $child.removeClass('open');
+ $child.find('img').attr('src', '/web/static/src/img/expand.gif');
+ }
+ $child.show();
+ }
+ }
+ },
+ add_field: function(field_id, string) {
+ var field_list = this.$el.find('#fields_list');
+ if (this.$el.find("#fields_list option[value='" + field_id + "']")
+ && !this.$el.find("#fields_list option[value='" + field_id + "']").length) {
+ field_list.append(new Option(string, field_id));
+ }
+ },
+ get_fields: function() {
+ var export_fields = this.$("#fields_list option").map(function() {
+ return $(this).val();
+ }).get();
+ if (!export_fields.length) {
+ alert(_t("Please select fields to save export list..."));
+ }
+ return export_fields;
+ },
+ on_click_export_data: function() {
+ var self = this;
+ var exported_fields = this.$el.find('#fields_list option').map(function () {
+ // DOM property is textContent, but IE8 only knows innerText
+ return {name: self.records[this.value] || this.value,
+ label: this.textContent || this.innerText};
+ }).get();
+
+ if (_.isEmpty(exported_fields)) {
+ alert(_t("Please select fields to export..."));
+ return;
+ }
+ exported_fields.unshift({name: 'id', label: 'External ID'});
+
+ var export_format = this.$el.find("#export_format").val();
+
+ framework.blockUI();
+ this.session.get_file({
+ url: '/web/export/' + export_format,
+ data: {data: JSON.stringify({
+ model: this.dataset.model,
+ fields: exported_fields,
+ ids: this.ids_to_export,
+ domain: this.domain,
+ context: this.dataset.context,
+ import_compat: !!this.$el.find("#import_compat").val(),
+ })},
+ complete: framework.unblockUI,
+ error: crash_manager.rpc_error.bind(crash_manager),
+ });
+ },
+ close: function() {
+ this._super();
+ }
+});
+
+return DataExport;
+});
diff --git a/addons/web/static/src/js/widgets/database_manager.js b/addons/web/static/src/js/widgets/database_manager.js
new file mode 100644
index 00000000000..9bd5b4d05c3
--- /dev/null
+++ b/addons/web/static/src/js/widgets/database_manager.js
@@ -0,0 +1,217 @@
+odoo.define('web.DatabaseManager', ['web.core', 'web.Dialog', 'web.framework', 'web.web_client', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var framework = require('web.framework');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+var QWeb = core.qweb;
+
+var DataBaseManager = Widget.extend({
+ init: function(parent) {
+ this._super(parent);
+ $.validator.addMethod('matches', function (s, _, re) {
+ return new RegExp(re).test(s);
+ }, _t("Invalid database name"));
+ },
+ start: function() {
+ var self = this;
+ $('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty();
+ var fetch_db = this.rpc("/web/database/get_list", {}).then(
+ function(result) {
+ self.db_list = result;
+ },
+ function (_, ev) {
+ ev.preventDefault();
+ self.db_list = null;
+ });
+ var fetch_langs = this.rpc("/web/session/get_lang_list", {}).done(function(result) {
+ self.lang_list = result;
+ });
+ return $.when(fetch_db, fetch_langs).always(self.do_render);
+ },
+ do_render: function() {
+ var self = this;
+ core.bus.trigger('web_client_toggle_bars', true);
+ self.$el.html(QWeb.render("DatabaseManager", { widget : self }));
+ $('.oe_user_menu_placeholder').append(QWeb.render("DatabaseManager.user_menu",{ widget : self }));
+ $('.oe_secondary_menus_container').append(QWeb.render("DatabaseManager.menu",{ widget : self }));
+ $('ul.oe_secondary_submenu > li:first').addClass('active');
+ $('ul.oe_secondary_submenu > li').bind('click', function (event) {
+ var menuitem = $(this);
+ menuitem.addClass('active').siblings().removeClass('active');
+ var form_id =menuitem.find('a').attr('href');
+ $(form_id).show().siblings().hide();
+ event.preventDefault();
+ });
+ $('#back-to-login').click(self.do_exit);
+ self.$el.find("td").addClass("oe_form_group_cell");
+ self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label");
+ self.$el.find("label").addClass("oe_form_label");
+ self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create });
+ self.$el.find("form[name=duplicate_db_form]").validate({ submitHandler: self.do_duplicate });
+ self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop });
+ self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup });
+ self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore });
+ self.$el.find("form[name=change_pwd_form]").validate({
+ messages: {
+ old_pwd: _t("Please enter your previous password"),
+ new_pwd: _t("Please enter your new password"),
+ confirm_pwd: {
+ required: _t("Please confirm your new password"),
+ equalTo: _t("The confirmation does not match the password")
+ }
+ },
+ submitHandler: self.do_change_password
+ });
+ },
+ destroy: function () {
+ this.$el.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty();
+ this._super();
+ },
+ /**
+ * Blocks UI and replaces $.unblockUI by a noop to prevent third parties
+ * from unblocking the UI
+ */
+ blockUI: function () {
+ framework.blockUI();
+ $.unblockUI = function () {};
+ },
+ /**
+ * Reinstates $.unblockUI so third parties can play with blockUI, and
+ * unblocks the UI
+ */
+ unblockUI: function () {
+ $.unblockUI = framework.unblockUI;
+ framework.unblockUI();
+ },
+ /**
+ * Displays an error dialog resulting from the various RPC communications
+ * failing over themselves
+ *
+ * @param {Object} error error description
+ * @param {String} error.title title of the error dialog
+ * @param {String} error.error message of the error dialog
+ */
+ display_error: function (error) {
+ return new Dialog(this, {
+ size: 'medium',
+ title: error.title,
+ buttons: [
+ {text: _t("Ok"), click: function() { this.parents('.modal').modal('hide'); }}
+ ]
+ }, $('<div>').html(error.error)).open();
+ },
+ do_create: function(form) {
+ var fields = $(form).serializeArray();
+ this.rpc("/web/database/create", {'fields': fields}).done(function(result) {
+ if (result) {
+ framework.redirect('/web');
+ } else {
+ alert("Failed to create database");
+ }
+ });
+ },
+ do_duplicate: function(form) {
+ var self = this;
+ var fields = $(form).serializeArray();
+ self.rpc("/web/database/duplicate", {'fields': fields}).then(function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ self.do_notify(_t("Duplicating database"), _t("The database has been duplicated."));
+ self.start();
+ });
+ },
+ do_drop: function(form) {
+ var self = this;
+ var $form = $(form),
+ fields = $form.serializeArray(),
+ $db_list = $form.find('[name=drop_db]'),
+ db = $db_list.val();
+ if (!db || !confirm(_.str.sprintf(_t("Do you really want to delete the database: %s ?"), db))) {
+ return;
+ }
+ self.rpc("/web/database/drop", {'fields': fields}).done(function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ self.do_notify(_t("Dropping database"), _.str.sprintf(_t("The database %s has been dropped"), db));
+ self.start();
+ });
+ },
+ do_backup: function(form) {
+ var self = this;
+ self.blockUI();
+ self.session.get_file({
+ form: form,
+ success: function () {
+ self.do_notify(_t("Backed"), _t("Database backed up successfully"));
+ },
+ error: function(error){
+ if (error && error[1]) {
+ self.display_error(error[1][0]);
+ }
+ },
+ complete: function() {
+ self.unblockUI();
+ }
+ });
+ },
+ do_restore: function(form) {
+ var self = this;
+ self.blockUI();
+ $(form).ajaxSubmit({
+ url: '/web/database/restore',
+ type: 'POST',
+ resetForm: true,
+ success: function (body) {
+ // If empty body, everything went fine
+ if (!body) { return; }
+
+ if (body.indexOf('403 Forbidden') !== -1) {
+ self.display_error({
+ title: _t("Access Denied"),
+ error: _t("Incorrect super-administrator password")
+ });
+ } else {
+ self.display_error({
+ title: _t("Restore Database"),
+ error: _t("Could not restore the database")
+ });
+ }
+ },
+ complete: function() {
+ self.unblockUI();
+ self.do_notify(_t("Restored"), _t("Database restored successfully"));
+ }
+ });
+ },
+ do_change_password: function(form) {
+ var self = this;
+ self.rpc("/web/database/change_password", {
+ 'fields': $(form).serializeArray()
+ }).done(function(result) {
+ if (result.error) {
+ self.display_error(result);
+ return;
+ }
+ self.unblockUI();
+ self.do_notify(_t("Changed Password"), _t("Password has been changed successfully"));
+ });
+ },
+ do_exit: function () {
+ this.$el.remove();
+ framework.redirect('/web');
+ }
+});
+
+core.action_registry.add("database_manager", DataBaseManager);
+
+return DataBaseManager;
+
+});
diff --git a/addons/web/static/src/js/widgets/date_picker.js b/addons/web/static/src/js/widgets/date_picker.js
new file mode 100644
index 00000000000..7e242cf1d6e
--- /dev/null
+++ b/addons/web/static/src/js/widgets/date_picker.js
@@ -0,0 +1,114 @@
+odoo.define('web.datepicker', ['web.core', 'web.formats', 'web.time', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var formats = require('web.formats');
+var time = require('web.time');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+
+var DateTimeWidget = Widget.extend({
+ template: "web.datepicker",
+ type_of_date: "datetime",
+ events: {
+ 'dp.change .oe_datepicker_main': 'change_datetime',
+ 'dp.show .oe_datepicker_main': 'set_datetime_default',
+ 'keypress .oe_datepicker_master': 'change_datetime',
+ },
+ init: function(parent) {
+ this._super(parent);
+ this.name = parent.name;
+ },
+ start: function() {
+ var l10n = _t.database.parameters;
+ var options = {
+ pickTime: true,
+ useSeconds: true,
+ startDate: moment({ y: 1900 }),
+ endDate: moment().add(200, "y"),
+ calendarWeeks: true,
+ icons : {
+ time: 'fa fa-clock-o',
+ date: 'fa fa-calendar',
+ up: 'fa fa-chevron-up',
+ down: 'fa fa-chevron-down'
+ },
+ language : moment.locale(),
+ format : time.strftime_to_moment_format(l10n.date_format +' '+ l10n.time_format),
+ };
+ this.$input = this.$el.find('input.oe_datepicker_master');
+ if (this.type_of_date === 'date') {
+ options.pickTime = false;
+ options.useSeconds = false;
+ options.format = time.strftime_to_moment_format(l10n.date_format);
+ }
+ this.picker = this.$('.oe_datepicker_main').datetimepicker(options);
+ this.set_readonly(false);
+ this.set({'value': false});
+ },
+ set_value: function(value_) {
+ this.set({'value': value_});
+ this.$input.val(value_ ? this.format_client(value_) : '');
+ },
+ get_value: function() {
+ return this.get('value');
+ },
+ set_value_from_ui_: function() {
+ var value_ = this.$input.val() || false;
+ this.set_value(this.parse_client(value_));
+ },
+ set_readonly: function(readonly) {
+ this.readonly = readonly;
+ this.$input.prop('readonly', this.readonly);
+ },
+ is_valid_: function() {
+ var value_ = this.$input.val();
+ if (value_ === "") {
+ return true;
+ } else {
+ try {
+ this.parse_client(value_);
+ return true;
+ } catch(e) {
+ return false;
+ }
+ }
+ },
+ parse_client: function(v) {
+ return formats.parse_value(v, {"widget": this.type_of_date});
+ },
+ format_client: function(v) {
+ return formats.format_value(v, {"widget": this.type_of_date});
+ },
+ set_datetime_default: function(){
+ //when opening datetimepicker the date and time by default should be the one from
+ //the input field if any or the current day otherwise
+ if (this.type_of_date === 'datetime') {
+ var value = moment().second(0);
+ if (this.$input.val().length !== 0 && this.is_valid_()){
+ value = this.$input.val();
+ }
+ this.$('.oe_datepicker_main').data('DateTimePicker').setValue(value);
+ }
+ },
+ change_datetime: function(e) {
+ if ((e.type !== "keypress" || e.which === 13) && this.is_valid_()) {
+ this.set_value_from_ui_();
+ this.trigger("datetime_changed");
+ }
+ },
+ commit_value: function () {
+ this.change_datetime();
+ },
+});
+
+var DateWidget = DateTimeWidget.extend({
+ type_of_date: "date"
+});
+
+return {
+ DateTimeWidget: DateTimeWidget,
+ DateWidget: DateWidget,
+};
+});
diff --git a/addons/web/static/src/js/widgets/loading.js b/addons/web/static/src/js/widgets/loading.js
new file mode 100644
index 00000000000..75ac1a8b4b8
--- /dev/null
+++ b/addons/web/static/src/js/widgets/loading.js
@@ -0,0 +1,66 @@
+odoo.define('web.Loading', ['web.core', 'web.framework', 'web.session', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var framework = require('web.framework');
+var session = require('web.session');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+
+var Loading = Widget.extend({
+ template: _t("Loading"),
+ init: function(parent) {
+ this._super(parent);
+ this.count = 0;
+ this.blocked_ui = false;
+ session.on("request", this, this.request_call);
+ session.on("response", this, this.response_call);
+ session.on("response_failed", this, this.response_call);
+ },
+ destroy: function() {
+ this.on_rpc_event(-this.count);
+ this._super();
+ },
+ request_call: function() {
+ this.on_rpc_event(1);
+ },
+ response_call: function() {
+ this.on_rpc_event(-1);
+ },
+ on_rpc_event : function(increment) {
+ var self = this;
+ if (!this.count && increment === 1) {
+ // Block UI after 3s
+ this.long_running_timer = setTimeout(function () {
+ self.blocked_ui = true;
+ framework.blockUI();
+ }, 3000);
+ }
+
+ this.count += increment;
+ if (this.count > 0) {
+ if (session.debug) {
+ this.$el.text(_.str.sprintf( _t("Loading (%d)"), this.count));
+ } else {
+ this.$el.text(_t("Loading"));
+ }
+ this.$el.show();
+ this.getParent().$el.addClass('oe_wait');
+ } else {
+ this.count = 0;
+ clearTimeout(this.long_running_timer);
+ // Don't unblock if blocked by somebody else
+ if (self.blocked_ui) {
+ this.blocked_ui = false;
+ framework.unblockUI();
+ }
+ this.$el.fadeOut();
+ this.getParent().$el.removeClass('oe_wait');
+ }
+ }
+});
+
+return Loading;
+});
+
diff --git a/addons/web/static/src/js/widgets/notification.js b/addons/web/static/src/js/widgets/notification.js
new file mode 100644
index 00000000000..d99ef75b324
--- /dev/null
+++ b/addons/web/static/src/js/widgets/notification.js
@@ -0,0 +1,44 @@
+odoo.define('web.Notification', ['web.Widget'], function (require) {
+"use strict";
+
+var Widget = require('web.Widget');
+
+var Notification = Widget.extend({
+ template: 'Notification',
+ init: function() {
+ this._super.apply(this, arguments);
+ },
+ start: function() {
+ this._super.apply(this, arguments);
+ this.$el.notify({
+ speed: 500,
+ expires: 2500
+ });
+ },
+ notify: function(title, text, sticky) {
+ sticky = !!sticky;
+ var opts = {};
+ if (sticky) {
+ opts.expires = false;
+ }
+ return this.$el.notify('create', {
+ title: title,
+ text: text
+ }, opts);
+ },
+ warn: function(title, text, sticky) {
+ sticky = !!sticky;
+ var opts = {};
+ if (sticky) {
+ opts.expires = false;
+ }
+ return this.$el.notify('create', 'oe_notification_alert', {
+ title: title,
+ text: text
+ }, opts);
+ }
+});
+
+return Notification;
+
+});
diff --git a/addons/web/static/src/js/widgets/sidebar.js b/addons/web/static/src/js/widgets/sidebar.js
new file mode 100644
index 00000000000..7005ec2012e
--- /dev/null
+++ b/addons/web/static/src/js/widgets/sidebar.js
@@ -0,0 +1,215 @@
+odoo.define('web.Sidebar', ['web.core', 'web.data', 'web.Dialog', 'web.framework', 'web.pyeval', 'web.session', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var data = require('web.data');
+var Dialog = require('web.Dialog');
+var framework = require('web.framework');
+var pyeval = require('web.pyeval');
+var session = require('web.session');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+var _t = core._t;
+
+var Sidebar = Widget.extend({
+ init: function(parent) {
+ var self = this;
+ this._super(parent);
+ this.sections = [
+ { 'name' : 'print', 'label' : _t('Print'), },
+ { 'name' : 'other', 'label' : _t('More'), }
+ ];
+ this.items = {
+ 'print' : [],
+ 'other' : []
+ };
+ this.fileupload_id = _.uniqueId('oe_fileupload');
+ $(window).on(this.fileupload_id, function() {
+ var args = [].slice.call(arguments).slice(1);
+ self.do_attachement_update(self.dataset, self.model_id,args);
+ framework.unblockUI();
+ });
+ },
+ start: function() {
+ var self = this;
+ this._super(this);
+ this.redraw();
+ this.$el.on('click','.dropdown-menu li a', function(event) {
+ var section = $(this).data('section');
+ var index = $(this).data('index');
+ var item = self.items[section][index];
+ if (item.callback) {
+ item.callback.apply(self, [item]);
+ } else if (item.action) {
+ self.on_item_action_clicked(item);
+ } else if (item.url) {
+ return true;
+ }
+ event.preventDefault();
+ });
+ },
+ redraw: function() {
+ var self = this;
+ self.$el.html(QWeb.render('Sidebar', {widget: self}));
+
+ // Hides Sidebar sections when item list is empty
+ this.$('.oe_form_dropdown_section').each(function() {
+ $(this).toggle(!!$(this).find('li').length);
+ });
+ self.$("[title]").tooltip({
+ delay: { show: 500, hide: 0}
+ });
+ },
+ /**
+ * For each item added to the section:
+ *
+ * ``label``
+ * 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
+ * ``callback`` should be exclusive
+ *
+ * ``callback``
+ * function to call when the item is clicked in the sidebar, called
+ * with the item descriptor as its first argument (so information
+ * can be stored as additional keys on the object passed to
+ * ``add_items``)
+ *
+ * ``classname`` (optional)
+ * ``@class`` set on the sidebar serialization of the item
+ *
+ * ``title`` (optional)
+ * will be set as the item's ``@title`` (tooltip)
+ *
+ * @param {String} section_code
+ * @param {Array<{label, action | callback[, classname][, title]}>} items
+ */
+ add_items: function(section_code, items) {
+ if (items) {
+ this.items[section_code].unshift.apply(this.items[section_code],items);
+ this.redraw();
+ }
+ },
+ add_toolbar: function(toolbar) {
+ var self = this;
+ _.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
+ };
+ }
+ self.add_items(type=='print' ? 'print' : 'other', items);
+ }
+ });
+ },
+ on_item_action_clicked: function(item) {
+ var self = this;
+ self.getParent().sidebar_eval_context().done(function (sidebar_eval_context) {
+ var ids = self.getParent().get_selected_ids();
+ var domain;
+ if (self.getParent().get_active_domain) {
+ domain = self.getParent().get_active_domain();
+ }
+ else {
+ domain = $.Deferred().resolve(undefined);
+ }
+ if (ids.length === 0) {
+ new Dialog(this, { title: _t("Warning"), size: 'medium',}, $("<div />").text(_t("You must choose at least one record."))).open();
+ return false;
+ }
+ var dataset = self.getParent().dataset;
+ var active_ids_context = {
+ active_id: ids[0],
+ active_ids: ids,
+ active_model: dataset.model,
+ };
+
+ $.when(domain).done(function (domain) {
+ if (domain !== undefined) {
+ active_ids_context.active_domain = domain;
+ }
+ var c = pyeval.eval('context',
+ new data.CompoundContext(
+ sidebar_eval_context, active_ids_context));
+
+ self.rpc("/web/action/load", {
+ action_id: item.action.id,
+ context: new data.CompoundContext(
+ dataset.get_context(), active_ids_context).eval()
+ }).done(function(result) {
+ result.context = new data.CompoundContext(
+ result.context || {}, active_ids_context)
+ .set_eval_context(c);
+ result.flags = result.flags || {};
+ result.flags.new_window = true;
+ self.do_action(result, {
+ on_close: function() {
+ // reload view
+ self.getParent().reload();
+ },
+ });
+ });
+ });
+ });
+ },
+ do_attachement_update: function(dataset, model_id, args) {
+ this.dataset = dataset;
+ this.model_id = model_id;
+ if (args && args[0].error) {
+ this.do_warn(_t('Uploading Error'), args[0].error);
+ }
+ if (!model_id) {
+ this.on_attachments_loaded([]);
+ } else {
+ var dom = [ ['res_model', '=', dataset.model], ['res_id', '=', model_id], ['type', 'in', ['binary', 'url']] ];
+ var ds = new data.DataSetSearch(this, 'ir.attachment', dataset.get_context(), dom);
+ ds.read_slice(['name', 'url', 'type', 'create_uid', 'create_date', 'write_uid', 'write_date'], {}).done(this.on_attachments_loaded);
+ }
+ },
+ on_attachments_loaded: function(attachments) {
+ var self = this;
+ var prefix = session.url('/web/binary/saveas', {model: 'ir.attachment', field: 'datas', filename_field: 'name'});
+ _.each(attachments,function(a) {
+ a.label = a.name;
+ if(a.type === "binary") {
+ a.url = prefix + '&id=' + a.id + '&t=' + (new Date().getTime());
+ }
+ });
+ self.items.files = attachments;
+ self.redraw();
+ this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed);
+ this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete);
+ },
+ on_attachment_changed: function(e) {
+ var $e = $(e.target);
+ if ($e.val() !== '') {
+ this.$el.find('form.oe_form_binary_form').submit();
+ $e.parent().find('input[type=file]').prop('disabled', true);
+ $e.parent().find('button').prop('disabled', true).find('img, span').toggle();
+ this.$('.oe_sidebar_add_attachment a').text(_t('Uploading...'));
+ framework.blockUI();
+ }
+ },
+ on_attachment_delete: function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ var self = this;
+ var $e = $(e.currentTarget);
+ if (confirm(_t("Do you really want to delete this attachment ?"))) {
+ (new data.DataSet(this, 'ir.attachment')).unlink([parseInt($e.attr('data-id'), 10)]).done(function() {
+ self.do_attachement_update(self.dataset, self.model_id);
+ });
+ }
+ }
+});
+
+return Sidebar;
+
+});
+
diff --git a/addons/web/static/src/js/widgets/systray_menu.js b/addons/web/static/src/js/widgets/systray_menu.js
new file mode 100644
index 00000000000..eb218a51bc7
--- /dev/null
+++ b/addons/web/static/src/js/widgets/systray_menu.js
@@ -0,0 +1,38 @@
+odoo.define('web.SystrayMenu', ['web.Widget'], function (require) {
+"use strict";
+
+var Widget = require('web.Widget');
+
+var SystrayMenu = Widget.extend({
+ /**
+ * This widget renders the systray menu. It creates and renders widgets
+ * pushed in instance.web.SystrayItems.
+ */
+ init: function(parent) {
+ this._super(parent);
+ this.items = [];
+ this.load = $.Deferred();
+ },
+ start: function() {
+ var self = this;
+ self._super.apply(this, arguments);
+ self.load_items();
+ return $.when.apply($, self.items).done(function () {
+ self.load.resolve();
+ });
+ },
+ load_items: function() {
+ var self = this;
+ _.each(SystrayMenu.Items, function(widgetCls) {
+ var cur_systray_item = new widgetCls(self);
+ self.items.push(cur_systray_item.appendTo(self.$el));
+ });
+ },
+});
+
+SystrayMenu.Items = [];
+
+return SystrayMenu;
+
+});
+
diff --git a/addons/web/static/src/js/widgets/user_menu.js b/addons/web/static/src/js/widgets/user_menu.js
new file mode 100644
index 00000000000..450b0191486
--- /dev/null
+++ b/addons/web/static/src/js/widgets/user_menu.js
@@ -0,0 +1,112 @@
+odoo.define('web.UserMenu', ['web.core', 'web.Dialog', 'web.framework', 'web.Model', 'web.session', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var Dialog = require('web.Dialog');
+var framework = require('web.framework');
+var Model = require('web.Model');
+var session = require('web.session');
+var Widget = require('web.Widget');
+
+var _t = core._t;
+var QWeb = core.qweb;
+
+var SystrayMenu = Widget.extend({
+ template: "UserMenu",
+ init: function(parent) {
+ this._super(parent);
+ this.update_promise = $.Deferred().resolve();
+ },
+ start: function() {
+ var self = this;
+ this.$el.on('click', '.dropdown-menu li a[data-menu]', function(ev) {
+ ev.preventDefault();
+ var f = self['on_menu_' + $(this).data('menu')];
+ if (f) {
+ f($(this));
+ }
+ });
+ this.$el.parent().show();
+ return this._super.apply(this, arguments);
+ },
+ do_update: function () {
+ var self = this;
+ var fct = function() {
+ var $avatar = self.$el.find('.oe_topbar_avatar');
+ $avatar.attr('src', $avatar.data('default-src'));
+ if (!session.uid)
+ return;
+ var func = new Model("res.users").get_func("read");
+ return self.alive(func(session.uid, ["name", "company_id"])).then(function(res) {
+ var topbar_name = res.name;
+ if(session.debug)
+ topbar_name = _.str.sprintf("%s (%s)", topbar_name, session.db);
+ if(res.company_id[0] > 1)
+ topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]);
+ self.$el.find('.oe_topbar_name').text(topbar_name);
+ if (!session.debug) {
+ topbar_name = _.str.sprintf("%s (%s)", topbar_name, session.db);
+ }
+ var avatar_src = session.url('/web/binary/image', {model:'res.users', field: 'image_small', id: session.uid});
+ $avatar.attr('src', avatar_src);
+
+ core.bus.trigger('resize'); // Re-trigger the reflow logic
+ });
+ };
+ this.update_promise = this.update_promise.then(fct, fct);
+ },
+ on_menu_help: function() {
+ window.open('http://help.odoo.com', '_blank');
+ },
+ on_menu_logout: function() {
+ this.trigger('user_logout');
+ },
+ on_menu_settings: function() {
+ var self = this;
+ if (!this.getParent().has_uncommitted_changes()) {
+ self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }).done(function(result) {
+ result.res_id = session.uid;
+ self.getParent().action_manager.do_action(result);
+ });
+ }
+ },
+ on_menu_account: function() {
+ if (!this.getParent().has_uncommitted_changes()) {
+ var P = new Model('ir.config_parameter');
+ P.call('get_param', ['database.uuid']).then(function(dbuuid) {
+ var state = {
+ 'd': session.db,
+ 'u': window.location.protocol + '//' + window.location.host,
+ };
+ var params = {
+ response_type: 'token',
+ client_id: dbuuid || '',
+ state: JSON.stringify(state),
+ scope: 'userinfo',
+ };
+ framework.redirect('https://accounts.odoo.com/oauth2/auth?'+$.param(params));
+ }).fail(function(result, ev){
+ ev.preventDefault();
+ framework.redirect('https://accounts.odoo.com/web');
+ });
+ }
+ },
+ on_menu_about: function() {
+ var self = this;
+ self.rpc("/web/webclient/version_info", {}).done(function(res) {
+ var $help = $(QWeb.render("UserMenu.about", {version_info: res}));
+ $help.find('a.oe_activate_debug_mode').click(function (e) {
+ e.preventDefault();
+ window.location = $.param.querystring( window.location.href, 'debug');
+ });
+ new Dialog(this, {
+ size: 'medium',
+ dialogClass: 'oe_act_window',
+ title: _t("About"),
+ }, $help).open();
+ });
+ },
+});
+
+return SystrayMenu;
+});