datetime.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. /* Fix ExtJS 3.4 issue with new timezones */
  2. Ext.override(Ext.form.TimeField, {
  3. initDate: '2/1/2008'
  4. });
  5. Ext.ns('Ext.ux.form');
  6. /**
  7. * Creates new DateTime
  8. * @constructor
  9. * @param {Object} config A config object
  10. */
  11. Ext.ux.form.DateTime = Ext.extend(Ext.form.Field, {
  12. /**
  13. * @cfg {Function} dateValidator A custom validation function to be called during date field
  14. * validation (defaults to null)
  15. */
  16. dateValidator:null
  17. /**
  18. * @cfg {String/Object} defaultAutoCreate DomHelper element spec
  19. * Let superclass to create hidden field instead of textbox. Hidden will be submittend to server
  20. */
  21. ,defaultAutoCreate:{tag:'input', type:'hidden'}
  22. /**
  23. * @cfg {String} dtSeparator Date - Time separator. Used to split date and time (defaults to ' ' (space))
  24. */
  25. ,dtSeparator:' '
  26. /**
  27. * @cfg {String} hiddenFormat Format of datetime used to store value in hidden field
  28. * and submitted to server (defaults to 'Y-m-d H:i:s' that is mysql format)
  29. */
  30. ,hiddenFormat:'Y-m-d H:i:s'
  31. /**
  32. * @cfg {Boolean} otherToNow Set other field to now() if not explicly filled in (defaults to true)
  33. */
  34. ,otherToNow:true
  35. /**
  36. * @cfg {Boolean} emptyToNow Set field value to now on attempt to set empty value.
  37. * If it is true then setValue() sets value of field to current date and time (defaults to false)
  38. */
  39. /**
  40. * @cfg {String} timePosition Where the time field should be rendered. 'right' is suitable for forms
  41. * and 'below' is suitable if the field is used as the grid editor (defaults to 'right')
  42. */
  43. ,timePosition:'right' // valid values:'below', 'right'
  44. /**
  45. * @cfg {Function} timeValidator A custom validation function to be called during time field
  46. * validation (defaults to null)
  47. */
  48. ,timeValidator:null
  49. /**
  50. * @cfg {Number} timeWidth Width of time field in pixels (defaults to 100)
  51. */
  52. ,timeWidth:100
  53. /**
  54. * @cfg {String} dateFormat Format of DateField. Can be localized. (defaults to 'm/y/d')
  55. */
  56. ,dateFormat:'m/d/y'
  57. /**
  58. * @cfg {String} timeFormat Format of TimeField. Can be localized. (defaults to 'g:i A')
  59. */
  60. ,timeFormat:'g:i A'
  61. /**
  62. * @cfg {Object} dateConfig Config for DateField constructor.
  63. */
  64. /**
  65. * @cfg {Object} timeConfig Config for TimeField constructor.
  66. */
  67. ,maxDateValue: ''
  68. ,minDateValue: ''
  69. ,timeIncrement: 15
  70. ,maxTimeValue: null
  71. ,minTimeValue: null
  72. ,disabledDates: null
  73. ,hideTime: false
  74. // {{{
  75. /**
  76. * @private
  77. * creates DateField and TimeField and installs the necessary event handlers
  78. */
  79. ,initComponent:function() {
  80. // call parent initComponent
  81. Ext.ux.form.DateTime.superclass.initComponent.call(this);
  82. // offset time
  83. if (!this.hasOwnProperty('offset_time') || isNaN(this.offset_time)) {
  84. this.offset_time = 0;
  85. }
  86. // create DateField
  87. var dateConfig = Ext.apply({}, {
  88. id:this.id + '-date'
  89. ,format:this.dateFormat || Ext.form.DateField.prototype.format
  90. ,width:this.timeWidth
  91. ,selectOnFocus:this.selectOnFocus
  92. ,validator:this.dateValidator
  93. ,disabledDates: this.disabledDates || null
  94. ,disabledDays: this.disabledDays || []
  95. ,showToday: this.showToday || true
  96. ,maxValue: this.maxDateValue || ''
  97. ,minValue: this.minDateValue || ''
  98. ,startDay: this.startDay || 0
  99. ,allowBlank: this.allowBlank
  100. ,listeners:{
  101. blur:{scope:this, fn:this.onBlur}
  102. ,focus:{scope:this, fn:this.onFocus}
  103. }
  104. }, this.dateConfig);
  105. this.df = new Ext.form.DateField(dateConfig);
  106. this.df.ownerCt = this;
  107. delete(this.dateFormat);
  108. delete(this.disabledDates);
  109. delete(this.disabledDays);
  110. delete(this.maxDateValue);
  111. delete(this.minDateValue);
  112. delete(this.startDay);
  113. // create TimeField
  114. var timeConfig = Ext.apply({}, {
  115. id:this.id + '-time'
  116. ,format:this.timeFormat || Ext.form.TimeField.prototype.format
  117. ,width:this.timeWidth
  118. ,selectOnFocus:this.selectOnFocus
  119. ,validator:this.timeValidator
  120. ,increment: this.timeIncrement || 15
  121. ,maxValue: this.maxTimeValue || null
  122. ,minValue: this.minTimeValue || null
  123. ,hidden: this.hideTime
  124. ,allowBlank: this.allowBlank
  125. ,listeners:{
  126. blur:{scope:this, fn:this.onBlur}
  127. ,focus:{scope:this, fn:this.onFocus}
  128. }
  129. }, this.timeConfig);
  130. this.tf = new Ext.form.TimeField(timeConfig);
  131. this.tf.ownerCt = this;
  132. delete(this.timeFormat);
  133. delete(this.maxTimeValue);
  134. delete(this.minTimeValue);
  135. delete(this.timeIncrement);
  136. // relay events
  137. this.relayEvents(this.df, ['focus', 'specialkey', 'invalid', 'valid']);
  138. this.relayEvents(this.tf, ['focus', 'specialkey', 'invalid', 'valid']);
  139. this.on('specialkey', this.onSpecialKey, this);
  140. } // eo function initComponent
  141. // }}}
  142. // {{{
  143. /**
  144. * @private
  145. * Renders underlying DateField and TimeField and provides a workaround for side error icon bug
  146. */
  147. ,onRender:function(ct, position) {
  148. // don't run more than once
  149. if(this.isRendered) {
  150. return;
  151. }
  152. // render underlying hidden field
  153. Ext.ux.form.DateTime.superclass.onRender.call(this, ct, position);
  154. // render DateField and TimeField
  155. // create bounding table
  156. var t;
  157. if('below' === this.timePosition || 'bellow' === this.timePosition) {
  158. t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
  159. {tag:'tr',children:[{tag:'td', style:'padding-bottom:1px', cls:'ux-datetime-date'}]}
  160. ,{tag:'tr',children:[{tag:'td', cls:'ux-datetime-time'}]}
  161. ]}, true);
  162. }
  163. else {
  164. t = Ext.DomHelper.append(ct, {tag:'table',style:'border-collapse:collapse',children:[
  165. {tag:'tr',children:[
  166. {tag:'td',style:'padding-right:4px', cls:'ux-datetime-date'},{tag:'td', cls:'ux-datetime-time'}
  167. ]}
  168. ]}, true);
  169. }
  170. this.tableEl = t;
  171. this.wrap = t.wrap({cls:'x-form-field-wrap x-datetime-wrap'});
  172. // this.wrap = t.wrap();
  173. this.wrap.on("mousedown", this.onMouseDown, this, {delay:10});
  174. // render DateField & TimeField
  175. this.df.render(t.child('td.ux-datetime-date'));
  176. this.tf.render(t.child('td.ux-datetime-time'));
  177. // workaround for IE trigger misalignment bug
  178. // see http://extjs.com/forum/showthread.php?p=341075#post341075
  179. // if(Ext.isIE && Ext.isStrict) {
  180. // t.select('input').applyStyles({top:0});
  181. // }
  182. this.df.el.swallowEvent(['keydown', 'keypress']);
  183. this.tf.el.swallowEvent(['keydown', 'keypress']);
  184. // create icon for side invalid errorIcon
  185. if('side' === this.msgTarget) {
  186. var elp = this.el.findParent('.x-form-element', 10, true);
  187. if(elp) {
  188. this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
  189. }
  190. var o = {
  191. errorIcon:this.errorIcon
  192. ,msgTarget:'side'
  193. ,alignErrorIcon:this.alignErrorIcon.createDelegate(this)
  194. };
  195. Ext.apply(this.df, o);
  196. Ext.apply(this.tf, o);
  197. // this.df.errorIcon = this.errorIcon;
  198. // this.tf.errorIcon = this.errorIcon;
  199. }
  200. // setup name for submit
  201. this.el.dom.name = this.hiddenName || this.name || this.id;
  202. // prevent helper fields from being submitted
  203. this.df.el.dom.removeAttribute("name");
  204. this.tf.el.dom.removeAttribute("name");
  205. // we're rendered flag
  206. this.isRendered = true;
  207. // update hidden field
  208. this.updateHidden();
  209. } // eo function onRender
  210. // }}}
  211. // {{{
  212. /**
  213. * @private
  214. */
  215. ,adjustSize:Ext.BoxComponent.prototype.adjustSize
  216. // }}}
  217. // {{{
  218. /**
  219. * @private
  220. */
  221. ,alignErrorIcon:function() {
  222. this.errorIcon.alignTo(this.tableEl, 'tl-tr', [2, 0]);
  223. }
  224. // }}}
  225. // {{{
  226. /**
  227. * @private initializes internal dateValue
  228. */
  229. ,initDateValue:function() {
  230. this.dateValue = this.otherToNow ? new Date() : new Date(1970, 0, 1, 0, 0, 0);
  231. }
  232. // }}}
  233. // {{{
  234. /**
  235. * Calls clearInvalid on the DateField and TimeField
  236. */
  237. ,clearInvalid:function(){
  238. this.df.clearInvalid();
  239. this.tf.clearInvalid();
  240. } // eo function clearInvalid
  241. // }}}
  242. // {{{
  243. /**
  244. * Calls markInvalid on both DateField and TimeField
  245. * @param {String} msg Invalid message to display
  246. */
  247. ,markInvalid:function(msg){
  248. this.df.markInvalid(msg);
  249. this.tf.markInvalid(msg);
  250. } // eo function markInvalid
  251. // }}}
  252. // {{{
  253. /**
  254. * @private
  255. * called from Component::destroy.
  256. * Destroys all elements and removes all listeners we've created.
  257. */
  258. ,beforeDestroy:function() {
  259. if(this.isRendered) {
  260. // this.removeAllListeners();
  261. this.wrap.removeAllListeners();
  262. this.wrap.remove();
  263. this.tableEl.remove();
  264. this.df.destroy();
  265. this.tf.destroy();
  266. }
  267. } // eo function beforeDestroy
  268. // }}}
  269. // {{{
  270. /**
  271. * Disable this component.
  272. * @return {Ext.Component} this
  273. */
  274. ,disable:function() {
  275. if(this.isRendered) {
  276. this.df.disabled = this.disabled;
  277. this.df.onDisable();
  278. this.tf.onDisable();
  279. }
  280. this.disabled = true;
  281. this.df.disabled = true;
  282. this.tf.disabled = true;
  283. this.fireEvent("disable", this);
  284. return this;
  285. } // eo function disable
  286. // }}}
  287. // {{{
  288. /**
  289. * Enable this component.
  290. * @return {Ext.Component} this
  291. */
  292. ,enable:function() {
  293. if(this.rendered){
  294. this.df.onEnable();
  295. this.tf.onEnable();
  296. }
  297. this.disabled = false;
  298. this.df.disabled = false;
  299. this.tf.disabled = false;
  300. this.fireEvent("enable", this);
  301. return this;
  302. } // eo function enable
  303. // }}}
  304. // {{{
  305. /**
  306. * @private Focus date filed
  307. */
  308. ,focus:function() {
  309. this.df.focus();
  310. } // eo function focus
  311. // }}}
  312. // {{{
  313. /**
  314. * @private
  315. */
  316. ,getPositionEl:function() {
  317. return this.wrap;
  318. }
  319. // }}}
  320. // {{{
  321. /**
  322. * @private
  323. */
  324. ,getResizeEl:function() {
  325. return this.wrap;
  326. }
  327. // }}}
  328. // {{{
  329. /**
  330. * @return {Date/String} Returns value of this field
  331. */
  332. ,getValue:function() {
  333. // create new instance of date
  334. return this.dateValue ? new Date(this.dateValue) : '';
  335. } // eo function getValue
  336. // }}}
  337. // {{{
  338. /**
  339. * @return {Boolean} true = valid, false = invalid
  340. * @private Calls isValid methods of underlying DateField and TimeField and returns the result
  341. */
  342. ,isValid:function() {
  343. return this.df.isValid() && this.tf.isValid();
  344. } // eo function isValid
  345. // }}}
  346. // {{{
  347. /**
  348. * Returns true if this component is visible
  349. * @return {boolean}
  350. */
  351. ,isVisible : function(){
  352. return this.df.rendered && this.df.getActionEl().isVisible();
  353. } // eo function isVisible
  354. // }}}
  355. // {{{
  356. /**
  357. * @private Handles blur event
  358. */
  359. ,onBlur:function(f) {
  360. // called by both DateField and TimeField blur events
  361. // revert focus to previous field if clicked in between
  362. if(this.wrapClick) {
  363. f.focus();
  364. this.wrapClick = false;
  365. }
  366. // update underlying value
  367. if(f === this.df) {
  368. this.updateDate();
  369. }
  370. else {
  371. this.updateTime();
  372. }
  373. this.updateHidden();
  374. this.validate();
  375. // fire events later
  376. (function() {
  377. if(!this.df.hasFocus && !this.tf.hasFocus) {
  378. var v = this.getValue();
  379. if(String(v) !== String(this.startValue)) {
  380. this.fireEvent("change", this, v, this.startValue);
  381. }
  382. this.hasFocus = false;
  383. this.fireEvent('blur', this);
  384. }
  385. }).defer(100, this);
  386. } // eo function onBlur
  387. // }}}
  388. // {{{
  389. /**
  390. * @private Handles focus event
  391. */
  392. ,onFocus:function() {
  393. if(!this.hasFocus){
  394. this.hasFocus = true;
  395. this.startValue = this.getValue();
  396. this.fireEvent("focus", this);
  397. }
  398. }
  399. // }}}
  400. // {{{
  401. /**
  402. * @private Just to prevent blur event when clicked in the middle of fields
  403. */
  404. ,onMouseDown:function(e) {
  405. if(!this.disabled) {
  406. this.wrapClick = 'td' === e.target.nodeName.toLowerCase();
  407. }
  408. }
  409. // }}}
  410. // {{{
  411. /**
  412. * @private
  413. * Handles Tab and Shift-Tab events
  414. */
  415. ,onSpecialKey:function(t, e) {
  416. var key = e.getKey();
  417. if(key === e.TAB) {
  418. if(t === this.df && !e.shiftKey) {
  419. e.stopEvent();
  420. this.tf.focus();
  421. }
  422. if(t === this.tf && e.shiftKey) {
  423. e.stopEvent();
  424. this.df.focus();
  425. }
  426. this.updateValue();
  427. }
  428. // otherwise it misbehaves in editor grid
  429. if(key === e.ENTER) {
  430. this.updateValue();
  431. }
  432. } // eo function onSpecialKey
  433. // }}}
  434. // {{{
  435. /**
  436. * Resets the current field value to the originally loaded value
  437. * and clears any validation messages. See Ext.form.BasicForm.trackResetOnLoad
  438. */
  439. ,reset:function() {
  440. this.df.setValue(this.originalValue);
  441. this.tf.setValue(this.originalValue);
  442. } // eo function reset
  443. // }}}
  444. // {{{
  445. /**
  446. * @private Sets the value of DateField
  447. */
  448. ,setDate:function(date) {
  449. if (date && this.offset_time != 0) {
  450. date = date.add(Date.MINUTE, 60 * new Number(this.offset_time));
  451. }
  452. this.df.setValue(date);
  453. } // eo function setDate
  454. // }}}
  455. // {{{
  456. /**
  457. * @private Sets the value of TimeField
  458. */
  459. ,setTime:function(date) {
  460. if (date && this.offset_time != 0) {
  461. date = date.add(Date.MINUTE, 60 * new Number(this.offset_time));
  462. }
  463. this.tf.setValue(date);
  464. } // eo function setTime
  465. // }}}
  466. // {{{
  467. /**
  468. * @private
  469. * Sets correct sizes of underlying DateField and TimeField
  470. * With workarounds for IE bugs
  471. */
  472. ,setSize:function(w, h) {
  473. if(!w) {
  474. return;
  475. }
  476. if('below' === this.timePosition) {
  477. this.df.setSize(w, h);
  478. this.tf.setSize(w, h);
  479. if(Ext.isIE) {
  480. this.df.el.up('td').setWidth(w);
  481. this.tf.el.up('td').setWidth(w);
  482. }
  483. }
  484. else {
  485. this.df.setSize(w - this.timeWidth - 4, h);
  486. this.tf.setSize(this.timeWidth, h);
  487. if(Ext.isIE) {
  488. this.df.el.up('td').setWidth(w - this.timeWidth - 4);
  489. this.tf.el.up('td').setWidth(this.timeWidth);
  490. }
  491. }
  492. } // eo function setSize
  493. // }}}
  494. // {{{
  495. /**
  496. * @param {Mixed} val Value to set
  497. * Sets the value of this field
  498. */
  499. ,setValue:function(val) {
  500. if(!val && true === this.emptyToNow) {
  501. this.setValue(new Date());
  502. return;
  503. }
  504. else if(!val) {
  505. this.setDate('');
  506. this.setTime('');
  507. this.updateValue();
  508. return;
  509. }
  510. if ('number' === typeof val) {
  511. val = new Date(val);
  512. }
  513. else if('string' === typeof val && this.hiddenFormat) {
  514. val = Date.parseDate(val, this.hiddenFormat);
  515. }
  516. val = val ? val : new Date(1970, 0 ,1, 0, 0, 0);
  517. var da;
  518. if(val instanceof Date) {
  519. this.setDate(val);
  520. this.setTime(val);
  521. this.dateValue = new Date(Ext.isIE ? val.getTime() : val);
  522. }
  523. else {
  524. da = val.split(this.dtSeparator);
  525. this.setDate(da[0]);
  526. if(da[1]) {
  527. if(da[2]) {
  528. // add am/pm part back to time
  529. da[1] += da[2];
  530. }
  531. this.setTime(da[1]);
  532. }
  533. }
  534. this.updateValue();
  535. } // eo function setValue
  536. // }}}
  537. // {{{
  538. /**
  539. * Hide or show this component by boolean
  540. * @return {Ext.Component} this
  541. */
  542. ,setVisible: function(visible){
  543. if(visible) {
  544. this.df.show();
  545. this.tf.show();
  546. }else{
  547. this.df.hide();
  548. this.tf.hide();
  549. }
  550. return this;
  551. } // eo function setVisible
  552. // }}}
  553. //{{{
  554. ,show:function() {
  555. return this.setVisible(true);
  556. } // eo function show
  557. //}}}
  558. //{{{
  559. ,hide:function() {
  560. return this.setVisible(false);
  561. } // eo function hide
  562. //}}}
  563. // {{{
  564. /**
  565. * @private Updates the date part
  566. */
  567. ,updateDate:function() {
  568. var d = this.df.getValue();
  569. if(d) {
  570. if(!(this.dateValue instanceof Date)) {
  571. this.initDateValue();
  572. if(!this.tf.getValue()) {
  573. this.setTime(this.dateValue);
  574. }
  575. }
  576. this.dateValue.setMonth(0); // because of leap years
  577. this.dateValue.setFullYear(d.getFullYear());
  578. this.dateValue.setMonth(d.getMonth(), d.getDate());
  579. // this.dateValue.setDate(d.getDate());
  580. }
  581. else {
  582. this.dateValue = '';
  583. this.setTime('');
  584. }
  585. } // eo function updateDate
  586. // }}}
  587. // {{{
  588. /**
  589. * @private
  590. * Updates the time part
  591. */
  592. ,updateTime:function() {
  593. var t = this.tf.getValue();
  594. if(t && !(t instanceof Date)) {
  595. t = Date.parseDate(t, this.tf.format);
  596. }
  597. if(t && !this.df.getValue()) {
  598. this.initDateValue();
  599. this.setDate(this.dateValue);
  600. }
  601. if(this.dateValue instanceof Date) {
  602. if(t) {
  603. this.dateValue.setHours(t.getHours());
  604. this.dateValue.setMinutes(t.getMinutes());
  605. this.dateValue.setSeconds(t.getSeconds());
  606. }
  607. else {
  608. this.dateValue.setHours(0);
  609. this.dateValue.setMinutes(0);
  610. this.dateValue.setSeconds(0);
  611. }
  612. }
  613. } // eo function updateTime
  614. // }}}
  615. // {{{
  616. /**
  617. * @private Updates the underlying hidden field value
  618. */
  619. ,updateHidden:function() {
  620. if(this.isRendered) {
  621. var value = '';
  622. if (this.dateValue instanceof Date) {
  623. value = this.dateValue.add(Date.MINUTE, 0 - 60 * new Number(this.offset_time)).format(this.hiddenFormat);
  624. }
  625. this.el.dom.value = value;
  626. }
  627. }
  628. // }}}
  629. // {{{
  630. /**
  631. * @private Updates all of Date, Time and Hidden
  632. */
  633. ,updateValue:function() {
  634. this.updateDate();
  635. this.updateTime();
  636. this.updateHidden();
  637. return;
  638. } // eo function updateValue
  639. // }}}
  640. // {{{
  641. /**
  642. * @return {Boolean} true = valid, false = invalid
  643. * calls validate methods of DateField and TimeField
  644. */
  645. ,validate:function() {
  646. return this.df.validate() && this.tf.validate();
  647. } // eo function validate
  648. // }}}
  649. // {{{
  650. /**
  651. * Returns renderer suitable to render this field
  652. * @param {Object} Column model config
  653. */
  654. ,renderer: function(field) {
  655. var format = field.editor.dateFormat || Ext.ux.form.DateTime.prototype.dateFormat;
  656. format += ' ' + (field.editor.timeFormat || Ext.ux.form.DateTime.prototype.timeFormat);
  657. var renderer = function(val) {
  658. var retval = Ext.util.Format.date(val, format);
  659. return retval;
  660. };
  661. return renderer;
  662. } // eo function renderer
  663. // }}}
  664. }); // eo extend
  665. // register xtype
  666. Ext.reg('xdatetime', Ext.ux.form.DateTime);