diff options
Diffstat (limited to 'addons/web/static/src/js/widgets')
| -rw-r--r-- | addons/web/static/src/js/widgets/change_password.js | 47 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/data_export.js | 442 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/database_manager.js | 217 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/date_picker.js | 114 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/loading.js | 66 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/notification.js | 44 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/sidebar.js | 215 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/systray_menu.js | 38 | ||||
| -rw-r--r-- | addons/web/static/src/js/widgets/user_menu.js | 112 |
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; +}); |
