Complete rebuild of datepicker form widget

The datepicker now handles timezones and locale mainly on the client side. When a user selects a date/time, the value is chosen in their timezone preference, the script will then convert the value to the application timezone (UTC) for storage in the database. The reverse is true: when the value is loaded, it is converted from UTC to the user preference. The entire process is seamless. Dates are also formatted in the locale preference.

Example scenario: This fixes the issue when selecting the blog post published date. In some cases, the date could be set to 24th April but the server time is 23rd April, so the post appears unpublished against the user's intent.

There is still some issues around DATE and TIME column types stored in the database. It is best to always use TIMESTAMP/DATETIME to retain the timezone conversions. DATE will reset the time to 00:00 UTC which can cause issues and TIME does not play nicely with Carbon at all. We should still try to add support for these columns in the datepicker, even though they are not recommended.
This commit is contained in:
Samuel Georges 2016-04-24 09:33:39 +10:00
parent d49cef2201
commit e772e87de5
11 changed files with 425 additions and 308 deletions

View File

@ -1732,7 +1732,7 @@ this.appTimezone=$('meta[name="app-timezone"]').attr('content')
if(!this.appTimezone){this.appTimezone='UTC'}}
DateTimeConverter.prototype.getDateTimeValue=function(){this.datetime=this.$el.attr('datetime')
var momentObj=moment.tz(this.datetime,this.appTimezone),result
if(this.options.locale){moment.locale(this.options.locale)}
if(this.options.locale){momentObj=momentObj.locale(this.options.locale)}
if(this.options.timezone){momentObj=momentObj.tz(this.options.timezone)}
if(this.options.timeSince){result=momentObj.fromNow()}
else if(this.options.timeTense){result=momentObj.calendar()}

View File

@ -81,7 +81,7 @@
result
if (this.options.locale) {
moment.locale(this.options.locale)
momentObj = momentObj.locale(this.options.locale)
}
if (this.options.timezone) {
@ -164,7 +164,6 @@
return this
}
// Add this only if required
$(document).render(function (){
$('time[data-datetime-control]').dateTimeConverter()
})

View File

@ -3,6 +3,7 @@
use Carbon\Carbon;
use Backend\Classes\FormField;
use Backend\Classes\FormWidgetBase;
use System\Helpers\DateTime as DateTimeHelper;
/**
* Date picker
@ -13,17 +14,10 @@ use Backend\Classes\FormWidgetBase;
*/
class DatePicker extends FormWidgetBase
{
const TIME_PREFIX = '___time_';
//
// Configurable properties
//
/**
* @var string Display format.
*/
public $format = 'YYYY-MM-DD';
/**
* @var bool Display mode: datetime, date, time.
*/
@ -93,45 +87,42 @@ class DatePicker extends FormWidgetBase
{
$this->vars['name'] = $this->formField->getName();
$this->vars['timeName'] = self::TIME_PREFIX.$this->formField->getName(false);
$this->vars['timeValue'] = null;
if ($value = $this->getLoadValue()) {
$value = $value instanceof Carbon ? $value->toDateTimeString() : $value;
/*
* Date / Time
*/
if ($this->mode == 'datetime') {
if (is_object($value)) {
$value = $value->toDateTimeString();
}
// if ($this->mode == 'datetime') {
// if (is_object($value)) {
// $value = $value->toDateTimeString();
// }
$dateTime = explode(' ', $value);
$value = $dateTime[0];
$this->vars['timeValue'] = isset($dateTime[1]) ? substr($dateTime[1], 0, 5) : '';
}
// $dateTime = explode(' ', $value);
// $value = $dateTime[0];
// $this->vars['timeValue'] = isset($dateTime[1]) ? substr($dateTime[1], 0, 5) : '';
// }
/*
* Date
*/
elseif ($this->mode == 'date') {
if (is_string($value)) {
$value = substr($value, 0, 10);
}
elseif (is_object($value)) {
$value = $value->toDateString();
}
}
elseif ($this->mode == 'time') {
if (is_object($value)) {
$value = $value->toTimeString();
}
}
// elseif ($this->mode == 'date') {
// if (is_string($value)) {
// $value = substr($value, 0, 10);
// }
// elseif (is_object($value)) {
// $value = $value->toDateString();
// }
// }
// elseif ($this->mode == 'time') {
// if (is_object($value)) {
// $value = $value->toTimeString();
// }
// }
}
$this->vars['value'] = $value ?: '';
$this->vars['field'] = $this->formField;
$this->vars['format'] = $this->format;
$this->vars['mode'] = $this->mode;
$this->vars['minDate'] = $this->minDate;
$this->vars['maxDate'] = $this->maxDate;
@ -161,13 +152,13 @@ class DatePicker extends FormWidgetBase
return null;
}
$timeValue = post(self::TIME_PREFIX . $this->formField->getName(false));
if ($this->mode == 'datetime' && $timeValue) {
$value .= ' ' . $timeValue . ':00';
}
elseif ($this->mode == 'time') {
$value = substr($value, 0, 5) . ':00';
}
// $timeValue = post(self::TIME_PREFIX . $this->formField->getName(false));
// if ($this->mode == 'datetime' && $timeValue) {
// $value .= ' ' . $timeValue . ':00';
// }
// elseif ($this->mode == 'time') {
// $value = substr($value, 0, 5) . ':00';
// }
return $value;
}

View File

@ -152,7 +152,7 @@ plate.prepend(canvas);clearTimeout(movingTimer);$body.removeClass('clockpicker-m
if(svgSupported){var canvas=popover.find('.clockpicker-canvas'),svg=createSvgElement('svg');svg.setAttribute('class','clockpicker-svg');svg.setAttribute('width',diameter);svg.setAttribute('height',diameter);var g=createSvgElement('g');g.setAttribute('transform','translate('+dialRadius+','+dialRadius+')');var bearing=createSvgElement('circle');bearing.setAttribute('class','clockpicker-canvas-bearing');bearing.setAttribute('cx',0);bearing.setAttribute('cy',0);bearing.setAttribute('r',2);var hand=createSvgElement('line');hand.setAttribute('x1',0);hand.setAttribute('y1',0);var bg=createSvgElement('circle');bg.setAttribute('class','clockpicker-canvas-bg');bg.setAttribute('r',tickRadius);var fg=createSvgElement('circle');fg.setAttribute('class','clockpicker-canvas-fg');fg.setAttribute('r',3.5);g.appendChild(hand);g.appendChild(bg);g.appendChild(fg);g.appendChild(bearing);svg.appendChild(g);canvas.append(svg);this.hand=hand;this.bg=bg;this.fg=fg;this.bearing=bearing;this.g=g;this.canvas=canvas;}
raiseCallback(this.options.init);}
function raiseCallback(callbackFunction){if(callbackFunction&&typeof callbackFunction==="function"){callbackFunction();}}
ClockPicker.DEFAULTS={'default':'',fromnow:0,placement:'bottom',align:'left',donetext:'完成',autoclose:false,twelvehour:false,vibrate:true};ClockPicker.prototype.toggle=function(){this[this.isShown?'hide':'show']();};ClockPicker.prototype.locate=function(){var element=this.element,popover=this.popover,offset=element.offset(),width=element.outerWidth(),height=element.outerHeight(),placement=this.options.placement,align=this.options.align,styles={},self=this;popover.show();switch(placement){case'bottom':styles.top=offset.top+height;break;case'right':styles.left=offset.left+width;break;case'top':styles.top=offset.top-popover.outerHeight();break;case'left':styles.left=offset.left-popover.outerWidth();break;}
ClockPicker.DEFAULTS={'default':'',fromnow:0,placement:'bottom',align:'left',donetext:'Done',autoclose:false,twelvehour:false,vibrate:true};ClockPicker.prototype.toggle=function(){this[this.isShown?'hide':'show']();};ClockPicker.prototype.locate=function(){var element=this.element,popover=this.popover,offset=element.offset(),width=element.outerWidth(),height=element.outerHeight(),placement=this.options.placement,align=this.options.align,styles={},self=this;popover.show();switch(placement){case'bottom':styles.top=offset.top+height;break;case'right':styles.left=offset.left+width;break;case'top':styles.top=offset.top-popover.outerHeight();break;case'left':styles.left=offset.left-popover.outerWidth();break;}
switch(align){case'left':styles.left=offset.left;break;case'right':styles.left=offset.left+width-popover.outerWidth();break;case'top':styles.top=offset.top;break;case'bottom':styles.top=offset.top+height-popover.outerHeight();break;}
popover.css(styles);};ClockPicker.prototype.show=function(e){if(this.isShown){return;}
raiseCallback(this.options.beforeShow);var self=this;if(!this.isAppended){$body=$(document.body).append(this.popover);$win.on('resize.clockpicker'+this.id,function(){if(self.isShown){self.locate();}});this.isAppended=true;}
@ -167,54 +167,100 @@ if(value===60){value=0;}}}
if(this[this.currentView]!==value){if(vibrate&&this.options.vibrate){if(!this.vibrateTimer){navigator[vibrate](10);this.vibrateTimer=setTimeout($.proxy(function(){this.vibrateTimer=null;},this),100);}}}
this[this.currentView]=value;this[isHours?'spanHours':'spanMinutes'].html(leadingZero(value));if(!svgSupported){this[isHours?'hoursView':'minutesView'].find('.clockpicker-tick').each(function(){var tick=$(this);tick.toggleClass('active',value===+tick.html());});return;}
if(dragging||(!isHours&&value%5)){this.g.insertBefore(this.hand,this.bearing);this.g.insertBefore(this.bg,this.fg);this.bg.setAttribute('class','clockpicker-canvas-bg clockpicker-canvas-bg-trans');}else{this.g.insertBefore(this.hand,this.bg);this.g.insertBefore(this.fg,this.bg);this.bg.setAttribute('class','clockpicker-canvas-bg');}
var cx=Math.sin(radian)*radius,cy=-Math.cos(radian)*radius;this.hand.setAttribute('x2',cx);this.hand.setAttribute('y2',cy);this.bg.setAttribute('cx',cx);this.bg.setAttribute('cy',cy);this.fg.setAttribute('cx',cx);this.fg.setAttribute('cy',cy);};ClockPicker.prototype.done=function(){raiseCallback(this.options.beforeDone);this.hide();var last=this.input.prop('value'),value=leadingZero(this.hours)+':'+leadingZero(this.minutes);if(this.options.twelvehour){value=value+this.amOrPm;}
var cx=Math.sin(radian)*radius,cy=-Math.cos(radian)*radius;this.hand.setAttribute('x2',cx);this.hand.setAttribute('y2',cy);this.bg.setAttribute('cx',cx);this.bg.setAttribute('cy',cy);this.fg.setAttribute('cx',cx);this.fg.setAttribute('cy',cy);};ClockPicker.prototype.done=function(){raiseCallback(this.options.beforeDone);this.hide();var last=this.input.prop('value'),value=leadingZero(this.hours)+':'+leadingZero(this.minutes);if(this.options.twelvehour){value=value+' '+this.amOrPm;}
this.input.prop('value',value);if(value!==last){this.input.triggerHandler('change');if(!this.isInput){this.element.trigger('change');}}
if(this.options.autoclose){this.input.trigger('blur');}
raiseCallback(this.options.afterDone);};ClockPicker.prototype.remove=function(){this.element.removeData('clockpicker');this.input.off('focus.clockpicker click.clockpicker');this.addon.off('click.clockpicker');if(this.isShown){this.hide();}
if(this.isAppended){$win.off('resize.clockpicker'+this.id);this.popover.remove();}};$.fn.clockpicker=function(option){var args=Array.prototype.slice.call(arguments,1);return this.each(function(){var $this=$(this),data=$this.data('clockpicker');if(!data){var options=$.extend({},ClockPicker.DEFAULTS,$this.data(),typeof option=='object'&&option);$this.data('clockpicker',new ClockPicker($this,options));}else{if(typeof data[option]==='function'){data[option].apply(data,args);}}});};}());+function($){"use strict";var DatePicker=function(element,options){var self=this
this.options=options
this.$el=$(element)
this.$input=this.$el.find('input:first')
var $form=this.$el.closest('form'),changeMonitor=$form.data('oc.changeMonitor')
if(changeMonitor!==undefined)
changeMonitor.pause()
var pikadayOptions={yearRange:options.yearRange,format:options.format,setDefaultDate:moment(this.$input.val()).toDate(),i18n:$.oc.lang.get('datepicker'),onOpen:function(){var $field=$(this._o.trigger)
$(this.el).css({left:'auto',right:$(window).width()-$field.offset().left-$field.outerWidth()})}}
if(options.minDate){pikadayOptions.minDate=new Date(options.minDate)}
if(options.maxDate){pikadayOptions.maxDate=new Date(options.maxDate)}
this.$input.pikaday(pikadayOptions)
if(changeMonitor!==undefined)
changeMonitor.resume()}
if(this.isAppended){$win.off('resize.clockpicker'+this.id);this.popover.remove();}};$.fn.clockpicker=function(option){var args=Array.prototype.slice.call(arguments,1);return this.each(function(){var $this=$(this),data=$this.data('clockpicker');if(!data){var options=$.extend({},ClockPicker.DEFAULTS,$this.data(),typeof option=='object'&&option);$this.data('clockpicker',new ClockPicker($this,options));}else{if(typeof data[option]==='function'){data[option].apply(data,args);}}});};}());+function($){"use strict";var Base=$.oc.foundation.base,BaseProto=Base.prototype
var DatePicker=function(element,options){this.$el=$(element)
this.options=options||{}
$.oc.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()}
DatePicker.prototype=Object.create(BaseProto)
DatePicker.prototype.constructor=DatePicker
DatePicker.prototype.init=function(){var self=this,$form=this.$el.closest('form'),changeMonitor=$form.data('oc.changeMonitor')
if(changeMonitor!==undefined){changeMonitor.pause()}
this.dbDateTimeFormat='YYYY-MM-DD HH:mm:ss'
this.dbDateFormat='YYYY-MM-DD'
this.dbTimeFormat='HH:mm:ss'
this.$dataLocker=$('[data-datetime-value]',this.$el)
this.$datePicker=$('[data-datepicker]',this.$el)
this.$timePicker=$('[data-timepicker]',this.$el)
this.hasDate=!!this.$datePicker.length
this.hasTime=!!this.$timePicker.length
this.initRegion()
if(this.hasDate){this.initDatePicker()}
if(this.hasTime){this.initTimePicker()}
if(changeMonitor!==undefined){changeMonitor.resume()}
this.$timePicker.on('change.oc.datepicker',function(){if(!$.trim($(this).val())){self.emptyValues()}})
this.$datePicker.on('change.oc.datepicker',function(){if(!$.trim($(this).val())){self.emptyValues()}})
this.$el.one('dispose-control',this.proxy(this.dispose))}
DatePicker.prototype.dispose=function(){this.$timePicker.off('change.oc.datepicker')
this.$datePicker.off('change.oc.datepicker')
this.$el.off('dispose-control',this.proxy(this.dispose))
this.$el.removeData('oc.datePicker')
this.$el=null
this.options=null
BaseProto.dispose.call(this)}
DatePicker.prototype.initDatePicker=function(){var self=this
var pikadayOptions={yearRange:this.options.yearRange,format:this.getDateFormat(),setDefaultDate:moment().tz(this.timezone).format('l'),i18n:$.oc.lang.get('datepicker'),onOpen:function(){var $field=$(this._o.trigger)
$(this.el).css({left:'auto',right:$(window).width()-$field.offset().left-$field.outerWidth()})},onSelect:function(){self.onSelectDatePicker.call(self,this.getMoment())}}
this.$datePicker.val(this.getDataLockerValue('l'))
if(this.options.minDate){pikadayOptions.minDate=new Date(this.options.minDate)}
if(this.options.maxDate){pikadayOptions.maxDate=new Date(this.options.maxDate)}
this.$datePicker.pikaday(pikadayOptions)}
DatePicker.prototype.onSelectDatePicker=function(pickerMoment){var pickerValue=pickerMoment.format(this.dbDateFormat)
var timeValue=this.getTimePickerValue()
var momentObj=moment.tz(pickerValue+' '+timeValue,this.dbDateTimeFormat,this.timezone).tz(this.appTimezone)
var lockerValue=momentObj.format(this.dbDateTimeFormat)
this.$dataLocker.val(lockerValue)}
DatePicker.prototype.getDatePickerValue=function(){var value=this.$datePicker.val()
if(!this.hasDate||!value){return moment.tz(this.appTimezone).tz(this.timezone).format(this.dbDateFormat)}
return moment(value,this.getDateFormat()).format(this.dbDateFormat)}
DatePicker.prototype.getDateFormat=function(){var format=this.options.format
if(this.locale){format=moment().locale(this.locale).localeData().longDateFormat('l')}
return format}
DatePicker.prototype.initTimePicker=function(){this.$timePicker.clockpicker({autoclose:'true',placement:'bottom',align:'right',twelvehour:this.isTimeTwelveHour(),afterDone:this.proxy(this.onSelectTimePicker)})
this.$timePicker.val(this.getDataLockerValue(this.getTimeFormat()))}
DatePicker.prototype.onSelectTimePicker=function(){var pickerValue=this.$timePicker.val()
var timeValue=moment(pickerValue,this.getTimeFormat()).format(this.dbTimeFormat)
var dateValue=this.getDatePickerValue()
var momentObj=moment.tz(dateValue+' '+timeValue,this.dbDateTimeFormat,this.timezone).tz(this.appTimezone)
var lockerValue=momentObj.format(this.dbDateTimeFormat)
this.$dataLocker.val(lockerValue)}
DatePicker.prototype.getTimePickerValue=function(){var value=this.$timePicker.val()
if(!this.hasTime||!value){return moment.tz(this.appTimezone).tz(this.timezone).format(this.dbTimeFormat)}
return moment(value,this.getTimeFormat()).format(this.dbTimeFormat)}
DatePicker.prototype.getTimeFormat=function(){return this.isTimeTwelveHour()?'hh:mm A':'HH:mm'}
DatePicker.prototype.isTimeTwelveHour=function(){var momentObj=moment()
if(this.locale){momentObj=momentObj.locale(this.locale)}
return momentObj.localeData().longDateFormat('LT').indexOf('A')!==-1;}
DatePicker.prototype.emptyValues=function(){this.$dataLocker.val('')
this.$datePicker.val('')
this.$timePicker.val('')}
DatePicker.prototype.getDataLockerValue=function(format){var value=this.$dataLocker.val()
return value?this.getMomentLoadValue(value,format):null}
DatePicker.prototype.getMomentLoadValue=function(value,format){var momentObj=moment.tz(value,this.appTimezone)
if(this.locale){momentObj=momentObj.locale(this.locale)}
momentObj=momentObj.tz(this.timezone)
return momentObj.format(format)}
DatePicker.prototype.initRegion=function(){this.locale=$('meta[name="backend-locale"]').attr('content')
this.timezone=$('meta[name="backend-timezone"]').attr('content')
this.appTimezone=$('meta[name="app-timezone"]').attr('content')
if(!this.appTimezone){this.appTimezone='UTC'}
if(!this.timezone){this.timezone='UTC'}}
DatePicker.DEFAULTS={minDate:null,maxDate:null,format:'YYYY-MM-DD',yearRange:10}
var old=$.fn.datePicker
$.fn.datePicker=function(option){var args=Array.prototype.slice.call(arguments,1)
return this.each(function(){var $this=$(this)
var data=$this.data('oc.datepicker')
$.fn.datePicker=function(option){var args=Array.prototype.slice.call(arguments,1),items,result
items=this.each(function(){var $this=$(this)
var data=$this.data('oc.datePicker')
var options=$.extend({},DatePicker.DEFAULTS,$this.data(),typeof option=='object'&&option)
if(!data)$this.data('oc.datepicker',(data=new DatePicker(this,options)))
else if(typeof option=='string')data[option].apply(data,args)})}
if(!data)$this.data('oc.datePicker',(data=new DatePicker(this,options)))
if(typeof option=='string')result=data[option].apply(data,args)
if(typeof result!='undefined')return false})
return result?result:items}
$.fn.datePicker.Constructor=DatePicker
$.fn.datePicker.noConflict=function(){$.fn.datePicker=old
return this}
$(document).on('render',function(){$('[data-control="datepicker"]').datePicker()});}(window.jQuery);+function($){"use strict";var TimePicker=function(element,options){var self=this
this.options=options
this.$el=$(element)
this.$input=this.$el.find('input:first')
var $form=this.$el.closest('form'),changeMonitor=$form.data('oc.changeMonitor')
if(changeMonitor!==undefined)
changeMonitor.pause()
this.$input.clockpicker()
if(changeMonitor!==undefined)
changeMonitor.resume()}
TimePicker.DEFAULTS={}
var old=$.fn.timePicker
$.fn.timePicker=function(option){var args=Array.prototype.slice.call(arguments,1)
return this.each(function(){var $this=$(this)
var data=$this.data('oc.timepicker')
var options=$.extend({},TimePicker.DEFAULTS,$this.data(),typeof option=='object'&&option)
if(!data)$this.data('oc.timepicker',(data=new TimePicker(this,options)))
else if(typeof option=='string')data[option].apply(data,args)})}
$.fn.timePicker.Constructor=TimePicker
$.fn.timePicker.noConflict=function(){$.fn.timePicker=old
return this}
$(document).on('render',function(){$('[data-control="timepicker"]').timePicker()});}(window.jQuery);
$(document).on('render',function(){$('[data-control="datepicker"]').datePicker()});}(window.jQuery);

