summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/widgets
diff options
context:
space:
mode:
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;
+});