Ext.require([ 'Ext.form.*', 'Ext.layout.container.Column', 'Ext.tab.Panel' ]); /** * @class Ext.aria.AriaController *
This class supplies support methods for integrating ARIA support into Components.
* */ Ext.define('Ext.aria.AriaController', { singleton: true, requires: ['Ext.AbstractComponent'], handlerRe: /^on(\w+)/, // This function is called as an interceptor of the target class's fireEvent method // It invokes the event's ariaHandler if present ariaFireEvent: function(evt){ var me = this, evtName = evt.toLowerCase(), args = Array.prototype.slice.call(arguments, 1), classFn = Ext.aria.AriaController.getClassAriaHandler(this.self.prototype, evtName), ariaFn = Ext.aria.AriaController[evtName], result; // Call user event handlers first. result = Ext.util.Observable.prototype.fireEvent.apply(me, arguments); if (result !== false) { // Then AriaController's event handler if (typeof ariaFn === 'function') { result = ariaFn.apply(me, args); } // Then class's configured ARIA event handler if (typeof classFn === 'function') { result = classFn.apply(me, args); } } return result; }, // Examine the passed class prototype for an aria_event_handler, and if not found, continue up the prototype chain. getClassAriaHandler: function(clsProto, evtName) { var curProto = clsProto, generatedHandlerName = 'generated_aria_' + evtName + '_handler', configuredHandlerName = 'aria_' + evtName + '_handler', fn, result = clsProto.hasOwnProperty(generatedHandlerName) ? clsProto[generatedHandlerName] : null; // If there is no generated ariaEventHandler for the class/event, we must generate one // which calls the cascade of event handlers for the inheritance chain if (!result) { var handlerStack = []; // Collect the stack of event handlers from each level in the inheritance tree while (curProto) { if (curProto.hasOwnProperty(configuredHandlerName)) { fn = curProto[configuredHandlerName]; if (fn) { handlerStack.unshift(fn); } } curProto = curProto.superclass; } // Generate a closure which captures the handler stack, and loops through it // to invoke them all. result = clsProto[generatedHandlerName] = (function(handlerStack) { var result = handlerStack.length ? function() { var i = 0, len = handlerStack.length; for (; i < len; i++) { handlerStack[i].apply(this, arguments); } } : Ext.emptyFn; return result; })(handlerStack); } return result; }, processClass: function(cls, props) { var me = this, clsProto = cls.prototype, propName, value, k; for (propName in props) { if (props.hasOwnProperty(propName)) { value = props[propName]; k = me.handlerRe.exec(propName); // Handle properties like onHide: function(){...} // Copy it into ariaHandlers.hide where it can be called // by the fireEvent interceptor if (k && (typeof value === 'function')) { propName = propName.toLowerCase(); // Hang the ariaHandlers on the class's prototype. clsProto['aria_' + k[1].toLowerCase() + '_handler'] = value; } else if (propName === 'role') { clsProto.ariaRole = value; } } } // Replace the class's fireEvent method with our code which intercepts relevant events, // performs known processing (such as setting the role on render), and calls any defined // ariaHandlers clsProto.fireEvent = me.ariaFireEvent; }, // ARIA event handler for the render event. render: function() { var me = this, p = me.pendingProps, len = p ? p.length : 0, i = 0; // Apply any pending ARIA updates from when applied before render for (; i < len; i++) { me.updateAria.apply(me, p[i]); } } }, function() { // Inject an updateAria method into AbstractComponent's prorotype Ext.override(Ext.AbstractComponent, { getElConfig: function() { var result = this.callOverridden(); result.role = this.ariaRole; return result; }, updateAria: function(el, props) { var me = this; // Queue the attributes up if not rendered. // They will be applied in the global render handler. if (!me.rendered) { if (!me.pendingProps) { me.pendingProps = []; } me.pendingProps.push(Array.prototype.slice.call(arguments)); return; } // The one argument form updates the actionEl if (arguments.length == 1) { props = el; el = this.getActionEl(); } // Ensure events are added if (!me.events.beforeariaupdate) { me.addEvents('beforeariaupdate', 'ariaupdate'); } if (me.fireEvent('beforeariaupdate', el, props) !== false) { Ext.fly(el).set(props); me.fireEvent('ariaupdate', el, props); } } }); Ext.core.Element.prototype.set = function(o, useSet) { var el = this.dom, attr, val; useSet = (useSet !== false) && !!el.setAttribute; for (attr in o) { if (o.hasOwnProperty(attr)) { val = o[attr]; if (attr == 'style') { DH.applyStyles(el, val); } else if (attr == 'cls') { el.className = val; } else if (useSet) { if (val === undefined) { el.removeAttribute(attr); } else { el.setAttribute(attr, val); } } else { el[attr] = val; } } } return this; }; }); Ext.onReady(function() { Ext.aria.AriaController.processClass(Ext.AbstractComponent, { onDisable: function() { this.updateAria(this.getActionEl(), { 'aria-disabled': true }) }, onEnable: function() { this.updateAria(this.getActionEl(), { 'aria-disabled': false }) }, onHide: function() { this.updateAria(this.getActionEl(), { 'aria-hidden': true }) }, onShow: function() { this.updateAria(this.getActionEl(), { 'aria-hidden': false }) } }); Ext.aria.AriaController.processClass(Ext.form.FieldSet, { onRender: function() { this.updateAria({ 'aria-expanded': !this.collapsed }); }, onExpand: function() { this.updateAria({ 'aria-expanded': true }); }, onCollapse: function() { this.updateAria({ 'aria-expanded': false }); } }); Ext.aria.AriaController.processClass(Ext.form.field.Base, { onRender: function() { if (this.labelEl) { this.updateAria(this.inputEl, { 'aria-labelledby': this.labelEl.id }); } }, onValidityChange: function(f, isValid) { this.updateAria(this.inputEl, { 'aria-invalid': !isValid, 'aria-describedby': isValid ? undefined : f.errorEl.id }); } }); Ext.aria.AriaController.processClass(Ext.form.field.Text, { role: 'textbox' }); Ext.aria.AriaController.processClass(Ext.form.field.Number, { role: 'spinbutton', onRender: function() { var me = this, props = {}; if (isFinite(me.minValue)) { props['aria-valuemin'] = me.minValue; } if (isFinite(me.maxValue)) { props['aria-valuemax'] = me.maxValue; } me.updateAria(me.inputEl, props); }, onChange: function(f) { var v = this.getValue(); this.updateAria(this.inputEl, { 'aria-valuenow': (this.isValid() && v !== null) ? v: undefined }); } }); // Picker fields are combobox type functionality Ext.aria.AriaController.processClass(Ext.form.field.Picker, function() { var listFns = { onListHighlight: function(node) { this.updateAria({ 'aria-activedescendant': node.id }); }, onListUnhighlight: function(node) { var n = this.getPicker().getSelectedNodes(); this.updateAria({ 'aria-activedescendant': n.length ? n[0].id : undefined }); }, onListSelectionChange: function(sm, selected) { if (selected.length) { var n = this.getPicker().getSelectedNodes(); this.updateAria({ 'aria-activedescendant': n[0].id }); } else { this.updateAria({ 'aria-activedescendant': undefined }); } } }; return { role: 'combobox', onRender: function() { this.updateAria(this.inputEl, { 'aria-autocomplete': 'inline' }); }, onExpand: function() { var me = this, picker = me.getPicker(); me.updateAria(me.inputEl, { 'aria-owns': picker.el.id, 'aria-expanded': true }); // On first expand, add listeners to the "picker" list to maintain our ARIA state if (!me.pickerListenersAdded) { picker.mon(picker, { highlight: listFns.onListHighlight, unhighlight: listFns.onListUnhighlight, selectionchange: listFns.onListSelectionChange, scope: me }); me.pickerListenersAdded = true; } }, onCollapse: function() { var p = this.getPicker(), n = p.getSelectedNodes ? p.getSelectedNodes() : [null]; this.updateAria({ 'aria-expanded': false, 'aria-activedescendant': n.length ? n[0].id: undefined }); } }; }()); Ext.aria.AriaController.processClass(Ext.view.BoundList, { role: 'listbox' }); Ext.QuickTips.init(); var bd = Ext.getBody(); /* * ================ Simple form ======================= */ bd.createChild({tag: 'h2', html: 'Form 1 - Very Simple'}); var simple = Ext.create('Ext.form.Panel', { url:'save-form.php', frame:true, title: 'Simple Form', bodyStyle:'padding:5px 5px 0', width: 350, fieldDefaults: { msgTarget: 'side', labelWidth: 75 }, defaultType: 'textfield', defaults: { anchor: '100%' }, items: [{ fieldLabel: 'First Name', name: 'first', allowBlank:false },{ fieldLabel: 'Last Name', name: 'last' },{ fieldLabel: 'Company', name: 'company' }, { fieldLabel: 'Email', name: 'email', vtype:'email' }, { fieldLabel: 'DOB', name: 'dob', xtype: 'datefield' }, { fieldLabel: 'Age', name: 'age', xtype: 'numberfield', minValue: 0, maxValue: 100 }, { xtype: 'timefield', fieldLabel: 'Time', name: 'time', minValue: '8:00am', maxValue: '6:00pm' }], buttons: [{ text: 'Save' },{ text: 'Cancel' }] }); simple.render(document.body); /* * ================ Form 2 ======================= */ bd.createChild({tag: 'h2', html: 'Form 2 - Adding fieldsets'}); var fsf = Ext.create('Ext.form.Panel', { url:'save-form.php', frame:true, title: 'Simple Form with FieldSets', bodyStyle:'padding:5px 5px 0', width: 350, fieldDefaults: { msgTarget: 'side', labelWidth: 75 }, defaults: { anchor: '100%' }, items: [{ xtype:'fieldset', checkboxToggle:true, title: 'User Information', defaultType: 'textfield', collapsed: true, layout: 'anchor', defaults: { anchor: '100%' }, items :[{ fieldLabel: 'First Name', name: 'first', allowBlank:false },{ fieldLabel: 'Last Name', name: 'last' },{ fieldLabel: 'Company', name: 'company' }, { fieldLabel: 'Email', name: 'email', vtype:'email' }] },{ xtype:'fieldset', title: 'Phone Number', collapsible: true, defaultType: 'textfield', layout: 'anchor', defaults: { anchor: '100%' }, items :[{ fieldLabel: 'Home', name: 'home', value: '(888) 555-1212' },{ fieldLabel: 'Business', name: 'business' },{ fieldLabel: 'Mobile', name: 'mobile' },{ fieldLabel: 'Fax', name: 'fax' }] }], buttons: [{ text: 'Save' },{ text: 'Cancel' }] }); fsf.render(document.body); /* * ================ Form 3 ======================= */ bd.createChild({tag: 'h2', html: 'Form 3 - A little more complex'}); var top = Ext.create('Ext.form.Panel', { frame:true, title: 'Multi Column, Nested Layouts and Anchoring', bodyStyle:'padding:5px 5px 0', width: 600, fieldDefaults: { labelAlign: 'top', msgTarget: 'side' }, items: [{ xtype: 'container', anchor: '100%', layout:'column', items:[{ xtype: 'container', columnWidth:.5, layout: 'anchor', items: [{ xtype:'textfield', fieldLabel: 'First Name', name: 'first', anchor:'96%' }, { xtype:'textfield', fieldLabel: 'Company', name: 'company', anchor:'96%' }] },{ xtype: 'container', columnWidth:.5, layout: 'anchor', items: [{ xtype:'textfield', fieldLabel: 'Last Name', name: 'last', anchor:'100%' },{ xtype:'textfield', fieldLabel: 'Email', name: 'email', vtype:'email', anchor:'100%' }] }] }, { xtype: 'htmleditor', name: 'bio', fieldLabel: 'Biography', height: 200, anchor: '100%' }], buttons: [{ text: 'Save' },{ text: 'Cancel' }] }); top.render(document.body); /* * ================ Form 4 ======================= */ bd.createChild({tag: 'h2', html: 'Form 4 - Forms can be a TabPanel...'}); var tabs = Ext.create('Ext.form.Panel', { width: 350, border: false, bodyBorder: false, fieldDefaults: { labelWidth: 75, msgTarget: 'side' }, defaults: { anchor: '100%' }, items: { xtype:'tabpanel', activeTab: 0, defaults:{ bodyStyle:'padding:10px' }, items:[{ title:'Personal Details', defaultType: 'textfield', items: [{ fieldLabel: 'First Name', name: 'first', allowBlank:false, value: 'Ed' },{ fieldLabel: 'Last Name', name: 'last', value: 'Spencer' },{ fieldLabel: 'Company', name: 'company', value: 'Ext JS' }, { fieldLabel: 'Email', name: 'email', vtype:'email' }] },{ title:'Phone Numbers', defaultType: 'textfield', items: [{ fieldLabel: 'Home', name: 'home', value: '(888) 555-1212' },{ fieldLabel: 'Business', name: 'business' },{ fieldLabel: 'Mobile', name: 'mobile' },{ fieldLabel: 'Fax', name: 'fax' }] }] }, buttons: [{ text: 'Save' },{ text: 'Cancel' }] }); tabs.render(document.body); /* * ================ Form 5 ======================= */ bd.createChild({tag: 'h2', html: 'Form 5 - ... and forms can contain TabPanel(s)'}); var tab2 = Ext.create('Ext.form.Panel', { title: 'Inner Tabs', bodyStyle:'padding:5px', width: 600, fieldDefaults: { labelAlign: 'top', msgTarget: 'side' }, defaults: { anchor: '100%' }, items: [{ layout:'column', border:false, items:[{ columnWidth:.5, border:false, layout: 'anchor', defaultType: 'textfield', items: [{ fieldLabel: 'First Name', name: 'first', anchor:'95%' }, { fieldLabel: 'Company', name: 'company', anchor:'95%' }] },{ columnWidth:.5, border:false, layout: 'anchor', defaultType: 'textfield', items: [{ fieldLabel: 'Last Name', name: 'last', anchor:'95%' },{ fieldLabel: 'Email', name: 'email', vtype:'email', anchor:'95%' }] }] },{ xtype:'tabpanel', plain:true, activeTab: 0, height:235, defaults:{bodyStyle:'padding:10px'}, items:[{ title:'Personal Details', defaults: {width: 230}, defaultType: 'textfield', items: [{ fieldLabel: 'First Name', name: 'first', allowBlank:false, value: 'Jamie' },{ fieldLabel: 'Last Name', name: 'last', value: 'Avins' },{ fieldLabel: 'Company', name: 'company', value: 'Ext JS' }, { fieldLabel: 'Email', name: 'email', vtype:'email' }] },{ title:'Phone Numbers', defaults: {width: 230}, defaultType: 'textfield', items: [{ fieldLabel: 'Home', name: 'home', value: '(888) 555-1212' },{ fieldLabel: 'Business', name: 'business' },{ fieldLabel: 'Mobile', name: 'mobile' },{ fieldLabel: 'Fax', name: 'fax' }] },{ cls: 'x-plain', title: 'Biography', layout: 'fit', items: { xtype: 'htmleditor', name: 'bio2', fieldLabel: 'Biography' } }] }], buttons: [{ text: 'Save' },{ text: 'Cancel' }] }); tab2.render(document.body); }); /** * @class Ext.picker.Date * @extends Ext.Component *A date picker. This class is used by the {@link Ext.form.field.Date} field to allow browsing and * selection of valid dates in a popup next to the field, but may also be used with other components.
*Typically you will need to implement a handler function to be notified when the user chooses a color from the * picker; you can register the handler using the {@link #select} event, or by implementing the {@link #handler} * method.
*By default the user will be allowed to pick any date; this can be changed by using the {@link #minDate}, * {@link #maxDate}, {@link #disabledDays}, {@link #disabledDatesRE}, and/or {@link #disabledDates} configs.
*All the string values documented below may be overridden by including an Ext locale file in your page.
*Example usage:
*new Ext.panel.Panel({
title: 'Choose a future date:',
width: 200,
bodyPadding: 10,
renderTo: Ext.getBody(),
items: [{
xtype: 'datepicker',
minDate: new Date(),
handler: function(picker, date) {
// do something with the selected date
}
}]
});
* {@img Ext.picker.Date/Ext.picker.Date.png Ext.picker.Date component}
*
*/
Ext.define('Ext.picker.NewDate', {
extend: 'Ext.Container',
requires: [
'Ext.data.Model',
'Ext.view.BoundList',
'Ext.XTemplate',
'Ext.button.Button',
'Ext.button.Split',
'Ext.util.ClickRepeater',
'Ext.util.KeyNav',
'Ext.EventObject',
'Ext.fx.Manager',
'Ext.picker.Month'
],
alias: 'widget.newdatepicker',
alternateClassName: 'Ext.DatePicker',
renderTpl: [
'',
{
longDay: function(value){
return Ext.Date.format(value, this.longDayFormat);
}
}
],
calendarTpl: [
'{.:this.firstInitial} | ' + '
---|
' + '' + '{.:date("j")' + '' + ' | ' + '
'Today'
)
*/
todayText : 'Today',
/**
* @cfg {Function} handler
* Optional. A function that will handle the select event of this picker.
* The handler is passed the following parameters:picker
: Ext.picker.Date date
: Date this
reference) in which the {@link #handler}
* function will be called. Defaults to this DatePicker instance.
*/
/**
* @cfg {String} todayTip
* A string used to format the message for displaying in a tooltip over the button that
* selects the current date. Defaults to '{0} (Spacebar)'
where
* the {0}
token is replaced by today's date.
*/
todayTip : '{0} (Spacebar)',
/**
* @cfg {String} minText
* The error text to display if the minDate validation fails (defaults to 'This date is before the minimum date'
)
*/
minText : 'This date is before the minimum date',
/**
* @cfg {String} maxText
* The error text to display if the maxDate validation fails (defaults to 'This date is after the maximum date'
)
*/
maxText : 'This date is after the maximum date',
/**
* @cfg {String} format
* The default date format string which can be overriden for localization support. The format must be
* valid according to {@link Ext.Date#parse} (defaults to {@link Ext.Date#defaultFormat}).
*/
/**
* @cfg {String} disabledDaysText
* The tooltip to display when the date falls on a disabled day (defaults to 'Disabled'
)
*/
disabledDaysText : 'Disabled',
/**
* @cfg {String} disabledDatesText
* The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled'
)
*/
disabledDatesText : 'Disabled',
/**
* @cfg {Array} monthNames
* An array of textual month names which can be overriden for localization support (defaults to Ext.Date.monthNames)
*/
/**
* @cfg {Array} dayNames
* An array of textual day names which can be overriden for localization support (defaults to Ext.Date.dayNames)
*/
/**
* @cfg {String} nextText
* The next month navigation button tooltip (defaults to 'Next Month (Control+Right)'
)
*/
nextText : 'Next Month (Control+Right)',
/**
* @cfg {String} prevText
* The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)'
)
*/
prevText : 'Previous Month (Control+Left)',
/**
* @cfg {String} monthYearText
* The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)'
)
*/
monthYearText : 'Choose a month (Control+Up/Down to move years)',
/**
* @cfg {Number} startDay
* Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
*/
startDay : 0,
/**
* @cfg {Boolean} showToday
* False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
* that selects the current date (defaults to true
).
*/
showToday : true,
/**
* @cfg {Date} minDate
* Minimum allowable date (JavaScript date object, defaults to null)
*/
/**
* @cfg {Date} maxDate
* Maximum allowable date (JavaScript date object, defaults to null)
*/
/**
* @cfg {Array} disabledDays
* An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
*/
/**
* @cfg {RegExp} disabledDatesRE
* JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
* config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
* disabledDates value.
*/
/**
* @cfg {Array} disabledDates
* An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
* expression so they are very powerful. Some examples:
*