major doc changes
[roojs1] / Roo / bootstrap / menu / Menu.js
index ba3b857..caf2b13 100644 (file)
-/*
- * - 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
+ * @parent none
+ * Bootstrap Menu class - container for MenuItems - normally has to be added to a object that supports the menu property
  * 
+ * @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();
+    }
 });