View File

@ -11,6 +11,5 @@
=require ../vendor/pikaday/js/pikaday.jquery.js
=require ../vendor/clockpicker/js/jquery-clockpicker.js
=require datepicker.js
=require timepicker.js
*/

View File

@ -1,45 +1,90 @@
/*
* DatePicker plugin
*
* Data attributes:
* - data-control="datepicker" - enables the plugin on an element
* - data-format="value" - display format
* - data-min-date="value" - minimum date to allow
* - data-max-date="value" - maximum date to allow
* - data-year-range="value" - range of years to display
*
* JavaScript API:
* $('a#someElement').datePicker({ option: 'value' })
*
* Dependences:
* - Pikaday plugin (pikaday.js)
* - Pikaday jQuery addon (pikaday.jquery.js)
* - Moment library (moment.js)
*/
+function ($) { "use strict";
var Base = $.oc.foundation.base,
BaseProto = Base.prototype
// DATEPICKER CLASS DEFINITION
// ============================
var DatePicker = function(element, options) {
var self = this
this.options = options
var DatePicker = function (element, options) {
this.$el = $(element)
this.$input = this.$el.find('input:first')
this.options = options || {}
// Init
$.oc.foundation.controlUtils.markDisposable(element)
Base.call(this)
this.init()
}
var $form = this.$el.closest('form'),
DatePicker.prototype = Object.create(BaseProto)
DatePicker.prototype.constructor = DatePicker
DatePicker.prototype.init = function() {
var self = this,
$form = this.$el.closest('form'),
changeMonitor = $form.data('oc.changeMonitor')
if (changeMonitor !== undefined)
if (changeMonitor !== undefined) {
changeMonitor.pause()
}
this.dbDateTimeFormat = 'YYYY-MM-DD HH:mm:ss'
this.dbDateFormat = 'YYYY-MM-DD'
this.dbTimeFormat = 'HH:mm:ss'
this.$dataLocker = $('[data-datetime-value]', this.$el)
this.$datePicker = $('[data-datepicker]', this.$el)
this.$timePicker = $('[data-timepicker]', this.$el)
this.hasDate = !!this.$datePicker.length
this.hasTime = !!this.$timePicker.length
this.initRegion()
if (this.hasDate) {
this.initDatePicker()
}
if (this.hasTime) {
this.initTimePicker()
}
if (changeMonitor !== undefined) {
changeMonitor.resume()
}
this.$timePicker.on('change.oc.datepicker', function() {
if (!$.trim($(this).val())) {
self.emptyValues()
}
})
this.$datePicker.on('change.oc.datepicker', function() {
if (!$.trim($(this).val())) {
self.emptyValues()
}
})
this.$el.one('dispose-control', this.proxy(this.dispose))
}
DatePicker.prototype.dispose = function() {
this.$timePicker.off('change.oc.datepicker')
this.$datePicker.off('change.oc.datepicker')
this.$el.off('dispose-control', this.proxy(this.dispose))
this.$el.removeData('oc.datePicker')
this.$el = null
this.options = null
BaseProto.dispose.call(this)
}
//
// Datepicker
//
DatePicker.prototype.initDatePicker = function() {
var self = this
var pikadayOptions = {
yearRange: options.yearRange,
format: options.format,
setDefaultDate: moment(this.$input.val()).toDate(),
yearRange: this.options.yearRange,
format: this.getDateFormat(),
setDefaultDate: moment().tz(this.timezone).format('l'), // now
i18n: $.oc.lang.get('datepicker'),
onOpen: function() {
var $field = $(this._o.trigger)
@ -48,21 +93,171 @@
left: 'auto',
right: $(window).width() - $field.offset().left - $field.outerWidth()
})
},
onSelect: function() {
self.onSelectDatePicker.call(self, this.getMoment())
}
}
if (options.minDate) {
pikadayOptions.minDate = new Date(options.minDate)
this.$datePicker.val(this.getDataLockerValue('l'))
if (this.options.minDate) {
pikadayOptions.minDate = new Date(this.options.minDate)
}
if (options.maxDate) {
pikadayOptions.maxDate = new Date(options.maxDate)
if (this.options.maxDate) {
pikadayOptions.maxDate = new Date(this.options.maxDate)
}
this.$input.pikaday(pikadayOptions)
this.$datePicker.pikaday(pikadayOptions)
}
if (changeMonitor !== undefined)
changeMonitor.resume()
DatePicker.prototype.onSelectDatePicker = function(pickerMoment) {
var pickerValue = pickerMoment.format(this.dbDateFormat)
var timeValue = this.getTimePickerValue()
var momentObj = moment
.tz(pickerValue + ' ' + timeValue, this.dbDateTimeFormat, this.timezone)
.tz(this.appTimezone)
var lockerValue = momentObj.format(this.dbDateTimeFormat)
this.$dataLocker.val(lockerValue)
}
// Returns in user preference timezone
DatePicker.prototype.getDatePickerValue = function() {
var value = this.$datePicker.val()
if (!this.hasDate || !value) {
return moment.tz(this.appTimezone)
.tz(this.timezone)
.format(this.dbDateFormat)
}
return moment(value, this.getDateFormat()).format(this.dbDateFormat)
}
DatePicker.prototype.getDateFormat = function() {
var format = this.options.format
if (this.locale) {
format = moment()
.locale(this.locale)
.localeData()
.longDateFormat('l')
}
return format
}
//
// Timepicker
//
DatePicker.prototype.initTimePicker = function() {
this.$timePicker.clockpicker({
autoclose: 'true',
placement: 'bottom',
align: 'right',
twelvehour: this.isTimeTwelveHour(),
afterDone: this.proxy(this.onSelectTimePicker)
})
this.$timePicker.val(this.getDataLockerValue(this.getTimeFormat()))
}
DatePicker.prototype.onSelectTimePicker = function() {
var pickerValue = this.$timePicker.val()
var timeValue = moment(pickerValue, this.getTimeFormat()).format(this.dbTimeFormat)
var dateValue = this.getDatePickerValue()
var momentObj = moment
.tz(dateValue + ' ' + timeValue, this.dbDateTimeFormat, this.timezone)
.tz(this.appTimezone)
var lockerValue = momentObj.format(this.dbDateTimeFormat)
this.$dataLocker.val(lockerValue)
}
// Returns in user preference timezone
DatePicker.prototype.getTimePickerValue = function() {
var value = this.$timePicker.val()
if (!this.hasTime || !value) {
return moment.tz(this.appTimezone)
.tz(this.timezone)
.format(this.dbTimeFormat)
}
return moment(value, this.getTimeFormat()).format(this.dbTimeFormat)
}
DatePicker.prototype.getTimeFormat = function() {
return this.isTimeTwelveHour()
? 'hh:mm A'
: 'HH:mm'
}
DatePicker.prototype.isTimeTwelveHour = function() {
var momentObj = moment()
if (this.locale) {
momentObj = momentObj.locale(this.locale)
}
return momentObj
.localeData()
.longDateFormat('LT')
.indexOf('A') !== -1;
}
//
// Utilities
//
DatePicker.prototype.emptyValues = function() {
this.$dataLocker.val('')
this.$datePicker.val('')
this.$timePicker.val('')
}
DatePicker.prototype.getDataLockerValue = function(format) {
var value = this.$dataLocker.val()
return value
? this.getMomentLoadValue(value, format)
: null
}
DatePicker.prototype.getMomentLoadValue = function(value, format) {
var momentObj = moment.tz(value, this.appTimezone)
if (this.locale) {
momentObj = momentObj.locale(this.locale)
}
momentObj = momentObj.tz(this.timezone)
return momentObj.format(format)
}
DatePicker.prototype.initRegion = function() {
this.locale = $('meta[name="backend-locale"]').attr('content')
this.timezone = $('meta[name="backend-timezone"]').attr('content')
this.appTimezone = $('meta[name="app-timezone"]').attr('content')
if (!this.appTimezone) {
this.appTimezone = 'UTC'
}
if (!this.timezone) {
this.timezone = 'UTC'
}
}
DatePicker.DEFAULTS = {
@ -72,35 +267,33 @@
yearRange: 10
}
// DATEPICKER PLUGIN DEFINITION
// PLUGIN DEFINITION
// ============================
var old = $.fn.datePicker
$.fn.datePicker = function (option) {
var args = Array.prototype.slice.call(arguments, 1)
return this.each(function () {
var args = Array.prototype.slice.call(arguments, 1), items, result
items = this.each(function () {
var $this = $(this)
var data = $this.data('oc.datepicker')
var data = $this.data('oc.datePicker')
var options = $.extend({}, DatePicker.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.datepicker', (data = new DatePicker(this, options)))
else if (typeof option == 'string') data[option].apply(data, args)
if (!data) $this.data('oc.datePicker', (data = new DatePicker(this, options)))
if (typeof option == 'string') result = data[option].apply(data, args)
if (typeof result != 'undefined') return false
})
return result ? result : items
}
$.fn.datePicker.Constructor = DatePicker
// DATEPICKER NO CONFLICT
// =================
$.fn.datePicker.noConflict = function () {
$.fn.datePicker = old
return this
}
// DATEPICKER DATA-API
// ===============
$(document).on('render', function() {
$('[data-control="datepicker"]').datePicker()
});

View File

@ -1,75 +0,0 @@
/*
* TimePicker plugin
*
* Data attributes:
* - data-control="timepicker" - enables the plugin on an element
*
* JavaScript API:
* $('a#someElement').timePicker({ option: 'value' })
*
* Dependences:
* - Clockpicker plugin (jquery-clockpicker.js)
*/
+function ($) { "use strict";
// DATEPICKER CLASS DEFINITION
// ============================
var TimePicker = function(element, options) {
var self = this
this.options = options
this.$el = $(element)
this.$input = this.$el.find('input:first')
// Init
var $form = this.$el.closest('form'),
changeMonitor = $form.data('oc.changeMonitor')
if (changeMonitor !== undefined)
changeMonitor.pause()
this.$input.clockpicker()
if (changeMonitor !== undefined)
changeMonitor.resume()
}
TimePicker.DEFAULTS = {
}
// DATEPICKER PLUGIN DEFINITION
// ============================
var old = $.fn.timePicker
$.fn.timePicker = function (option) {
var args = Array.prototype.slice.call(arguments, 1)
return this.each(function () {
var $this = $(this)
var data = $this.data('oc.timepicker')
var options = $.extend({}, TimePicker.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.timepicker', (data = new TimePicker(this, options)))
else if (typeof option == 'string') data[option].apply(data, args)
})
}
$.fn.timePicker.Constructor = TimePicker
// DATEPICKER NO CONFLICT
// =================
$.fn.timePicker.noConflict = function () {
$.fn.timePicker = old
return this
}
// DATEPICKER DATA-API
// ===============
$(document).on('render', function() {
$('[data-control="timepicker"]').timePicker()
});
}(window.jQuery);

View File

@ -2,6 +2,10 @@
* ClockPicker v0.0.7 (http://weareoutman.github.io/clockpicker/)
* Copyright 2014 Wang Shenwei.
* Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
* This library is no longer maintained by Weareoutman, hence
* no further updates will be provided. Consider this file
* a forked version and you a free to make changes.
*/
;(function(){
@ -367,7 +371,7 @@
fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
placement: 'bottom', // clock popover placement
align: 'left', // popover arrow align
donetext: '完成', // done button text
donetext: 'Done', // done button text
autoclose: false, // auto close when minute is selected
twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
vibrate: true // vibrate the device when dragging clock hand
@ -676,8 +680,9 @@
this.hide();
var last = this.input.prop('value'),
value = leadingZero(this.hours) + ':' + leadingZero(this.minutes);
if (this.options.twelvehour) {
value = value + this.amOrPm;
value = value + ' ' +this.amOrPm;
}
this.input.prop('value', value);

View File

@ -2,101 +2,38 @@
<div class="form-control"><?= $value ?></div>
<?php else: ?>
<?php if ($mode == 'date'): ?>
<div
id="<?= $this->getId() ?>"
class="field-datepicker"
data-control="datepicker"
data-format="<?= $format ?>"
<?php if ($minDate): ?>data-min-date="<?= $minDate ?>"<?php endif ?>
<?php if ($maxDate): ?>data-max-date="<?= $maxDate ?>"<?php endif ?>
>
<div class="input-with-icon right-align">
<i class="icon icon-calendar-o"></i>
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off"
<?= $field->getAttributes() ?> />
</div>
</div>
<?php if ($mode == 'date'): ?>
<?= $this->makePartial('picker_date') ?>
<?php elseif ($mode == 'datetime'): ?>
<div class="row">
<div class="col-md-6">
<!-- Date -->
<div
id="<?= $this->getId() ?>"
class="field-datepicker"
data-control="datepicker"
data-format="<?= $format ?>"
<?php if ($minDate): ?>data-min-date="<?= $minDate ?>"<?php endif ?>
<?php if ($maxDate): ?>data-max-date="<?= $maxDate ?>"<?php endif ?>
>
<div class="input-with-icon right-align">
<i class="icon icon-calendar-o"></i>
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off"
<?= $field->getAttributes() ?> />
</div>
</div>
<?= $this->makePartial('picker_date') ?>
</div>
<div class="col-md-6">
<!-- Time -->
<div
id="<?= $this->getId() ?>-time"
data-control="timepicker"
class="field-timepicker clockpicker">
<div class="input-with-icon right-align">
<i class="icon icon-clock-o"></i>
<input
type="text"
id="<?= $this->getId('input') ?>-time"
name="<?= $timeName ?>"
value="<?= $timeValue ?>"
class="form-control align-right"
autocomplete="off"
data-autoclose="true"
data-placement="bottom"
data-align="right"
<?= $field->getAttributes() ?> />
<?= $this->makePartial('picker_time') ?>
</div>
</div>
</div>
</div>
<?php elseif ($mode == 'time'): ?>
<div
id="<?= $this->getId() ?>"
data-control="timepicker"
class="field-timepicker clockpicker">
<div class="input-with-icon right-align">
<i class="icon icon-clock-o"></i>
<input
type="text"
id="<?= $this->getId('input') ?>"
name="<?= $name ?>"
value="<?= $value ?>"
class="form-control align-right"
autocomplete="off"
data-autoclose="true"
data-placement="bottom"
data-align="right"
<?= $field->getAttributes() ?> />
</div>
</div>
<?= $this->makePartial('picker_time') ?>
<?php endif ?>
<!-- Data locker -->
<input
type="hidden"
name="<?= $field->getName() ?>"
id="<?= $field->getId() ?>"
value="<?= e($value) ?>"
data-datetime-value
/>
</div>
<?php endif ?>

View File

@ -0,0 +1,11 @@
<!-- Date -->
<div class="input-with-icon right-align">
<i class="icon icon-calendar-o"></i>
<input
type="text"
id="<?= $this->getId('date') ?>"
class="form-control align-right"
autocomplete="off"
<?= $field->getAttributes() ?>
data-datepicker />
</div>

View File

@ -0,0 +1,11 @@
<!-- Date -->
<div class="input-with-icon right-align">
<i class="icon icon-clock-o"></i>
<input
type="text"
id="<?= $this->getId('time') ?>"
class="form-control align-right"
autocomplete="off"
<?= $field->getAttributes() ?>
data-timepicker />
</div>