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