3 * Copyright(c) 2006-2010 Sencha Inc.
5 * http://www.sencha.com/license
10 Ext.a11y.Frame = Ext.extend(Object, {
13 constructor: function(size, color){
14 this.setSize(size || 1);
15 this.setColor(color || '15428B');
19 if (!this.initialized) {
24 this.ct = Ext.DomHelper.append(document.body, {
25 cls: 'x-a11y-focusframe'
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
33 s.visibilityMode = Ext.Element.DISPLAY;
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]);
50 this.unframeTask = new Ext.util.DelayedTask(function(){
51 if (this.initialized) {
59 this.initialized = true;
65 this.unframeTask.cancel();
66 this.frameTask.delay(2, false, false, [el]);
71 this.unframeTask.delay(2);
74 setSize: function(size){
78 setColor: function(color){
83 Ext.a11y.FocusFrame = new Ext.a11y.Frame(2, '15428B');
84 Ext.a11y.RelayFrame = new Ext.a11y.Frame(1, '6B8CBF');
86 Ext.a11y.Focusable = Ext.extend(Ext.util.Observable, {
87 constructor: function(el, relayTo, noFrame, frameEl){
88 Ext.a11y.Focusable.superclass.constructor.call(this);
90 this.addEvents('focus', 'blur', 'left', 'right', 'up', 'down', 'esc', 'enter', 'space');
92 if (el instanceof Ext.Component) {
94 this.setComponent(el);
97 this.el = Ext.get(el);
98 this.setComponent(null);
101 this.setRelayTo(relayTo)
102 this.setNoFrame(noFrame);
103 this.setFrameEl(frameEl);
107 Ext.a11y.FocusMgr.register(this);
111 this.el.dom.tabIndex = '1';
112 this.el.addClass('x-a11y-focusable');
116 keydown: this.onKeyDown,
121 setRelayTo: function(relayTo){
122 this.relayTo = relayTo ? Ext.a11y.FocusMgr.get(relayTo) : null;
125 setNoFrame: function(noFrame){
126 this.noFrame = (noFrame === true) ? true : false;
129 setFrameEl: function(frameEl){
130 this.frameEl = frameEl && Ext.get(frameEl) || this.el;
133 setComponent: function(cmp){
134 this.component = cmp || null;
137 onKeyDown: function(e, t){
138 var k = e.getKey(), SK = Ext.a11y.Focusable.SpecialKeys, ret, tf;
140 tf = (t !== this.el.dom) ? Ext.a11y.FocusMgr.get(t, true) : this;
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'));
147 if (SK[k] !== undefined) {
148 ret = this.fireEvent(SK[k], e, t, tf, this);
150 if (ret === false || this.fireEvent('keydown', e, t, tf, this) === false) {
163 onFocus: function(e, t){
164 this.el.addClass('x-a11y-focused');
166 this.relayTo.el.addClass('x-a11y-focused-relay');
167 if (!this.relayTo.noFrame) {
168 Ext.a11y.FocusFrame.frame(this.relayTo.frameEl);
171 Ext.a11y.RelayFrame.frame(this.frameEl);
176 Ext.a11y.FocusFrame.frame(this.frameEl);
180 this.fireEvent('focus', e, t, this);
183 onBlur: function(e, t){
185 this.relayTo.el.removeClass('x-a11y-focused-relay');
186 Ext.a11y.RelayFrame.unframe();
188 this.el.removeClass('x-a11y-focused');
189 Ext.a11y.FocusFrame.unframe();
190 this.fireEvent('blur', e, t, this);
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');
200 this.relayTo.el.removeClass('x-a11y-focused-relay');
205 Ext.a11y.FocusItem = Ext.extend(Object, {
206 constructor: function(el, enableTabbing){
207 Ext.a11y.FocusItem.superclass.constructor.call(this);
209 this.el = Ext.get(el);
210 this.fi = new Ext.a11y.Focusable(el);
211 this.fi.setComponent(this);
213 this.fi.on('tab', this.onTab, this);
215 this.enableTabbing = enableTabbing === true ? true : false;
218 getEnterItem: function(){
219 if (this.enableTabbing) {
220 var items = this.getFocusItems();
221 if (items && items.length) {
227 getFocusItems: function(){
228 if (this.enableTabbing) {
229 return this.el.query('a, button, input, select');
234 onTab: function(e, t){
235 var items = this.getFocusItems(), i;
237 if (items && items.length && (i = items.indexOf(t)) !== -1) {
238 if (e.shiftKey && i > 0) {
240 items[i - 1].focus();
241 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);
245 if (!e.shiftKey && i < items.length - 1) {
247 items[i + 1].focus();
248 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);
255 if (this.enableTabbing) {
256 var items = this.getFocusItems();
257 if (items && items.length) {
259 Ext.a11y.FocusFrame.frame.defer(20, Ext.a11y.FocusFrame, [this.el]);
271 Ext.a11y.FocusMgr = function(){
272 var all = new Ext.util.MixedCollection();
275 register: function(f){
276 all.add(f.el && Ext.id(f.el), f);
279 unregister: function(f){
283 get: function(el, noCreate){
284 return all.get(Ext.id(el)) || (noCreate ? false : new Ext.a11y.Focusable(el));
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';
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){
307 Ext.override(Ext.Component, {
308 initFocus: Ext.emptyFn,
309 initARIA: Ext.emptyFn
312 Ext.override(Ext.Container, {
317 initFocus: function(){
318 if (!this.fi && !this.noFocus) {
319 this.fi = new Ext.a11y.Focusable(this);
331 this.isFocusable = false;
334 this.on('show', function(){
335 this.isFocusable = true;
337 this.on('hide', function(){
338 this.isFocusable = false;
351 var eitem = this.getEnterItem();
357 onFocus: Ext.emptyFn,
360 onTab: function(e, t, tf){
361 var rf = tf.relayTo || tf;
362 if (rf.component && rf.component !== this) {
364 var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);
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) {
378 onEsc: function(e, t){
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
388 this.ownerCt.focus();
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();
397 if (si && si.getCount() > 1) {
405 getFocusItems: function(){
407 this.items.filterBy(function(o){
408 return o.isFocusable;
413 getEnterItem: function(){
414 var ci = this.getFocusItems(), length = ci ? ci.getCount() : 0;
417 return ci.first().getEnterItem && ci.first().getEnterItem() || ci.first();
419 else if (length > 1) {
424 getNextFocus: function(current){
425 var items = this.getFocusItems(), next = current, i = items.indexOf(current), length = items.getCount();
427 if (i === length - 1) {
428 next = items.first();
431 next = items.get(i + 1);
436 getPreviousFocus: function(current){
437 var items = this.getFocusItems(), prev = current, i = items.indexOf(current), length = items.getCount();
443 prev = items.get(i - 1);
448 getFocusable : function() {
453 Ext.override(Ext.Panel, {
455 * @cfg {Boolean} enableTabbing <tt>true</tt> to enable tabbing. Default is <tt>false</tt>.
457 getFocusItems: function(){
458 // items gets all the items inside the body
459 var items = Ext.Panel.superclass.getFocusItems.call(this), bodyFocus = null;
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);
466 // but panels can also have tbar, bbar, fbar
467 if (this.tbar && this.topToolbar) {
468 items.insert(0, this.topToolbar);
470 if (this.bbar && this.bottomToolbar) {
471 items.add(this.bottomToolbar);
474 items.add(this.fbar);
481 Ext.override(Ext.TabPanel, {
483 initFocus: function(){
484 Ext.TabPanel.superclass.initFocus.call(this);
493 if (!this.activeTab) {
497 var prev = this.items.itemAt(this.items.indexOf(this.activeTab) - 1);
499 this.setActiveTab(prev);
504 onRight: function(e){
505 if (!this.activeTab) {
509 var next = this.items.itemAt(this.items.indexOf(this.activeTab) + 1);
511 this.setActiveTab(next);
517 Ext.override(Ext.tree.TreeNodeUI, {
520 this.node.getOwnerTree().bodyFocus.focus();
524 Ext.override(Ext.tree.TreePanel, {
526 afterRender : function(){
527 Ext.tree.TreePanel.superclass.afterRender.call(this);
529 if(!this.rootVisible){
530 this.root.renderChildren();
532 this.bodyFocus = new Ext.a11y.FocusItem(this.body.down('.x-tree-root-ct'));
533 this.bodyFocus.fi.setFrameEl(this.body);
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);
545 Ext.override(Ext.Button, {
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);
561 this.mon(this.fi, 'down', this.showMenu, this);
562 this.on('menuhide', this.focus, this);
566 this.isFocusable = false;
569 this.on('show', function(){
570 this.isFocusable = true;
572 this.on('hide', function(){
573 this.isFocusable = false;
586 if (!this.disabled) {
587 this.el.addClass("x-btn-focus");
592 this.el.removeClass("x-btn-focus");
596 Ext.override(Ext.Toolbar, {
597 initFocus: function(){
598 Ext.Toolbar.superclass.initFocus.call(this);
605 this.on('focus', this.onButtonFocus, this, {
611 var item = Ext.Toolbar.superclass.add.apply(this, arguments);
612 if(!item || !item.events) {
615 if (item.rendered && item.fi !== undefined) {
616 item.fi.setRelayTo(this.el);
617 this.relayEvents(item.fi, ['focus']);
620 item.on('render', function(){
621 if (item.fi !== undefined) {
622 item.fi.setRelayTo(this.el);
623 this.relayEvents(item.fi, ['focus']);
633 var items = this.getFocusItems();
634 if (items && items.getCount() > 0) {
635 if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {
636 this.lastFocus.focus();
639 items.first().focus();
644 onButtonFocus: function(e, t, tf){
645 this.lastFocus = tf.component || null;
648 onLeft: function(e, t, tf){
650 this.getPreviousFocus(tf.component).focus();
653 onRight: function(e, t, tf){
655 this.getNextFocus(tf.component).focus();
658 getEnterItem: Ext.emptyFn,
663 Ext.override(Ext.menu.BaseItem, {
664 initFocus: function(){
665 this.fi = new Ext.a11y.Focusable(this, this.parentMenu && this.parentMenu.el || null, true);
669 Ext.override(Ext.menu.Menu, {
670 initFocus: function(){
671 this.fi = new Ext.a11y.Focusable(this);
672 this.focusEl = this.fi;
676 Ext.a11y.WindowMgr = new Ext.WindowGroup();
678 Ext.apply(Ext.WindowMgr, {
679 bringToFront: function(win){
680 Ext.a11y.WindowMgr.bringToFront.call(this, win);
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();
700 Ext.override(Ext.form.Field, {
704 initFocus: function(){
705 this.fi = this.fi || new Ext.a11y.Focusable(this, null, true);
707 Ext.form.Field.superclass.initFocus.call(this);
710 this.isFocusable = false;
713 this.on('show', function(){
714 this.isFocusable = true;
716 this.on('hide', function(){
717 this.isFocusable = false;
722 Ext.override(Ext.FormPanel, {
723 initFocus: function(){
724 Ext.FormPanel.superclass.initFocus.call(this);
725 this.on('focus', this.onFieldFocus, this, {
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);
738 formItemAdd: function(item){
739 item.on('render', function(field){
740 field.fi.setRelayTo(this.el);
741 this.relayEvents(field.fi, ['focus']);
748 var items = this.getFocusItems();
749 if (items && items.getCount() > 0) {
750 if (this.lastFocus && items.indexOf(this.lastFocus) !== -1) {
751 this.lastFocus.focus();
754 items.first().focus();
759 onFieldFocus: function(e, t, tf){
760 this.lastFocus = tf.component || null;
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);
773 Ext.FormPanel.superclass.onTab.apply(this, arguments);
776 getNextFocus: function(current){
777 var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();
779 return (i < length - 1) ? items.get(i + 1) : false;
782 getPreviousFocus: function(current){
783 var items = this.getFocusItems(), i = items.indexOf(current), length = items.getCount();
785 return (i > 0) ? items.get(i - 1) : false;
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);
797 onTab: function(e, t, tf, f){
801 items = this.getFocusItems();
802 if (items && items.getCount() > 0) {
803 items.first().focus();
807 var rf = tf.relayTo || tf;
808 var item = e.shiftKey ? this.getPreviousFocus(rf.component) : this.getNextFocus(rf.component);