summaryrefslogtreecommitdiff
path: root/addons/web/static/src/js/menu.js
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/menu.js
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/menu.js')
-rw-r--r--addons/web/static/src/js/menu.js261
1 files changed, 261 insertions, 0 deletions
diff --git a/addons/web/static/src/js/menu.js b/addons/web/static/src/js/menu.js
new file mode 100644
index 00000000000..c7bf4b07828
--- /dev/null
+++ b/addons/web/static/src/js/menu.js
@@ -0,0 +1,261 @@
+odoo.define('web.Menu', ['web.core', 'web.session', 'web.Widget'], function (require) {
+"use strict";
+
+var core = require('web.core');
+var session = require('web.session');
+var Widget = require('web.Widget');
+
+var QWeb = core.qweb;
+
+var Menu = Widget.extend({
+ init: function() {
+ var self = this;
+ this._super.apply(this, arguments);
+ this.is_bound = $.Deferred();
+ this.data = {data:{children:[]}};
+ this.on("menu_bound", this, function() {
+ // launch the fetch of needaction counters, asynchronous
+ var $all_menus = self.$el.parents('body').find('.oe_webclient').find('[data-menu]');
+ var all_menu_ids = _.map($all_menus, function (menu) {return parseInt($(menu).attr('data-menu'), 10);});
+ if (!_.isEmpty(all_menu_ids)) {
+ this.do_load_needaction(all_menu_ids);
+ }
+ });
+ core.bus.on('do_reload_needaction', this, this.do_reload_needaction);
+ },
+ start: function() {
+ this._super.apply(this, arguments);
+ return this.bind_menu();
+ },
+ do_reload: function() {
+ var self = this;
+ self.bind_menu();
+ },
+ bind_menu: function() {
+ var self = this;
+ this.$secondary_menus = this.$el.parents().find('.oe_secondary_menus_container');
+ this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click);
+ this.$el.on('click', 'a[data-menu]', this.on_top_menu_click);
+ // Hide second level submenus
+ this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide();
+ if (self.current_menu) {
+ self.open_menu(self.current_menu);
+ }
+ this.trigger('menu_bound');
+
+ var lazyreflow = _.debounce(this.reflow.bind(this), 200);
+ core.bus.on('resize', this, function() {
+ if ($(window).width() < 768 ) {
+ lazyreflow('all_outside');
+ } else {
+ lazyreflow();
+ }
+ });
+ core.bus.trigger('resize');
+
+ this.is_bound.resolve();
+ },
+ do_load_needaction: function (menu_ids) {
+ var self = this;
+ menu_ids = _.compact(menu_ids);
+ if (_.isEmpty(menu_ids)) {
+ return $.when();
+ }
+ return this.rpc("/web/menu/load_needaction", {'menu_ids': menu_ids}).done(function(r) {
+ self.on_needaction_loaded(r);
+ });
+ },
+ on_needaction_loaded: function(data) {
+ var self = this;
+ this.needaction_data = data;
+ _.each(this.needaction_data, function (item, menu_id) {
+ var $item = self.$secondary_menus.find('a[data-menu="' + menu_id + '"]');
+ $item.find('.badge').remove();
+ if (item.needaction_counter && item.needaction_counter > 0) {
+ $item.append(QWeb.render("Menu.needaction_counter", { widget : item }));
+ }
+ });
+ },
+ /**
+ * Reflow the menu items and dock overflowing items into a "More" menu item.
+ * Automatically called when 'menu_bound' event is triggered and on window resizing.
+ *
+ * @param {string} behavior If set to 'all_outside', all the items are displayed.
+ * If not set, only the overflowing items are hidden.
+ */
+ reflow: function(behavior) {
+ var self = this;
+ var $more_container = this.$('#menu_more_container').hide();
+ var $more = this.$('#menu_more');
+ var $systray = this.$el.parents().find('.oe_systray');
+
+ $more.children('li').insertBefore($more_container); // Pull all the items out of the more menu
+
+ // 'all_outside' beahavior should display all the items, so hide the more menu and exit
+ if (behavior === 'all_outside') {
+ // Show list of menu items
+ self.$el.show();
+ this.$el.find('li').show();
+ $more_container.hide();
+ return;
+ }
+
+ // Hide all menu items
+ var $toplevel_items = this.$el.find('li').not($more_container).not($systray.find('li')).hide();
+ // Show list of menu items (which is empty for now since all menu items are hidden)
+ self.$el.show();
+ $toplevel_items.each(function() {
+ var remaining_space = self.$el.parent().width() - $more_container.outerWidth();
+ self.$el.parent().children(':visible').each(function() {
+ remaining_space -= $(this).outerWidth();
+ });
+
+ if ($(this).width() >= remaining_space) {
+ return false; // the current item will be appended in more_container
+ }
+ $(this).show(); // show the current item in menu bar
+ });
+ $more.append($toplevel_items.filter(':hidden').show());
+ $more_container.toggle(!!$more.children().length);
+ // Hide toplevel item if there is only one
+ var $toplevel = self.$el.children("li:visible");
+ if ($toplevel.length === 1) {
+ $toplevel.hide();
+ }
+ },
+ /**
+ * Opens a given menu by id, as if a user had browsed to that menu by hand
+ * except does not trigger any event on the way
+ *
+ * @param {Number} id database id of the terminal menu to select
+ */
+ open_menu: function (id) {
+ this.current_menu = id;
+ session.active_id = id;
+ var $clicked_menu, $sub_menu, $main_menu;
+ $clicked_menu = this.$el.add(this.$secondary_menus).find('a[data-menu=' + id + ']');
+ this.trigger('open_menu', id, $clicked_menu);
+
+ if (this.$secondary_menus.has($clicked_menu).length) {
+ $sub_menu = $clicked_menu.parents('.oe_secondary_menu');
+ $main_menu = this.$el.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']');
+ } else {
+ $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']');
+ $main_menu = $clicked_menu;
+ }
+
+ // Activate current main menu
+ this.$el.find('.active').removeClass('active');
+ $main_menu.parent().addClass('active');
+
+ // Show current sub menu
+ this.$secondary_menus.find('.oe_secondary_menu').hide();
+ $sub_menu.show();
+
+ // Hide/Show the leftbar menu depending of the presence of sub-items
+ this.$secondary_menus.parent('.oe_leftbar').toggle(!!$sub_menu.children().length);
+
+ // Activate current menu item and show parents
+ this.$secondary_menus.find('.active').removeClass('active');
+ if ($main_menu !== $clicked_menu) {
+ $clicked_menu.parents().show();
+ if ($clicked_menu.is('.oe_menu_toggler')) {
+ $clicked_menu.toggleClass('oe_menu_opened').siblings('.oe_secondary_submenu:first').toggle();
+ } else {
+ $clicked_menu.parent().addClass('active');
+ }
+ }
+ // add a tooltip to cropped menu items
+ this.$secondary_menus.find('.oe_secondary_submenu li a span').each(function() {
+ $(this).tooltip(this.scrollWidth > this.clientWidth ? {title: $(this).text().trim(), placement: 'right'} :'destroy');
+ });
+ },
+ /**
+ * Call open_menu with the first menu_item matching an action_id
+ *
+ * @param {Number} id the action_id to match
+ */
+ open_action: function (id) {
+ var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]');
+ var menu_id = $menu.data('menu');
+ if (menu_id) {
+ this.open_menu(menu_id);
+ }
+ },
+ /**
+ * Process a click on a menu item
+ *
+ * @param {Number} id the menu_id
+ * @param {Boolean} [needaction=false] whether the triggered action should execute in a `needs action` context
+ */
+ menu_click: function(id, needaction) {
+ if (!id) { return; }
+
+ // find back the menuitem in dom to get the action
+ var $item = this.$el.find('a[data-menu=' + id + ']');
+ if (!$item.length) {
+ $item = this.$secondary_menus.find('a[data-menu=' + id + ']');
+ }
+ var action_id = $item.data('action-id');
+ // If first level menu doesnt have action trigger first leaf
+ if (!action_id) {
+ if(this.$el.has($item).length) {
+ var $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']');
+ var $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]');
+ if($items.length) {
+ action_id = $items.data('action-id');
+ id = $items.data('menu');
+ }
+ }
+ }
+ if (action_id) {
+ this.trigger('menu_click', {
+ action_id: action_id,
+ needaction: needaction,
+ id: id,
+ previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu)
+ }, $item);
+ } else {
+ console.log('Menu no action found web test 04 will fail');
+ }
+ this.open_menu(id);
+ },
+ do_reload_needaction: function () {
+ var self = this;
+ if (self.current_menu) {
+ self.do_load_needaction([self.current_menu]).then(function () {
+ self.trigger("need_action_reloaded");
+ });
+ }
+ },
+ /**
+ * Jquery event handler for menu click
+ *
+ * @param {Event} ev the jquery event
+ */
+ on_top_menu_click: function(ev) {
+ ev.preventDefault();
+ var self = this;
+ var id = $(ev.currentTarget).data('menu');
+
+ // Fetch the menu leaves ids in order to check if they need a 'needaction'
+ var $secondary_menu = this.$el.parents().find('.oe_secondary_menu[data-menu-parent=' + id + ']');
+ var $menu_leaves = $secondary_menu.children().find('.oe_menu_leaf');
+ var menu_ids = _.map($menu_leaves, function (leave) {return parseInt($(leave).attr('data-menu'), 10);});
+
+ self.do_load_needaction(menu_ids).then(function () {
+ self.trigger("need_action_reloaded");
+ });
+ this.$el.parents().find(".oe_secondary_menus_container").scrollTop(0,0);
+
+ this.on_menu_click(ev);
+ },
+ on_menu_click: function(ev) {
+ ev.preventDefault();
+ var needaction = $(ev.target).is('div#menu_counter');
+ this.menu_click($(ev.currentTarget).data('menu'), needaction);
+ },
+});
+
+return Menu;
+});