Merge pull request #2832 from razzfazz/handle_eintr
[mono.git] / mcs / class / System.Web / System.Web.UI.WebControls / MenuModern.js
1 /*
2  * Authors:
3  *      Marek Habersack <grendel@twistedcode.net>
4  *
5  * (C) 2010 Novell, Inc (http: *novell.com)
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files (the
9  * "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be
16  * included in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  *
26  * This code serves only the List rendering mode of the Menu control in Mono
27  *
28  */
29 if (!window.Sys) { window.Sys = {}; }
30 if (!Sys.WebForms) { Sys.WebForms = {}; }
31
32 Sys.WebForms.Menu = function (options)
33 {
34         if (options == null)
35                 throw "Sys.WebForms.Menu constructor requires options to be not null.";
36
37         if (options.element == null)
38                 throw "options.element is required.";
39
40         if (options.orientation == null)
41                 throw "options.orientation is required.";
42
43         if (typeof (options.element) == "string")
44                 this.menuID = options.element;
45         else
46                 this.mainElement = options.element;
47
48         this.disappearAfter = options.disappearAfter || 500;
49         this.orientation = options.orientation;
50         this.tabIndex = options.tabIndex || 0;
51         this.disabled = options.disabled || false;
52         this.level = options.level || 0;
53         this.menuItemIndex = 0;
54
55         if (this.level != 0) {
56                 if (options.parentMenu == null)
57                         throw "options.parentMenu is required for all submenus.";
58
59                 this.parentMenu = options.parentMenu;
60         }
61
62         if (this.mainElement == null) {
63                 this.mainElement = document.getElementById (this.menuID);
64                 if (this.mainElement == null)
65                         throw "Unable to find menu element with id '" + this.menuID + "'.";
66
67                 if (this.mainElement.tagName != "DIV")
68                         throw "This script must be used only when the menu containing element is DIV.";
69         }
70
71         /* Due to the way we generate the menu in the list mode, every submenu other than the root one is dynamic */
72         if (this.level > 1) {
73                 this.menuType = "dynamic";
74                 if (options.parentMenu == null)
75                         throw "options.parent is required for all submenus.";
76
77                 var subMenuId = Sys.WebForms.Menu.Helpers.getNextSubMenuId ();
78                 this.parentMenu = options.parentMenu;
79                 this.orientation = this.parentMenu.orientation;
80                 this.path = this.parentMenu.path + subMenuId;
81                 this.menuID = this.parentMenu.menuID;
82                 if (this.mainElement.id == null || this.mainElement.id == "")
83                         this.mainElement.id = this.menuID + ":submenu:" + subMenuId;
84         } else {
85                 this.menuType = "static";
86                 if (this.level == 1) {
87                         this.menuID = this.parentMenu.menuID;
88                         this.orientation = this.parentMenu.orientation;
89                 }
90                 this.parentMenu = null;
91                 this.path = "0";
92                 this.mainElement.setAttribute ("tabindex", this.tabIndex);
93                 this.mainElement.setAttribute ("role", this.orientation == "vertical" ? "menu" : "menubar");
94                 with (this.mainElement.style) {
95                         width = "auto";
96                         position = "relative";
97                 }
98         }
99
100         if (this.level > 0) {
101                 Sys.WebForms.Menu.Helpers.appendCssClass (this.mainElement, this.menuType);
102         }
103
104         if (this.level <= 1)
105                 Sys.WebForms.Menu.Helpers.setFloat (this.mainElement, "left");
106
107         this.loadItems ();
108 }
109
110 Sys.WebForms.Menu.Helpers = {
111         __subMenuCounter: 0,
112         __menuItems: [],
113         __popupToClose: null,
114
115         setPopupToClose: function (element) {
116                 this.__popupToClose = element;
117         },
118
119         getPopupToClose: function () {
120                 return this.__popupToClose;
121         },
122
123         setFloat: function (element, side) {
124                 /* For standards-compliant browsers */
125                 element.style.cssFloat = "left";
126
127                 /* For IE */
128                 element.style.styleFloat = "left";
129         },
130
131         appendCssClass: function (element, className) {
132                 if (element == null || className == null)
133                         return;
134
135                 var cname = element.className;
136                 if (cname == null || cname == "")
137                         cname = className;
138                 else
139                         cname += " " + className;
140
141                 element.className = cname;
142         },
143
144         highlight: function (element) {
145                 if (element == null)
146                         return;
147                 if (element.classList.contains ("highlighted"))
148                         return;
149                 element.classList.add ("highlighted");
150         },
151
152         unhighlight: function (element) {
153                 if (element == null)
154                         return;
155                 if (!element.classList.contains ("highlighted"))
156                         return;
157                 element.classList.remove ("highlighted");
158         },
159
160         getNextSubMenuId: function () {
161                 return ++this.__subMenuCounter;
162         },
163
164         addMenuItem: function (item) {
165                 if (item == null)
166                         return;
167
168                 if (!(item instanceof Sys.WebForms.MenuItem))
169                         throw "item must be an instance of Sys.WebForms.MenuItem";
170
171                 if (this.__menuItems [item.path] != null)
172                         throw "item already exists (path " + item.path + ")";
173
174                 this.__menuItems [item.path] = item;
175         },
176
177         getMenuItem: function (element) {
178                 if (element == null)
179                         return null;
180
181                 var itemPath = element ["__MonoMenuItemPath"];
182                 if (itemPath == null)
183                         return null;
184
185                 return this.__menuItems [itemPath];
186         },
187
188         addEventHandler: function (element, eventType, handler, capture) {
189                 /* There's also element.attachEvent, but it changes handler semantics on IE, so we don't
190                  * even take it into consideration.
191                  */
192                 if (element.addEventListener)
193                         element.addEventListener(eventType, handler, !!capture);
194                 else
195                         element ["on" + eventType] = handler;
196         }
197 };
198
199 Sys.WebForms.Menu.prototype.loadItems = function ()
200 {
201         var children = this.mainElement.childNodes;
202         var count = children.length;
203         var child;
204
205         for (var i = 0; i < count; i++) {
206                 child = children [i];
207                 if (child.nodeType != 1)
208                         continue;
209
210                 if (child.tagName == "UL") {
211                         var submenu = new Sys.WebForms.Menu ({ element: child, disappearAfter: this.disappearAfter, orientation: this.orientation,
212                                                                disabled: this.disabled, level: this.level + 1, tabIndex: this.tabIndex, parentMenu: this});
213                 } else if (child.tagName == "LI") {
214                         var menuItem = new Sys.WebForms.MenuItem ({ element: child, menuType: this.menuType, disappearAfter: this.disappearAfter, orientation: this.orientation,
215                                                                     disabled: this.disabled, level: this.level + 1, tabIndex: this.tabIndex, parentMenu: this});
216                 }
217         }
218 }
219
220 Sys.WebForms.Menu.prototype.getNextMenuItemId = function ()
221 {
222         return ++this.menuItemIndex;
223 }
224
225 Sys.WebForms.MenuItem = function (options)
226 {
227         if (options == null)
228                 throw "Sys.WebForms.MenuItem constructor requires options to be not null.";
229
230         if (options.element == null)
231                 throw "options.element must be set.";
232
233         if (options.menuType == null)
234                 throw "options.menuType must be set.";
235
236         if (options.parentMenu == null)
237                 throw "options.parentMenu is required.";
238
239         this.element = options.element;
240         this.menuType = options.menuType;
241         this.parentMenu = options.parentMenu;
242         this.path = this.parentMenu.path + this.parentMenu.getNextMenuItemId ();
243
244         var children = this.element.childNodes;
245         var child;
246         var subMenu = null;
247
248         for (var i = 0; i < children.length; i++) {
249                 child = children [i];
250                 if (child.nodeType != 1)
251                         continue;
252
253                 switch (child.tagName) {
254                         case "A":
255                                 Sys.WebForms.Menu.Helpers.appendCssClass (child, this.menuType);
256                                 Sys.WebForms.Menu.Helpers.addEventHandler (child, "mouseover", this.mouseOverHandler);
257                                 Sys.WebForms.Menu.Helpers.addEventHandler (child, "mouseout", this.mouseOutHandler);
258                                 child.setAttribute ("tabindex", "-1");
259                                 break;
260
261                         case "UL":
262                                 this.subMenu = new Sys.WebForms.Menu ({ element: child, disappearAfter: options.disappearAfter, orientation: options.orientation,
263                                                                         disabled: options.disabled, level: options.level, tabIndex: options.tabIndex, parentMenu: options.parentMenu});
264                                 if (this.subMenu.menuType == "dynamic") {
265                                         var topValue;
266                                         var leftValue;
267
268                                         if (this.subMenu.orientation == "horizontal" && this.subMenu.parentMenu != null && this.subMenu.parentMenu.menuType == "static") {
269                                                 topValue = "100%";
270                                                 leftValue = "0px";
271                                         } else {
272                                                 topValue = "0px";
273                                                 leftValue = "100%";
274                                         }
275
276                                         with (this.subMenu.mainElement.style) {
277                                                 display = "none";
278                                                 position = "absolute";
279                                                 top = topValue;
280                                                 left = leftValue;
281                                         }
282                                 }
283
284                                 Sys.WebForms.Menu.Helpers.appendCssClass (this.element, "has-popup");
285                                 this.element.setAttribute ("aria-haspopup", this.subMenu.mainElement.id);
286                                 Sys.WebForms.Menu.Helpers.addEventHandler (this.element, "mouseover", this.mouseOverHandler);
287                                 Sys.WebForms.Menu.Helpers.addEventHandler (this.element, "mouseout", this.mouseOutHandler);
288                                 break;
289                 }
290         }
291
292         Sys.WebForms.Menu.Helpers.appendCssClass (this.element, this.menuType);
293         this.element.style.position = "relative";
294         this.element.setAttribute ("role", "menuitem");
295         this.element ["__MonoMenuItemPath"] = this.path;
296
297         if (this.parentMenu.orientation == "horizontal" && this.parentMenu.menuType == "static")
298                 Sys.WebForms.Menu.Helpers.setFloat (this.element, "left");
299
300         Sys.WebForms.Menu.Helpers.addMenuItem (this);
301 }
302
303 Sys.WebForms.MenuItem.prototype.log = function (msg)
304 {
305         if (console && console.log)
306                 console.log (msg);
307 }
308
309 Sys.WebForms.MenuItem.prototype.hide = function (popup, leaveParentOpen)
310 {
311         if (popup == null || popup.mainElement == null || popup.menuType == "static")
312                 return;
313
314         var current = popup;
315         while (current != null) {
316                 if (current.menuType == "static" || (leaveParentOpen && current == this.parentMenu))
317                         break;
318
319                 if (current.mainElement != null) {
320                         current.mainElement.style.display = "none";
321                         Sys.WebForms.Menu.Helpers.unhighlight (current.mainElement.parentNode.children [0]);    
322                 }
323
324                 if (current.hideTimerId != null) {
325                         window.clearTimeout (current.hideTimerId);
326                         current.hideTimerId = null;
327                 }
328
329                 current = current.parentMenu;
330         }
331 }
332
333 Sys.WebForms.MenuItem.prototype.isPopupChildOf = function (popup, element)
334 {
335         if (popup == null || element == null)
336                 return false;
337
338         var cur = popup.parentNode;
339         while (cur != null) {
340                 if (cur == element)
341                         return true;
342                 cur = cur.parentNode;
343         }
344
345         return false;
346 }
347
348 Sys.WebForms.MenuItem.prototype.onMouseOver = function (popupId)
349 {
350         var popup;
351         var noPopup = popupId == null || popupId == "";
352         if (noPopup)
353                 popup = null;
354         else
355                 popup = document.getElementById (popupId);
356
357         var cur = Sys.WebForms.Menu.Helpers.getPopupToClose ();
358         if (cur != null) {
359                 if (cur.hideTimerId != null) {
360                         window.clearTimeout (cur.hideTimerId);
361                         cur.hideTimerId = null;
362                 }
363
364                 if (popup == null || (popup != cur.mainElement && !this.isPopupChildOf (popup, cur.mainElement)))
365                         this.hide (cur, true);
366                 Sys.WebForms.Menu.Helpers.setPopupToClose (null);
367         }
368
369         if (noPopup)
370                 return;
371
372         if (popup == null)
373                 throw "Popup with id '" + popupId + "' could not be found.";
374
375         if (cur != null && popup != cur.mainElement)
376                 this.hide (cur, true);
377         if (popup.style.display != "block")
378                 popup.style.display = "block";
379         Sys.WebForms.Menu.Helpers.highlight (popup.parentNode.children [0]);
380 }
381
382 Sys.WebForms.MenuItem.prototype.onMouseOut = function (popupId)
383 {
384         if (popupId == null || popupId == "")
385                 return;
386
387         var popup = document.getElementById (popupId);
388         if (popup == null)
389                 throw "Popup with id '" + popupId + "' could not be found.";
390
391         var cur = this.subMenu;
392         if (cur != null) {
393                 var myself = this;
394                 cur.hideTimerId = window.setTimeout (function () {
395                                                              myself.hide (cur, false);
396                                                      },
397                                                      this.subMenu.disappearAfter);
398
399         }
400         Sys.WebForms.Menu.Helpers.setPopupToClose (cur);
401 }
402
403 Sys.WebForms.MenuItem.prototype.mouseOverHandler = function (e)
404 {
405
406         if (this.nodeName == "A") {
407                 if (this.parentNode.getAttribute ("role") != "menuitem" || this.parentNode.getAttribute ("aria-haspopup") != null)
408                         return;
409                 Sys.WebForms.Menu.Helpers.highlight (this);
410                 return;
411         }
412
413         var menuItem = Sys.WebForms.Menu.Helpers.getMenuItem (this);
414         if (menuItem == null || !(menuItem instanceof Sys.WebForms.MenuItem)) {
415                 e.returnResult = false;
416                 e.cancelBuble = false;
417                 throw "MenuItem could not be found in mouseover handler.";
418         }
419
420         menuItem.onMouseOver (this.getAttribute ("aria-haspopup"));
421         menuItem.finalizeEvent (e);
422 }
423
424 Sys.WebForms.MenuItem.prototype.mouseOutHandler = function (e)
425 {
426         if (this.nodeName == "A") {
427                 if (this.parentNode.getAttribute ("role") != "menuitem" || this.parentNode.getAttribute ("aria-haspopup") != null)
428                         return;
429                 Sys.WebForms.Menu.Helpers.unhighlight (this);
430                 return;
431         }
432
433         var menuItem = Sys.WebForms.Menu.Helpers.getMenuItem (this);
434
435         if (menuItem == null || !(menuItem instanceof Sys.WebForms.MenuItem)) {
436                 e.returnResult = false;
437                 e.cancelBuble = false;
438                 throw "MenuItem could not be found in mouseout handler.";
439         }
440         menuItem.onMouseOut (this.getAttribute ("aria-haspopup"));
441         menuItem.finalizeEvent (e);
442 }
443
444 Sys.WebForms.MenuItem.prototype.finalizeEvent = function (e)
445 {
446         /* For standards-compliant browsers */
447         if (e != null) {
448                 if (e.preventDefault)
449                         e.preventDefault();
450                 else
451                         e.returnResult = false;
452
453                 if (e.stopPropagation)
454                         e.stopPropagation();
455                 else
456                         e.cancelBubble = true;
457         }
458
459         /* For IE */
460         if (window.event != null)
461                 window.event.cancelBubble = true;
462 }