-/*
- * - LGPL
- *
- * menu
- *
- */
-Roo.bootstrap.menu = Roo.bootstrap.menu || {};
-
+
/**
* @class Roo.bootstrap.menu.Menu
* @extends Roo.bootstrap.Component
- * Bootstrap Menu class - container for Menu
- * @cfg {String} html Text of the menu
- * @cfg {String} weight (default | primary | success | info | warning | danger | inverse)
- * @cfg {String} icon Font awesome icon
- * @cfg {String} pos Menu align to (top | bottom) default bottom
- *
+ * @licence LGPL
+ * @children Roo.bootstrap.menu.Item
+ * Bootstrap Menu class - container for MenuItems
*
+ * @cfg {String} type (dropdown|treeview|submenu) type of menu
+ * @cfg {bool} hidden if the menu should be hidden when rendered.
+ * @cfg {bool} stopEvent (true|false) Stop event after trigger press (default true)
+ * @cfg {bool} isLink (true|false) the menu has link disable auto expand and collaspe (default false)
+ * @cfg {bool} hideTrigger (true|false) default false - hide the carret for trigger.
+ * @cfg {String} align default tl-bl? == below - how the menu should be aligned.
+
* @constructor
* Create a new Menu
- * @param {Object} config The config object
+ * @param {Object} config The config objectQ
*/
Roo.bootstrap.menu.Menu = function(config){
+
+ if (config.type == 'treeview') {
+ // normally menu's are drawn attached to the document to handle layering etc..
+ // however treeview (used by the docs menu is drawn into the parent element)
+ this.container_method = 'getChildContainer';
+ }
+
Roo.bootstrap.menu.Menu.superclass.constructor.call(this, config);
+ if (this.registerMenu && this.type != 'treeview') {
+ Roo.bootstrap.menu.Manager.register(this);
+ }
+
this.addEvents({
/**
* @event beforeshow
- * Fires before this menu is displayed
- * @param {Roo.bootstrap.menu.Menu} this
+ * Fires before this menu is displayed (return false to block)
+ * @param {Roo.menu.Menu} this
*/
beforeshow : true,
/**
* @event beforehide
- * Fires before this menu is hidden
- * @param {Roo.bootstrap.menu.Menu} this
+ * Fires before this menu is hidden (return false to block)
+ * @param {Roo.menu.Menu} this
*/
beforehide : true,
/**
* @event show
* Fires after this menu is displayed
- * @param {Roo.bootstrap.menu.Menu} this
+ * @param {Roo.menu.Menu} this
*/
show : true,
/**
* @event hide
* Fires after this menu is hidden
- * @param {Roo.bootstrap.menu.Menu} this
+ * @param {Roo.menu.Menu} this
*/
hide : true,
/**
* @event click
* Fires when this menu is clicked (or when the enter key is pressed while it is active)
- * @param {Roo.bootstrap.menu.Menu} this
+ * @param {Roo.menu.Menu} this
+ * @param {Roo.menu.Item} menuItem The menu item that was clicked
+ * @param {Roo.EventObject} e
+ */
+ click : true,
+ /**
+ * @event mouseover
+ * Fires when the mouse is hovering over this menu
+ * @param {Roo.menu.Menu} this
+ * @param {Roo.EventObject} e
+ * @param {Roo.menu.Item} menuItem The menu item that was clicked
+ */
+ mouseover : true,
+ /**
+ * @event mouseout
+ * Fires when the mouse exits this menu
+ * @param {Roo.menu.Menu} this
+ * @param {Roo.EventObject} e
+ * @param {Roo.menu.Item} menuItem The menu item that was clicked
+ */
+ mouseout : true,
+ /**
+ * @event itemclick
+ * Fires when a menu item contained in this menu is clicked
+ * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
* @param {Roo.EventObject} e
*/
- click : true
+ itemclick: true
});
-
+ this.menuitems = new Roo.util.MixedCollection(false, function(o) { return o.el.id; });
};
Roo.extend(Roo.bootstrap.menu.Menu, Roo.bootstrap.Component, {
- submenu : false,
- html : '',
- weight : 'default',
- icon : false,
- pos : 'bottom',
+ /// html : false,
+
+ triggerEl : false, // is this set by component builder? -- it should really be fetched from parent()???
+ type: false,
+ /**
+ * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
+ */
+ registerMenu : true,
+ menuItems :false, // stores the menu items..
- getChildContainer : function() {
- if(this.isSubMenu){
- return this.el;
- }
+ hidden:true,
- return this.el.select('ul.dropdown-menu', true).first();
+ parentMenu : false,
+
+ stopEvent : true,
+
+ isLink : false,
+
+ container_method : 'getDocumentBody', // so the menu is rendered on the body and zIndex works.
+
+ hideTrigger : false,
+
+ align : 'tl-bl?',
+
+
+ getChildContainer : function() {
+ return this.el;
},
- getAutoCreate : function()
- {
- var text = [
- {
- tag : 'span',
- cls : 'roo-menu-text',
- html : this.html
- }
- ];
-
- if(this.icon){
- text.unshift({
- tag : 'i',
- cls : 'fa ' + this.icon
- })
- }
-
-
+ getAutoCreate : function(){
+
+ //if (['right'].indexOf(this.align)!==-1) {
+ // cfg.cn[1].cls += ' pull-right'
+ //}
+
var cfg = {
- tag : 'div',
- cls : 'btn-group',
- cn : [
- {
- tag : 'button',
- cls : 'dropdown-button btn btn-' + this.weight,
- cn : text
- },
- {
- tag : 'button',
- cls : 'dropdown-toggle btn btn-' + this.weight,
- cn : [
- {
- tag : 'span',
- cls : 'caret'
- }
- ]
- },
- {
- tag : 'ul',
- cls : 'dropdown-menu'
- }
- ]
+ tag : 'ul',
+ cls : 'dropdown-menu shadow' ,
+ style : 'z-index:1000'
};
-
- if(this.pos == 'top'){
- cfg.cls += ' dropup';
+
+ if (this.type === 'submenu') {
+ cfg.cls = 'submenu active';
+ }
+ if (this.type === 'treeview') {
+ cfg.cls = 'treeview-menu';
}
- if(this.isSubMenu){
- cfg = {
- tag : 'ul',
- cls : 'dropdown-menu'
+ return cfg;
+ },
+ initEvents : function() {
+
+ // Roo.log("ADD event");
+ // Roo.log(this.triggerEl.dom);
+ if (this.triggerEl) {
+
+ this.triggerEl.on('click', this.onTriggerClick, this);
+
+ this.triggerEl.on(Roo.isTouch ? 'touchstart' : 'mouseup', this.onTriggerPress, this);
+
+ if (!this.hideTrigger) {
+ if (this.triggerEl.hasClass('nav-item') && this.triggerEl.select('.nav-link',true).length) {
+ // dropdown toggle on the 'a' in BS4?
+ this.triggerEl.select('.nav-link',true).first().addClass('dropdown-toggle');
+ } else {
+ this.triggerEl.addClass('dropdown-toggle');
+ }
}
}
-
- return cfg;
+
+ if (Roo.isTouch) {
+ this.el.on('touchstart' , this.onTouch, this);
+ }
+ this.el.on('click' , this.onClick, this);
+
+ this.el.on("mouseover", this.onMouseOver, this);
+ this.el.on("mouseout", this.onMouseOut, this);
+
},
- onRender : function(ct, position)
+ findTargetItem : function(e)
{
- this.isSubMenu = ct.hasClass('dropdown-submenu');
+ var t = e.getTarget(".dropdown-menu-item", this.el, true);
+ if(!t){
+ return false;
+ }
+ //Roo.log(t); Roo.log(t.id);
+ if(t && t.id){
+ //Roo.log(this.menuitems);
+ return this.menuitems.get(t.id);
+
+ //return this.items.get(t.menuItemId);
+ }
- Roo.bootstrap.menu.Menu.superclass.onRender.call(this, ct, position);
+ return false;
},
- initEvents : function()
+ onTouch : function(e)
{
- if(this.isSubMenu){
+ Roo.log("menu.onTouch");
+ //e.stopEvent(); this make the user popdown broken
+ this.onClick(e);
+ },
+
+ onClick : function(e)
+ {
+ Roo.log("menu.onClick");
+
+ var t = this.findTargetItem(e);
+ if(!t || t.isContainer){
+ return;
+ }
+ Roo.log(e);
+ /*
+ if (Roo.isTouch && e.type == 'touchstart' && t.menu && !t.disabled) {
+ if(t == this.activeItem && t.shouldDeactivate(e)){
+ this.activeItem.deactivate();
+ delete this.activeItem;
+ return;
+ }
+ if(t.canActivate){
+ this.setActiveItem(t, true);
+ }
return;
+
+
}
+ */
+
+ Roo.log('pass click event');
- this.hidden = true;
+ t.onClick(e);
- this.triggerEl = this.el.select('button.dropdown-toggle', true).first();
- this.triggerEl.on('click', this.onTriggerPress, this);
+ this.fireEvent("click", this, t, e);
- this.buttonEl = this.el.select('button.dropdown-button', true).first();
- this.buttonEl.on('click', this.onClick, this);
+ var _this = this;
- },
-
- list : function()
- {
- if(this.isSubMenu){
- return this.el;
+ if(!t.href.length || t.href == '#'){
+ (function() { _this.hide(); }).defer(100);
}
- return this.el.select('ul.dropdown-menu', true).first();
- },
-
- onClick : function(e)
- {
- this.fireEvent("click", this, e);
},
- onTriggerPress : function(e)
- {
- if (this.isVisible()) {
- this.hide();
- } else {
- this.show();
- }
+ onMouseOver : function(e){
+ var t = this.findTargetItem(e);
+ //Roo.log(t);
+ //if(t){
+ // if(t.canActivate && !t.disabled){
+ // this.setActiveItem(t, true);
+ // }
+ //}
+
+ this.fireEvent("mouseover", this, e, t);
},
-
isVisible : function(){
return !this.hidden;
},
+ onMouseOut : function(e){
+ var t = this.findTargetItem(e);
+
+ //if(t ){
+ // if(t == this.activeItem && t.shouldDeactivate(e)){
+ // this.activeItem.deactivate();
+ // delete this.activeItem;
+ // }
+ //}
+ this.fireEvent("mouseout", this, e, t);
+ },
+
- show : function()
+ /**
+ * Displays this menu relative to another element
+ * @param {String/HTMLElement/Roo.Element} element The element to align to
+ * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
+ * the element (defaults to this.defaultAlign)
+ * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
+ */
+ show : function(el, pos, parentMenu)
{
- this.fireEvent("beforeshow", this);
+ if (false === this.fireEvent("beforeshow", this)) {
+ Roo.log("show canceled");
+ return;
+ }
+ this.parentMenu = parentMenu;
+ if(!this.el){
+ this.render();
+ }
+ this.el.addClass('show'); // show otherwise we do not know how big we are..
+
+ var xy = this.el.getAlignToXY(el, pos);
+
+ // bl-tl << left align below
+ // tl-bl << left align
+
+ if(this.el.getWidth() + xy[0] >= Roo.lib.Dom.getViewWidth()){
+ // if it goes to far to the right.. -> align left.
+ xy = this.el.getAlignToXY(el, this.align.replace('/l/g', 'r'))
+ }
+ if(xy[0] < 0){
+ // was left align - go right?
+ xy = this.el.getAlignToXY(el, this.align.replace('/r/g', 'l'))
+ }
+
+ // goes down the bottom
+ if(this.el.getHeight() + xy[1] >= Roo.lib.Dom.getViewHeight() ||
+ xy[1] < 0 ){
+ var a = this.align.replace('?', '').split('-');
+ xy = this.el.getAlignToXY(el, a[1] + '-' + a[0] + '?')
+
+ }
+ this.showAt( xy , parentMenu, false);
+ },
+ /**
+ * Displays this menu at a specific xy position
+ * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
+ * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
+ */
+ showAt : function(xy, parentMenu, /* private: */_e){
+ this.parentMenu = parentMenu;
+ if(!this.el){
+ this.render();
+ }
+ if(_e !== false){
+ this.fireEvent("beforeshow", this);
+ //xy = this.el.adjustForConstraints(xy);
+ }
+
+ //this.el.show();
+ this.hideMenuItems();
this.hidden = false;
- this.el.addClass('open');
+ if (this.triggerEl) {
+ this.triggerEl.addClass('open');
+ }
+
+ this.el.addClass('show');
- Roo.get(document).on("mouseup", this.onMouseUp, this);
+
+
+ // reassign x when hitting right
+
+ // reassign y when hitting bottom
+
+ // but the list may align on trigger left or trigger top... should it be a properity?
+
+ if(this.el.getStyle('top') != 'auto' && this.el.getStyle('top').slice(-1) != "%"){
+ this.el.setXY(xy);
+ }
+ this.focus();
this.fireEvent("show", this);
+ },
+
+ focus : function(){
+ return;
+ if(!this.hidden){
+ this.doFocus.defer(50, this);
+ }
+ },
+
+ doFocus : function(){
+ if(!this.hidden){
+ this.focusEl.focus();
+ }
+ },
+
+ /**
+ * Hides this menu and optionally all parent menus
+ * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
+ */
+ hide : function(deep)
+ {
+ if (false === this.fireEvent("beforehide", this)) {
+ Roo.log("hide canceled");
+ return;
+ }
+ this.hideMenuItems();
+ if(this.el && this.isVisible()){
+
+ if(this.activeItem){
+ this.activeItem.deactivate();
+ this.activeItem = null;
+ }
+ if (this.triggerEl) {
+ this.triggerEl.removeClass('open');
+ }
+
+ this.el.removeClass('show');
+ this.hidden = true;
+ this.fireEvent("hide", this);
+ }
+ if(deep === true && this.parentMenu){
+ this.parentMenu.hide(true);
+ }
+ },
+
+ onTriggerClick : function(e)
+ {
+ Roo.log('trigger click');
+ var target = e.getTarget();
+
+ Roo.log(target.nodeName.toLowerCase());
+
+ if(target.nodeName.toLowerCase() === 'i'){
+ e.preventDefault();
+ }
},
- hide : function()
+ onTriggerPress : function(e)
{
- this.fireEvent("beforehide", this);
+ Roo.log('trigger press');
+ //Roo.log(e.getTarget());
+ // Roo.log(this.triggerEl.dom);
+
+ // trigger only occurs on normal menu's -- if it's a treeview or dropdown... do not hide/show..
+ var pel = Roo.get(e.getTarget());
+ if (pel.findParent('.dropdown-menu') || pel.findParent('.treeview-menu') ) {
+ Roo.log('is treeview or dropdown?');
+ return;
+ }
- this.hidden = true;
- this.el.removeClass('open');
+ if(e.getTarget().nodeName.toLowerCase() !== 'i' && this.isLink){
+ return;
+ }
- Roo.get(document).un("mouseup", this.onMouseUp);
+ if (this.isVisible()) {
+ Roo.log('hide');
+ this.hide();
+ } else {
+ Roo.log('show');
+
+ this.show(this.triggerEl, this.align, false);
+ }
+
+ if(this.stopEvent || e.getTarget().nodeName.toLowerCase() === 'i'){
+ e.stopEvent();
+ }
- this.fireEvent("hide", this);
},
+
- onMouseUp : function()
+ hideMenuItems : function()
{
- this.hide();
- }
+ Roo.log("hide Menu Items");
+ if (!this.el) {
+ return;
+ }
+
+ this.el.select('.open',true).each(function(aa) {
+
+ aa.removeClass('open');
+
+ });
+ },
+ addxtypeChild : function (tree, cntr) {
+ var comp= Roo.bootstrap.menu.Menu.superclass.addxtypeChild.call(this, tree, cntr);
+
+ this.menuitems.add(comp);
+ return comp;
+
+ },
+ getEl : function()
+ {
+ Roo.log(this.el);
+ return this.el;
+ },
+ clear : function()
+ {
+ this.getEl().dom.innerHTML = '';
+ this.menuitems.clear();
+ }
});