]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dijit/Menu.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dijit / Menu.js
1 if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit.Menu"] = true;
3 dojo.provide("dijit.Menu");
4
5 dojo.require("dijit._Widget");
6 dojo.require("dijit._Container");
7 dojo.require("dijit._Templated");
8
9 dojo.declare("dijit.Menu",
10         [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
11         {
12         // summary
13         //      A context menu you can assign to multiple elements
14
15         constructor: function(){
16                 this._bindings = [];
17         },
18
19         templateString:
20                         '<table class="dijit dijitMenu dijitReset dijitMenuTable" waiRole="menu" dojoAttachEvent="onkeypress:_onKeyPress">' +
21                                 '<tbody class="dijitReset" dojoAttachPoint="containerNode"></tbody>'+
22                         '</table>',
23
24         // targetNodeIds: String[]
25         //      Array of dom node ids of nodes to attach to.
26         //      Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
27         targetNodeIds: [],
28
29         // contextMenuForWindow: Boolean
30         //      if true, right clicking anywhere on the window will cause this context menu to open;
31         //      if false, must specify targetNodeIds
32         contextMenuForWindow: false,
33
34         // leftClickToOpen: Boolean
35         //      If true, menu will open on left click instead of right click, similiar to a file menu.
36         leftClickToOpen: false,
37         
38         // parentMenu: Widget
39         // pointer to menu that displayed me
40         parentMenu: null,
41
42         // popupDelay: Integer
43         //      number of milliseconds before hovering (without clicking) causes the popup to automatically open
44         popupDelay: 500,
45
46         // _contextMenuWithMouse: Boolean
47         //      used to record mouse and keyboard events to determine if a context
48         //      menu is being opened with the keyboard or the mouse
49         _contextMenuWithMouse: false,
50
51         postCreate: function(){
52                 if(this.contextMenuForWindow){
53                         this.bindDomNode(dojo.body());
54                 }else{
55                         dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
56                 }
57                 this.connectKeyNavHandlers([dojo.keys.UP_ARROW], [dojo.keys.DOWN_ARROW]);
58         },
59
60         startup: function(){
61                 if(this._started){ return; }
62
63                 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
64                 this.startupKeyNavChildren();
65
66                 this.inherited(arguments);
67         },
68
69         onExecute: function(){
70                 // summary: attach point for notification about when a menu item has been executed
71         },
72
73         onCancel: function(/*Boolean*/ closeAll){
74                 // summary: attach point for notification about when the user cancels the current menu
75         },
76
77         _moveToPopup: function(/*Event*/ evt){
78                 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
79                         this.focusedChild._onClick(evt);
80                 }
81         },
82
83         _onKeyPress: function(/*Event*/ evt){
84                 // summary: Handle keyboard based menu navigation.
85                 if(evt.ctrlKey || evt.altKey){ return; }
86
87                 switch(evt.keyCode){
88                         case dojo.keys.RIGHT_ARROW:
89                                 this._moveToPopup(evt);
90                                 dojo.stopEvent(evt);
91                                 break;
92                         case dojo.keys.LEFT_ARROW:
93                                 if(this.parentMenu){
94                                         this.onCancel(false);
95                                 }else{
96                                         dojo.stopEvent(evt);
97                                 }
98                                 break;
99                 }
100         },
101
102         onItemHover: function(/*MenuItem*/ item){
103                 // summary: Called when cursor is over a MenuItem
104                 this.focusChild(item);
105
106                 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
107                         this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay);
108                 }
109         },
110
111         _onChildBlur: function(item){
112                 // summary: Close all popups that are open and descendants of this menu
113                 dijit.popup.close(item.popup);
114                 item._blur();
115                 this._stopPopupTimer();
116         },
117
118         onItemUnhover: function(/*MenuItem*/ item){
119                 // summary: Callback fires when mouse exits a MenuItem
120         },
121
122         _stopPopupTimer: function(){
123                 if(this.hover_timer){
124                         clearTimeout(this.hover_timer);
125                         this.hover_timer = null;
126                 }
127         },
128
129         _getTopMenu: function(){
130                 for(var top=this; top.parentMenu; top=top.parentMenu);
131                 return top;
132         },
133
134         onItemClick: function(/*Widget*/ item, /*Event*/ evt){
135                 // summary: user defined function to handle clicks on an item
136                 if(item.disabled){ return false; }
137
138                 if(item.popup){
139                         if(!this.is_open){
140                                 this._openPopup();
141                         }
142                 }else{
143                         // before calling user defined handler, close hierarchy of menus
144                         // and restore focus to place it was when menu was opened
145                         this.onExecute();
146
147                         // user defined handler for click
148                         item.onClick(evt);
149                 }
150         },
151
152         // thanks burstlib!
153         _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
154                 // summary:
155                 //      Returns the window reference of the passed iframe
156                 var win = dijit.getDocumentWindow(dijit.Menu._iframeContentDocument(iframe_el)) ||
157                         // Moz. TODO: is this available when defaultView isn't?
158                         dijit.Menu._iframeContentDocument(iframe_el)['__parent__'] ||
159                         (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
160                 return win;     //      Window
161         },
162
163         _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
164                 // summary:
165                 //      Returns a reference to the document object inside iframe_el
166                 var doc = iframe_el.contentDocument // W3
167                         || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
168                         || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
169                         || null;
170                 return doc;     //      HTMLDocument
171         },
172
173         bindDomNode: function(/*String|DomNode*/ node){
174                 // summary: attach menu to given node
175                 node = dojo.byId(node);
176
177                 //TODO: this is to support context popups in Editor.  Maybe this shouldn't be in dijit.Menu
178                 var win = dijit.getDocumentWindow(node.ownerDocument);
179                 if(node.tagName.toLowerCase()=="iframe"){
180                         win = this._iframeContentWindow(node);
181                         node = dojo.withGlobal(win, dojo.body);
182                 }
183
184                 // to capture these events at the top level,
185                 // attach to document, not body
186                 var cn = (node == dojo.body() ? dojo.doc : node);
187
188                 node[this.id] = this._bindings.push([
189                         dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, "_openMyself"),
190                         dojo.connect(cn, "onkeydown", this, "_contextKey"),
191                         dojo.connect(cn, "onmousedown", this, "_contextMouse")
192                 ]);
193         },
194
195         unBindDomNode: function(/*String|DomNode*/ nodeName){
196                 // summary: detach menu from given node
197                 var node = dojo.byId(nodeName);
198                 if(node){
199                         var bid = node[this.id]-1, b = this._bindings[bid];
200                         dojo.forEach(b, dojo.disconnect);
201                         delete this._bindings[bid];
202                 }
203         },
204
205         _contextKey: function(e){
206                 this._contextMenuWithMouse = false;
207                 if(e.keyCode == dojo.keys.F10){
208                         dojo.stopEvent(e);
209                         if(e.shiftKey && e.type=="keydown"){
210                                 // FF: copying the wrong property from e will cause the system
211                                 // context menu to appear in spite of stopEvent. Don't know
212                                 // exactly which properties cause this effect.
213                                 var _e = { target: e.target, pageX: e.pageX, pageY: e.pageY };
214                                 _e.preventDefault = _e.stopPropagation = function(){};
215                                 // IE: without the delay, focus work in "open" causes the system
216                                 // context menu to appear in spite of stopEvent.
217                                 window.setTimeout(dojo.hitch(this, function(){ this._openMyself(_e); }), 1);
218                         }
219                 }
220         },
221
222         _contextMouse: function(e){
223                 this._contextMenuWithMouse = true;
224         },
225
226         _openMyself: function(/*Event*/ e){
227                 // summary:
228                 //              Internal function for opening myself when the user
229                 //              does a right-click or something similar
230
231                 if(this.leftClickToOpen&&e.button>0){
232                         return;
233                 }
234                 dojo.stopEvent(e);
235
236                 // Get coordinates.
237                 // if we are opening the menu with the mouse or on safari open
238                 // the menu at the mouse cursor
239                 // (Safari does not have a keyboard command to open the context menu
240                 // and we don't currently have a reliable way to determine
241                 // _contextMenuWithMouse on Safari)
242                 var x,y;
243                 if(dojo.isSafari || this._contextMenuWithMouse){
244                         x=e.pageX;
245                         y=e.pageY;
246                 }else{
247                         // otherwise open near e.target
248                         var coords = dojo.coords(e.target, true);
249                         x = coords.x + 10;
250                         y = coords.y + 10;
251                 }
252
253                 var self=this;
254                 var savedFocus = dijit.getFocus(this);
255                 function closeAndRestoreFocus(){
256                         // user has clicked on a menu or popup
257                         dijit.focus(savedFocus);
258                         dijit.popup.close(self);
259                 }
260                 dijit.popup.open({
261                         popup: this,
262                         x: x,
263                         y: y,
264                         onExecute: closeAndRestoreFocus,
265                         onCancel: closeAndRestoreFocus,
266                         orient: this.isLeftToRight() ? 'L' : 'R'
267                 });
268                 this.focus();
269
270                 this._onBlur = function(){
271                         this.inherited('_onBlur', arguments);
272                         // Usually the parent closes the child widget but if this is a context
273                         // menu then there is no parent
274                         dijit.popup.close(this);
275                         // don't try to restore focus; user has clicked another part of the screen
276                         // and set focus there
277                 }
278         },
279
280         onOpen: function(/*Event*/ e){
281                 // summary: Open menu relative to the mouse
282                 this.isShowingNow = true;
283         },
284
285         onClose: function(){
286                 // summary: callback when this menu is closed
287                 this._stopPopupTimer();
288                 this.parentMenu = null;
289                 this.isShowingNow = false;
290                 this.currentPopup = null;
291                 if(this.focusedChild){
292                         this._onChildBlur(this.focusedChild);
293                         this.focusedChild = null;
294                 }
295         },
296
297         _openPopup: function(){
298                 // summary: open the popup to the side of the current menu item
299                 this._stopPopupTimer();
300                 var from_item = this.focusedChild;
301                 var popup = from_item.popup;
302
303                 if(popup.isShowingNow){ return; }
304                 popup.parentMenu = this;
305                 var self = this;
306                 dijit.popup.open({
307                         parent: this,
308                         popup: popup,
309                         around: from_item.arrowCell,
310                         orient: this.isLeftToRight() ? {'TR': 'TL', 'TL': 'TR'} : {'TL': 'TR', 'TR': 'TL'},
311                         onCancel: function(){
312                                 // called when the child menu is canceled
313                                 dijit.popup.close(popup);
314                                 from_item.focus();      // put focus back on my node
315                                 self.currentPopup = null;
316                         }
317                 });
318
319                 this.currentPopup = popup;
320
321                 if(popup.focus){
322                         popup.focus();
323                 }
324         },
325         
326         uninitialize: function(){
327                 dojo.forEach(this.targetNodeIds, this.unBindDomNode, this);
328                 this.inherited(arguments);
329         }
330 }
331 );
332
333 dojo.declare("dijit.MenuItem",
334         [dijit._Widget, dijit._Templated, dijit._Contained],
335         {
336         // summary: A line item in a Menu Widget
337
338         // Make 3 columns
339         //   icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
340         templateString:
341                  '<tr class="dijitReset dijitMenuItem" '
342                 +'dojoAttachEvent="onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick">'
343                 +'<td class="dijitReset"><div class="dijitMenuItemIcon ${iconClass}" dojoAttachPoint="iconNode"></div></td>'
344                 +'<td tabIndex="-1" class="dijitReset dijitMenuItemLabel" dojoAttachPoint="containerNode,focusNode" waiRole="menuitem"></td>'
345                 +'<td class="dijitReset" dojoAttachPoint="arrowCell">'
346                         +'<div class="dijitMenuExpand" dojoAttachPoint="expand" style="display:none">'
347                         +'<span class="dijitInline dijitArrowNode dijitMenuExpandInner">+</span>'
348                         +'</div>'
349                 +'</td>'
350                 +'</tr>',
351
352         // label: String
353         //      menu text
354         label: '',
355
356         // iconClass: String
357         //      class to apply to div in button to make it display an icon
358         iconClass: "",
359
360         // disabled: Boolean
361         //  if true, the menu item is disabled
362         //  if false, the menu item is enabled
363         disabled: false,
364
365         postCreate: function(){
366                 dojo.setSelectable(this.domNode, false);
367                 this.setDisabled(this.disabled);
368                 if(this.label){
369                         this.setLabel(this.label);
370                 }
371         },
372
373         _onHover: function(){
374                 // summary: callback when mouse is moved onto menu item
375                 this.getParent().onItemHover(this);
376         },
377
378         _onUnhover: function(){
379                 // summary: callback when mouse is moved off of menu item
380
381                 // if we are unhovering the currently selected item
382                 // then unselect it
383                 this.getParent().onItemUnhover(this);
384         },
385
386         _onClick: function(evt){
387                 this.getParent().onItemClick(this, evt);
388                 dojo.stopEvent(evt);
389         },
390
391         onClick: function(/*Event*/ evt){
392                 // summary: User defined function to handle clicks
393         },
394
395         focus: function(){
396                 dojo.addClass(this.domNode, 'dijitMenuItemHover');
397                 try{
398                         dijit.focus(this.containerNode);
399                 }catch(e){
400                         // this throws on IE (at least) in some scenarios
401                 }
402         },
403
404         _blur: function(){
405                 dojo.removeClass(this.domNode, 'dijitMenuItemHover');
406         },
407         
408         setLabel: function(/*String*/ value){
409                 this.containerNode.innerHTML=this.label=value;
410         },
411
412         setDisabled: function(/*Boolean*/ value){
413                 // summary: enable or disable this menu item
414                 this.disabled = value;
415                 dojo[value ? "addClass" : "removeClass"](this.domNode, 'dijitMenuItemDisabled');
416                 dijit.setWaiState(this.containerNode, 'disabled', value ? 'true' : 'false');
417         }
418 });
419
420 dojo.declare("dijit.PopupMenuItem",
421         dijit.MenuItem,
422         {
423         _fillContent: function(){
424                 // summary: The innerHTML contains both the menu item text and a popup widget
425                 // description: the first part holds the menu item text and the second part is the popup
426                 // example: 
427                 // |    <div dojoType="dijit.PopupMenuItem">
428                 // |            <span>pick me</span>
429                 // |            <popup> ... </popup>
430                 // |    </div>
431                 if(this.srcNodeRef){
432                         var nodes = dojo.query("*", this.srcNodeRef);
433                         dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
434
435                         // save pointer to srcNode so we can grab the drop down widget after it's instantiated
436                         this.dropDownContainer = this.srcNodeRef;
437                 }
438         },
439
440         startup: function(){
441                 if(this._started){ return; }
442                 this.inherited(arguments);
443
444                 // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's
445                 // land now.  move it to dojo.doc.body.
446                 if(!this.popup){
447                         var node = dojo.query("[widgetId]", this.dropDownContainer)[0];
448                         this.popup = dijit.byNode(node);
449                 }
450                 dojo.body().appendChild(this.popup.domNode);
451
452                 this.popup.domNode.style.display="none";
453                 dojo.addClass(this.expand, "dijitMenuExpandEnabled");
454                 dojo.style(this.expand, "display", "");
455                 dijit.setWaiState(this.containerNode, "haspopup", "true");
456         },
457         
458         destroyDescendants: function(){
459                 if(this.popup){
460                         this.popup.destroyRecursive();
461                         delete this.popup;
462                 }
463                 this.inherited(arguments);
464         }
465 });
466
467 dojo.declare("dijit.MenuSeparator",
468         [dijit._Widget, dijit._Templated, dijit._Contained],
469         {
470         // summary: A line between two menu items
471
472         templateString: '<tr class="dijitMenuSeparator"><td colspan=3>'
473                         +'<div class="dijitMenuSeparatorTop"></div>'
474                         +'<div class="dijitMenuSeparatorBottom"></div>'
475                         +'</td></tr>',
476
477         postCreate: function(){
478                 dojo.setSelectable(this.domNode, false);
479         },
480         
481         isFocusable: function(){
482                 // summary: over ride to always return false
483                 return false; // Boolean
484         }
485 });
486
487 }