Skip to content
This repository was archived by the owner on Jan 31, 2022. It is now read-only.

Commit b42d228

Browse files
committed
Initial commit
0 parents  commit b42d228

File tree

2 files changed

+325
-0
lines changed

2 files changed

+325
-0
lines changed

README.md

Whitespace-only changes.

src/jquery.ui.navigation.js

Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/**
2+
* Menu Widget
3+
*
4+
* @author Michael van Engelshoven <mve@brainbits.net>
5+
* @copyright 2011 Brainbits GmbH
6+
* @version $id$
7+
*/
8+
(function ($, undefined) {
9+
10+
$.widget('phlex.navigation', {
11+
12+
/**
13+
* Widget options
14+
*/
15+
options: {
16+
position: {
17+
my: 'left top',
18+
at: 'left bottom'
19+
},
20+
timeout: 350,
21+
sensity: 10,
22+
itemSelector: 'li',
23+
submenuSelector: 'ul'
24+
},
25+
26+
/**
27+
* Indicates that the menu is open
28+
*/
29+
_isOpen: false,
30+
31+
/**
32+
* jQuery object wich contains all navigation items
33+
*/
34+
_items: [],
35+
36+
/**
37+
* ID returned by setTimeoput for the close timer
38+
*/
39+
_closeTimer: null,
40+
41+
/**
42+
* ID returned by setInterval for the mouse tracking
43+
*/
44+
_trackTimer: null,
45+
46+
/**
47+
* Inititializes the navigation widget
48+
*/
49+
_create: function () {
50+
51+
var self = this,
52+
options = this.options
53+
menu = this.element;
54+
items = this._items = menu.find(options.itemSelector);
55+
56+
self._setCurrent(items.first());
57+
58+
menu.bind('mouseenter.' + self.widgetEventPrefix, $.proxy(self._handleHover, self))
59+
.bind('mouseleave.' + self.widgetEventPrefix, $.proxy(self._handleLeave, self))
60+
.bind('keydown.' + self.widgetEventPrefix, function (event){
61+
switch (event.keyCode) {
62+
case $.ui.keyCode.UP:
63+
self._handleToPrevious(event);
64+
event.preventDefault();
65+
break;
66+
case $.ui.keyCode.DOWN:
67+
self._handleToNext(event);
68+
event.preventDefault();
69+
break;
70+
case $.ui.keyCode.LEFT:
71+
self._handleToParent(event);
72+
event.preventDefault();
73+
break;
74+
case $.ui.keyCode.RIGHT:
75+
self._handleToSubmenu(event);
76+
event.preventDefault();
77+
break;
78+
};
79+
});
80+
81+
items.each(function(){
82+
83+
var item = $(this),
84+
button = item.children('a').first(),
85+
submenu = item.children(options.submenuSelector);
86+
87+
button.button();
88+
89+
button.attr('role', 'menuitem');
90+
91+
button.bind('mouseenter.' + self.widgetEventPrefix + ' focus.' + self.widgetEventPrefix, function(event) {
92+
self._setCurrent(item);
93+
if (event.type === 'focus') {
94+
self.open();
95+
}
96+
});
97+
98+
// This is a workaroud for touch devices. The button can only be clicked, if the submenu is visible
99+
button.bind('click.' + self.widgetEventPrefix, function() {
100+
if (submenu.length && !submenu.is(':visible')) {
101+
return false;
102+
}
103+
});
104+
105+
if (submenu.length) {
106+
button.button('option', 'icons', {secondary: "icon-arrow-down"})
107+
.attr('aria-haspopup', 'true');
108+
}
109+
110+
});
111+
},
112+
113+
/**
114+
* Create option object for specified depth
115+
*
116+
* Some options can be configured as array, with differen values for different nestings. This method
117+
* resolved the actual option values for the given depth. If given depth is not configured in array,
118+
* the most recent one will be used.
119+
*
120+
* @param {integer} depth Nesting depth of item
121+
* @return {object} Option object
122+
*/
123+
_getOptionsForDepth: function (depth) {
124+
125+
var depthOptions = $.extend({}, this.options),
126+
index;
127+
128+
if ($.isArray(depthOptions.position)) {
129+
index = Math.min(depth, depthOptions.position.length) - 1;
130+
depthOptions.position = depthOptions.position[index];
131+
}
132+
133+
return depthOptions;
134+
},
135+
136+
_setCurrent: function(elem) {
137+
138+
link = elem.children('a').removeAttr('tabindex');
139+
140+
this._items.children('a')
141+
.not(link)
142+
.attr('tabindex', '-1');
143+
144+
this._current = elem;
145+
this._refresh();
146+
},
147+
148+
/**
149+
* Refreshes the display status of sub menues
150+
*/
151+
_refresh: function () {
152+
153+
var options = this.options,
154+
items = this._items,
155+
menu = this.element,
156+
current = this._current,
157+
link = current.children('a').first(),
158+
depth = current.parents(options.submenuSelector).not(menu.parents()).length
159+
currentPath = current.parents(options.itemSelector).andSelf();
160+
161+
options = this._getOptionsForDepth(depth);
162+
163+
this._trigger('refresh');
164+
165+
if (this._isOpen) {
166+
current.children(options.submenuSelector)
167+
.show()
168+
.position($.extend({
169+
of: link,
170+
collision: 'none'
171+
}, options.position));
172+
173+
items.not(currentPath)
174+
.children(options.submenuSelector)
175+
.hide();
176+
} else {
177+
items.children(options.submenuSelector).hide();
178+
}
179+
},
180+
181+
/**
182+
* Handles action when mouse enters the menu
183+
*
184+
* @param {eventObject} event jQuery event object
185+
*/
186+
_handleHover: function (event) {
187+
188+
clearTimeout(this._closeTimer);
189+
190+
if (!this._isOpen) {
191+
this._trackMousemove(event);
192+
}
193+
},
194+
195+
/**
196+
* Handles action when mouse leave the menu
197+
*
198+
* @param {eventObject} event jQuery event object
199+
*/
200+
_handleLeave: function (event) {
201+
202+
var self = this;
203+
204+
// Reset mouse tracking for opening
205+
self.element.unbind('mousemove.' + self.widgetEventPefix);
206+
clearInterval(self._trackTimer);
207+
208+
if (self._isOpen) {
209+
self._closeTimer = setTimeout(function () {
210+
self.close();
211+
}, self.options.timeout);
212+
}
213+
},
214+
215+
/**
216+
* Handles keydown for the up key
217+
*/
218+
_handleToPrevious: function (event) {
219+
this._current
220+
.prev()
221+
.children('a')
222+
.focus();
223+
},
224+
225+
/**
226+
* Handles keydown for the down key
227+
*/
228+
_handleToNext: function (event) {
229+
this._current
230+
.next()
231+
.children('a')
232+
.focus();
233+
},
234+
235+
/**
236+
* Handles keydown for the left key
237+
*/
238+
_handleToParent: function (event) {
239+
this._current
240+
.parent(this.options.submenuSelector)
241+
.prev('a')
242+
.focus();
243+
},
244+
245+
/**
246+
* Handles keydown for the right key
247+
*/
248+
_handleToSubmenu: function (event) {
249+
this.open();
250+
this._current
251+
.children(this.options.submenuSelector)
252+
.children(this.options.itemSelector)
253+
.first()
254+
.children('a')
255+
.focus();
256+
},
257+
258+
/**
259+
* Checks if user intend to open the menu.
260+
*
261+
* @param {eventObject} event jQuery event object
262+
*/
263+
_trackMousemove: function (event) {
264+
265+
var self = this,
266+
menu = self.element,
267+
start = {x: event.clientX, y: event.clientY},
268+
current = {x: event.clientX, y: event.clientY};
269+
270+
menu.bind('mousemove.' + self.widgetEventPefix, function (event) {
271+
current.x = event.clientX;
272+
current.y = event.clientY;
273+
});
274+
275+
self._trackTimer = setInterval(function () {
276+
277+
var distance = Math.sqrt(Math.pow(start.x - current.x, 2) + Math.pow(start.y - current.y, 2)),
278+
mouseTooSlow = self.options.sensity > distance;
279+
280+
if (!mouseTooSlow) {
281+
start.x = current.x;
282+
start.y = current.y;
283+
} else {
284+
// Stop tracking
285+
clearInterval(self._trackTimer);
286+
menu.unbind('mousemove.' + self.widgetEventPefix);
287+
// Open menu
288+
self.open();
289+
}
290+
291+
}, 100);
292+
},
293+
294+
/**
295+
* Opens the menu
296+
*/
297+
open: function () {
298+
299+
if (this._trigger('beforeopen') === false){
300+
return;
301+
}
302+
303+
this._trigger('open');
304+
this._isOpen = true;
305+
this._refresh();
306+
},
307+
308+
/**
309+
* Closes the menu
310+
*/
311+
close: function () {
312+
313+
if (this._trigger('beforeclose') === false){
314+
return;
315+
}
316+
317+
this._setCurrent(this._items.first());
318+
this._trigger('close');
319+
this._isOpen = false;
320+
this._refresh();
321+
}
322+
323+
});
324+
325+
}(jQuery));

0 commit comments

Comments
 (0)