Upgrade to ExtJS 3.0.0 - Released 07/06/2009
[extjs.git] / examples / ux / Focus.js
1 /*!
2  * Ext JS Library 3.0.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 (function(){\r
8 Ext.ns('Ext.a11y');\r
9 \r
10 Ext.a11y.Frame = Ext.extend(Object, {\r
11     initialized: false,\r
12     \r
13     constructor: function(size, color){\r
14         this.setSize(size || 1);\r
15         this.setColor(color || '15428B');\r
16     },\r
17     \r
18     init: function(){\r
19         if (!this.initialized) {\r
20             this.sides = [];\r
21             \r
22             var s, i;\r
23             \r
24             this.ct = Ext.DomHelper.append(document.body, {\r
25                 cls: 'x-a11y-focusframe'\r
26             }, true);\r
27             \r
28             for (i = 0; i < 4; i++) {\r
29                 s = Ext.DomHelper.append(this.ct, {\r
30                     cls: 'x-a11y-focusframe-side',\r
31                     style: 'background-color: #' + this.color\r
32                 }, true);\r
33                 s.visibilityMode = Ext.Element.DISPLAY;\r
34                 this.sides.push(s);\r
35             }\r
36             \r
37             this.frameTask = new Ext.util.DelayedTask(function(el){\r
38                 var newEl = Ext.get(el);\r
39                 if (newEl != this.curEl) {\r
40                     var w = newEl.getWidth();\r
41                     var h = newEl.getHeight();\r
42                     this.sides[0].show().setSize(w, this.size).anchorTo(el, 'tl', [0, -1]);\r
43                     this.sides[2].show().setSize(w, this.size).anchorTo(el, 'bl', [0, -1]);\r
44                     this.sides[1].show().setSize(this.size, h).anchorTo(el, 'tr', [-1, 0]);\r
45                     this.sides[3].show().setSize(this.size, h).anchorTo(el, 'tl', [-1, 0]);\r
46                     this.curEl = newEl;\r
47                 }\r
48             }, this);\r
49             \r
50             this.unframeTask = new Ext.util.DelayedTask(function(){\r
51                 if (this.initialized) {\r
52                     this.sides[0].hide();\r
53                     this.sides[1].hide();\r
54                     this.sides[2].hide();\r
55                     this.sides[3].hide();\r
56                     this.curEl = null;\r
57                 }\r
58             }, this);\r
59             this.initialized = true;\r
60         }\r
61     },\r
62     \r
63     frame: function(el){\r
64         this.init();\r
65         this.unframeTask.cancel();\r
66         this.frameTask.delay(2, false, false, [el]);\r
67     },\r
68     \r
69     unframe: function(){\r
70         this.init();\r
71         this.unframeTask.delay(2);\r
72     },\r
73     \r
74     setSize: function(size){\r
75         this.size = size;\r
76     },\r
77     \r
78     setColor: function(color){\r
79         this.color = color;\r
80     }\r
81 });\r
82 \r
83 Ext.a11y.FocusFrame = new Ext.a11y.Frame(2, '15428B');\r
84 Ext.a11y.RelayFrame = new Ext.a11y.Frame(1, '6B8CBF');\r
85 \r
86 Ext.a11y.Focusable = Ext.extend(Ext.util.Observable, {\r
87     constructor: function(el, relayTo, noFrame, frameEl){\r
88         Ext.a11y.Focusable.superclass.constructor.call(this);\r
89         \r
90         this.addEvents('focus', 'blur', 'left', 'right', 'up', 'down', 'esc', 'enter', 'space');\r
91         \r
92         if (el instanceof Ext.Component) {\r
93             this.el = el.el;\r
94             this.setComponent(el);\r
95         }\r
96         else {\r
97             this.el = Ext.get(el);\r
98             this.setComponent(null);\r
99         }\r
100         \r
101         this.setRelayTo(relayTo)\r
102         this.setNoFrame(noFrame);\r
103         this.setFrameEl(frameEl);\r
104         \r
105         this.init();\r
106         \r
107         Ext.a11y.FocusMgr.register(this);\r
108     },\r
109     \r
110     init: function(){\r
111         this.el.dom.tabIndex = '1';\r
112         this.el.addClass('x-a11y-focusable');\r
113         this.el.on({\r
114             focus: this.onFocus,\r
115             blur: this.onBlur,\r
116             keydown: this.onKeyDown,\r
117             scope: this\r
118         });\r
119     },\r
120     \r
121     setRelayTo: function(relayTo){\r
122         this.relayTo = relayTo ? Ext.a11y.FocusMgr.get(relayTo) : null;\r
123     },\r
124     \r
125     setNoFrame: function(noFrame){\r
126         this.noFrame = (noFrame === true) ? true : false;\r
127     },\r
128     \r
129     setFrameEl: function(frameEl){\r
130         this.frameEl = frameEl && Ext.get(frameEl) || this.el;\r
131     },\r
132     \r
133     setComponent: function(cmp){\r
134         this.component = cmp || null;\r
135     },\r
136     \r
137     onKeyDown: function(e, t){\r
138         var k = e.getKey(), SK = Ext.a11y.Focusable.SpecialKeys, ret, tf;\r
139         \r
140         tf = (t !== this.el.dom) ? Ext.a11y.FocusMgr.get(t, true) : this;\r
141         if (!tf) {\r
142             // this can happen when you are on a focused item within a panel body\r
143             // that is not a Ext.a11y.Focusable\r
144             tf = Ext.a11y.FocusMgr.get(Ext.fly(t).parent('.x-a11y-focusable'));\r
145         }\r
146         \r
147         if (SK[k] !== undefined) {\r
148             ret = this.fireEvent(SK[k], e, t, tf, this);\r
149         }\r
150         if (ret === false || this.fireEvent('keydown', e, t, tf, this) === false) {\r
151             e.stopEvent();\r
152         }\r
153     },\r
154     \r
155     focus: function(){\r
156         this.el.dom.focus();\r
157     },\r
158     \r
159     blur: function(){\r
160         this.el.dom.blur();\r
161     },\r
162     \r
163     onFocus: function(e, t){\r
164         this.el.addClass('x-a11y-focused');\r
165         if (this.relayTo) {\r
166             this.relayTo.el.addClass('x-a11y-focused-relay');\r
167             if (!this.relayTo.noFrame) {\r
168                 Ext.a11y.FocusFrame.frame(this.relayTo.frameEl);\r
169             }\r
170             if (!this.noFrame) {\r
171                 Ext.a11y.RelayFrame.frame(this.frameEl);\r
172             }\r
173         }\r
174         else {\r
175             if (!this.noFrame) {\r
176                 Ext.a11y.FocusFrame.frame(this.frameEl);\r
177             }\r
178         }\r
179         \r
180         this.fireEvent('focus', e, t, this);\r
181     },\r
182     \r
183     onBlur: function(e, t){\r
184         if (this.relayTo) {\r
185             this.relayTo.el.removeClass('x-a11y-focused-relay');\r
186             Ext.a11y.RelayFrame.unframe();\r
187         }\r
188         this.el.removeClass('x-a11y-focused');\r
189         Ext.a11y.FocusFrame.unframe();\r
190         this.fireEvent('blur', e, t, this);\r
191     },\r
192     \r
193     destroy: function(){\r
194         this.el.un('keydown', this.onKeyDown);\r
195         this.el.un('focus', this.onFocus);\r
196         this.el.un('blur', this.onBlur);\r
197         this.el.removeClass('x-a11y-focusable');\r
198         this.el.removeClass('x-a11y-focused');\r
199         if (this.relayTo) {\r
200             this.relayTo.el.removeClass('x-a11y-focused-relay');\r
201         }\r
202     }\r
203 });\r
204 \r
205 Ext.a11y.FocusItem = Ext.extend(Object, {\r
206     constructor: function(el, enableTabbing){\r
207         Ext.a11y.FocusItem.superclass.constructor.call(this);\r
208         \r
209         this.el = Ext.get(el);\r
210         this.fi = new Ext.a11y.Focusable(el);\r
211         this.fi.setComponent(this);\r
212         \r
213         this.fi.on('tab', this.onTab, this);\r
214         \r
215         this.enableTabbing = enableTabbing === true ? true : false;\r
216     },\r
217     \r
218     getEnterItem: function(){\r
219         if (this.enableTabbing) {\r
220             var items = this.getFocusItems();\r
221             if (items && items.length) {\r
222                 return items[0];\r
223             }\r
224         }\r
225     },\r
226     \r
227     getFocusItems: function(){\r
228         if (this.enableTabbing) {\r
229             return this.el.query('a, button, input, select');\r
230         }\r
231         return null;\r
232     },\r
233     \r
234     onTab: function(e, t){\r
235         var items = this.getFocusItems(), i;\r
236         \r
237         if (items && items.length && (i = items.indexOf(t)) !== -1) {\r
238             if (e.shiftKey && i > 0) {\r
239                 e.stopEvent();\r
240                 items[i - 1].focus();\r
241                 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
242                 return;\r
243             }\r
244             else \r
245                 if (!e.shiftKey && i < items.length - 1) {\r
246                     e.stopEvent();\r
247                     items[i + 1].focus();\r
248                     Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
249                     return;\r
250                 }\r
251         }\r
252     },\r
253     \r
254     focus: function(){\r
255         if (this.enableTabbing) {\r
256             var items = this.getFocusItems();\r
257             if (items && items.length) {\r
258                 items[0].focus();\r
259                 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);\r
260                 return;\r
261             }\r
262         }\r
263         this.fi.focus();\r
264     },\r
265     \r
266     blur: function(){\r
267         this.fi.blur();\r
268     }\r
269 });\r
270 \r
271 Ext.a11y.FocusMgr = function(){\r
272     var all = new Ext.util.MixedCollection();\r
273     \r
274     return {\r
275         register: function(f){\r
276             all.add(f.el && Ext.id(f.el), f);\r
277         },\r
278         \r
279         unregister: function(f){\r
280             all.remove(f);\r
281         },\r
282         \r
283         get: function(el, noCreate){\r
284             return all.get(Ext.id(el)) || (noCreate ? false : new Ext.a11y.Focusable(el));\r
285         },\r
286         \r
287         all: all\r
288     }\r
289 }();\r
290 \r
291 Ext.a11y.Focusable.SpecialKeys = {};\r
292 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.LEFT] = 'left';\r
293 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.RIGHT] = 'right';\r
294 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.DOWN] = 'down';\r
295 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.UP] = 'up';\r
296 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ESC] = 'esc';\r
297 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.ENTER] = 'enter';\r
298 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.SPACE] = 'space';\r
299 Ext.a11y.Focusable.SpecialKeys[Ext.EventObjectImpl.prototype.TAB] = 'tab';\r
300 \r
301 // we use the new observeClass method to fire our new initFocus method on components\r
302 Ext.util.Observable.observeClass(Ext.Component);\r
303 Ext.Component.on('render', function(cmp){\r
304     cmp.initFocus();\r
305     cmp.initARIA();\r
306 });\r
307 Ext.override(Ext.Component, {\r
308     initFocus: Ext.emptyFn,\r
309     initARIA: Ext.emptyFn\r
310 });\r
311 \r
312 Ext.override(Ext.Container, {\r
313     isFocusable: true,\r
314     noFocus: false,\r
315     \r
316     // private\r
317     initFocus: function(){\r
318         if (!this.fi && !this.noFocus) {\r
319             this.fi = new Ext.a11y.Focusable(this);\r
320         }\r
321         this.mon(this.fi, {\r
322             focus: this.onFocus,\r
323             blur: this.onBlur,\r
324             tab: this.onTab,\r
325             enter: this.onEnter,\r
326             esc: this.onEsc,\r
327             scope: this\r
328         });\r
329         \r
330         if (this.hidden) {\r
331             this.isFocusable = false;\r
332         }\r
333         \r
334         this.on('show', function(){\r
335             this.isFocusable = true;\r
336         }, this);\r
337         this.on('hide', function(){\r
338             this.isFocusable = false;\r
339         }, this);\r
340     },\r
341     \r
342     focus: function(){\r
343         this.fi.focus();\r
344     },\r
345     \r
346     blur: function(){\r
347         this.fi.blur();\r
348     },\r
349     \r
350     enter: function(){\r
351         var eitem = this.getEnterItem();\r
352         if (eitem) {\r
353             eitem.focus();\r
354         }\r
355     },\r
356     \r
357     onFocus: Ext.emptyFn,\r
358     onBlur: Ext.emptyFn,\r
359     \r
360     onTab: function(e, t, tf){\r
361         var rf = tf.relayTo || tf;\r
362         if (rf.component && rf.component !== this) {\r
363             e.stopEvent();\r
364             var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);\r
365             item.focus();\r
366         }\r
367     },\r
368     \r
369     onEnter: function(e, t, tf){\r
370         // check to see if enter is pressed while "on" the panel\r
371         if (tf.component && tf.component === this) {\r
372             e.stopEvent();\r
373             this.enter();\r
374         }\r
375         e.stopPropagation();\r
376     },\r
377     \r
378     onEsc: function(e, t){\r
379         e.preventDefault();\r
380         \r
381         // check to see if esc is pressed while "inside" the panel\r
382         // or while "on" the panel\r
383         if (t === this.el.dom) {\r
384             // "on" the panel, check if this panel has an owner panel and focus that\r
385             // we dont stop the event in this case so that this same check will be\r
386             // done for this ownerCt\r
387             if (this.ownerCt) {\r
388                 this.ownerCt.focus();\r
389             }\r
390         }\r
391         else {\r
392             // we were inside the panel when esc was pressed,\r
393             // so go back "on" the panel\r
394             if (this.ownerCt && this.ownerCt.isFocusable) {\r
395                 var si = this.ownerCt.getFocusItems();\r
396                 \r
397                 if (si && si.getCount() > 1) {\r
398                     e.stopEvent();\r
399                 }\r
400             }\r
401             this.focus();\r
402         }\r
403     },\r
404     \r
405     getFocusItems: function(){\r
406         return this.items &&\r
407         this.items.filterBy(function(o){\r
408             return o.isFocusable;\r
409         }) ||\r
410         null;\r
411     },\r
412     \r
413     getEnterItem: function(){\r
414         var ci = this.getFocusItems(), length = ci ? ci.getCount() : 0;\r
415         \r
416         if (length === 1) {\r
417             return ci.first().getEnterItem && ci.first().getEnterItem() || ci.first();\r
418         }\r
419         else \r
420             if (length > 1) {\r
421                 return ci.first();\r
422             }\r
423     },\r
424     \r
425     getNextFocus: function(current){\r
426         var items = this.getFocusItems(), next = current, i = items.indexOf(current), length = items.getCount();\r
427         \r
428         if (i === length - 1) {\r
429             next = items.first();\r
430         }\r
431         else {\r
432             next = items.get(i + 1);\r
433         }\r
434         return next;\r
435     },\r
436     \r
437     getPreviousFocus: function(current){\r
438         var items = this.getFocusItems(), prev = current, i = items.indexOf(current), length = items.getCount();\r
439         \r
440         if (i === 0) {\r
441             prev = items.last();\r
442         }\r
443         else {\r
444             prev = items.get(i - 1);\r
445         }\r
446         return prev;\r
447     },\r
448     \r
449     getFocusable : function() {\r
450         return this.fi;\r
451     }\r
452 });\r
453 \r
454 Ext.override(Ext.Panel, {\r
455     /**\r
456      * @cfg {Boolean} enableTabbing <tt>true</tt> to enable tabbing. Default is <tt>false</tt>.\r
457      */        \r
458     getFocusItems: function(){\r
459         // items gets all the items inside the body\r
460         var items = Ext.Panel.superclass.getFocusItems.call(this), bodyFocus = null;\r
461         \r
462         if (!items) {\r
463             items = new Ext.util.MixedCollection();\r
464             this.bodyFocus = this.bodyFocus || new Ext.a11y.FocusItem(this.body, this.enableTabbing);\r
465             items.add('body', this.bodyFocus);\r
466         }\r
467         // but panels can also have tbar, bbar, fbar\r
468         if (this.tbar && this.topToolbar) {\r
469             items.insert(0, this.topToolbar);\r
470         }\r
471         if (this.bbar && this.bottomToolbar) {\r
472             items.add(this.bottomToolbar);\r
473         }\r
474         if (this.fbar) {\r
475             items.add(this.fbar);\r
476         }\r
477         \r
478         return items;\r
479     }\r
480 });\r
481 \r
482 Ext.override(Ext.TabPanel, {\r
483     // private\r
484     initFocus: function(){\r
485         Ext.TabPanel.superclass.initFocus.call(this);\r
486         this.mon(this.fi, {\r
487             left: this.onLeft,\r
488             right: this.onRight,\r
489             scope: this\r
490         });\r
491     },\r
492     \r
493     onLeft: function(e){\r
494         if (!this.activeTab) {\r
495             return;\r
496         }\r
497         e.stopEvent();\r
498         var prev = this.items.itemAt(this.items.indexOf(this.activeTab) - 1);\r
499         if (prev) {\r
500             this.setActiveTab(prev);\r
501         }\r
502         return false;\r
503     },\r
504     \r
505     onRight: function(e){\r
506         if (!this.activeTab) {\r
507             return;\r
508         }\r
509         e.stopEvent();\r
510         var next = this.items.itemAt(this.items.indexOf(this.activeTab) + 1);\r
511         if (next) {\r
512             this.setActiveTab(next);\r
513         }\r
514         return false;\r
515     }\r
516 });\r
517 \r
518 Ext.override(Ext.tree.TreeNodeUI, {\r
519     // private\r
520     focus: function(){\r
521         this.node.getOwnerTree().bodyFocus.focus();\r
522     }\r
523 });\r
524 \r
525 Ext.override(Ext.tree.TreePanel, {\r
526     // private\r
527     afterRender : function(){\r
528         Ext.tree.TreePanel.superclass.afterRender.call(this);\r
529         this.root.render();\r
530         if(!this.rootVisible){\r
531             this.root.renderChildren();\r
532         }\r
533         this.bodyFocus = new Ext.a11y.FocusItem(this.body.down('.x-tree-root-ct'));\r
534         this.bodyFocus.fi.setFrameEl(this.body);\r
535     } \r
536 });\r
537 \r
538 Ext.override(Ext.grid.GridPanel, {\r
539     initFocus: function(){\r
540         Ext.grid.GridPanel.superclass.initFocus.call(this);\r
541         this.bodyFocus = new Ext.a11y.FocusItem(this.view.focusEl);\r
542         this.bodyFocus.fi.setFrameEl(this.body);\r
543     }\r
544 });\r
545 \r
546 Ext.override(Ext.Button, {\r
547     isFocusable: true,\r
548     noFocus: false,\r
549     \r
550     initFocus: function(){\r
551         Ext.Button.superclass.initFocus.call(this);\r
552         this.fi = this.fi || new Ext.a11y.Focusable(this.btnEl, null, null, this.el);\r
553         this.fi.setComponent(this);\r
554         \r
555         this.mon(this.fi, {\r
556             focus: this.onFocus,\r
557             blur: this.onBlur,\r
558             scope: this\r
559         });\r
560         \r
561         if (this.menu) {\r
562             this.mon(this.fi, 'down', this.showMenu, this);\r
563             this.on('menuhide', this.focus, this);\r
564         }\r
565         \r
566         if (this.hidden) {\r
567             this.isFocusable = false;\r
568         }\r
569         \r
570         this.on('show', function(){\r
571             this.isFocusable = true;\r
572         }, this);\r
573         this.on('hide', function(){\r
574             this.isFocusable = false;\r
575         }, this);\r
576     },\r
577     \r
578     focus: function(){\r
579         this.fi.focus();\r
580     },\r
581     \r
582     blur: function(){\r
583         this.fi.blur();\r
584     },\r
585     \r
586     onFocus: function(){\r
587         if (!this.disabled) {\r
588             this.el.addClass("x-btn-focus");\r
589         }\r
590     },\r
591     \r
592     onBlur: function(){\r
593         this.el.removeClass("x-btn-focus");\r
594     }\r
595 });\r
596 \r
597 Ext.override(Ext.Toolbar, {\r
598     initFocus: function(){\r
599         Ext.Toolbar.superclass.initFocus.call(this);\r
600         this.mon(this.fi, {\r
601             left: this.onLeft,\r
602             right: this.onRight,\r
603             scope: this\r
604         });\r
605         \r
606         this.on('focus', this.onButtonFocus, this, {\r
607             stopEvent: true\r
608         });\r
609     },\r
610     \r
611     addItem: function(item){\r
612         Ext.Toolbar.superclass.add.apply(this, arguments);\r
613         if (item.rendered && item.fi !== undefined) {\r
614             item.fi.setRelayTo(this.el);\r
615             this.relayEvents(item.fi, ['focus']);\r
616         }\r
617         else {\r
618             item.on('render', function(){\r
619                 if (item.fi !== undefined) {\r
620                     item.fi.setRelayTo(this.el);\r
621                     this.relayEvents(item.fi, ['focus']);\r
622                 }\r
623             }, this, {\r
624                 single: true\r
625             });\r
626         }\r
627         return item;\r
628     },\r
629     \r
630     onFocus: function(){\r
631         var items = this.getFocusItems();\r
632         if (items && items.getCount() > 0) {\r
633             if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {\r
634                 this.lastFocus.focus();\r
635             }\r
636             else {\r
637                 items.first().focus();\r
638             }\r
639         }\r
640     },\r
641     \r
642     onButtonFocus: function(e, t, tf){\r
643         this.lastFocus = tf.component || null;\r
644     },\r
645     \r
646     onLeft: function(e, t, tf){\r
647         e.stopEvent();\r
648         this.getPreviousFocus(tf.component).focus();\r
649     },\r
650     \r
651     onRight: function(e, t, tf){\r
652         e.stopEvent();\r
653         this.getNextFocus(tf.component).focus();\r
654     },\r
655     \r
656     getEnterItem: Ext.emptyFn,\r
657     onTab: Ext.emptyFn,\r
658     onEsc: Ext.emptyFn\r
659 });\r
660 \r
661 Ext.override(Ext.menu.BaseItem, {\r
662     initFocus: function(){\r
663         this.fi = new Ext.a11y.Focusable(this, this.parentMenu && this.parentMenu.el || null, true);\r
664     }\r
665 });\r
666 \r
667 Ext.override(Ext.menu.Menu, {\r
668     initFocus: function(){\r
669         this.fi = new Ext.a11y.Focusable(this);\r
670         this.focusEl = this.fi;\r
671     }\r
672 });\r
673 \r
674 Ext.a11y.WindowMgr = new Ext.WindowGroup();\r
675 \r
676 Ext.apply(Ext.WindowMgr, {\r
677     bringToFront: function(win){\r
678         Ext.a11y.WindowMgr.bringToFront.call(this, win);\r
679         if (win.modal) {\r
680             win.enter();\r
681         }\r
682         else {\r
683             win.focus();\r
684         }\r
685     }\r
686 });\r
687 \r
688 Ext.override(Ext.Window, {\r
689     initFocus: function(){\r
690         Ext.Window.superclass.initFocus.call(this);\r
691         this.on('beforehide', function(){\r
692             Ext.a11y.RelayFrame.unframe();\r
693             Ext.a11y.FocusFrame.unframe();\r
694         });\r
695     }\r
696 });\r
697 \r
698 Ext.override(Ext.form.Field, {\r
699     isFocusable: true,\r
700     noFocus: false,\r
701     \r
702     initFocus: function(){\r
703         this.fi = this.fi || new Ext.a11y.Focusable(this, null, true);\r
704         \r
705         Ext.form.Field.superclass.initFocus.call(this);\r
706         \r
707         if (this.hidden) {\r
708             this.isFocusable = false;\r
709         }\r
710         \r
711         this.on('show', function(){\r
712             this.isFocusable = true;\r
713         }, this);\r
714         this.on('hide', function(){\r
715             this.isFocusable = false;\r
716         }, this);\r
717     }\r
718 });\r
719 \r
720 Ext.override(Ext.FormPanel, {\r
721     initFocus: function(){\r
722         Ext.FormPanel.superclass.initFocus.call(this);\r
723         this.on('focus', this.onFieldFocus, this, {\r
724             stopEvent: true\r
725         });\r
726     },\r
727     \r
728     // private\r
729     createForm: function(){\r
730         delete this.initialConfig.listeners;\r
731         var form = new Ext.form.BasicForm(null, this.initialConfig);\r
732         form.afterMethod('add', this.formItemAdd, this);\r
733         return form;\r
734     },\r
735     \r
736     formItemAdd: function(item){\r
737         item.on('render', function(field){\r
738             field.fi.setRelayTo(this.el);\r
739             this.relayEvents(field.fi, ['focus']);\r
740         }, this, {\r
741             single: true\r
742         });\r
743     },\r
744     \r
745     onFocus: function(){\r
746         var items = this.getFocusItems();\r
747         if (items && items.getCount() > 0) {\r
748             if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {\r
749                 this.lastFocus.focus();\r
750             }\r
751             else {\r
752                 items.first().focus();\r
753             }\r
754         }\r
755     },\r
756     \r
757     onFieldFocus: function(e, t, tf){\r
758         this.lastFocus = tf.component || null;\r
759     },\r
760     \r
761     onTab: function(e, t, tf){\r
762         if (tf.relayTo.component === this) {\r
763             var item = e.shiftKey ? this.getPreviousFocus(tf.component) : this.getNextFocus(tf.component);\r
764             \r
765             if (item) {\r
766                 ev.stopEvent();\r
767                 item.focus();\r
768                 return;\r
769             }\r
770         }\r
771         Ext.FormPanel.superclass.onTab.apply(this, arguments);\r
772     },\r
773     \r
774     getNextFocus: function(current){\r
775         var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();\r
776         \r
777         return (i < length - 1) ? items.get(i + 1) : false;\r
778     },\r
779     \r
780     getPreviousFocus: function(current){\r
781         var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();\r
782         \r
783         return (i > 0) ? items.get(i - 1) : false;\r
784     }\r
785 });\r
786 \r
787 Ext.override(Ext.Viewport, {\r
788     initFocus: function(){\r
789         Ext.Viewport.superclass.initFocus.apply(this);\r
790         this.mon(Ext.get(document), 'focus', this.focus, this);\r
791         this.mon(Ext.get(document), 'blur', this.blur, this);\r
792         this.fi.setNoFrame(true);\r
793     },\r
794     \r
795     onTab: function(e, t, tf, f){\r
796         e.stopEvent();\r
797         \r
798         if (tf === f) {\r
799             items = this.getFocusItems();\r
800             if (items && items.getCount() > 0) {\r
801                 items.first().focus();\r
802             }\r
803         }\r
804         else {\r
805             var rf = tf.relayTo || tf;\r
806             var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);\r
807             item.focus();\r
808         }\r
809     }\r
810 });\r
811     \r
812 })();