]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/layout/StackController.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dijit / layout / StackController.js.uncompressed.js
1 define("dijit/layout/StackController", [
2 "dojo/_base/array", // array.forEach array.indexOf array.map
3 "dojo/_base/declare", // declare
4 "dojo/dom-class",
5 "dojo/_base/event", // event.stop
6 "dojo/keys", // keys
7 "dojo/_base/lang", // lang.getObject
8 "dojo/on",
9 "../focus", // focus.focus()
10 "../registry", // registry.byId
11 "../_Widget",
12 "../_TemplatedMixin",
13 "../_Container",
14 "../form/ToggleButton",
15 "dojo/i18n!../nls/common"
16 ], function(array, declare, domClass, event, keys, lang, on,
17 focus, registry, _Widget, _TemplatedMixin, _Container, ToggleButton){
18
19 // module:
20 // dijit/layout/StackController
21
22 var StackButton = declare("dijit.layout._StackButton", ToggleButton, {
23 // summary:
24 // Internal widget used by StackContainer.
25 // description:
26 // The button-like or tab-like object you click to select or delete a page
27 // tags:
28 // private
29
30 // Override _FormWidget.tabIndex.
31 // StackContainer buttons are not in the tab order by default.
32 // Probably we should be calling this.startupKeyNavChildren() instead.
33 tabIndex: "-1",
34
35 // closeButton: Boolean
36 // When true, display close button for this tab
37 closeButton: false,
38
39 _aria_attr: "aria-selected",
40
41 buildRendering: function(/*Event*/ evt){
42 this.inherited(arguments);
43 (this.focusNode || this.domNode).setAttribute("role", "tab");
44 }
45 });
46
47
48 var StackController = declare("dijit.layout.StackController", [_Widget, _TemplatedMixin, _Container], {
49 // summary:
50 // Set of buttons to select a page in a `dijit/layout/StackContainer`
51 // description:
52 // Monitors the specified StackContainer, and whenever a page is
53 // added, deleted, or selected, updates itself accordingly.
54
55 baseClass: "dijitStackController",
56
57 templateString: "<span role='tablist' data-dojo-attach-event='onkeypress'></span>",
58
59 // containerId: [const] String
60 // The id of the page container that I point to
61 containerId: "",
62
63 // buttonWidget: [const] Constructor
64 // The button widget to create to correspond to each page
65 buttonWidget: StackButton,
66
67 // buttonWidgetCloseClass: String
68 // CSS class of [x] close icon, used by event delegation code to tell when close button was clicked
69 buttonWidgetCloseClass: "dijitStackCloseButton",
70
71 constructor: function(params /*===== , srcNodeRef =====*/){
72 // summary:
73 // Create the widget.
74 // params: Object|null
75 // Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
76 // and functions, typically callbacks like onClick.
77 // The hash can contain any of the widget's properties, excluding read-only properties.
78 // srcNodeRef: DOMNode|String?
79 // If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree
80
81 this.pane2button = {}; // mapping from pane id to buttons
82 },
83
84 postCreate: function(){
85 this.inherited(arguments);
86
87 // Listen to notifications from StackContainer.
88 // TODO: do this through bubbled events instead of topics
89 this.subscribe(this.containerId+"-startup", "onStartup");
90 this.subscribe(this.containerId+"-addChild", "onAddChild");
91 this.subscribe(this.containerId+"-removeChild", "onRemoveChild");
92 this.subscribe(this.containerId+"-selectChild", "onSelectChild");
93 this.subscribe(this.containerId+"-containerKeyPress", "onContainerKeyPress");
94
95 // Listen for click events to select or close tabs.
96 // No need to worry about ENTER/SPACE key handling: tabs are selected via left/right arrow keys,
97 // and closed via shift-F10 (to show the close menu).
98 this.connect(this.containerNode, 'click', function(evt){
99 var button = registry.getEnclosingWidget(evt.target);
100 if(button != this.containerNode && !button.disabled && button.page){
101 for(var target = evt.target; target !== this.containerNode; target = target.parentNode){
102 if(domClass.contains(target, this.buttonWidgetCloseClass)){
103 this.onCloseButtonClick(button.page);
104 break;
105 }else if(target == button.domNode){
106 this.onButtonClick(button.page);
107 break;
108 }
109 }
110 }
111 });
112 },
113
114 onStartup: function(/*Object*/ info){
115 // summary:
116 // Called after StackContainer has finished initializing
117 // tags:
118 // private
119 array.forEach(info.children, this.onAddChild, this);
120 if(info.selected){
121 // Show button corresponding to selected pane (unless selected
122 // is null because there are no panes)
123 this.onSelectChild(info.selected);
124 }
125
126 // Reflect events like page title changes to tab buttons
127 var containerNode = registry.byId(this.containerId).containerNode,
128 pane2button = this.pane2button,
129 paneToButtonAttr = {
130 "title": "label",
131 "showtitle": "showLabel",
132 "iconclass": "iconClass",
133 "closable": "closeButton",
134 "tooltip": "title",
135 "disabled": "disabled"
136 },
137 connectFunc = function(attr, buttonAttr){
138 return on(containerNode, "attrmodified-" + attr, function(evt){
139 var button = pane2button[evt.detail && evt.detail.widget && evt.detail.widget.id];
140 if(button){
141 button.set(buttonAttr, evt.detail.newValue);
142 }
143 });
144 };
145 for(var attr in paneToButtonAttr){
146 this.own(connectFunc(attr, paneToButtonAttr[attr]));
147 }
148 },
149
150 destroy: function(){
151 // Since the buttons are internal to the StackController widget, destroy() should remove them, which is
152 // done by calling onRemoveChild().
153 for(var pane in this.pane2button){
154 this.onRemoveChild(registry.byId(pane));
155 }
156
157 // TODO: destroyRecursive() will call destroy() on each child button twice. Once from the above code,
158 // and once because _WidgetBase.destroyDescendants() deletes anything inside of this.containerNode.
159 // Probably shouldn't attach that DOMNode as this.containerNode.
160
161 this.inherited(arguments);
162 },
163
164 onAddChild: function(/*dijit/_WidgetBase*/ page, /*Integer?*/ insertIndex){
165 // summary:
166 // Called whenever a page is added to the container.
167 // Create button corresponding to the page.
168 // tags:
169 // private
170
171 // create an instance of the button widget
172 // (remove typeof buttonWidget == string support in 2.0)
173 var Cls = lang.isString(this.buttonWidget) ? lang.getObject(this.buttonWidget) : this.buttonWidget;
174 var button = new Cls({
175 id: this.id + "_" + page.id,
176 name: this.id + "_" + page.id,
177 label: page.title,
178 disabled: page.disabled,
179 ownerDocument: this.ownerDocument,
180 dir: page.dir,
181 lang: page.lang,
182 textDir: page.textDir,
183 showLabel: page.showTitle,
184 iconClass: page.iconClass,
185 closeButton: page.closable,
186 title: page.tooltip,
187 page: page
188 });
189
190 this.addChild(button, insertIndex);
191 this.pane2button[page.id] = button;
192 page.controlButton = button; // this value might be overwritten if two tabs point to same container
193 if(!this._currentChild){
194 // If this is the first child then StackContainer will soon publish that it's selected,
195 // but before that StackContainer calls layout(), and before layout() is called the
196 // StackController needs to have the proper height... which means that the button needs
197 // to be marked as selected now. See test_TabContainer_CSS.html for test.
198 this.onSelectChild(page);
199 }
200 },
201
202 onRemoveChild: function(/*dijit/_WidgetBase*/ page){
203 // summary:
204 // Called whenever a page is removed from the container.
205 // Remove the button corresponding to the page.
206 // tags:
207 // private
208
209 if(this._currentChild === page){ this._currentChild = null; }
210
211 var button = this.pane2button[page.id];
212 if(button){
213 this.removeChild(button);
214 delete this.pane2button[page.id];
215 button.destroy();
216 }
217 delete page.controlButton;
218 },
219
220 onSelectChild: function(/*dijit/_WidgetBase*/ page){
221 // summary:
222 // Called when a page has been selected in the StackContainer, either by me or by another StackController
223 // tags:
224 // private
225
226 if(!page){ return; }
227
228 if(this._currentChild){
229 var oldButton=this.pane2button[this._currentChild.id];
230 oldButton.set('checked', false);
231 oldButton.focusNode.setAttribute("tabIndex", "-1");
232 }
233
234 var newButton=this.pane2button[page.id];
235 newButton.set('checked', true);
236 this._currentChild = page;
237 newButton.focusNode.setAttribute("tabIndex", "0");
238 var container = registry.byId(this.containerId);
239 container.containerNode.setAttribute("aria-labelledby", newButton.id);
240 },
241
242 onButtonClick: function(/*dijit/_WidgetBase*/ page){
243 // summary:
244 // Called whenever one of my child buttons is pressed in an attempt to select a page
245 // tags:
246 // private
247
248 var button = this.pane2button[page.id];
249
250 // For TabContainer where the tabs are <span>, need to set focus explicitly when left/right arrow
251 focus.focus(button.focusNode);
252
253 if(this._currentChild && this._currentChild.id === page.id) {
254 //In case the user clicked the checked button, keep it in the checked state because it remains to be the selected stack page.
255 button.set('checked', true);
256 }
257 var container = registry.byId(this.containerId);
258 container.selectChild(page);
259 },
260
261 onCloseButtonClick: function(/*dijit/_WidgetBase*/ page){
262 // summary:
263 // Called whenever one of my child buttons [X] is pressed in an attempt to close a page
264 // tags:
265 // private
266
267 var container = registry.byId(this.containerId);
268 container.closeChild(page);
269 if(this._currentChild){
270 var b = this.pane2button[this._currentChild.id];
271 if(b){
272 focus.focus(b.focusNode || b.domNode);
273 }
274 }
275 },
276
277 // TODO: this is a bit redundant with forward, back api in StackContainer
278 adjacent: function(/*Boolean*/ forward){
279 // summary:
280 // Helper for onkeypress to find next/previous button
281 // tags:
282 // private
283
284 if(!this.isLeftToRight() && (!this.tabPosition || /top|bottom/.test(this.tabPosition))){ forward = !forward; }
285 // find currently focused button in children array
286 var children = this.getChildren();
287 var idx = array.indexOf(children, this.pane2button[this._currentChild.id]),
288 current = children[idx];
289
290 // Pick next/previous non-disabled button to focus on. If we get back to the original button it means
291 // that all buttons must be disabled, so return current child to avoid an infinite loop.
292 var child;
293 do{
294 idx = (idx + (forward ? 1 : children.length - 1)) % children.length;
295 child = children[idx];
296 }while(child.disabled && child != current);
297
298 return child; // dijit/_WidgetBase
299 },
300
301 onkeypress: function(/*Event*/ e){
302 // summary:
303 // Handle keystrokes on the page list, for advancing to next/previous button
304 // and closing the current page if the page is closable.
305 // tags:
306 // private
307
308 if(this.disabled || e.altKey ){ return; }
309 var forward = null;
310 if(e.ctrlKey || !e._djpage){
311 switch(e.charOrCode){
312 case keys.LEFT_ARROW:
313 case keys.UP_ARROW:
314 if(!e._djpage){ forward = false; }
315 break;
316 case keys.PAGE_UP:
317 if(e.ctrlKey){ forward = false; }
318 break;
319 case keys.RIGHT_ARROW:
320 case keys.DOWN_ARROW:
321 if(!e._djpage){ forward = true; }
322 break;
323 case keys.PAGE_DOWN:
324 if(e.ctrlKey){ forward = true; }
325 break;
326 case keys.HOME:
327 // Navigate to first non-disabled child
328 var children = this.getChildren();
329 for(var idx = 0; idx < children.length; idx++){
330 var child = children[idx];
331 if(!child.disabled){
332 this.onButtonClick(child.page);
333 break;
334 }
335 }
336 event.stop(e);
337 break;
338 case keys.END:
339 // Navigate to last non-disabled child
340 var children = this.getChildren();
341 for(var idx = children.length-1; idx >= 0; idx--){
342 var child = children[idx];
343 if(!child.disabled){
344 this.onButtonClick(child.page);
345 break;
346 }
347 }
348 event.stop(e);
349 break;
350 case keys.DELETE:
351 if(this._currentChild.closable){
352 this.onCloseButtonClick(this._currentChild);
353 }
354 event.stop(e);
355 break;
356 default:
357 if(e.ctrlKey){
358 if(e.charOrCode === keys.TAB){
359 this.onButtonClick(this.adjacent(!e.shiftKey).page);
360 event.stop(e);
361 }else if(e.charOrCode == "w"){
362 if(this._currentChild.closable){
363 this.onCloseButtonClick(this._currentChild);
364 }
365 event.stop(e); // avoid browser tab closing.
366 }
367 }
368 }
369 // handle next/previous page navigation (left/right arrow, etc.)
370 if(forward !== null){
371 this.onButtonClick(this.adjacent(forward).page);
372 event.stop(e);
373 }
374 }
375 },
376
377 onContainerKeyPress: function(/*Object*/ info){
378 // summary:
379 // Called when there was a keypress on the container
380 // tags:
381 // private
382 info.e._djpage = info.page;
383 this.onkeypress(info.e);
384 }
385 });
386
387 StackController.StackButton = StackButton; // for monkey patching
388
389 return StackController;
390 });