/[projects]/misc/horsensspejder-web/jquery/jquery-ui-1.10.3/ui/jquery.ui.menu.js
ViewVC logotype

Annotation of /misc/horsensspejder-web/jquery/jquery-ui-1.10.3/ui/jquery.ui.menu.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2125 - (hide annotations) (download) (as text)
Wed Mar 12 19:30:05 2014 UTC (10 years, 4 months ago) by torben
File MIME type: application/javascript
File size: 16565 byte(s)
initial import
1 torben 2125 /*!
2     * jQuery UI Menu 1.10.3
3     * http://jqueryui.com
4     *
5     * Copyright 2013 jQuery Foundation and other contributors
6     * Released under the MIT license.
7     * http://jquery.org/license
8     *
9     * http://api.jqueryui.com/menu/
10     *
11     * Depends:
12     * jquery.ui.core.js
13     * jquery.ui.widget.js
14     * jquery.ui.position.js
15     */
16     (function( $, undefined ) {
17    
18     $.widget( "ui.menu", {
19     version: "1.10.3",
20     defaultElement: "<ul>",
21     delay: 300,
22     options: {
23     icons: {
24     submenu: "ui-icon-carat-1-e"
25     },
26     menus: "ul",
27     position: {
28     my: "left top",
29     at: "right top"
30     },
31     role: "menu",
32    
33     // callbacks
34     blur: null,
35     focus: null,
36     select: null
37     },
38    
39     _create: function() {
40     this.activeMenu = this.element;
41     // flag used to prevent firing of the click handler
42     // as the event bubbles up through nested menus
43     this.mouseHandled = false;
44     this.element
45     .uniqueId()
46     .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
47     .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
48     .attr({
49     role: this.options.role,
50     tabIndex: 0
51     })
52     // need to catch all clicks on disabled menu
53     // not possible through _on
54     .bind( "click" + this.eventNamespace, $.proxy(function( event ) {
55     if ( this.options.disabled ) {
56     event.preventDefault();
57     }
58     }, this ));
59    
60     if ( this.options.disabled ) {
61     this.element
62     .addClass( "ui-state-disabled" )
63     .attr( "aria-disabled", "true" );
64     }
65    
66     this._on({
67     // Prevent focus from sticking to links inside menu after clicking
68     // them (focus should always stay on UL during navigation).
69     "mousedown .ui-menu-item > a": function( event ) {
70     event.preventDefault();
71     },
72     "click .ui-state-disabled > a": function( event ) {
73     event.preventDefault();
74     },
75     "click .ui-menu-item:has(a)": function( event ) {
76     var target = $( event.target ).closest( ".ui-menu-item" );
77     if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
78     this.mouseHandled = true;
79    
80     this.select( event );
81     // Open submenu on click
82     if ( target.has( ".ui-menu" ).length ) {
83     this.expand( event );
84     } else if ( !this.element.is( ":focus" ) ) {
85     // Redirect focus to the menu
86     this.element.trigger( "focus", [ true ] );
87    
88     // If the active item is on the top level, let it stay active.
89     // Otherwise, blur the active item since it is no longer visible.
90     if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
91     clearTimeout( this.timer );
92     }
93     }
94     }
95     },
96     "mouseenter .ui-menu-item": function( event ) {
97     var target = $( event.currentTarget );
98     // Remove ui-state-active class from siblings of the newly focused menu item
99     // to avoid a jump caused by adjacent elements both having a class with a border
100     target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
101     this.focus( event, target );
102     },
103     mouseleave: "collapseAll",
104     "mouseleave .ui-menu": "collapseAll",
105     focus: function( event, keepActiveItem ) {
106     // If there's already an active item, keep it active
107     // If not, activate the first item
108     var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
109    
110     if ( !keepActiveItem ) {
111     this.focus( event, item );
112     }
113     },
114     blur: function( event ) {
115     this._delay(function() {
116     if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
117     this.collapseAll( event );
118     }
119     });
120     },
121     keydown: "_keydown"
122     });
123    
124     this.refresh();
125    
126     // Clicks outside of a menu collapse any open menus
127     this._on( this.document, {
128     click: function( event ) {
129     if ( !$( event.target ).closest( ".ui-menu" ).length ) {
130     this.collapseAll( event );
131     }
132    
133     // Reset the mouseHandled flag
134     this.mouseHandled = false;
135     }
136     });
137     },
138    
139     _destroy: function() {
140     // Destroy (sub)menus
141     this.element
142     .removeAttr( "aria-activedescendant" )
143     .find( ".ui-menu" ).addBack()
144     .removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
145     .removeAttr( "role" )
146     .removeAttr( "tabIndex" )
147     .removeAttr( "aria-labelledby" )
148     .removeAttr( "aria-expanded" )
149     .removeAttr( "aria-hidden" )
150     .removeAttr( "aria-disabled" )
151     .removeUniqueId()
152     .show();
153    
154     // Destroy menu items
155     this.element.find( ".ui-menu-item" )
156     .removeClass( "ui-menu-item" )
157     .removeAttr( "role" )
158     .removeAttr( "aria-disabled" )
159     .children( "a" )
160     .removeUniqueId()
161     .removeClass( "ui-corner-all ui-state-hover" )
162     .removeAttr( "tabIndex" )
163     .removeAttr( "role" )
164     .removeAttr( "aria-haspopup" )
165     .children().each( function() {
166     var elem = $( this );
167     if ( elem.data( "ui-menu-submenu-carat" ) ) {
168     elem.remove();
169     }
170     });
171    
172     // Destroy menu dividers
173     this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
174     },
175    
176     _keydown: function( event ) {
177     /*jshint maxcomplexity:20*/
178     var match, prev, character, skip, regex,
179     preventDefault = true;
180    
181     function escape( value ) {
182     return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
183     }
184    
185     switch ( event.keyCode ) {
186     case $.ui.keyCode.PAGE_UP:
187     this.previousPage( event );
188     break;
189     case $.ui.keyCode.PAGE_DOWN:
190     this.nextPage( event );
191     break;
192     case $.ui.keyCode.HOME:
193     this._move( "first", "first", event );
194     break;
195     case $.ui.keyCode.END:
196     this._move( "last", "last", event );
197     break;
198     case $.ui.keyCode.UP:
199     this.previous( event );
200     break;
201     case $.ui.keyCode.DOWN:
202     this.next( event );
203     break;
204     case $.ui.keyCode.LEFT:
205     this.collapse( event );
206     break;
207     case $.ui.keyCode.RIGHT:
208     if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
209     this.expand( event );
210     }
211     break;
212     case $.ui.keyCode.ENTER:
213     case $.ui.keyCode.SPACE:
214     this._activate( event );
215     break;
216     case $.ui.keyCode.ESCAPE:
217     this.collapse( event );
218     break;
219     default:
220     preventDefault = false;
221     prev = this.previousFilter || "";
222     character = String.fromCharCode( event.keyCode );
223     skip = false;
224    
225     clearTimeout( this.filterTimer );
226    
227     if ( character === prev ) {
228     skip = true;
229     } else {
230     character = prev + character;
231     }
232    
233     regex = new RegExp( "^" + escape( character ), "i" );
234     match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
235     return regex.test( $( this ).children( "a" ).text() );
236     });
237     match = skip && match.index( this.active.next() ) !== -1 ?
238     this.active.nextAll( ".ui-menu-item" ) :
239     match;
240    
241     // If no matches on the current filter, reset to the last character pressed
242     // to move down the menu to the first item that starts with that character
243     if ( !match.length ) {
244     character = String.fromCharCode( event.keyCode );
245     regex = new RegExp( "^" + escape( character ), "i" );
246     match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
247     return regex.test( $( this ).children( "a" ).text() );
248     });
249     }
250    
251     if ( match.length ) {
252     this.focus( event, match );
253     if ( match.length > 1 ) {
254     this.previousFilter = character;
255     this.filterTimer = this._delay(function() {
256     delete this.previousFilter;
257     }, 1000 );
258     } else {
259     delete this.previousFilter;
260     }
261     } else {
262     delete this.previousFilter;
263     }
264     }
265    
266     if ( preventDefault ) {
267     event.preventDefault();
268     }
269     },
270    
271     _activate: function( event ) {
272     if ( !this.active.is( ".ui-state-disabled" ) ) {
273     if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
274     this.expand( event );
275     } else {
276     this.select( event );
277     }
278     }
279     },
280    
281     refresh: function() {
282     var menus,
283     icon = this.options.icons.submenu,
284     submenus = this.element.find( this.options.menus );
285    
286     // Initialize nested menus
287     submenus.filter( ":not(.ui-menu)" )
288     .addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
289     .hide()
290     .attr({
291     role: this.options.role,
292     "aria-hidden": "true",
293     "aria-expanded": "false"
294     })
295     .each(function() {
296     var menu = $( this ),
297     item = menu.prev( "a" ),
298     submenuCarat = $( "<span>" )
299     .addClass( "ui-menu-icon ui-icon " + icon )
300     .data( "ui-menu-submenu-carat", true );
301    
302     item
303     .attr( "aria-haspopup", "true" )
304     .prepend( submenuCarat );
305     menu.attr( "aria-labelledby", item.attr( "id" ) );
306     });
307    
308     menus = submenus.add( this.element );
309    
310     // Don't refresh list items that are already adapted
311     menus.children( ":not(.ui-menu-item):has(a)" )
312     .addClass( "ui-menu-item" )
313     .attr( "role", "presentation" )
314     .children( "a" )
315     .uniqueId()
316     .addClass( "ui-corner-all" )
317     .attr({
318     tabIndex: -1,
319     role: this._itemRole()
320     });
321    
322     // Initialize unlinked menu-items containing spaces and/or dashes only as dividers
323     menus.children( ":not(.ui-menu-item)" ).each(function() {
324     var item = $( this );
325     // hyphen, em dash, en dash
326     if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) {
327     item.addClass( "ui-widget-content ui-menu-divider" );
328     }
329     });
330    
331     // Add aria-disabled attribute to any disabled menu item
332     menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
333    
334     // If the active item has been removed, blur the menu
335     if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
336     this.blur();
337     }
338     },
339    
340     _itemRole: function() {
341     return {
342     menu: "menuitem",
343     listbox: "option"
344     }[ this.options.role ];
345     },
346    
347     _setOption: function( key, value ) {
348     if ( key === "icons" ) {
349     this.element.find( ".ui-menu-icon" )
350     .removeClass( this.options.icons.submenu )
351     .addClass( value.submenu );
352     }
353     this._super( key, value );
354     },
355    
356     focus: function( event, item ) {
357     var nested, focused;
358     this.blur( event, event && event.type === "focus" );
359    
360     this._scrollIntoView( item );
361    
362     this.active = item.first();
363     focused = this.active.children( "a" ).addClass( "ui-state-focus" );
364     // Only update aria-activedescendant if there's a role
365     // otherwise we assume focus is managed elsewhere
366     if ( this.options.role ) {
367     this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
368     }
369    
370     // Highlight active parent menu item, if any
371     this.active
372     .parent()
373     .closest( ".ui-menu-item" )
374     .children( "a:first" )
375     .addClass( "ui-state-active" );
376    
377     if ( event && event.type === "keydown" ) {
378     this._close();
379     } else {
380     this.timer = this._delay(function() {
381     this._close();
382     }, this.delay );
383     }
384    
385     nested = item.children( ".ui-menu" );
386     if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
387     this._startOpening(nested);
388     }
389     this.activeMenu = item.parent();
390    
391     this._trigger( "focus", event, { item: item } );
392     },
393    
394     _scrollIntoView: function( item ) {
395     var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
396     if ( this._hasScroll() ) {
397     borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
398     paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
399     offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
400     scroll = this.activeMenu.scrollTop();
401     elementHeight = this.activeMenu.height();
402     itemHeight = item.height();
403    
404     if ( offset < 0 ) {
405     this.activeMenu.scrollTop( scroll + offset );
406     } else if ( offset + itemHeight > elementHeight ) {
407     this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
408     }
409     }
410     },
411    
412     blur: function( event, fromFocus ) {
413     if ( !fromFocus ) {
414     clearTimeout( this.timer );
415     }
416    
417     if ( !this.active ) {
418     return;
419     }
420    
421     this.active.children( "a" ).removeClass( "ui-state-focus" );
422     this.active = null;
423    
424     this._trigger( "blur", event, { item: this.active } );
425     },
426    
427     _startOpening: function( submenu ) {
428     clearTimeout( this.timer );
429    
430     // Don't open if already open fixes a Firefox bug that caused a .5 pixel
431     // shift in the submenu position when mousing over the carat icon
432     if ( submenu.attr( "aria-hidden" ) !== "true" ) {
433     return;
434     }
435    
436     this.timer = this._delay(function() {
437     this._close();
438     this._open( submenu );
439     }, this.delay );
440     },
441    
442     _open: function( submenu ) {
443     var position = $.extend({
444     of: this.active
445     }, this.options.position );
446    
447     clearTimeout( this.timer );
448     this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
449     .hide()
450     .attr( "aria-hidden", "true" );
451    
452     submenu
453     .show()
454     .removeAttr( "aria-hidden" )
455     .attr( "aria-expanded", "true" )
456     .position( position );
457     },
458    
459     collapseAll: function( event, all ) {
460     clearTimeout( this.timer );
461     this.timer = this._delay(function() {
462     // If we were passed an event, look for the submenu that contains the event
463     var currentMenu = all ? this.element :
464     $( event && event.target ).closest( this.element.find( ".ui-menu" ) );
465    
466     // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
467     if ( !currentMenu.length ) {
468     currentMenu = this.element;
469     }
470    
471     this._close( currentMenu );
472    
473     this.blur( event );
474     this.activeMenu = currentMenu;
475     }, this.delay );
476     },
477    
478     // With no arguments, closes the currently active menu - if nothing is active
479     // it closes all menus. If passed an argument, it will search for menus BELOW
480     _close: function( startMenu ) {
481     if ( !startMenu ) {
482     startMenu = this.active ? this.active.parent() : this.element;
483     }
484    
485     startMenu
486     .find( ".ui-menu" )
487     .hide()
488     .attr( "aria-hidden", "true" )
489     .attr( "aria-expanded", "false" )
490     .end()
491     .find( "a.ui-state-active" )
492     .removeClass( "ui-state-active" );
493     },
494    
495     collapse: function( event ) {
496     var newItem = this.active &&
497     this.active.parent().closest( ".ui-menu-item", this.element );
498     if ( newItem && newItem.length ) {
499     this._close();
500     this.focus( event, newItem );
501     }
502     },
503    
504     expand: function( event ) {
505     var newItem = this.active &&
506     this.active
507     .children( ".ui-menu " )
508     .children( ".ui-menu-item" )
509     .first();
510    
511     if ( newItem && newItem.length ) {
512     this._open( newItem.parent() );
513    
514     // Delay so Firefox will not hide activedescendant change in expanding submenu from AT
515     this._delay(function() {
516     this.focus( event, newItem );
517     });
518     }
519     },
520    
521     next: function( event ) {
522     this._move( "next", "first", event );
523     },
524    
525     previous: function( event ) {
526     this._move( "prev", "last", event );
527     },
528    
529     isFirstItem: function() {
530     return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
531     },
532    
533     isLastItem: function() {
534     return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
535     },
536    
537     _move: function( direction, filter, event ) {
538     var next;
539     if ( this.active ) {
540     if ( direction === "first" || direction === "last" ) {
541     next = this.active
542     [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
543     .eq( -1 );
544     } else {
545     next = this.active
546     [ direction + "All" ]( ".ui-menu-item" )
547     .eq( 0 );
548     }
549     }
550     if ( !next || !next.length || !this.active ) {
551     next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
552     }
553    
554     this.focus( event, next );
555     },
556    
557     nextPage: function( event ) {
558     var item, base, height;
559    
560     if ( !this.active ) {
561     this.next( event );
562     return;
563     }
564     if ( this.isLastItem() ) {
565     return;
566     }
567     if ( this._hasScroll() ) {
568     base = this.active.offset().top;
569     height = this.element.height();
570     this.active.nextAll( ".ui-menu-item" ).each(function() {
571     item = $( this );
572     return item.offset().top - base - height < 0;
573     });
574    
575     this.focus( event, item );
576     } else {
577     this.focus( event, this.activeMenu.children( ".ui-menu-item" )
578     [ !this.active ? "first" : "last" ]() );
579     }
580     },
581    
582     previousPage: function( event ) {
583     var item, base, height;
584     if ( !this.active ) {
585     this.next( event );
586     return;
587     }
588     if ( this.isFirstItem() ) {
589     return;
590     }
591     if ( this._hasScroll() ) {
592     base = this.active.offset().top;
593     height = this.element.height();
594     this.active.prevAll( ".ui-menu-item" ).each(function() {
595     item = $( this );
596     return item.offset().top - base + height > 0;
597     });
598    
599     this.focus( event, item );
600     } else {
601     this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
602     }
603     },
604    
605     _hasScroll: function() {
606     return this.element.outerHeight() < this.element.prop( "scrollHeight" );
607     },
608    
609     select: function( event ) {
610     // TODO: It should never be possible to not have an active item at this
611     // point, but the tests don't trigger mouseenter before click.
612     this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
613     var ui = { item: this.active };
614     if ( !this.active.has( ".ui-menu" ).length ) {
615     this.collapseAll( event, true );
616     }
617     this._trigger( "select", event, ui );
618     }
619     });
620    
621     }( jQuery ));

  ViewVC Help
Powered by ViewVC 1.1.20