Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

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