X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/6746dc89c47ed01b165cc1152533605f97eb8e8d..refs/heads/master:/docs/source/Time.html diff --git a/docs/source/Time.html b/docs/source/Time.html index d45c4c2c..94332bfe 100644 --- a/docs/source/Time.html +++ b/docs/source/Time.html @@ -3,8 +3,8 @@
/** - * @class Ext.picker.Time - * @extends Ext.view.BoundList - * <p>A time picker which provides a list of times from which to choose. This is used by the - * {@link Ext.form.field.Time} class to allow browsing and selection of valid times, but could also be used - * with other components.</p> - * <p>By default, all times starting at midnight and incrementing every 15 minutes will be presented. - * This list of available times can be controlled using the {@link #minValue}, {@link #maxValue}, and - * {@link #increment} configuration properties. The format of the times presented in the list can be - * customized with the {@link #format} config.</p> - * <p>To handle when the user selects a time from the list, you can subscribe to the {@link #selectionchange} - * event.</p> +/** + * Provides a time input field with a time dropdown and automatic time validation. * - * {@img Ext.picker.Time/Ext.picker.Time.png Ext.picker.Time component} + * This field recognizes and uses JavaScript Date objects as its main {@link #value} type (only the time portion of the + * date is used; the month/day/year are ignored). In addition, it recognizes string values which are parsed according to + * the {@link #format} and/or {@link #altFormats} configs. These may be reconfigured to use time formats appropriate for + * the user's locale. * - * ## Code - new Ext.create('Ext.picker.Time', { - width: 60, - minValue: Ext.Date.parse('04:30:00 AM', 'h:i:s A'), - maxValue: Ext.Date.parse('08:00:00 AM', 'h:i:s A'), - renderTo: Ext.getBody() - }); + * The field may be limited to a certain range of times by using the {@link #minValue} and {@link #maxValue} configs, + * and the interval between time options in the dropdown can be changed with the {@link #increment} config. * + * Example usage: + * + * @example + * Ext.create('Ext.form.Panel', { + * title: 'Time Card', + * width: 300, + * bodyPadding: 10, + * renderTo: Ext.getBody(), + * items: [{ + * xtype: 'timefield', + * name: 'in', + * fieldLabel: 'Time In', + * minValue: '6:00 AM', + * maxValue: '8:00 PM', + * increment: 30, + * anchor: '100%' + * }, { + * xtype: 'timefield', + * name: 'out', + * fieldLabel: 'Time Out', + * minValue: '6:00 AM', + * maxValue: '8:00 PM', + * increment: 30, + * anchor: '100%' + * }] + * }); */ -Ext.define('Ext.picker.Time', { - extend: 'Ext.view.BoundList', - alias: 'widget.timepicker', - requires: ['Ext.data.Store', 'Ext.Date'], +Ext.define('Ext.form.field.Time', { + extend:'Ext.form.field.Picker', + alias: 'widget.timefield', + requires: ['Ext.form.field.Date', 'Ext.picker.Time', 'Ext.view.BoundListKeyNav', 'Ext.Date'], + alternateClassName: ['Ext.form.TimeField', 'Ext.form.Time'], - /** - * @cfg {Date} minValue - * The minimum time to be shown in the list of times. This must be a Date object (only the time fields - * will be used); no parsing of String values will be done. Defaults to undefined. + /** + * @cfg {String} triggerCls + * An additional CSS class used to style the trigger button. The trigger will always get the {@link #triggerBaseCls} + * by default and triggerCls will be **appended** if specified. Defaults to 'x-form-time-trigger' for the Time field + * trigger. */ + triggerCls: Ext.baseCSSPrefix + 'form-time-trigger', - /** - * @cfg {Date} maxValue - * The maximum time to be shown in the list of times. This must be a Date object (only the time fields - * will be used); no parsing of String values will be done. Defaults to undefined. + /** + * @cfg {Date/String} minValue + * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string time in a + * valid format -- see {@link #format} and {@link #altFormats}. */ - /** - * @cfg {Number} increment - * The number of minutes between each time value in the list (defaults to 15). + /** + * @cfg {Date/String} maxValue + * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string time in a + * valid format -- see {@link #format} and {@link #altFormats}. */ - increment: 15, - /** + /** + * @cfg {String} minText + * The error text to display when the entered time is before {@link #minValue}. + */ + minText : "The time in this field must be equal to or after {0}", + + /** + * @cfg {String} maxText + * The error text to display when the entered time is after {@link #maxValue}. + */ + maxText : "The time in this field must be equal to or before {0}", + + /** + * @cfg {String} invalidText + * The error text to display when the time in the field is invalid. + */ + invalidText : "{0} is not a valid time", + + /** * @cfg {String} format - * The default time format string which can be overriden for localization support. The format must be - * valid according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time - * format try 'H:i' instead. + * The default time format string which can be overriden for localization support. The format must be valid + * according to {@link Ext.Date#parse} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time format try 'H:i' + * instead. */ format : "g:i A", - /** - * @hide - * The field in the implicitly-generated Model objects that gets displayed in the list. This is - * an internal field name only and is not useful to change via config. + /** + * @cfg {String} submitFormat + * The date format string which will be submitted to the server. The format must be valid according to {@link + * Ext.Date#parse} (defaults to {@link #format}). */ - displayField: 'disp', - /** - * @private - * Year, month, and day that all times will be normalized into internally. + /** + * @cfg {String} altFormats + * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined + * format. */ - initDate: [2008,1,1], + altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A", - componentCls: Ext.baseCSSPrefix + 'timepicker', + /** + * @cfg {Number} increment + * The number of minutes between each time value in the list. + */ + increment: 15, - /** - * @hide + /** + * @cfg {Number} pickerMaxHeight + * The maximum height of the {@link Ext.picker.Time} dropdown. */ - loadMask: false, + pickerMaxHeight: 300, + + /** + * @cfg {Boolean} selectOnTab + * Whether the Tab key should select the currently highlighted item. + */ + selectOnTab: true, + + /** + * @private + * This is the date to use when generating time values in the absence of either minValue + * or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an + * arbitrary "safe" date that can be any date aside from DST boundary dates. + */ + initDate: '1/1/2008', + initDateFormat: 'j/n/Y', + initComponent: function() { var me = this, - dateUtil = Ext.Date, - clearTime = dateUtil.clearTime, - initDate = me.initDate.join('/'); + min = me.minValue, + max = me.maxValue; + if (min) { + me.setMinValue(min); + } + if (max) { + me.setMaxValue(max); + } + this.callParent(); + }, - // Set up absolute min and max for the entire day - me.absMin = clearTime(new Date(initDate)); - me.absMax = dateUtil.add(clearTime(new Date(initDate)), 'mi', (24 * 60) - 1); + initValue: function() { + var me = this, + value = me.value; - me.store = me.createStore(); - me.updateList(); + // If a String value was supplied, try to convert it to a proper Date object + if (Ext.isString(value)) { + me.value = me.rawToValue(value); + } - this.callParent(); + me.callParent(); }, - /** - * Set the {@link #minValue} and update the list of available times. This must be a Date - * object (only the time fields will be used); no parsing of String values will be done. - * @param {Date} value + /** + * Replaces any existing {@link #minValue} with the new time and refreshes the picker's range. + * @param {Date/String} value The minimum time that can be selected */ setMinValue: function(value) { - this.minValue = value; - this.updateList(); + var me = this, + picker = me.picker; + me.setLimit(value, true); + if (picker) { + picker.setMinValue(me.minValue); + } }, - /** - * Set the {@link #maxValue} and update the list of available times. This must be a Date - * object (only the time fields will be used); no parsing of String values will be done. - * @param {Date} value + /** + * Replaces any existing {@link #maxValue} with the new time and refreshes the picker's range. + * @param {Date/String} value The maximum time that can be selected */ setMaxValue: function(value) { - this.maxValue = value; - this.updateList(); + var me = this, + picker = me.picker; + me.setLimit(value, false); + if (picker) { + picker.setMaxValue(me.maxValue); + } }, - /** + /** * @private - * Sets the year/month/day of the given Date object to the {@link #initDate}, so that only - * the time fields are significant. This makes values suitable for time comparison. - * @param {Date} date + * Updates either the min or max value. Converts the user's value into a Date object whose + * year/month/day is set to the {@link #initDate} so that only the time fields are significant. */ - normalizeDate: function(date) { - var initDate = this.initDate; - date.setFullYear(initDate[0], initDate[1] - 1, initDate[2]); - return date; + setLimit: function(value, isMin) { + var me = this, + d, val; + if (Ext.isString(value)) { + d = me.parseDate(value); + } + else if (Ext.isDate(value)) { + d = value; + } + if (d) { + val = Ext.Date.clearTime(new Date(me.initDate)); + val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds()); + me[isMin ? 'minValue' : 'maxValue'] = val; + } + }, + + rawToValue: function(rawValue) { + return this.parseDate(rawValue) || rawValue || null; + }, + + valueToRaw: function(value) { + return this.formatDate(this.parseDate(value)); }, - /** - * Update the list of available times in the list to be constrained within the - * {@link #minValue} and {@link #maxValue}. + /** + * Runs all of Time's validations and returns an array of any errors. Note that this first runs Text's validations, + * so the returned array is an amalgamation of all field errors. The additional validation checks are testing that + * the time format is valid, that the chosen time is within the {@link #minValue} and {@link #maxValue} constraints + * set. + * @param {Object} [value] The value to get errors for (defaults to the current field value) + * @return {String[]} All validation errors for this field */ - updateList: function() { + getErrors: function(value) { var me = this, - min = me.normalizeDate(me.minValue || me.absMin), - max = me.normalizeDate(me.maxValue || me.absMax); + format = Ext.String.format, + errors = me.callParent(arguments), + minValue = me.minValue, + maxValue = me.maxValue, + date; - me.store.filterBy(function(record) { - var date = record.get('date'); - return date >= min && date <= max; - }); + value = me.formatDate(value || me.processRawValue(me.getRawValue())); + + if (value === null || value.length < 1) { // if it's blank and textfield didn't flag it then it's valid + return errors; + } + + date = me.parseDate(value); + if (!date) { + errors.push(format(me.invalidText, value, me.format)); + return errors; + } + + if (minValue && date < minValue) { + errors.push(format(me.minText, me.formatDate(minValue))); + } + + if (maxValue && date > maxValue) { + errors.push(format(me.maxText, me.formatDate(maxValue))); + } + + return errors; + }, + + formatDate: function() { + return Ext.form.field.Date.prototype.formatDate.apply(this, arguments); }, - /** + /** * @private - * Creates the internal {@link Ext.data.Store} that contains the available times. The store - * is loaded with all possible times, and it is later filtered to hide those times outside - * the minValue/maxValue. + * Parses an input value into a valid Date object. + * @param {String/Date} value */ - createStore: function() { + parseDate: function(value) { + if (!value || Ext.isDate(value)) { + return value; + } + + var me = this, + val = me.safeParse(value, me.format), + altFormats = me.altFormats, + altFormatsArray = me.altFormatsArray, + i = 0, + len; + + if (!val && altFormats) { + altFormatsArray = altFormatsArray || altFormats.split('|'); + len = altFormatsArray.length; + for (; i < len && !val; ++i) { + val = me.safeParse(value, altFormatsArray[i]); + } + } + return val; + }, + + safeParse: function(value, format){ var me = this, utilDate = Ext.Date, - times = [], - min = me.absMin, - max = me.absMax; - - while(min <= max){ - times.push({ - disp: utilDate.dateFormat(min, me.format), - date: min - }); - min = utilDate.add(min, 'mi', me.increment); + parsedDate, + result = null; + + if (utilDate.formatContainsDateInfo(format)) { + // assume we've been given a full date + result = utilDate.parse(value, format); + } else { + // Use our initial safe date + parsedDate = utilDate.parse(me.initDate + ' ' + value, me.initDateFormat + ' ' + format); + if (parsedDate) { + result = parsedDate; + } } + return result; + }, - return Ext.create('Ext.data.Store', { - fields: ['disp', 'date'], - data: times + // @private + getSubmitValue: function() { + var me = this, + format = me.submitFormat || me.format, + value = me.getValue(); + + return value ? Ext.Date.format(value, format) : null; + }, + + /** + * @private + * Creates the {@link Ext.picker.Time} + */ + createPicker: function() { + var me = this, + picker = Ext.create('Ext.picker.Time', { + pickerField: me, + selModel: { + mode: 'SINGLE' + }, + floating: true, + hidden: true, + minValue: me.minValue, + maxValue: me.maxValue, + increment: me.increment, + format: me.format, + ownerCt: this.ownerCt, + renderTo: document.body, + maxHeight: me.pickerMaxHeight, + focusOnToFront: false + }); + + me.mon(picker.getSelectionModel(), { + selectionchange: me.onListSelect, + scope: me }); - } + return picker; + }, + + /** + * @private + * Enables the key nav for the Time picker when it is expanded. + * TODO this is largely the same logic as ComboBox, should factor out. + */ + onExpand: function() { + var me = this, + keyNav = me.pickerKeyNav, + selectOnTab = me.selectOnTab, + picker = me.getPicker(), + lastSelected = picker.getSelectionModel().lastSelected, + itemNode; + + if (!keyNav) { + keyNav = me.pickerKeyNav = Ext.create('Ext.view.BoundListKeyNav', this.inputEl, { + boundList: picker, + forceKeyDown: true, + tab: function(e) { + if (selectOnTab) { + if(me.picker.highlightedItem) { + this.selectHighlighted(e); + } else { + me.collapse(); + } + me.triggerBlur(); + } + // Tab key event is allowed to propagate to field + return true; + } + }); + // stop tab monitoring from Ext.form.field.Trigger so it doesn't short-circuit selectOnTab + if (selectOnTab) { + me.ignoreMonitorTab = true; + } + } + Ext.defer(keyNav.enable, 1, keyNav); //wait a bit so it doesn't react to the down arrow opening the picker + + // Highlight the last selected item and scroll it into view + if (lastSelected) { + itemNode = picker.getNode(lastSelected); + if (itemNode) { + picker.highlightItem(itemNode); + picker.el.scrollChildIntoView(itemNode, false); + } + } + }, + + /** + * @private + * Disables the key nav for the Time picker when it is collapsed. + */ + onCollapse: function() { + var me = this, + keyNav = me.pickerKeyNav; + if (keyNav) { + keyNav.disable(); + me.ignoreMonitorTab = false; + } + }, + + /** + * @private + * Clears the highlighted item in the picker on change. + * This prevents the highlighted item from being selected instead of the custom typed in value when the tab key is pressed. + */ + onChange: function() { + var me = this, + picker = me.picker; + + me.callParent(arguments); + if(picker) { + picker.clearHighlight(); + } + }, + + /** + * @private + * Handles a time being selected from the Time picker. + */ + onListSelect: function(list, recordArray) { + var me = this, + record = recordArray[0], + val = record ? record.get('date') : null; + me.setValue(val); + me.fireEvent('select', me, val); + me.picker.clearHighlight(); + me.collapse(); + me.inputEl.focus(); + } }); +