Upgrade to ExtJS 3.3.1 - Released 11/30/2010
[extjs.git] / src / ext-core / examples / menu / menu.js
1 /*!
2  * Ext JS Library 3.3.1
3  * Copyright(c) 2006-2010 Sencha Inc.
4  * licensing@sencha.com
5  * http://www.sencha.com/license
6  */
7 /*
8  * Ext Core Library Examples 3.0 Beta
9  * http://extjs.com/
10  * Copyright(c) 2006-2009, Ext JS, LLC.
11  * 
12  * The MIT License
13  * 
14  * Permission is hereby granted, free of charge, to any person obtaining a copy
15  * of this software and associated documentation files (the "Software"), to deal
16  * in the Software without restriction, including without limitation the rights
17  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
18  * copies of the Software, and to permit persons to whom the Software is
19  * furnished to do so, subject to the following conditions:
20  * 
21  * The above copyright notice and this permission notice shall be included in
22  * all copies or substantial portions of the Software.
23  * 
24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
27  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
29  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30  * THE SOFTWARE.
31  * 
32  */
33
34 Ext.ns('Ext.ux');
35
36 Ext.ux.Menu = Ext.extend(Ext.util.Observable, {
37     direction: 'horizontal',
38     delay: 0.2,
39     autoWidth: true,
40     transitionType: 'fade',
41     transitionDuration: 0.3,
42     animate: true,
43     currentClass: 'current',
44
45     constructor: function(elId, config) {
46         config = config || {};
47         Ext.apply(this, config);
48
49         Ext.ux.Menu.superclass.constructor.call(this, config);
50
51         this.addEvents(
52             'show',
53             'hide',
54             'click'
55         );
56
57         this.el = Ext.get(elId);
58
59         this.initMarkup();
60         this.initEvents();
61
62         this.setCurrent();
63     },
64
65     initMarkup: function(){
66         this.container = this.el.wrap({cls: 'ux-menu-container', style: 'z-index: ' + --Ext.ux.Menu.zSeed});
67         this.items = this.el.select('li');
68
69         this.el.addClass('ux-menu ux-menu-' + this.direction);
70         this.el.select('>li').addClass('ux-menu-item-main');
71
72         this.el.select('li:has(>ul)').addClass('ux-menu-item-parent').each(function(item) {
73             item.down('a')
74                 .addClass('ux-menu-link-parent')
75                 .createChild({tag: 'span', cls: 'ux-menu-arrow'});
76         });
77         
78         this.el.select('li:first-child>a').addClass('ux-menu-link-first');
79         this.el.select('li:last-child>a').addClass('ux-menu-link-last');
80
81         // create clear fixes for the floating stuff
82         this.container.addClass('ux-menu-clearfix');
83
84         // if autoWidth make every submenu as wide as its biggest child;
85         if(this.autoWidth) {
86             this.doAutoWidth();
87         }
88
89         var subs = this.el.select('ul');
90         subs.addClass('ux-menu-sub');
91         
92         //ie6 and ie7/ie8 quirksmode need iframes behind the submenus
93         if(Ext.isBorderBox || Ext.isIE7) {
94             subs.each(function(item) {
95                 item.parent().createChild({tag: 'iframe', cls: 'ux-menu-ie-iframe'})
96                     .setWidth(item.getWidth())
97                     .setHeight(item.getHeight());
98             });
99         }
100         
101         subs.addClass('ux-menu-hidden');
102     },
103
104     initEvents: function() {
105         this.showTask = new Ext.util.DelayedTask(this.showMenu, this);
106         this.hideTask = new Ext.util.DelayedTask(function() {
107             this.showTask.cancel();
108             this.hideAll();
109             this.fireEvent('hide');
110         }, this);
111
112         this.el.hover(function() {
113             this.hideTask.cancel();
114         }, function() {
115             this.hideTask.delay(this.delay*1000);
116         }, this);
117
118         // for each item that has a submenu, create a mouseenter function that shows its submenu
119         // delay 5 to make sure enter is fired after mouseover
120         this.el.select('li.ux-menu-item-parent').on('mouseenter', this.onParentEnter, false, {me: this, delay: 5});
121
122         // listen for mouseover events on items to hide other items submenus and remove hovers
123         this.el.on('mouseover', function(ev, t) {
124             this.manageSiblings(t);
125             // if this item does not have a submenu, the showMenu task for a sibling could potentially still be fired, so cancel it
126             if(!Ext.fly(t).hasClass('ux-menu-item-parent')) {
127                 this.showTask.cancel();
128             }
129         }, this, {delegate: 'li'});
130
131         this.el.on('click', function(ev, t) {
132             return this.fireEvent('click', ev, t, this);
133         }, this, {delegate: 'a'})
134     },
135
136     onParentEnter: function(ev, link, o) {
137         var item = Ext.get(this),
138             me = o.me;
139
140         // if this item is in a submenu and contains a submenu, check if the submenu is not still animating
141         if(!item.hasClass('ux-menu-item-main') && item.parent('ul').hasActiveFx()) {
142             item.parent('ul').stopFx(true);
143         }
144
145         // if submenu is already shown dont do anything
146         if(!item.child('ul').hasClass('ux-menu-hidden')) {
147             return;
148         }
149         
150         me.showTask.delay(me.delay*1000, false, false, [item]);   
151     },
152
153     showMenu : function(item) {
154         var menu = item.child('ul'),
155             x = y = 0;
156
157         item.select('>a').addClass('ux-menu-link-hover');
158
159         // some different configurations require different positioning
160         if(this.direction == 'horizontal' && item.hasClass('ux-menu-item-main')) {
161             y = item.getHeight()+1;
162         }
163         else {
164             x = item.getWidth()+1;
165         }
166
167         // if its ie, force a repaint of the submenu
168         if(Ext.isIE) {
169             menu.select('ul').addClass('ux-menu-hidden');
170             // ie bugs...
171             if(Ext.isBorderBox || Ext.isIE7) {
172                 item.down('iframe').setStyle({left: x + 'px', top: y + 'px', display: 'block'});
173             }
174         }
175
176         menu.setStyle({left: x + 'px', top: y + 'px'}).removeClass('ux-menu-hidden');
177
178         if(this.animate) {
179             switch(this.transitionType) {
180                 case 'slide':
181                     if(this.direction == 'horizontal' && item.hasClass('ux-menu-item-main')) {
182                         menu.slideIn('t', {
183                             duration: this.transitionDuration
184                         });
185                     }
186                     else {
187                         menu.slideIn('l', {
188                             duration: this.transitionDuration
189                         });
190                     }
191                 break;
192
193                 default:
194                     menu.setOpacity(0.001).fadeIn({
195                         duration: this.transitionDuration
196                     });
197                 break
198             }
199         }
200         
201         this.fireEvent('show', item, menu, this);
202     },
203
204     manageSiblings: function(item) {
205         var item = Ext.get(item);
206         item.parent().select('li.ux-menu-item-parent').each(function(child) {
207             if(child.dom.id !== item.dom.id) {
208                 child.select('>a').removeClass('ux-menu-link-hover');
209                 child.select('ul').stopFx(false).addClass('ux-menu-hidden');
210                 if (Ext.isBorderBox || Ext.isIE7) {
211                     child.select('iframe').setStyle('display', 'none');
212                 }
213             }
214         });
215     },
216
217     hideAll: function() {
218         this.manageSiblings(this.el);
219     },
220     
221     setCurrent: function() {
222         var els = this.el.query('.' + this.currentClass);
223         if(!els.length) {
224             return;
225         }
226         var item = Ext.get(els[els.length-1]).removeClass(this.currentClass).findParent('li', null, true);
227         while(item && item.parent('.ux-menu')) {
228             item.down('a').addClass(this.currentClass);
229             item = item.parent('li');
230         }
231     },
232
233     doAutoWidth: function() {
234         var fixWidth = function(sub) {
235             var widest = 0;
236             var items = sub.select('>li');
237
238             sub.setStyle({width: 3000 + 'px'});
239             items.each(function(item) {
240                 widest = Math.max(widest, item.getWidth());
241             });
242
243             widest = Ext.isIE ? widest + 1 : widest;
244             items.setWidth(widest + 'px');
245             sub.setWidth(widest + 'px');
246         }
247
248         if(this.direction == 'vertical') {
249             this.container.select('ul').each(fixWidth);
250         }
251         else {
252             this.el.select('ul').each(fixWidth);
253         }
254         
255     }
256 });
257
258 Ext.ux.Menu.zSeed = 10000;