1/* SWIM2.0 :: Simple website menu
2****************************************************************
3* DOM scripting by brothercake -- http://www.brothercake.com/
4* Licensed under GPL -- http://www.gnu.org/copyleft/gpl.html
5****************************************************************
6* For professional menu solutions visit -- http://www.udm4.com/
7****************************************************************
8*/
9
10
11
12//initialise the menu(s)
13//you can replace this with an encapsulated onload if necessary
14//http://www.brothercake.com/site/resources/onload/
15//you can also take it out of this script and put it somewhere else
16window.onload = function()
17{
18	//create a new menu ('menu-id', 'orientation')
19	var verticals = new simpleMenu('menu-v', 'vertical');
20	var horizontals = new simpleMenu('menu-h', 'horizontal');
21};
22
23
24
25
26
27
28//menu object constructor
29function simpleMenu(navid, orient)
30{
31	//if the dom is not supported, or this is opera 5 or 6, don't continue
32	if(typeof document.getElementById == 'undefined' || /opera[\/ ][56]/i.test(navigator.userAgent)) { return; }
33
34	//identify konqueror
35	this.iskde = navigator.vendor == 'KDE';
36
37	//identify internet explorer [but both opera and konqueror recognise the .all collection]
38	this.isie = typeof document.all != 'undefined' && typeof window.opera == 'undefined' && !this.iskde;
39
40	//identify old safari [< 1.2]
41	this.isoldsaf = navigator.vendor == 'Apple Computer, Inc.' && typeof XMLHttpRequest == 'undefined';
42
43	//ul tree
44	this.tree = document.getElementById(navid);
45
46	//if it exists
47	if(this.tree != null)
48	{
49		//get trigger elements
50		this.items = this.tree.getElementsByTagName('li');
51		this.itemsLen = this.items.length;
52
53		//initialise each trigger, using do .. while because it's faster
54		var i = 0;
55		do
56		{
57			this.init(this.items[i], this.isie, this.isoldsaf, this.iskde, navid, orient);
58		}
59		while (++i < this.itemsLen);
60	}
61}
62
63
64//trigger initialiser
65simpleMenu.prototype.init = function(trigger, isie, isoldsaf, iskde, navid, ishoriz)
66{
67	//store menu object, or null if there isn't one
68	//extend it as a property of the trigger argument
69	//so it's global within [and unique to] the scope of this instantiation
70	//which is the same trick as going "var self = this"
71	trigger.menu = trigger.getElementsByTagName('ul').length > 0 ? trigger.getElementsByTagName('ul')[0] : null;
72
73	//store link object
74	trigger.link = trigger.getElementsByTagName('a')[0];
75
76	//store whther this is a submenu or child menu
77	//a submenu's parent node will have the navbar id
78	trigger.issub = trigger.parentNode.id == navid;
79
80	//whether this is a horizontal navbar
81	trigger.ishoriz = ishoriz == 'horizontal';
82
83	//menu opening events
84	//onfocus doesn't bubble in ie, but its proprietary 'onactivate' event does
85	//which works in win/ie5.5+
86	this.openers = { 'm' : 'onmouseover', 'k' : (isie ? 'onactivate' : 'onfocus') };
87
88	//bind menu openers
89	for(var i in this.openers)
90	{
91		trigger[this.openers[i]] = function(e)
92		{
93			//set rollover persistence classname -- we have to check for an existing value first
94			//because some opera builds don't allow the class attribute to have a leading space
95			//don't do persistent rollovers for konqueror, because they stick in kde <= 3.0.4
96			if(!iskde) { trigger.link.className += (trigger.link.className == '' ? '' : ' ') + 'rollover'; }
97
98			//if trigger has a menu
99			if(trigger.menu != null)
100			{
101				//show the menu by positioning it back on the screen
102				//we can use the same positions as in pure CSS for most browsers ['css']
103				//but we have to compute the positions for ie ['compute']
104				//because it uses position:relative on <a>
105				//whereas the others have position:relative on <li>
106				//we also need to use those values for old safari builds ['compute']
107				//because the regular positioning doesn't work
108
109				//if this is a horizontal navbar
110				//set the left position to auto [css] or compute a position [compute]
111				if(trigger.ishoriz) { trigger.menu.style.left = (isie || isoldsaf) ? trigger.offsetLeft + 'px' : 'auto'; }
112
113				//if this is a horizontal navbar and a first-level submenu
114				//set the top position to auto [css] or compute a position [compute]
115				//otherwise set it to 0 [css] or compute a position [compute]
116				trigger.menu.style.top = (trigger.ishoriz && trigger.issub) ? (isie || (trigger.ishoriz && isoldsaf)) ? trigger.link.offsetHeight + 'px' : 'auto' : (isie || (trigger.ishoriz && isoldsaf)) ? trigger.offsetTop + 'px' : '0';
117			}
118		};
119	}
120
121
122	//menu closing events
123	//'ondeactivate' is the equivalent blur handler for 'onactivate'
124	this.closers = { 'm' : 'onmouseout', 'k' : (isie ? 'ondeactivate' : 'onblur') };
125
126	//bind menu closers
127	for(i in this.closers)
128	{
129		trigger[this.closers[i]] = function(e)
130		{
131			//store event-related-target property
132			this.related = (!e) ? window.event.toElement : e.relatedTarget;
133
134			//if event came from outside current trigger branch
135			if(!this.contains(this.related))
136			{
137				//reset rollover persistence classname; not for konqueror
138				if(!iskde) { trigger.link.className = trigger.link.className.replace(/[ ]?rollover/g, ''); }
139
140				//if trigger has a menu
141				if(trigger.menu != null)
142				{
143					//hide menu using left for a horizontal menu, or top for a vertical
144					trigger.menu.style[(trigger.ishoriz ? 'left' : 'top')] = trigger.ishoriz ? '-10000px' : '-100em';
145				}
146			}
147		};
148	}
149
150
151	//contains method by jkd -- http://www.jasonkarldavis.com/
152	//not necessary for ie because we're re-creating in ie-proprietary method
153	//and it would throw errors in mac/ie5 anyway
154	//not actually necessary for opera 7 either, because it's already implemented
155	//but it won't do any harm, so spoofing doesn't matter
156	if(!isie)
157	{
158		trigger.contains = function(node)
159		{
160			if (node == null) { return false; }
161			if (node == this) { return true; }
162			else { return this.contains(node.parentNode); }
163		};
164	}
165}
166
167