]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dijit/layout/StackContainer.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dijit / layout / StackContainer.js
1 if(!dojo._hasResource["dijit.layout.StackContainer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit.layout.StackContainer"] = true;
3 dojo.provide("dijit.layout.StackContainer");
4
5 dojo.require("dijit._Templated");
6 dojo.require("dijit.layout._LayoutWidget");
7 dojo.require("dijit.form.Button");
8 dojo.require("dijit.Menu");
9 dojo.requireLocalization("dijit", "common", null, "zh,pt,da,tr,ru,de,sv,ja,he,fi,nb,el,ar,ROOT,pt-pt,cs,fr,es,ko,nl,zh-tw,pl,it,hu");
10
11 dojo.declare(
12         "dijit.layout.StackContainer",
13         dijit.layout._LayoutWidget,
14         {
15         // summary: 
16         //      A container that has multiple children, but shows only
17         //      one child at a time
18         //
19         // description:
20         //      A container for widgets (ContentPanes, for example) That displays
21         //      only one Widget at a time.
22         //      
23         //      Publishes topics [widgetId]-addChild, [widgetId]-removeChild, and [widgetId]-selectChild
24         //
25         //      Can be base class for container, Wizard, Show, etc.
26         // 
27         //
28         // doLayout: Boolean
29         //  if true, change the size of my currently displayed child to match my size
30         doLayout: true,
31
32         _started: false,
33 /*=====
34         // selectedChildWidget: Widget
35         //      References the currently selected child widget, if any
36         //
37         selectedChildWidget: null,
38 =====*/
39         postCreate: function(){
40                 dijit.setWaiRole((this.containerNode || this.domNode), "tabpanel");
41                 this.connect(this.domNode, "onkeypress", this._onKeyPress);
42         },
43         
44         startup: function(){
45                 if(this._started){ return; }
46
47                 var children = this.getChildren();
48
49                 // Setup each page panel
50                 dojo.forEach(children, this._setupChild, this);
51
52                 // Figure out which child to initially display
53                 dojo.some(children, function(child){
54                         if(child.selected){
55                                 this.selectedChildWidget = child;
56                         }
57                         return child.selected;
58                 }, this);
59
60                 var selected = this.selectedChildWidget;
61
62                 // Default to the first child
63                 if(!selected && children[0]){
64                         selected = this.selectedChildWidget = children[0];
65                         selected.selected = true;
66                 }
67                 if(selected){
68                         this._showChild(selected);
69                 }
70
71                 // Now publish information about myself so any StackControllers can initialize..
72                 dojo.publish(this.id+"-startup", [{children: children, selected: selected}]);
73
74                 this.inherited(arguments);
75         },
76
77         _setupChild: function(/*Widget*/ page){
78                 // Summary: prepare the given child
79
80                 page.domNode.style.display = "none";
81
82                 // since we are setting the width/height of the child elements, they need
83                 // to be position:relative, or IE has problems (See bug #2033)
84                 page.domNode.style.position = "relative";
85
86                 return page; // dijit._Widget
87         },
88
89         addChild: function(/*Widget*/ child, /*Integer?*/ insertIndex){
90                 // summary: Adds a widget to the stack
91                  
92                 dijit._Container.prototype.addChild.apply(this, arguments);
93                 child = this._setupChild(child);
94
95                 if(this._started){
96                         // in case the tab titles have overflowed from one line to two lines
97                         this.layout();
98
99                         dojo.publish(this.id+"-addChild", [child, insertIndex]);
100
101                         // if this is the first child, then select it
102                         if(!this.selectedChildWidget){
103                                 this.selectChild(child);
104                         }
105                 }
106         },
107
108         removeChild: function(/*Widget*/ page){
109                 // summary: Removes the pane from the stack
110
111                 dijit._Container.prototype.removeChild.apply(this, arguments);
112
113                 // If we are being destroyed than don't run the code below (to select another page), because we are deleting
114                 // every page one by one
115                 if(this._beingDestroyed){ return; }
116
117                 if(this._started){
118                         // this will notify any tablists to remove a button; do this first because it may affect sizing
119                         dojo.publish(this.id+"-removeChild", [page]);
120
121                         // in case the tab titles now take up one line instead of two lines
122                         this.layout();
123                 }
124
125                 if(this.selectedChildWidget === page){
126                         this.selectedChildWidget = undefined;
127                         if(this._started){
128                                 var children = this.getChildren();
129                                 if(children.length){
130                                         this.selectChild(children[0]);
131                                 }
132                         }
133                 }
134         },
135
136         selectChild: function(/*Widget*/ page){
137                 // summary:
138                 //      Show the given widget (which must be one of my children)
139
140                 page = dijit.byId(page);
141
142                 if(this.selectedChildWidget != page){
143                         // Deselect old page and select new one
144                         this._transition(page, this.selectedChildWidget);
145                         this.selectedChildWidget = page;
146                         dojo.publish(this.id+"-selectChild", [page]);
147                 }
148         },
149
150         _transition: function(/*Widget*/newWidget, /*Widget*/oldWidget){
151                 if(oldWidget){
152                         this._hideChild(oldWidget);
153                 }
154                 this._showChild(newWidget);
155
156                 // Size the new widget, in case this is the first time it's being shown,
157                 // or I have been resized since the last time it was shown.
158                 // page must be visible for resizing to work
159                 if(this.doLayout && newWidget.resize){
160                         newWidget.resize(this._containerContentBox || this._contentBox);
161                 }
162         },
163
164         _adjacent: function(/*Boolean*/ forward){
165                 // summary: Gets the next/previous child widget in this container from the current selection
166                 var children = this.getChildren();
167                 var index = dojo.indexOf(children, this.selectedChildWidget);
168                 index += forward ? 1 : children.length - 1;
169                 return children[ index % children.length ]; // dijit._Widget
170         },
171
172         forward: function(){
173                 // Summary: advance to next page
174                 this.selectChild(this._adjacent(true));
175         },
176
177         back: function(){
178                 // Summary: go back to previous page
179                 this.selectChild(this._adjacent(false));
180         },
181
182         _onKeyPress: function(e){
183                 dojo.publish(this.id+"-containerKeyPress", [{ e: e, page: this}]);
184         },
185
186         layout: function(){
187                 if(this.doLayout && this.selectedChildWidget && this.selectedChildWidget.resize){
188                         this.selectedChildWidget.resize(this._contentBox);
189                 }
190         },
191
192         _showChild: function(/*Widget*/ page){
193                 var children = this.getChildren();
194                 page.isFirstChild = (page == children[0]);
195                 page.isLastChild = (page == children[children.length-1]);
196                 page.selected = true;
197
198                 page.domNode.style.display="";
199                 if(page._loadCheck){
200                         page._loadCheck(); // trigger load in ContentPane
201                 }
202                 if(page.onShow){
203                         page.onShow();
204                 }
205         },
206
207         _hideChild: function(/*Widget*/ page){
208                 page.selected=false;
209                 page.domNode.style.display="none";
210                 if(page.onHide){
211                         page.onHide();
212                 }
213         },
214
215         closeChild: function(/*Widget*/ page){
216                 // summary:
217                 //      callback when user clicks the [X] to remove a page
218                 //      if onClose() returns true then remove and destroy the child
219                 var remove = page.onClose(this, page);
220                 if(remove){
221                         this.removeChild(page);
222                         // makes sure we can clean up executeScripts in ContentPane onUnLoad
223                         page.destroyRecursive();
224                 }
225         },
226
227         destroy: function(){
228                 this._beingDestroyed = true;
229                 this.inherited(arguments);
230         }
231 });
232
233 dojo.declare(
234         "dijit.layout.StackController",
235         [dijit._Widget, dijit._Templated, dijit._Container],
236         {
237         // summary:
238         //      Set of buttons to select a page in a page list.
239         //      Monitors the specified StackContainer, and whenever a page is
240         //      added, deleted, or selected, updates itself accordingly.
241
242                 templateString: "<span wairole='tablist' dojoAttachEvent='onkeypress' class='dijitStackController'></span>",
243
244                 // containerId: String
245                 //      the id of the page container that I point to
246                 containerId: "",
247
248                 // buttonWidget: String
249                 //      the name of the button widget to create to correspond to each page
250                 buttonWidget: "dijit.layout._StackButton",
251
252                 postCreate: function(){
253                         dijit.setWaiRole(this.domNode, "tablist");
254
255                         // TODO: change key from object to id, to get more separation from StackContainer
256                         this.pane2button = {};          // mapping from panes to buttons
257                         this.pane2menu = {};            // mapping from panes to close menu
258
259                         this._subscriptions=[
260                                 dojo.subscribe(this.containerId+"-startup", this, "onStartup"),
261                                 dojo.subscribe(this.containerId+"-addChild", this, "onAddChild"),
262                                 dojo.subscribe(this.containerId+"-removeChild", this, "onRemoveChild"),
263                                 dojo.subscribe(this.containerId+"-selectChild", this, "onSelectChild"),
264                                 dojo.subscribe(this.containerId+"-containerKeyPress", this, "onContainerKeyPress")
265                         ];
266                 },
267
268                 onStartup: function(/*Object*/ info){
269                         // summary: called after StackContainer has finished initializing
270                         dojo.forEach(info.children, this.onAddChild, this);
271                         this.onSelectChild(info.selected);
272                 },
273
274                 destroy: function(){
275                         for(var pane in this.pane2button){
276                                 this.onRemoveChild(pane);
277                         }
278                         dojo.forEach(this._subscriptions, dojo.unsubscribe);
279                         this.inherited(arguments);
280                 },
281
282                 onAddChild: function(/*Widget*/ page, /*Integer?*/ insertIndex){
283                         // summary:
284                         //   Called whenever a page is added to the container.
285                         //   Create button corresponding to the page.
286
287                         // add a node that will be promoted to the button widget
288                         var refNode = dojo.doc.createElement("span");
289                         this.domNode.appendChild(refNode);
290                         // create an instance of the button widget
291                         var cls = dojo.getObject(this.buttonWidget);
292                         var button = new cls({label: page.title, closeButton: page.closable}, refNode);
293                         this.addChild(button, insertIndex);
294                         this.pane2button[page] = button;
295                         page.controlButton = button;    // this value might be overwritten if two tabs point to same container
296                         
297                         dojo.connect(button, "onClick", dojo.hitch(this,"onButtonClick",page));
298                         if(page.closable){
299                                 dojo.connect(button, "onClickCloseButton", dojo.hitch(this,"onCloseButtonClick",page));
300                                 // add context menu onto title button
301                                 var _nlsResources = dojo.i18n.getLocalization("dijit", "common");
302                                 var closeMenu = new dijit.Menu({targetNodeIds:[button.id], id:button.id+"_Menu"});
303                                 var mItem = new dijit.MenuItem({label:_nlsResources.itemClose});
304                 dojo.connect(mItem, "onClick", dojo.hitch(this, "onCloseButtonClick", page));
305                         closeMenu.addChild(mItem);
306                         this.pane2menu[page] = closeMenu;
307                         }
308                         if(!this._currentChild){ // put the first child into the tab order
309                                 button.focusNode.setAttribute("tabIndex", "0");
310                                 this._currentChild = page;
311                         }
312                         //make sure all tabs have the same length
313                         if(!this.isLeftToRight() && dojo.isIE && this._rectifyRtlTabList){
314                                 this._rectifyRtlTabList();
315                         }
316                 },
317
318                 onRemoveChild: function(/*Widget*/ page){
319                         // summary:
320                         //   Called whenever a page is removed from the container.
321                         //   Remove the button corresponding to the page.
322                         if(this._currentChild === page){ this._currentChild = null; }
323                         var button = this.pane2button[page];
324                         var menu = this.pane2menu[page];
325                         if (menu){
326                                 menu.destroy();
327                         }
328                         if(button){
329                                 // TODO? if current child { reassign }
330                                 button.destroy();
331                         }
332                         this.pane2button[page] = null;
333                 },
334
335                 onSelectChild: function(/*Widget*/ page){
336                         // summary:
337                         //      Called when a page has been selected in the StackContainer, either by me or by another StackController
338
339                         if(!page){ return; }
340
341                         if(this._currentChild){
342                                 var oldButton=this.pane2button[this._currentChild];
343                                 oldButton.setAttribute('checked', false);
344                                 oldButton.focusNode.setAttribute("tabIndex", "-1");
345                         }
346
347                         var newButton=this.pane2button[page];
348                         newButton.setAttribute('checked', true);
349                         this._currentChild = page;
350                         newButton.focusNode.setAttribute("tabIndex", "0");
351                         var container = dijit.byId(this.containerId);
352                         dijit.setWaiState(container.containerNode || container.domNode, "labelledby", newButton.id);
353                 },
354
355                 onButtonClick: function(/*Widget*/ page){
356                         // summary:
357                         //   Called whenever one of my child buttons is pressed in an attempt to select a page
358                         var container = dijit.byId(this.containerId);   // TODO: do this via topics?
359                         container.selectChild(page); 
360                 },
361
362                 onCloseButtonClick: function(/*Widget*/ page){
363                         // summary:
364                         //   Called whenever one of my child buttons [X] is pressed in an attempt to close a page
365                         var container = dijit.byId(this.containerId);
366                         container.closeChild(page);
367                         var b = this.pane2button[this._currentChild];
368                         if(b){
369                                 dijit.focus(b.focusNode || b.domNode);
370                         }
371                 },
372                 
373                 // TODO: this is a bit redundant with forward, back api in StackContainer
374                 adjacent: function(/*Boolean*/ forward){
375                         if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; }
376                         // find currently focused button in children array
377                         var children = this.getChildren();
378                         var current = dojo.indexOf(children, this.pane2button[this._currentChild]);
379                         // pick next button to focus on
380                         var offset = forward ? 1 : children.length - 1;
381                         return children[ (current + offset) % children.length ]; // dijit._Widget
382                 },
383
384                 onkeypress: function(/*Event*/ e){
385                         // summary:
386                         //   Handle keystrokes on the page list, for advancing to next/previous button
387                         //   and closing the current page if the page is closable.
388
389                         if(this.disabled || e.altKey ){ return; }
390                         var forward = null;
391                         if(e.ctrlKey || !e._djpage){
392                                 var k = dojo.keys;
393                                 switch(e.keyCode){
394                                         case k.LEFT_ARROW:
395                                         case k.UP_ARROW:
396                                                 if(!e._djpage){ forward = false; }
397                                                 break;
398                                         case k.PAGE_UP:
399                                                 if(e.ctrlKey){ forward = false; }
400                                                 break;
401                                         case k.RIGHT_ARROW:
402                                         case k.DOWN_ARROW:
403                                                 if(!e._djpage){ forward = true; }
404                                                 break;
405                                         case k.PAGE_DOWN:
406                                                 if(e.ctrlKey){ forward = true; }
407                                                 break;
408                                         case k.DELETE:
409                                                 if(this._currentChild.closable){
410                                                         this.onCloseButtonClick(this._currentChild);
411                                                 }
412                                                 dojo.stopEvent(e);
413                                                 break;
414                                         default:
415                                                 if(e.ctrlKey){
416                                                         if(e.keyCode == k.TAB){
417                                                                 this.adjacent(!e.shiftKey).onClick();
418                                                                 dojo.stopEvent(e);
419                                                         }else if(e.keyChar == "w"){
420                                                                 if(this._currentChild.closable){
421                                                                         this.onCloseButtonClick(this._currentChild);
422                                                                 }
423                                                                 dojo.stopEvent(e); // avoid browser tab closing.
424                                                         }
425                                                 }
426                                 }
427                                 // handle page navigation
428                                 if(forward !== null){
429                                         this.adjacent(forward).onClick();
430                                         dojo.stopEvent(e);
431                                 }
432                         }
433                 },
434
435                 onContainerKeyPress: function(/*Object*/ info){
436                         info.e._djpage = info.page;
437                         this.onkeypress(info.e);
438                 }
439 });
440
441 dojo.declare("dijit.layout._StackButton",
442         dijit.form.ToggleButton,
443         {
444         // summary
445         //      Internal widget used by StackContainer.
446         //      The button-like or tab-like object you click to select or delete a page
447         
448         tabIndex: "-1", // StackContainer buttons are not in the tab order by default
449         
450         postCreate: function(/*Event*/ evt){
451                 dijit.setWaiRole((this.focusNode || this.domNode), "tab");
452                 this.inherited(arguments);
453         },
454         
455         onClick: function(/*Event*/ evt){
456                 // summary: This is for TabContainer where the tabs are <span> rather than button,
457                 //      so need to set focus explicitly (on some browsers)
458                 dijit.focus(this.focusNode);
459
460                 // ... now let StackController catch the event and tell me what to do
461         },
462
463         onClickCloseButton: function(/*Event*/ evt){
464                 // summary
465                 //      StackContainer connects to this function; if your widget contains a close button
466                 //      then clicking it should call this function.
467                 evt.stopPropagation();
468         }
469 });
470
471 // These arguments can be specified for the children of a StackContainer.
472 // Since any widget can be specified as a StackContainer child, mix them
473 // into the base widget class.  (This is a hack, but it's effective.)
474 dojo.extend(dijit._Widget, {
475         // title: String
476         //              Title of this widget.  Used by TabContainer to the name the tab, etc.
477         title: "",
478
479         // selected: Boolean
480         //              Is this child currently selected?
481         selected: false,
482
483         // closable: Boolean
484         //              True if user can close (destroy) this child, such as (for example) clicking the X on the tab.
485         closable: false,        // true if user can close this tab pane
486
487         onClose: function(){
488                 // summary: Callback if someone tries to close the child, child will be closed if func returns true
489                 return true;
490         }
491 });
492
493 }