Code:
// vim: ts=4:sw=4:nu:fdc=4:nospell
/*global Ext */
/**
* @class Ext.ux.form.DateTime
* @extends Ext.form.Field
*
* DateTime field, combination of DateField and TimeField
*
* @author Ing. Jozef Sakáloš
* @copyright (c) 2008, Ing. Jozef Sakáloš
* @version 2.0
* @revision $Id: Ext.ux.form.DateTime.js 749 2009-09-15 19:18:44Z jozo $
*
* @license Ext.ux.form.DateTime is licensed under the terms of
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
*
* <p>License details: <a href="http://www.gnu.org/licenses/lgpl.html"
* target="_blank">http://www.gnu.org/licenses/lgpl.html</a></p>
*
* @forum 22661
*
* @donate
* <form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_blank">
* <input type="hidden" name="cmd" value="_s-xclick">
* <input type="hidden" name="hosted_button_id" value="3430419">
* <input type="image" src="https://www.paypal.com/en_US/i/btn/x-click-butcc-donate.gif"
* border="0" name="submit" alt="PayPal - The safer, easier way to pay online.">
* <img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1">
* </form>
*/
Ext.ns('Ext.ux.form');
/**
* Creates new DateTime
* @constructor
* @param {Object} config A config object
*/
Ext.ux.form.DateTime = Ext.extend(Ext.form.Field, {
/**
* @cfg {Function} dateValidator A custom validation function to be called during date field
* validation (defaults to null)
*/
dateValidator: null
/**
* @cfg {String/Object} defaultAutoCreate DomHelper element spec
* Let superclass to create hidden field instead of textbox. Hidden will be submittend to server
*/
, defaultAutoCreate: { tag: 'input', type: 'hidden' }
/**
* @cfg {String} dtSeparator Date - Time separator. Used to split date and time (defaults to ' ' (space))
*/
, dtSeparator: ' '
/**
* @cfg {String} hiddenFormat Format of datetime used to store value in hidden field
* and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql format)
*/
, hiddenFormat: 'Y-m-d H:i:s'
/**
* @cfg {Boolean} otherToNow Set other field to now() if not explicly filled in (defaults to true)
*/
, otherToNow: true
/**
* @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty value.
* If it is true then setValue() sets value of field to current date and time (defaults to false)
*/
/**
* @cfg {String} timePosition Where the time field should be rendered. 'right' is suitable for forms
* and 'below' is suitable if the field is used as the grid editor (defaults to 'right')
*/
, timePosition: 'right' // valid values:'below', 'right'
/**
* @cfg {Function} timeValidator A custom validation function to be called during time field
* validation (defaults to null)
*/
, timeValidator: null
/**
* @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
*/
, timeWidth: 100
/**
* @cfg {String} dateFormat Format of DateField. Can be localized. (defaults to 'm/y/d')
*/
, dateFormat: 'm/d/y'
/**
* @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults to 'g:i A')
*/
, timeFormat: 'g:i A'
/**
* @cfg {Object} dateConfig Config for DateField constructor.
*/
/**
* @cfg {Object} timeConfig Config for TimeField constructor.
*/
// {{{
/**
* @private
* creates DateField and TimeField and installs the necessary event handlers
*/
, initComponent: function() {
// call parent initComponent
Ext.ux.form.DateTime.superclass.initComponent.call(this);
// create DateField
var dateConfig = Ext.apply({}, {
id: this.id + '-date'
, format: this.dateFormat || Ext.form.DateField.prototype.format
, width: this.timeWidth
, selectOnFocus: this.selectOnFocus
, validator: this.dateValidator
, listeners: {
blur: { scope: this, fn: this.onBlur }
, focus: { scope: this, fn: this.onFocus }
}
}, this.dateConfig);
this.df = new Ext.form.DateField(dateConfig);
this.df.ownerCt = this;
delete (this.dateFormat);
// create TimeField
var timeConfig = Ext.apply({}, {
id: this.id + '-time'
, format: this.timeFormat || Ext.form.TimeField.prototype.format
, width: this.timeWidth
, selectOnFocus: this.selectOnFocus
, validator: this.timeValidator
, listeners: {
blur: { scope: this, fn: this.onBlur }
, focus: { scope: this, fn: this.onFocus }
}
}, this.timeConfig);
this.tf = new Ext.form.TimeField(timeConfig);
this.tf.ownerCt = this;
delete (this.timeFormat);
// relay events
this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);
} // eo function initComponent
// }}}
// {{{
/**
* @private
* Renders underlying DateField and TimeField and provides a workaround for side error icon bug
*/
, onRender: function(ct, position) {
// don't run more than once
if (this.isRendered) {
return;
}
// render underlying hidden field
Ext.ux.form.DateTime.superclass.onRender.call(this, ct, position);
// render DateField and TimeField
// create bounding table
var t;
if ('below' === this.timePosition || 'bellow' === this.timePosition) {
t = Ext.DomHelper.append(ct, { tag: 'table', style: 'border-collapse:collapse', children: [
{ tag: 'tr', children: [{ tag: 'td', style: 'padding-bottom:1px', cls: 'ux-datetime-date'}] }
, { tag: 'tr', children: [{ tag: 'td', cls: 'ux-datetime-time'}] }
]
}, true);
}
else {
t = Ext.DomHelper.append(ct, { tag: 'table', style: 'border-collapse:collapse', children: [
{ tag: 'tr', children: [
{ tag: 'td', style: 'padding-right:4px', cls: 'ux-datetime-date' }, { tag: 'td', cls: 'ux-datetime-time' }
]
}
]
}, true);
}
this.tableEl = t;
this.wrap = t.wrap({ cls: 'x-form-field-wrap' });
// this.wrap = t.wrap();
this.wrap.on("mousedown", this.onMouseDown, this, { delay: 10 });
// render DateField & TimeField
this.df.render(t.child('td.ux-datetime-date'));
this.tf.render(t.child('td.ux-datetime-time'));
// workaround for IE trigger misalignment bug
// see http://extjs.com/forum/showthread.php?p=341075#post341075
// if(Ext.isIE && Ext.isStrict) {
// t.select('input').applyStyles({top:0});
// }
this.on('specialkey', this.onSpecialKey, this);
this.df.el.swallowEvent(['keydown', 'keypress']);
this.tf.el.swallowEvent(['keydown', 'keypress']);
// create icon for side invalid errorIcon
if ('side' === this.msgTarget) {
var elp = this.el.findParent('.x-form-element', 10, true);
this.errorIcon = elp.createChild({ cls: 'x-form-invalid-icon' });
var o = {
errorIcon: this.errorIcon
, msgTarget: 'side'
, alignErrorIcon: this.alignErrorIcon.createDelegate(this)
};
Ext.apply(this.df, o);
Ext.apply(this.tf, o);
// this.df.errorIcon = this.errorIcon;
// this.tf.errorIcon = this.errorIcon;
}
// setup name for submit
this.el.dom.name = this.hiddenName || this.name || this.id;
// prevent helper fields from being submitted
this.df.el.dom.removeAttribute("name");
this.tf.el.dom.removeAttribute("name");
// we're rendered flag
this.isRendered = true;
// update hidden field
this.updateHidden();
} // eo function onRender
// }}}
// {{{
/**
* @private
*/
, adjustSize: Ext.BoxComponent.prototype.adjustSize
// }}}
// {{{
/**
* @private
*/
, alignErrorIcon: function() {
this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
}
// }}}
// {{{
/**
* @private initializes internal dateValue
*/
, initDateValue: function() {
this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
}
// }}}
// {{{
/**
* Calls clearInvalid on the DateField and TimeField
*/
, clearInvalid: function() {
this.df.clearInvalid();
this.tf.clearInvalid();
} // eo function clearInvalid
// }}}
// {{{
/**
* Calls markInvalid on both DateField and TimeField
* @param {String} msg Invalid message to display
*/
, markInvalid: function(msg) {
this.df.markInvalid(msg);
this.tf.markInvalid(msg);
} // eo function markInvalid
// }}}
// {{{
/**
* @private
* called from Component::destroy.
* Destroys all elements and removes all listeners we've created.
*/
, beforeDestroy: function() {
if (this.isRendered) {
// this.removeAllListeners();
this.wrap.removeAllListeners();
this.wrap.remove();
this.tableEl.remove();
this.df.destroy();
this.tf.destroy();
}
} // eo function beforeDestroy
// }}}
// {{{
/**
* Disable this component.
* @return {Ext.Component} this
*/
, disable: function() {
if (this.isRendered) {
this.df.disabled = this.disabled;
this.df.onDisable();
this.tf.onDisable();
}
this.disabled = true;
this.df.disabled = true;
this.tf.disabled = true;
this.fireEvent("disable", this);
return this;
} // eo function disable
// }}}
// {{{
/**
* Enable this component.
* @return {Ext.Component} this
*/
, enable: function() {
if (this.rendered) {
this.df.onEnable();
this.tf.onEnable();
}
this.disabled = false;
this.df.disabled = false;
this.tf.disabled = false;
this.fireEvent("enable", this);
return this;
} // eo function enable
// }}}
// {{{
/**
* @private Focus date filed
*/
, focus: function() {
this.df.focus();
} // eo function focus
// }}}
// {{{
/**
* @private
*/
, getPositionEl: function() {
return this.wrap;
}
// }}}
// {{{
/**
* @private
*/
, getResizeEl: function() {
return this.wrap;
}
// }}}
// {{{
/**
* @return {Date/String} Returns value of this field
*/
, getValue: function() {
// create new instance of date
return this.dateValue ? new Date(this.dateValue) : '';
} // eo function getValue
// }}}
// {{{
/**
* @return {Boolean} true = valid, false = invalid
* @private Calls isValid methods of underlying DateField and TimeField and returns the result
*/
, isValid: function() {
return this.df.isValid() && this.tf.isValid();
} // eo function isValid
// }}}
// {{{
/**
* Returns true if this component is visible
* @return {boolean}
*/
, isVisible: function() {
return this.df.rendered && this.df.getActionEl().isVisible();
} // eo function isVisible
// }}}
// {{{
/**
* @private Handles blur event
*/
, onBlur: function(f) {
// called by both DateField and TimeField blur events
// revert focus to previous field if clicked in between
if (this.wrapClick) {
f.focus();
this.wrapClick = false;
}
// update underlying value
if (f === this.df) {
this.updateDate();
}
else {
this.updateTime();
}
this.updateHidden();
// fire events later
(function() {
if (!this.df.hasFocus && !this.tf.hasFocus) {
var v = this.getValue();
if (String(v) !== String(this.startValue)) {
this.fireEvent("change", this, v, this.startValue);
}
this.hasFocus = false;
this.fireEvent('blur', this);
}
}).defer(100, this);
} // eo function onBlur
// }}}
// {{{
/**
* @private Handles focus event
*/
, onFocus: function() {
if (!this.hasFocus) {
this.hasFocus = true;
this.startValue = this.getValue();
this.fireEvent("focus", this);
}
}
// }}}
// {{{
/**
* @private Just to prevent blur event when clicked in the middle of fields
*/
, onMouseDown: function(e) {
if (!this.disabled) {
this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
}
}
// }}}
// {{{
/**
* @private
* Handles Tab and Shift-Tab events
*/
, onSpecialKey: function(t, e) {
var key = e.getKey();
if (key === e.TAB) {
if (t === this.df && !e.shiftKey) {
e.stopEvent();
this.tf.focus();
}
if (t === this.tf && e.shiftKey) {
e.stopEvent();
this.df.focus();
}
}
// otherwise it misbehaves in editor grid
if (key === e.ENTER) {
this.updateValue();
}
} // eo function onSpecialKey
// }}}
// {{{
/**
* Resets the current field value to the originally loaded value
* and clears any validation messages. See Ext.form.BasicForm.trackResetOnLoad
*/
, reset: function() {
this.df.reset();
this.tf.reset();
} // eo function reset
// }}}
// {{{
/**
* @private Sets the value of DateField
*/
, setDate: function(date) {
this.df.setValue(date);
} // eo function setDate
// }}}
// {{{
/**
* @private Sets the value of TimeField
*/
, setTime: function(date) {
this.tf.setValue(date);
} // eo function setTime
// }}}
// {{{
/**
* @private
* Sets correct sizes of underlying DateField and TimeField
* With workarounds for IE bugs
*/
, setSize: function(w, h) {
if (!w) {
return;
}
if ('below' === this.timePosition) {
this.df.setSize(w, h);
this.tf.setSize(w, h);
if (Ext.isIE) {
this.df.el.up('td').setWidth(w);
this.tf.el.up('td').setWidth(w);
}
}
else {
this.df.setSize(w - this.timeWidth - 4, h);
this.tf.setSize(this.timeWidth, h);
if (Ext.isIE) {
this.df.el.up('td').setWidth(w - this.timeWidth - 4);
this.tf.el.up('td').setWidth(this.timeWidth);
}
}
} // eo function setSize
// }}}
// {{{
/**
* @param {Mixed} val Value to set
* Sets the value of this field
*/
, setValue: function(val) {
if (!val && true === this.emptyToNow) {
this.setValue(new Date());
return;
}
else if (!val) {
this.setDate('');
this.setTime('');
this.updateValue();
return;
}
if ('number' === typeof val) {
val = new Date(val);
}
else if ('string' === typeof val && this.hiddenFormat) {
val = Date.parseDate(val, this.hiddenFormat)
}
val = val ? val : new Date(1970, 0, 1, 0, 0, 0);
var da, time;
if (val instanceof Date) {
this.setDate(val);
this.setTime(val);
this.dateValue = new Date(Ext.isIE ? val.getTime() : val);
}
else {
da = val.split(this.dtSeparator);
this.setDate(da[0]);
if (da[1]) {
if (da[2]) {
// add am/pm part back to time
da[1] += da[2];
}
this.setTime(da[1]);
}
}
this.updateValue();
} // eo function setValue
// }}}
// {{{
/**
* Hide or show this component by boolean
* @return {Ext.Component} this
*/
, setVisible: function(visible) {
if (visible) {
this.df.show();
this.tf.show();
} else {
this.df.hide();
this.tf.hide();
}
return this;
} // eo function setVisible
// }}}
//{{{
, show: function() {
return this.setVisible(true);
} // eo function show
//}}}
//{{{
, hide: function() {
return this.setVisible(false);
} // eo function hide
//}}}
// {{{
/**
* @private Updates the date part
*/
, updateDate: function() {
var d = this.df.getValue();
if (d) {
if (!(this.dateValue instanceof Date)) {
this.initDateValue();
if (!this.tf.getValue()) {
this.setTime(this.dateValue);
}
}
this.dateValue.setMonth(0); // because of leap years
this.dateValue.setFullYear(d.getFullYear());
this.dateValue.setMonth(d.getMonth(), d.getDate());
// this.dateValue.setDate(d.getDate());
}
else {
this.dateValue = '';
this.setTime('');
}
} // eo function updateDate
// }}}
// {{{
/**
* @private
* Updates the time part
*/
, updateTime: function() {
var t = this.tf.getValue();
if (t && !(t instanceof Date)) {
t = Date.parseDate(t, this.tf.format);
}
if (t && !this.df.getValue()) {
this.initDateValue();
this.setDate(this.dateValue);
}
if (this.dateValue instanceof Date) {
if (t) {
this.dateValue.setHours(t.getHours());
this.dateValue.setMinutes(t.getMinutes());
this.dateValue.setSeconds(t.getSeconds());
}
else {
this.dateValue.setHours(0);
this.dateValue.setMinutes(0);
this.dateValue.setSeconds(0);
}
}
} // eo function updateTime
// }}}
// {{{
/**
* @private Updates the underlying hidden field value
*/
, updateHidden: function() {
if (this.isRendered) {
var value = this.dateValue instanceof Date ? this.dateValue.format(this.hiddenFormat) : '';
this.el.dom.value = value;
}
}
// }}}
// {{{
/**
* @private Updates all of Date, Time and Hidden
*/
, updateValue: function() {
this.updateDate();
this.updateTime();
this.updateHidden();
return;
} // eo function updateValue
// }}}
// {{{
/**
* @return {Boolean} true = valid, false = invalid
* calls validate methods of DateField and TimeField
*/
, validate: function() {
return this.df.validate() && this.tf.validate();
} // eo function validate
// }}}
// {{{
/**
* Returns renderer suitable to render this field
* @param {Object} Column model config
*/
, renderer: function(field) {
var format = field.editor.dateFormat || Ext.ux.form.DateTime.prototype.dateFormat;
format += ' ' + (field.editor.timeFormat || Ext.ux.form.DateTime.prototype.timeFormat);
var renderer = function(val) {
var retval = Ext.util.Format.date(val, format);
return retval;
};
return renderer;
} // eo function renderer
// }}}
}); // eo extend
// register xtype
Ext.reg('xdatetime', Ext.ux.form.DateTime); /*
* Ext JS Library 2.3.0
* Copyright(c) 2006-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
Ext.onReady(function(){
Ext.QuickTips.init();
function formatDate(value){
return value ? value.dateFormat('M d, Y') : '';
};
// shorthand alias
var fm = Ext.form;
// custom column plugin example
var checkColumn = new Ext.grid.CheckColumn({
header: "Indoor?",
dataIndex: 'indoor',
width: 55
});
// the column model has information about grid columns
// dataIndex maps the column to the specific data field in
// the data store (created below)
var cm = new Ext.grid.ColumnModel([{
id:'common',
header: "Common Name",
dataIndex: 'common',
width: 220,
editor: new fm.TextField({
allowBlank: false
})
},{
header: "Light",
dataIndex: 'light',
width: 130,
editor: new Ext.form.ComboBox({
typeAhead: true,
triggerAction: 'all',
transform:'light',
lazyRender:true,
listClass: 'x-combo-list-small'
})
},{
header: "Price",
dataIndex: 'price',
width: 70,
align: 'right',
renderer: 'usMoney',
editor: new fm.NumberField({
allowBlank: false,
allowNegative: false,
maxValue: 100000
})
},{
header: "Available",
dataIndex: 'availDate',
width: 140,
renderer: formatDate,
editor: new Ext.ux.form.DateTime()
},
checkColumn
]);
// by default columns are sortable
cm.defaultSortable = true;
// this could be inline, but we want to define the Plant record
// type so we can add records dynamically
var Plant = Ext.data.Record.create([
// the "name" below matches the tag name to read, except "availDate"
// which is mapped to the tag "availability"
{name: 'common', type: 'string'},
{name: 'botanical', type: 'string'},
{name: 'light'},
{name: 'price', type: 'float'}, // automatic date conversions
{name: 'availDate', mapping: 'availability', type: 'date', dateFormat: 'm/d/Y'},
{name: 'indoor', type: 'bool'}
]);
// create the Data Store
var store = new Ext.data.Store({
// load using HTTP
url: 'plants.xml',
// the return will be XML, so lets set up a reader
reader: new Ext.data.XmlReader({
// records will have a "plant" tag
record: 'plant'
}, Plant),
sortInfo:{field:'common', direction:'ASC'}
});
// create the editor grid
var grid = new Ext.grid.EditorGridPanel({
store: store,
cm: cm,
renderTo: 'editor-grid',
width:600,
height:300,
autoExpandColumn:'common',
title:'Edit Plants?',
frame:true,
plugins:checkColumn,
clicksToEdit:1,
tbar: [{
text: 'Add Plant',
handler : function(){
var p = new Plant({
common: 'New Plant 1',
light: 'Mostly Shade',
price: 0,
availDate: (new Date()).clearTime(),
indoor: false
});
grid.stopEditing();
store.insert(0, p);
grid.startEditing(0, 0);
}
}]
});
// trigger the data store load
store.load();
});
Ext.grid.CheckColumn = function(config){
Ext.apply(this, config);
if(!this.id){
this.id = Ext.id();
}
this.renderer = this.renderer.createDelegate(this);
};
Ext.grid.CheckColumn.prototype ={
init : function(grid){
this.grid = grid;
this.grid.on('render', function(){
var view = this.grid.getView();
view.mainBody.on('mousedown', this.onMouseDown, this);
}, this);
},
onMouseDown : function(e, t){
if(t.className && t.className.indexOf('x-grid3-cc-'+this.id) != -1){
e.stopEvent();
var index = this.grid.getView().findRowIndex(t);
var record = this.grid.store.getAt(index);
record.set(this.dataIndex, !record.data[this.dataIndex]);
}
},
renderer : function(v, p, record){
p.css += ' x-grid3-check-col-td';
return '<div class="x-grid3-check-col'+(v?'-on':'')+' x-grid3-cc-'+this.id+'"> </div>';
}
};