mirror of
https://github.com/processwire/processwire.git
synced 2025-08-09 08:17:12 +02:00
Major improvements to InputfieldDateTime including a significant refactoring, adding support for HTML5 date/time input types, and a new date selection input using separate selects for month, day and year
This commit is contained in:
@@ -165,6 +165,23 @@ class PageArray extends PaginatedArray implements WirePaginatable {
|
||||
public function makeBlankItem() {
|
||||
return $this->wire('pages')->newPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new blank instance of this PageArray, for internal use.
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return PageArray
|
||||
*
|
||||
*/
|
||||
public function makeNew() {
|
||||
$class = get_class($this);
|
||||
/** @var PageArray $newArray */
|
||||
$newArray = $this->wire(new $class());
|
||||
// $newArray->finderOptions($this->finderOptions());
|
||||
if($this->lazyLoad) $newArray->_lazy(true);
|
||||
return $newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the provided pages into this PageArray.
|
||||
@@ -651,12 +668,12 @@ class PageArray extends PaginatedArray implements WirePaginatable {
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @param array $options
|
||||
* @param array|null $options Specify array to set or omit this argument to get
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function finderOptions(array $options = array()) {
|
||||
$this->finderOptions = $options;
|
||||
public function finderOptions($options = null) {
|
||||
if(is_array($options)) $this->finderOptions = $options;
|
||||
return $this->finderOptions;
|
||||
}
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
.pw-content .ui-datepicker,#content .ui-datepicker{font-size:1.1em}.pw-content .ui-datepicker-calendar,#content .ui-datepicker-calendar{margin-top:0}.pw-content .ui-datepicker-inline,#content .ui-datepicker-inline{padding-bottom:.5em}.ui-timepicker-div .ui-widget-header,#content .ui-timepicker-div .ui-widget-header{margin-bottom:8px}.ui-timepicker-div dl,#content .ui-timepicker-div dl{text-align:left;border:none;margin:0;padding:0 0 0 5px}.ui-timepicker-div dl dt,#content .ui-timepicker-div dl dt{height:25px;margin-bottom:-25px;padding-top:0;border:none;font-weight:normal}.ui-timepicker-div dl dd,#content .ui-timepicker-div dl dd{margin:0 10px 10px 65px;padding:0}.ui-timepicker-div td,#content .ui-timepicker-div td{font-size:90%}.ui-tpicker-grid-label,#content .ui-tpicker-grid-label{background:none;border:none;margin:0;padding:0}#ui-datepicker-div{font-size:12px;line-height:14px;display:none}button.ui-datepicker-trigger,a.pw-ui-datepicker-trigger{margin:0 0 0 .5em}input.InputfieldDatetimeDatepicker{position:relative;z-index:10}
|
||||
.pw-content .ui-datepicker,#content .ui-datepicker{font-size:1.1em}.pw-content .ui-datepicker-calendar,#content .ui-datepicker-calendar{margin-top:0}.pw-content .ui-datepicker-inline,#content .ui-datepicker-inline{padding-bottom:.5em;font-size:14px}.ui-timepicker-div .ui-widget-header,#content .ui-timepicker-div .ui-widget-header{margin-bottom:8px}.ui-timepicker-div dl,#content .ui-timepicker-div dl{text-align:left;border:none;margin:0;padding:0 0 0 5px}.ui-timepicker-div dl dt,#content .ui-timepicker-div dl dt{height:25px;margin-bottom:-25px;padding-top:0;border:none;font-weight:normal}.ui-timepicker-div dl dd,#content .ui-timepicker-div dl dd{margin:0 10px 10px 65px;padding:0}.ui-timepicker-div td,#content .ui-timepicker-div td{font-size:90%}.ui-tpicker-grid-label,#content .ui-tpicker-grid-label{background:none;border:none;margin:0;padding:0}#ui-datepicker-div{font-size:12px;line-height:14px;display:none}button.ui-datepicker-trigger,a.pw-ui-datepicker-trigger{margin:0 0 0 .5em}input.InputfieldDatetimeDatepicker{position:relative;z-index:10}input.InputfieldDatetimeDatepicker2{display:none}.InputfieldDatetime input[type=date],.InputfieldDatetime input[type=time]{width:auto}.InputfieldDatetime.InputfieldDatetimeMulti input,.InputfieldDatetime.InputfieldDatetimeMulti select{margin-bottom:4px}.InputfieldDatetime.InputfieldDatetimeMulti select{width:auto}
|
||||
|
@@ -1,4 +1,7 @@
|
||||
|
||||
/**
|
||||
* Manages InputfieldDatetime (text) elements with jQuery UI datepickers
|
||||
*
|
||||
*/
|
||||
function InputfieldDatetimeDatepicker($t) {
|
||||
|
||||
var pickerVisible = $t.is(".InputfieldDatetimeDatepicker2");
|
||||
@@ -78,14 +81,112 @@ function InputfieldDatetimeDatepicker($t) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages InputfieldDatetimeSelect elements
|
||||
*
|
||||
*/
|
||||
function InputfieldDatetimeSelect() {
|
||||
|
||||
/**
|
||||
* Validate selection in InputfieldDatetime selects
|
||||
*
|
||||
*/
|
||||
function validate($select) {
|
||||
var $parent = $select.parent(),
|
||||
$month = $parent.children('.InputfieldDatetimeMonth'),
|
||||
month = parseInt($month.val()),
|
||||
$day = $parent.children('.InputfieldDatetimeDay'),
|
||||
day = parseInt($day.val()),
|
||||
$year = $parent.children('.InputfieldDatetimeYear'),
|
||||
year = parseInt($year.val()),
|
||||
$value = $parent.children('.InputfieldDatetimeValue'),
|
||||
date = month && day && year ? new Date(year, month - 1, day) : null,
|
||||
errorClass = 'InputfieldDatetimeError';
|
||||
|
||||
if(date && date.getMonth() + 1 != month) {
|
||||
// day not valid for month
|
||||
day = '';
|
||||
$day.val('').addClass(errorClass);
|
||||
} else {
|
||||
$day.removeClass(errorClass);
|
||||
}
|
||||
|
||||
$value.val(date && day ? year + '-' + month + '-' + day : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the Year select has changed in an InputfieldDatetimeSelect
|
||||
*
|
||||
* Enables addition of years before/after when "-" or "+" option is selected
|
||||
*
|
||||
*/
|
||||
function yearChange($select) {
|
||||
|
||||
var value = $select.val();
|
||||
if(value !== '-' && value !== '+') return;
|
||||
|
||||
var $blankOption = $select.find('option[value=""]'),
|
||||
$option = $select.find('option[value="' + value + '"]'),
|
||||
fromYear = parseInt($select.attr('data-from-year')),
|
||||
toYear = parseInt($select.attr('data-to-year')),
|
||||
numYears = toYear - fromYear,
|
||||
n = 0,
|
||||
$o;
|
||||
|
||||
if(numYears < 10) numYears = 10;
|
||||
|
||||
if(value === '-') {
|
||||
// add # years prior
|
||||
toYear = fromYear-1;
|
||||
fromYear = fromYear - numYears;
|
||||
for(n = toYear; n >= fromYear; n--) {
|
||||
$o = jQuery('<option />').val(n).text(n);
|
||||
$select.prepend($o);
|
||||
}
|
||||
$option.html('< ' + fromYear);
|
||||
$select.prepend($option).prepend($blankOption);
|
||||
$select.val(toYear);
|
||||
$select.attr('data-from-year', fromYear);
|
||||
|
||||
} else if(value === '+') {
|
||||
// add # years after
|
||||
fromYear = toYear+1;
|
||||
toYear += numYears;
|
||||
for(n = fromYear; n <= toYear; n++) {
|
||||
$o = $('<option />').val(n).text(n);
|
||||
$select.append($o);
|
||||
}
|
||||
$option.html('> ' + toYear);
|
||||
$select.append($option);
|
||||
$select.val(fromYear);
|
||||
$select.attr('data-to-year', toYear);
|
||||
}
|
||||
}
|
||||
|
||||
jQuery(document).on('change', '.InputfieldDatetimeSelect select', function() {
|
||||
var $select = jQuery(this);
|
||||
if($select.hasClass('InputfieldDatetimeYear')) yearChange($select);
|
||||
validate($select);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Document ready
|
||||
*
|
||||
*/
|
||||
jQuery(document).ready(function($) {
|
||||
|
||||
// init datepickers present when document is ready
|
||||
$("input.InputfieldDatetimeDatepicker:not(.InputfieldDatetimeDatepicker3):not(.initDatepicker)").each(function(n) {
|
||||
InputfieldDatetimeDatepicker($(this));
|
||||
});
|
||||
|
||||
// init datepicker that should appear on focus (3) of text input, that wasn't present at document.ready
|
||||
$(document).on('focus', 'input.InputfieldDatetimeDatepicker3:not(.hasDatepicker)', function() {
|
||||
InputfieldDatetimeDatepicker($(this));
|
||||
});
|
||||
|
||||
// init date selects
|
||||
InputfieldDatetimeSelect();
|
||||
|
||||
});
|
||||
|
@@ -1 +1 @@
|
||||
function InputfieldDatetimeDatepicker(h){var i=h.is(".InputfieldDatetimeDatepicker2");var k=parseInt(h.attr("data-ts"));var d=null;var b=h.attr("data-dateformat");var g=h.attr("data-timeformat");var f=parseInt(h.attr("data-timeselect"));var j=g.length>0&&!i;var m=h.is(".InputfieldDatetimeDatepicker3")?"focus":"button";var l=parseInt(h.attr("data-ampm"))>0;var o=h.attr("data-yearrange");if(k>1){d=new Date(k)}if(i){var c=$("<div></div>");h.after(c)}else{var c=h}var n={changeMonth:true,changeYear:true,showOn:m,buttonText:">",showAnim:"fadeIn",dateFormat:b,gotoCurrent:true,defaultDate:d};if(o&&o.length){n.yearRange=o}if(j){n.ampm=l;n.timeFormat=g;if(f>0){n.controlType="select";n.oneLine=true}if(g.indexOf("ss")>-1){n.showSecond=true}if(g.indexOf("m")==-1){n.showMinute=false}c.datetimepicker(n)}else{c.datepicker(n)}if(i){c.change(function(p){var r=c.datepicker("getDate");var q=$.datepicker.formatDate(b,r);h.val(q)})}if(m=="button"){var a=h.next("button.ui-datepicker-trigger");if(a.length){var e=$("<a class='pw-ui-datepicker-trigger' href='#'><i class='fa fa-calendar'></i></a>");a.after(e).hide();e.click(function(){a.click();return false})}}h.addClass("initDatepicker")}jQuery(document).ready(function(a){a("input.InputfieldDatetimeDatepicker:not(.InputfieldDatetimeDatepicker3):not(.initDatepicker)").each(function(b){InputfieldDatetimeDatepicker(a(this))});a(document).on("focus","input.InputfieldDatetimeDatepicker3:not(.hasDatepicker)",function(){InputfieldDatetimeDatepicker(a(this))})});
|
||||
function InputfieldDatetimeDatepicker($t){var pickerVisible=$t.is(".InputfieldDatetimeDatepicker2");var ts=parseInt($t.attr("data-ts"));var tsDate=null;var dateFormat=$t.attr("data-dateformat");var timeFormat=$t.attr("data-timeformat");var timeSelect=parseInt($t.attr("data-timeselect"));var hasTimePicker=timeFormat.length>0&&!pickerVisible;var showOn=$t.is(".InputfieldDatetimeDatepicker3")?"focus":"button";var ampm=parseInt($t.attr("data-ampm"))>0;var yearRange=$t.attr("data-yearrange");if(ts>1)tsDate=new Date(ts);if(pickerVisible){var $datepicker=$("<div></div>");$t.after($datepicker)}else{var $datepicker=$t}var options={changeMonth:true,changeYear:true,showOn:showOn,buttonText:">",showAnim:"fadeIn",dateFormat:dateFormat,gotoCurrent:true,defaultDate:tsDate};if(yearRange&&yearRange.length)options.yearRange=yearRange;if(hasTimePicker){options.ampm=ampm;options.timeFormat=timeFormat;if(timeSelect>0){options.controlType="select";options.oneLine=true}if(timeFormat.indexOf("ss")>-1)options.showSecond=true;if(timeFormat.indexOf("m")==-1)options.showMinute=false;$datepicker.datetimepicker(options)}else{$datepicker.datepicker(options)}if(pickerVisible){$datepicker.change(function(e){var d=$datepicker.datepicker("getDate");var str=$.datepicker.formatDate(dateFormat,d);$t.val(str)})}if(showOn=="button"){var $button=$t.next("button.ui-datepicker-trigger");if($button.length){var $a=$("<a class='pw-ui-datepicker-trigger' href='#'><i class='fa fa-calendar'></i></a>");$button.after($a).hide();$a.click(function(){$button.click();return false})}}$t.addClass("initDatepicker")}function InputfieldDatetimeSelect(){function validate($select){var $parent=$select.parent(),$month=$parent.children(".InputfieldDatetimeMonth"),month=parseInt($month.val()),$day=$parent.children(".InputfieldDatetimeDay"),day=parseInt($day.val()),$year=$parent.children(".InputfieldDatetimeYear"),year=parseInt($year.val()),$value=$parent.children(".InputfieldDatetimeValue"),date=month&&day&&year?new Date(year,month-1,day):null,errorClass="InputfieldDatetimeError";if(date&&date.getMonth()+1!=month){day="";$day.val("").addClass(errorClass)}else{$day.removeClass(errorClass)}$value.val(date&&day?year+"-"+month+"-"+day:"")}function yearChange($select){var value=$select.val();if(value!=="-"&&value!=="+")return;var $blankOption=$select.find('option[value=""]'),$option=$select.find('option[value="'+value+'"]'),fromYear=parseInt($select.attr("data-from-year")),toYear=parseInt($select.attr("data-to-year")),numYears=toYear-fromYear,n=0,$o;if(numYears<10)numYears=10;if(value==="-"){toYear=fromYear-1;fromYear=fromYear-numYears;for(n=toYear;n>=fromYear;n--){$o=jQuery("<option />").val(n).text(n);$select.prepend($o)}$option.html("< "+fromYear);$select.prepend($option).prepend($blankOption);$select.val(toYear);$select.attr("data-from-year",fromYear)}else if(value==="+"){fromYear=toYear+1;toYear+=numYears;for(n=fromYear;n<=toYear;n++){$o=$("<option />").val(n).text(n);$select.append($o)}$option.html("> "+toYear);$select.append($option);$select.val(fromYear);$select.attr("data-to-year",toYear)}}jQuery(document).on("change",".InputfieldDatetimeSelect select",function(){var $select=jQuery(this);if($select.hasClass("InputfieldDatetimeYear"))yearChange($select);validate($select)})}jQuery(document).ready(function($){$("input.InputfieldDatetimeDatepicker:not(.InputfieldDatetimeDatepicker3):not(.initDatepicker)").each(function(n){InputfieldDatetimeDatepicker($(this))});$(document).on("focus","input.InputfieldDatetimeDatepicker3:not(.hasDatepicker)",function(){InputfieldDatetimeDatepicker($(this))});InputfieldDatetimeSelect()});
|
@@ -8,63 +8,247 @@
|
||||
* For documentation about the fields used in this class, please see:
|
||||
* /wire/core/Fieldtype.php
|
||||
*
|
||||
* ProcessWire 3.x, Copyright 2016 by Ryan Cramer
|
||||
* ProcessWire 3.x, Copyright 2020 by Ryan Cramer
|
||||
* https://processwire.com
|
||||
*
|
||||
* @property string $dateInputFormat
|
||||
* @property String $timeInputFormat
|
||||
* @property int $timeInputSelect
|
||||
* @property int $datepicker
|
||||
* @property string $yearRange
|
||||
* @property int|bool $defaultToday
|
||||
*
|
||||
* ~~~~~~
|
||||
* // get a datetime Inputfield
|
||||
* $f = $modules->get('InputfieldDatetime');
|
||||
* $f->attr('name', 'test_date');
|
||||
* $f->label = 'Test date';
|
||||
* $f->val(time()); // value is get or set a UNIX timestamp
|
||||
*
|
||||
* // date input with jQuery UI datepicker on focus
|
||||
* $f->inputType = 'text'; // not necessary as this is the default
|
||||
* $f->datepicker = InputfieldDatetime::datepickerFocus;
|
||||
*
|
||||
* // date selects
|
||||
* $f->inputType = 'select';
|
||||
* $f->dateSelectFormat = 'mdy'; // month abbr (i.e. 'Sep'), day, year
|
||||
* $f->dateSelectFormat = 'Mdy'; // month full (i.e. 'September'), day, year
|
||||
* $f->yearFrom = 2019; // optional year range from
|
||||
* $f->yearTo = 2024; // optional year range to
|
||||
*
|
||||
* // HTML5 date, time or date+time inputs
|
||||
* $f->inputType = 'html';
|
||||
* $f->htmlType = 'date'; // or 'time' or 'datetime'
|
||||
* ~~~~~~
|
||||
*
|
||||
* @property int $value This Inputfield keeps the value in UNIX timestamp format (int).
|
||||
* @property string $inputType Input type to use, one of: "text", "select" or "html" (when html type is used, also specify $htmlType).
|
||||
* @property int|bool $defaultToday When no value is present, default to today’s date/time?
|
||||
* @property int $subYear Substitute year when month+day or time only selections are made (default=2010)
|
||||
* @property int $subDay Substitute day when month+year or time only selectinos are made (default=8)
|
||||
* @property int $subMonth Substitute month when time-only selections are made (default=4)
|
||||
* @property int $subHour Substitute hour when date-only selections are made (default=0)
|
||||
* @property int $subMinute Substitute minute when date-only selection are made (default=0)
|
||||
* @property bool|int $requiredAttr When combined with "required" option, this also makes it use the HTML5 "required" attribute (default=false).
|
||||
*
|
||||
* Properties specific to "text" input type (with optional jQuery UI datepicker)
|
||||
* =============================================================================
|
||||
* @property int $datepicker jQuery UI datepicker type (see `datepicker*` constants)
|
||||
* @property string $yearRange Selectable year range in the format `-30:+20` where -30 is number of years before now and +20 is number of years after now.
|
||||
* @property int $timeInputSelect jQuery UI timeSelect type (requires datepicker)—specify 1 to use a `<select>` for time input, or 0 to use a slider (default=0)
|
||||
* @property string $dateInputFormat Date input format to use, see WireDateTime::$dateFormats (default='Y-m-d')
|
||||
* @property string $timeInputFormat Time input format to use, see WireDateTime::$timeFormats (default='')
|
||||
*
|
||||
* Properties specific to "html" input type
|
||||
* ========================================
|
||||
* @property string $htmlType When "html" is selection for $inputType, this should be one of: "date", "time" or "datetime".
|
||||
* @property int $timeStep Refers to the step attribute on time inputs
|
||||
* @property string $timeMin Refers to the min attribute on time inputs (HH:MM)
|
||||
* @property string $timeMax Refers to the max attribute on time inputs (HH:MM)
|
||||
* @property int $dateStep Refers to the step attribute on date inputs
|
||||
* @property string $dateMin Refers to the min attribute on date inputs, ISO-8601 (YYYY-MM-DD)
|
||||
* @property string $dateMax Refers to the max attribute on date inputs, ISO-8601 (YYYY-MM-DD)
|
||||
*
|
||||
* Properties specific to "select" input type
|
||||
* ==========================================
|
||||
* @property string $dateSelectFormat Format to use for date select
|
||||
* @property string $timeSelectFormat Format to use for time select
|
||||
* @property int $yearFrom First selectable year (default=current year - 100)
|
||||
* @property int $yearTo Last selectable year (default=current year + 20)
|
||||
* @property bool|int $yearLock Disallow selection of years outside the yearFrom/yearTo range? (default=false)
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class InputfieldDatetime extends Inputfield {
|
||||
|
||||
const defaultDateInputFormat = 'Y-m-d';
|
||||
|
||||
const datepickerNo = 0; // no datepicker
|
||||
const datepickerClick = 1; // datepicker on click
|
||||
const datepickerInline = 2; // inline datepicker, always visible (no timepicker support)
|
||||
const datepickerFocus = 3; // datepicker on field focus
|
||||
|
||||
|
||||
public static function getModuleInfo() {
|
||||
return array(
|
||||
'title' => __('Datetime', __FILE__), // Module Title
|
||||
'summary' => __('Inputfield that accepts date and optionally time', __FILE__), // Module Summary
|
||||
'version' => 106,
|
||||
'permanent' => true,
|
||||
);
|
||||
'version' => 107,
|
||||
'permanent' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* ISO-8601 date/time formats (default date input format)
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
*/
|
||||
const defaultDateInputFormat = 'Y-m-d';
|
||||
const defaultTimeInputFormat = 'H:i';
|
||||
const secondsTimeInputFormat = 'H:i:s';
|
||||
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: None
|
||||
*
|
||||
*/
|
||||
const datepickerNo = 0;
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: Click button to show
|
||||
*
|
||||
*/
|
||||
const datepickerClick = 1;
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: Inline datepicker always visible (no timepicker support)
|
||||
*
|
||||
*/
|
||||
const datepickerInline = 2;
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: Show when input focused (recommend option when using datepicker)
|
||||
*
|
||||
*/
|
||||
const datepickerFocus = 3;
|
||||
|
||||
|
||||
/**
|
||||
* @var InputfieldDatetimeType[]
|
||||
*
|
||||
*/
|
||||
static protected $inputTypes = array();
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the date/time inputfield
|
||||
*
|
||||
*/
|
||||
public function init() {
|
||||
|
||||
$this->attr('type', 'text');
|
||||
$this->attr('size', 25);
|
||||
$this->attr('placeholder', '');
|
||||
$this->set('dateInputFormat', self::defaultDateInputFormat);
|
||||
$this->set('timeInputFormat', '');
|
||||
$this->set('timeInputSelect', 0);
|
||||
$this->set('datepicker', self::datepickerNo);
|
||||
$this->set('yearRange', '');
|
||||
|
||||
$this->set('defaultToday', 0);
|
||||
|
||||
if($this->languages) foreach($this->languages as $language) {
|
||||
/** @var Language $language */
|
||||
// account for alternate formats in other languages
|
||||
if($language->isDefault()) continue;
|
||||
$this->set("dateInputFormat$language", '');
|
||||
$this->set("timeInputFormat$language", '');
|
||||
$this->set('inputType', 'text');
|
||||
$this->set('subYear', 2010);
|
||||
$this->set('subMonth', 4);
|
||||
$this->set('subDay', 8);
|
||||
$this->set('subHour', 0);
|
||||
$this->set('subMinute', 0);
|
||||
$this->set('requiredAttr', 0);
|
||||
|
||||
foreach($this->getInputTypes() as $name => $type) {
|
||||
$this->setArray($type->getDefaultSettings());
|
||||
}
|
||||
|
||||
parent::init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ISO-8601 substitute date (combination of subYear, subMonth, subDay)
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function subDate() {
|
||||
$year = (int) parent::getSetting('subYear');
|
||||
$month = (int) parent::getSetting('subMonth');
|
||||
$day = (int) parent::getSetting('subDay');
|
||||
if($year < 1000 || $year > 2500) $year = (int) date('Y');
|
||||
if($month > 12 || $month < 1) $month = 1;
|
||||
if($month < 10) $month = "0$month";
|
||||
if($day > 31 || $day < 1) $day = 1;
|
||||
if($day < 10) $day = "0$day";
|
||||
return "$year-$month-$day";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return ISO-8601 substitute time (combination of subHour:subMinute:00)
|
||||
*
|
||||
* #pw-internal
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function subTime() {
|
||||
$hour = (int) parent::getSetting('subHour');
|
||||
$minute = (int) parent::getSetting('subMinute');
|
||||
if($hour > 23 || $hour < 0) $hour = 0;
|
||||
if($hour < 10) $hour = "0$hour";
|
||||
if($minute > 59 || $minute < 0) $minute = 0;
|
||||
if($minute < 10) $minute = "0$minute";
|
||||
return "$hour:$minute:00";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all date/time input types
|
||||
*
|
||||
* @return InputfieldDatetimeType[]
|
||||
*
|
||||
*/
|
||||
public function getInputTypes() {
|
||||
|
||||
if(count(self::$inputTypes)) return self::$inputTypes;
|
||||
|
||||
$path = dirname(__FILE__) . '/';
|
||||
require_once($path . 'InputfieldDatetimeType.php');
|
||||
$dir = new \DirectoryIterator($path . 'types/');
|
||||
|
||||
foreach($dir as $file) {
|
||||
if($file->isDir() || $file->isDot() || $file->getExtension() != 'php') continue;
|
||||
require_once($file->getPathname());
|
||||
$className = wireClassName($file->getBasename('.php'), true);
|
||||
/** @var InputfieldDatetimeType $type */
|
||||
$type = $this->wire(new $className($this));
|
||||
$name = $type->getTypeName();
|
||||
self::$inputTypes[$name] = $type;
|
||||
}
|
||||
|
||||
return self::$inputTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current date/time input type instance
|
||||
*
|
||||
* @param string $typeName
|
||||
* @return InputfieldDatetimeType
|
||||
*
|
||||
*/
|
||||
public function getInputType($typeName = '') {
|
||||
$inputTypes = $this->getInputTypes();
|
||||
if(!$typeName) $typeName = $this->inputType;
|
||||
if(!$typeName || !isset($inputTypes[$typeName])) $typeName = 'text';
|
||||
return $inputTypes[$typeName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set property
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return Inputfield|WireData
|
||||
*
|
||||
*/
|
||||
public function set($key, $value) {
|
||||
if($key === 'dateMin' || $key === 'dateMax') {
|
||||
if(is_int($value)) $value = date(self::defaultDateInputFormat, $value);
|
||||
} else if($key === 'timeMin' || $key === 'timeMax') {
|
||||
if(is_int($value)) $value = date(self::defaultTimeInputFormat, $value);
|
||||
}
|
||||
return parent::set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before the render method, from a hook in the Inputfield class
|
||||
*
|
||||
@@ -73,59 +257,13 @@ class InputfieldDatetime extends Inputfield {
|
||||
*
|
||||
* @param Inputfield $parent
|
||||
* @param bool $renderValueMode
|
||||
* @return $this
|
||||
* @return bool
|
||||
*
|
||||
*/
|
||||
public function renderReady(Inputfield $parent = null, $renderValueMode = false) {
|
||||
$this->addClass('InputfieldNoFocus', 'wrapClass');
|
||||
list($dateFormat, $timeFormat) = $this->getInputFormats();
|
||||
if($dateFormat) {}
|
||||
$useTime = false;
|
||||
$language = $this->wire('languages') ? $this->wire('user')->language : null;
|
||||
|
||||
if($this->datepicker) {
|
||||
|
||||
$this->wire('modules')->get('JqueryCore'); // Jquery Core required before Jquery UI
|
||||
$this->wire('modules')->get('JqueryUI');
|
||||
$this->addClass("InputfieldDatetimeDatepicker InputfieldDatetimeDatepicker{$this->datepicker}");
|
||||
|
||||
if(strlen($timeFormat) && $this->datepicker != self::datepickerInline) {
|
||||
// add in the timepicker script, if applicable
|
||||
$useTime = true;
|
||||
$url = $this->config->urls->get('InputfieldDatetime');
|
||||
$this->config->scripts->add($url . 'timepicker/jquery-ui-timepicker-addon.min.js');
|
||||
$this->config->styles->add($url . 'timepicker/jquery-ui-timepicker-addon.min.css');
|
||||
}
|
||||
if($language) {
|
||||
// include i18n support for the datepicker
|
||||
// note that the 'xx' in the filename is just a placeholder to indicate what should be replaced for translations, as that file doesn't exist
|
||||
$langFile = ltrim($this->_('/wire/modules/Jquery/JqueryUI/i18n/jquery.ui.datepicker-xx.js'), '/'); // Datepicker translation file // Replace 'xx' with jQuery UI language code or specify your own js file
|
||||
if(is_file($this->config->paths->root . $langFile)) {
|
||||
// add a custom language file
|
||||
$this->config->scripts->add($this->config->urls->root . $langFile);
|
||||
} else {
|
||||
// attempt to auto-find one based on the language name (which are often 2 char language codes)
|
||||
$langFile = "wire/modules/Jquery/JqueryUI/i18n/jquery.ui.datepicker-{$language->name}.js";
|
||||
if(is_file($this->config->paths->root . $langFile)) $this->config->scripts->add($this->config->urls->root . $langFile);
|
||||
}
|
||||
if($useTime) {
|
||||
$langFile = $this->_('timepicker/i18n/jquery-ui-timepicker-xx.js'); // Timepicker translation file // Replace 'xx' with jQuery UI language code or specify your own js file. Timepicker i18n files are located in /wire/modules/Inputfield/InputfieldDatetime/timepicker/i18n/.
|
||||
$path = $this->config->paths->get('InputfieldDatetime');
|
||||
$url = $this->config->urls->get('InputfieldDatetime');
|
||||
if(is_file($path . $langFile)) {
|
||||
// add a custom language file
|
||||
$this->config->scripts->add($url . $langFile);
|
||||
} else {
|
||||
// attempt to auto-find one based on the language name (which are often 2 char language codes)
|
||||
$langFile = str_replace('-xx.', "-$language->name.", $langFile);
|
||||
if(is_file($path . $langFile)) {
|
||||
$this->config->scripts->add($url . $langFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parent::renderReady($parent, $renderValueMode);
|
||||
$this->addClass("InputfieldNoFocus", 'wrapClass');
|
||||
$this->getInputType()->renderReady();
|
||||
return parent::renderReady($parent, $renderValueMode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,56 +273,7 @@ class InputfieldDatetime extends Inputfield {
|
||||
*
|
||||
*/
|
||||
public function ___render() {
|
||||
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
$datetime = $this->wire('datetime');
|
||||
list($dateFormat, $timeFormat) = $this->getInputFormats();
|
||||
$useTime = false;
|
||||
if(strlen($timeFormat) && $this->datepicker && $this->datepicker != self::datepickerInline) $useTime = true;
|
||||
|
||||
$attrs = $this->getAttributes();
|
||||
$value = $attrs['value'];
|
||||
$valueTS = (int) $value*1000; // TS=for datepicker/javascript, which uses milliseconds rather than seconds
|
||||
unset($attrs['value']);
|
||||
|
||||
if(!$value && $this->defaultToday) {
|
||||
$value = date($dateFormat);
|
||||
if($timeFormat) $value .= ' ' . date($timeFormat);
|
||||
$valueTS = time()*1000;
|
||||
|
||||
} else if($value) {
|
||||
$value = trim(date($dateFormat . ' ' . $timeFormat, (int) $value));
|
||||
}
|
||||
|
||||
$value = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$dateFormatJS = $sanitizer->entities($datetime->convertDateFormat($dateFormat, 'js'));
|
||||
$timeFormatJS = $useTime ? $datetime->convertDateFormat($timeFormat, 'js') : '';
|
||||
|
||||
if(strpos($timeFormatJS, 'h24') !== false) {
|
||||
// 24 hour format
|
||||
$timeFormatJS = str_replace(array('hh24', 'h24'), array('HH', 'H'), $timeFormatJS);
|
||||
$ampm = 0;
|
||||
} else {
|
||||
$ampm = 1;
|
||||
}
|
||||
|
||||
if(strlen($timeFormatJS)) $timeFormatJS = $sanitizer->entities($timeFormatJS);
|
||||
if(empty($value)) $value = '';
|
||||
$yearRange = $sanitizer->entities($this->yearRange);
|
||||
|
||||
$out =
|
||||
"<input " . $this->getAttributesString($attrs) . " " .
|
||||
"value='$value' " .
|
||||
"data-dateformat='$dateFormatJS' " .
|
||||
"data-timeformat='$timeFormatJS' " .
|
||||
"data-timeselect='$this->timeInputSelect' " .
|
||||
"data-ts='$valueTS' " .
|
||||
"data-ampm='$ampm' " .
|
||||
(strlen($yearRange) ? "data-yearrange='$yearRange' " : '') .
|
||||
"/>";
|
||||
|
||||
return $out;
|
||||
return $this->getInputType()->render();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,63 +281,77 @@ class InputfieldDatetime extends Inputfield {
|
||||
*
|
||||
*/
|
||||
public function ___renderValue() {
|
||||
$value = $this->attr('value');
|
||||
|
||||
$out = $this->getInputType()->renderValue();
|
||||
if($out) return $out;
|
||||
|
||||
$value = $this->attr('value');
|
||||
if(!$value) return '';
|
||||
$format = trim($this->dateInputFormat . ' ' . $this->timeInputFormat);
|
||||
$format = self::defaultDateInputFormat . ' ';
|
||||
if($this->timeStep > 0 && $this->timeStep < 60) {
|
||||
$format .= self::secondsTimeInputFormat;
|
||||
} else {
|
||||
$format .= self::defaultTimeInputFormat;
|
||||
}
|
||||
|
||||
return $this->wire('datetime')->formatDate($value, trim($format));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process input
|
||||
*
|
||||
* @param WireInputData $input
|
||||
* @return Inputfield|InputfieldDatetime
|
||||
*
|
||||
*/
|
||||
public function ___processInput(WireInputData $input) {
|
||||
|
||||
$valuePrevious = $this->val();
|
||||
$value = $this->getInputType()->processInput($input);
|
||||
|
||||
if($value === false) {
|
||||
// false indicates type is not processing input
|
||||
parent::___processInput($input);
|
||||
$value = $this->getAttribute('value');
|
||||
} else {
|
||||
$this->setAttribute('value', $value);
|
||||
}
|
||||
|
||||
if($value !== $valuePrevious) {
|
||||
$this->trackChange('value', $valuePrevious, $value);
|
||||
$parent = $this->getParent();
|
||||
if($parent) $parent->trackChange($this->name);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture setting of the 'value' attribute and convert string dates to unix timestamp
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @return $this
|
||||
* @return Inputfield|InputfieldDatetime
|
||||
*
|
||||
*/
|
||||
public function setAttribute($key, $value) {
|
||||
if($key == 'value') {
|
||||
$value = $this->wire('datetime')->stringToTimestamp($value, $this->getInputFormats(true));
|
||||
if($key === 'value') {
|
||||
if(empty($value) && "$value" !== "0") {
|
||||
// empty value that’s not 0
|
||||
$value = '';
|
||||
} else if(is_int($value) || ctype_digit("$value")) {
|
||||
// unix timestamp
|
||||
$value = (int) $value;
|
||||
} else if(strlen($value) > 8 && $value[4] === '-' && $value[7] === '-' && ctype_digit(substr($value, 0, 4))) {
|
||||
// ISO-8601, i.e. 2010-04-08 02:48:00
|
||||
$value = strtotime($value);
|
||||
} else {
|
||||
$value = $this->getInputType()->sanitizeValue($value);
|
||||
}
|
||||
}
|
||||
return parent::setAttribute($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input format string for the user's language
|
||||
*
|
||||
* thanks to @oliverwehn (#1463)
|
||||
*
|
||||
* @param bool $getString Specify true to get a format string rather than an array
|
||||
* @return array|string of dateInputFormat timeInputFormat
|
||||
*
|
||||
*/
|
||||
protected function getInputFormats($getString = false) {
|
||||
|
||||
$inputFormats = array();
|
||||
$language = $this->wire('user')->language;
|
||||
$useLanguages = $this->wire('languages') && $language && !$language->isDefault();
|
||||
|
||||
foreach(array('date', 'time') as $type) {
|
||||
|
||||
$inputFormat = '';
|
||||
|
||||
if($useLanguages) {
|
||||
$inputFormat = trim($this->getSetting("{$type}InputFormat{$language->id}"));
|
||||
}
|
||||
|
||||
if(!strlen($inputFormat)) {
|
||||
// fallback to default language
|
||||
$inputFormat = $this->get("{$type}InputFormat");
|
||||
}
|
||||
|
||||
$inputFormats[] = $inputFormat;
|
||||
}
|
||||
|
||||
if($getString) return trim(implode(' ', $inputFormats));
|
||||
|
||||
return $inputFormats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Date/time Inputfield configuration, per field
|
||||
*
|
||||
@@ -256,161 +359,44 @@ class InputfieldDatetime extends Inputfield {
|
||||
public function ___getConfigInputfields() {
|
||||
|
||||
$inputfields = parent::___getConfigInputfields();
|
||||
$languages = $this->wire('languages');
|
||||
$datetime = $this->wire('datetime');
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
$f = $this->modules->get('InputfieldInteger');
|
||||
$f->setAttribute('name', 'size');
|
||||
$f->label = $this->_('Size');
|
||||
$f->attr('value', $this->attr('size'));
|
||||
$f->attr('size', 4);
|
||||
$f->description = $this->_('The displayed width of this field (in characters).');
|
||||
$inputfields->append($f);
|
||||
$inputTypes = $this->getInputTypes();
|
||||
$modules = $this->wire('modules'); /** @var Modules $modules */
|
||||
|
||||
/** @var InputfieldRadios $f */
|
||||
$f= $this->modules->get('InputfieldRadios');
|
||||
$f->label = $this->_('Date Picker');
|
||||
$f->setAttribute('name', 'datepicker');
|
||||
$f->addOption(self::datepickerNo, $this->_('No date/time picker'));
|
||||
$f->addOption(self::datepickerFocus, $this->_('Date/time picker on field focus') . ' ' .
|
||||
$this->_('(recommended)'));
|
||||
$f->addOption(self::datepickerClick, $this->_('Date/time picker on button click'));
|
||||
// @todo this datepickerInline option displays a datepicker that is too large, not fully styled
|
||||
$f->addOption(self::datepickerInline, $this->_('Inline date picker always visible (no time picker)'));
|
||||
$f->attr('value', (int) $this->datepicker);
|
||||
$inputfields->append($f);
|
||||
|
||||
/** @var InputfieldFieldset $fieldset */
|
||||
$fieldset = $this->modules->get('InputfieldFieldset');
|
||||
$fieldset->label = $this->_('Date/Time Input Formats');
|
||||
|
||||
/** @var InputfieldSelect $f */
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', '_dateInputFormat');
|
||||
$f->label = $this->_('Date Input Format');
|
||||
$f->description = $this->_('Select the format to be used for user input to this field. Your selection will populate the field below this, which you may customize further if needed.');
|
||||
$f = $modules->get('InputfieldRadios');
|
||||
$f->attr('name', 'inputType');
|
||||
$f->label = $this->_('Input Type');
|
||||
$f->icon = 'calendar';
|
||||
//$f->addOption('', $this->_('None'));
|
||||
$date = strtotime('2016-04-08 5:10:02 PM');
|
||||
foreach($datetime->getDateFormats() as $format) {
|
||||
$dateFormatted = $datetime->formatDate($date, $format);
|
||||
if($format == 'U') $dateFormatted .= " " . $this->_('(unix timestamp)');
|
||||
$f->addOption($format, $dateFormatted);
|
||||
if($this->dateInputFormat == $format) $f->attr('value', $format);
|
||||
|
||||
foreach($inputTypes as $inputTypeName => $inputType) {
|
||||
$f->addOption($inputTypeName, $inputType->getTypeLabel());
|
||||
}
|
||||
$f->attr('onchange', "$('#Inputfield_dateInputFormat').val($(this).val());");
|
||||
$fieldset->add($f);
|
||||
|
||||
$inputTypeVal = $this->getSetting('inputType');
|
||||
if(!$inputTypeVal) $inputTypeVal = 'text';
|
||||
if(!isset($inputTypes[$inputTypeVal])) $inputTypeVal = 'text';
|
||||
$f->val($inputTypeVal);
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldSelect $f */
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', '_timeInputFormat');
|
||||
$f->label = $this->_('Time Input Format');
|
||||
$f->addOption('', $this->_('None'));
|
||||
$f->description = $this->_('Select an optional time format to be used for input. If used, the calendar option will include a time picker.');
|
||||
$f->icon = 'clock-o';
|
||||
foreach($datetime->getTimeFormats() as $format) {
|
||||
if(strpos($format, '!') === 0) continue; // skip relative formats
|
||||
$timeFormatted = $datetime->formatDate($date, $format);
|
||||
$f->addOption($format, $timeFormatted);
|
||||
if($this->timeInputFormat == $format) $f->attr('value', $format);
|
||||
foreach($inputTypes as $inputTypeName => $inputType) {
|
||||
/** @var InputfieldFieldset $inputfields */
|
||||
$fieldset = $modules->get('InputfieldFieldset');
|
||||
$fieldset->attr('name', '_' . $inputTypeName . 'Options');
|
||||
$fieldset->label = $inputType->getTypeLabel();
|
||||
$fieldset->showIf = 'inputType=' . $inputTypeName;
|
||||
$inputType->getConfigInputfields($fieldset);
|
||||
$inputfields->add($fieldset);
|
||||
}
|
||||
$f->attr('onchange', "$('#Inputfield_timeInputFormat').val($(this).val());");
|
||||
$f->collapsed = Inputfield::collapsedBlank;
|
||||
$f->columnWidth = 50;
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldRadios $f */
|
||||
$f = $this->modules->get("InputfieldRadios");
|
||||
$f->attr('name', 'timeInputSelect');
|
||||
$f->label = $this->_('Time Input Type');
|
||||
$f->description = $this->_('Sliders (default) let the user slide controls to choose the time, where as Select lets the user select the time from a drop-down select.');
|
||||
$f->icon = 'clock-o';
|
||||
$f->addOption(0, $this->_('Sliders'));
|
||||
$f->addOption(1, $this->_('Select'));
|
||||
$f->optionColumns = 1;
|
||||
$f->columnWidth = 50;
|
||||
$f->showIf = "_timeInputFormat!='', datepicker!=" . self::datepickerNo;
|
||||
$f->attr('value', $this->timeInputSelect);
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->modules->get("InputfieldText");
|
||||
$f->attr('name', 'dateInputFormat');
|
||||
$f->attr('value', $this->dateInputFormat ? $this->dateInputFormat : self::defaultDateInputFormat);
|
||||
$f->attr('size', 20);
|
||||
$f->label = $this->_('Date Input Format Code');
|
||||
$f->description = $this->_('This is automatically built from the date select above, unless you modify it.');
|
||||
$f->icon = 'calendar';
|
||||
$notes = $this->_('See the [PHP date](http://www.php.net/manual/en/function.date.php) function reference for more information on how to customize these formats.');
|
||||
if($languages) $notes .= "\n" . $this->_('You may optionally specify formats for other languages here as well. Any languages left blank will inherit the default setting.');
|
||||
$f->notes = $notes;
|
||||
$f->collapsed = Inputfield::collapsedYes;
|
||||
$f1 = $f;
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->modules->get("InputfieldText");
|
||||
$f->attr('name', 'timeInputFormat');
|
||||
$f->attr('value', $this->timeInputFormat ? $this->timeInputFormat : '');
|
||||
$f->attr('size', 20);
|
||||
$f->label = $this->_('Time Input Format Code');
|
||||
$f->description = $this->_('This is automatically built from the time select above, unless you modify it.');
|
||||
$f->icon = 'clock-o';
|
||||
$f->notes = $notes;
|
||||
$f->collapsed = Inputfield::collapsedYes;
|
||||
$f2 = $f;
|
||||
|
||||
|
||||
if($languages) {
|
||||
$f1->useLanguages = true;
|
||||
$f2->useLanguages = true;
|
||||
foreach($languages as $language) {
|
||||
if($language->isDefault()) continue;
|
||||
$f1->set("value$language", (string) $this->get("dateInputFormat$language"));
|
||||
$f2->set("value$language", (string) $this->get("timeInputFormat$language"));
|
||||
}
|
||||
}
|
||||
|
||||
$fieldset->add($f1);
|
||||
$fieldset->add($f2);
|
||||
|
||||
$inputfields->add($fieldset);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->modules->get("InputfieldText");
|
||||
$f->attr('name', 'yearRange');
|
||||
$f->attr('value', $this->yearRange);
|
||||
$f->attr('size', 10);
|
||||
$f->label = $this->_('Date Picker Year Range');
|
||||
$f->description = $this->_('When the date picker is used, it has a selectable year range minus and plus 10 years from the current year. To extend or reduce that, specify the quantity of years before and after [current year] in this format: "-30:+20", which would show 30 years before now and 20 years after now.');
|
||||
$f->notes = $this->_('Default when no value present is "-10:+10" which shows a date picker year range 10 years before now, and 10 years after now.');
|
||||
$f->icon = 'arrows-h';
|
||||
$f->collapsed = Inputfield::collapsedBlank;
|
||||
$f->showIf = 'datepicker!=' . self::datepickerNo;
|
||||
$inputfields->append($f);
|
||||
|
||||
/** @var InputfieldCheckbox $f */
|
||||
$f = $this->modules->get('InputfieldCheckbox');
|
||||
$f->setAttribute('name', 'defaultToday');
|
||||
$f->attr('value', 1);
|
||||
if($this->defaultToday) $f->attr('checked', 'checked');
|
||||
$f->label = $this->_("Default to today's date?");
|
||||
$f->description = $this->_("If checked, this field will hold the current date when no value is entered."); // Default today description
|
||||
$f->columnWidth = 50;
|
||||
$f->setAttribute('name', 'defaultToday');
|
||||
$f->attr('value', 1);
|
||||
if($this->defaultToday) $f->attr('checked', 'checked');
|
||||
$f->label = $this->_('Default to today’s date?');
|
||||
$f->description = $this->_('If checked, this field will hold the current date when no value is entered.'); // Default today description
|
||||
$inputfields->append($f);
|
||||
|
||||
/** @var InputfieldText $field */
|
||||
$field = $this->modules->get('InputfieldText');
|
||||
$field->setAttribute('name', 'placeholder');
|
||||
$field->label = $this->_('Placeholder Text');
|
||||
$field->setAttribute('value', $this->attr('placeholder'));
|
||||
$field->description = $this->_('Optional placeholder text that appears in the field when blank.');
|
||||
$field->columnWidth = 50;
|
||||
$inputfields->append($field);
|
||||
|
||||
|
||||
return $inputfields;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@
|
||||
}
|
||||
.ui-datepicker-inline {
|
||||
padding-bottom: 0.5em;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,3 +70,24 @@ input.InputfieldDatetimeDatepicker {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
input.InputfieldDatetimeDatepicker2 {
|
||||
// inline datepicker, do not show <input> element
|
||||
display: none;
|
||||
}
|
||||
|
||||
.InputfieldDatetime {
|
||||
input[type=date],
|
||||
input[type=time] {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
.InputfieldDatetime.InputfieldDatetimeMulti {
|
||||
// date and time represented by separate (multiple) inputs
|
||||
input, select {
|
||||
margin-bottom: 4px; // for when they stack
|
||||
}
|
||||
select {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,146 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
abstract class InputfieldDatetimeType extends WireData {
|
||||
|
||||
/**
|
||||
* @var InputfieldDatetime
|
||||
*
|
||||
*/
|
||||
protected $inputfield;
|
||||
|
||||
/**
|
||||
* Construct
|
||||
*
|
||||
* @param InputfieldDatetime $inputfield
|
||||
*
|
||||
*/
|
||||
public function __construct(InputfieldDatetime $inputfield) {
|
||||
$this->inputfield = $inputfield;
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name for this type
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function getTypeName() {
|
||||
return strtolower(str_replace('InputfieldDatetime', '', $this->className()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get type label
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function getTypeLabel() {
|
||||
return str_replace('InputfieldDatetime', '', $this->className());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute
|
||||
*
|
||||
* @param string $key
|
||||
* @return string|null
|
||||
*
|
||||
*/
|
||||
public function getAttribute($key) {
|
||||
return $this->inputfield->getAttribute($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @return self
|
||||
*
|
||||
*/
|
||||
public function setAttribute($key, $value) {
|
||||
$this->inputfield->setAttribute($key, $value);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setting
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*
|
||||
*/
|
||||
public function getSetting($key) {
|
||||
return $this->inputfield->getSetting($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setting or attribute or API var
|
||||
*
|
||||
* @param string $key
|
||||
* @return mixed|null
|
||||
*
|
||||
*/
|
||||
public function get($key) {
|
||||
return $this->inputfield->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of default settings
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getDefaultSettings() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function renderValue() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize value to unix timestamp integer or blank string (to represent no value)
|
||||
*
|
||||
* @param string|int $value
|
||||
* @return int|string
|
||||
*
|
||||
*/
|
||||
public function sanitizeValue($value) {
|
||||
if(is_int($value) || ctype_digit("$value")) return (int) $value;
|
||||
if(empty($value)) return '';
|
||||
return strtotime($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ready
|
||||
*
|
||||
*/
|
||||
abstract public function renderReady();
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
abstract public function render();
|
||||
|
||||
/**
|
||||
* Process input
|
||||
*
|
||||
* @param WireInputData $input
|
||||
* @return int|string|bool Int for UNIX timestamp date, blank string for no date, or boolean false if InputfieldDatetime should process input
|
||||
*
|
||||
*/
|
||||
abstract public function processInput(WireInputData $input);
|
||||
|
||||
/**
|
||||
* @param InputfieldWrapper $inputfields
|
||||
*
|
||||
*/
|
||||
abstract public function getConfigInputfields(InputfieldWrapper $inputfields);
|
||||
|
||||
}
|
@@ -0,0 +1,264 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
* HTML5 date/time input types
|
||||
*
|
||||
*/
|
||||
class InputfieldDatetimeHtml extends InputfieldDatetimeType {
|
||||
|
||||
public function getDefaultSettings() {
|
||||
return array(
|
||||
'htmlType' => 'date',
|
||||
'dateStep' => 0,
|
||||
'dateMin' => '',
|
||||
'dateMax' => '',
|
||||
'timeStep' => 0,
|
||||
'timeMin' => '',
|
||||
'timeMax' => '',
|
||||
);
|
||||
}
|
||||
|
||||
public function getTypeLabel() {
|
||||
return $this->_('HTML5 browser native date, time or both');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ready
|
||||
*
|
||||
*/
|
||||
public function renderReady() {
|
||||
if($this->getSetting('htmlType') === 'datetime') {
|
||||
$this->inputfield->addClass('InputfieldDatetimeMulti', 'wrapClass'); // multiple unputs
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function render() {
|
||||
$out = '';
|
||||
switch($this->getSetting('htmlType')) {
|
||||
case 'date': $out = $this->renderDate(); break;
|
||||
case 'time': $out = $this->renderTime(); break;
|
||||
case 'datetime': $out = $this->renderDate() . ' ' . $this->renderTime(); break;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render date input
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function renderDate() {
|
||||
|
||||
$format = InputfieldDatetime::defaultDateInputFormat;
|
||||
$dateStep = (int) $this->getSetting('dateStep');
|
||||
$attrs = $this->inputfield->getAttributes();
|
||||
|
||||
unset($attrs['size']);
|
||||
|
||||
$value = $attrs['value'];
|
||||
if(!$value && $this->getSetting('defaultToday')) $value = time();
|
||||
$value = $value ? date($format, $value) : '';
|
||||
|
||||
$attrs['type'] = 'date';
|
||||
$attrs['value'] = $value;
|
||||
$attrs['placeholder'] = 'yyyy-mm-dd'; // placeholder and pattern...
|
||||
$attrs['pattern'] = '[0-9]{4}-[0-9]{2}-[0-9]{2}'; // ...used only if browser does not support HTML5 date
|
||||
|
||||
if($dateStep > 1) {
|
||||
$attrs['step'] = $dateStep;
|
||||
}
|
||||
|
||||
foreach(array('min' => 'dateMin', 'max' => 'dateMax') as $attrName => $propertyName) {
|
||||
$attrValue = $this->getSetting($propertyName);
|
||||
if(!$attrValue || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $attrValue)) continue;
|
||||
$attrs[$attrName] = $attrValue;
|
||||
}
|
||||
|
||||
return "<input " . $this->inputfield->getAttributesString($attrs) . " />";
|
||||
}
|
||||
|
||||
/**
|
||||
* Render time input
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
protected function renderTime() {
|
||||
|
||||
$timeStep = (int) $this->getSetting('timeStep');
|
||||
$useSeconds = $timeStep > 0 && $timeStep < 60;
|
||||
$format = $useSeconds ? InputfieldDatetime::secondsTimeInputFormat : InputfieldDatetime::defaultTimeInputFormat;
|
||||
$attrs = $this->inputfield->getAttributes();
|
||||
|
||||
unset($attrs['size']);
|
||||
|
||||
$value = $attrs['value'];
|
||||
if(!$value && $this->getSetting('defaultToday')) $value = time();
|
||||
$value = $value ? date($format, $value) : '';
|
||||
|
||||
$attrs['type'] = 'time';
|
||||
$attrs['value'] = $value;
|
||||
|
||||
// placeholder and pattern used only if browser does not support HTML5 time
|
||||
$attrs['placeholder'] = 'hh:mm' . ($useSeconds ? ':ss' : '');
|
||||
$attrs['pattern'] = '[0-9]{2}:[0-9]{2}' . ($useSeconds ? ':[0-9]{2}' : '');
|
||||
|
||||
if($timeStep > 0) {
|
||||
$attrs['step'] = $timeStep;
|
||||
}
|
||||
|
||||
foreach(array('min' => 'timeMin', 'max' => 'timeMax') as $attrName => $propertyName) {
|
||||
$attrValue = $this->getSetting($propertyName);
|
||||
if(!$attrValue || !preg_match('/^\d{2}:\d{2}(:\d{2})?$/', $attrValue)) continue;
|
||||
$attrs[$attrName] = $attrValue;
|
||||
}
|
||||
|
||||
if($this->getSetting('htmlType') == 'datetime') {
|
||||
$attrs['name'] .= '__time';
|
||||
$attrs['id'] .= '__time';
|
||||
}
|
||||
|
||||
return "<input " . $this->inputfield->getAttributesString($attrs) . " />";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WireInputData $input
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function processInput(WireInputData $input) {
|
||||
|
||||
$name = $this->getAttribute('name');
|
||||
$value = $input->$name;
|
||||
|
||||
switch($this->getSetting('htmlType')) {
|
||||
case 'datetime':
|
||||
$dateValue = trim($input->$name);
|
||||
$timeName = $name . '__time';
|
||||
$timeValue = trim($input->$timeName);
|
||||
// if time present but no date, substitute today's date
|
||||
if(!strlen($dateValue) && strlen($timeValue)) $dateValue = date('Y-m-d');
|
||||
$value = strlen($dateValue) ? strtotime(trim("$dateValue $timeValue")) : '';
|
||||
break;
|
||||
case 'date':
|
||||
case 'time':
|
||||
$value = $this->sanitizeValue($value);
|
||||
break;
|
||||
default:
|
||||
$value = $value ? strtotime($value) : '';
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function sanitizeValue($value) {
|
||||
|
||||
$htmlType = $this->getSetting('htmlType');
|
||||
$value = trim($value);
|
||||
|
||||
if(!strlen($value)) return '';
|
||||
if(ctype_digit($value)) return (int) $value;
|
||||
|
||||
if($htmlType === 'time' && !strpos($value, '-') && preg_match('/^\d+:/', $value)) {
|
||||
// hh:mm:ss
|
||||
$subDate = $this->inputfield->subDate();
|
||||
$value = strtotime("$subDate $value");
|
||||
if($value === false) $value = '';
|
||||
} else if($htmlType === 'date') {
|
||||
$subTime = $this->inputfield->subTime();
|
||||
$value = strtotime("$value $subTime");
|
||||
} else {
|
||||
$value = parent::sanitizeValue($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputfieldWrapper $inputfields
|
||||
*
|
||||
*/
|
||||
public function getConfigInputfields(InputfieldWrapper $inputfields) {
|
||||
|
||||
/** @var Modules $modules */
|
||||
$modules = $this->wire('modules');
|
||||
|
||||
/** @var InputfieldRadios $f */
|
||||
$f = $modules->get('InputfieldRadios');
|
||||
$f->attr('name', 'htmlType');
|
||||
$f->label = $this->_('HTML input type');
|
||||
$f->addOption('date', $this->_('Date'));
|
||||
$f->addOption('time', $this->_('Time'));
|
||||
$f->addOption('datetime', $this->_('Both date and time'));
|
||||
$f->val($this->getSetting('htmlType'));
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
/*
|
||||
$f = $modules->get('InputfieldInteger');
|
||||
$f->attr('name', 'dateStep');
|
||||
$f->label = $this->_('Step days for date input');
|
||||
if((int) $this->getSetting('dateStep') > 0) $f->attr('value', (int) $this->getSetting('dateStep'));
|
||||
$f->columnWidth = 33;
|
||||
$f->showIf = 'htmlType=date|datetime';
|
||||
$inputfields->add($f);
|
||||
*/
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $modules->get('InputfieldText');
|
||||
$f->attr('type', 'date');
|
||||
$f->attr('name', 'dateMin');
|
||||
$f->label = $this->_('Minimum allowed date');
|
||||
if($this->getSetting('dateMin')) $f->val($this->getSetting('dateMin'));
|
||||
$f->showIf = 'htmlType=date|datetime';
|
||||
$f->columnWidth = 50;
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $modules->get('InputfieldText');
|
||||
$f->attr('type', 'date');
|
||||
$f->inputType = 'html';
|
||||
$f->attr('name', 'dateMax');
|
||||
$f->label = $this->_('Maximum allowed date');
|
||||
if($this->getSetting('dateMax')) $f->val($this->getSetting('dateMax'));
|
||||
$f->showIf = 'htmlType=date|datetime';
|
||||
$f->columnWidth = 50;
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
$f = $modules->get('InputfieldInteger');
|
||||
$f->attr('name', 'timeStep');
|
||||
$f->label = $this->_('Step seconds for time input');
|
||||
if((int) $this->getSetting('timeStep') > 0) $f->attr('value', (int) $this->getSetting('timeStep'));
|
||||
$f->showIf = 'htmlType=time|datetime';
|
||||
$f->columnWidth = 33;
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $modules->get('InputfieldText');
|
||||
$f->attr('type', 'time');
|
||||
$f->attr('name', 'timeMin');
|
||||
$f->label = $this->_('Minimum allowed time');
|
||||
if($this->getSetting('timeMin')) $f->val($this->getSetting('timeMin'));
|
||||
$f->showIf = 'htmlType=time|datetime';
|
||||
$f->columnWidth = 33;
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $modules->get('InputfieldText');
|
||||
$f->attr('type', 'time');
|
||||
$f->attr('name', 'timeMax');
|
||||
$f->label = $this->_('Maximum allowed time');
|
||||
if($this->getSetting('timeMax')) $f->val($this->getSetting('timeMax'));
|
||||
$f->showIf = 'htmlType=time|datetime';
|
||||
$f->columnWidth = 34;
|
||||
$inputfields->add($f);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,298 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class InputfieldDatetimeSelect extends InputfieldDatetimeType {
|
||||
|
||||
public function getDefaultSettings() {
|
||||
$year = (int) date('Y');
|
||||
return array(
|
||||
'dateSelectFormat' => 'yMd',
|
||||
'timeSelectFormat' => '',
|
||||
'yearFrom' => $year - 100,
|
||||
'yearTo' => $year + 20,
|
||||
'yearLock' => false,
|
||||
);
|
||||
}
|
||||
|
||||
public function getTypeLabel() {
|
||||
return $this->_('Separate select inputs for month, day, and year (and optionally time)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get years range
|
||||
*
|
||||
* @param int $valueYear
|
||||
* @return array of [ $yearFrom, $yearTo ]
|
||||
*
|
||||
*/
|
||||
protected function getYearsRange($valueYear) {
|
||||
|
||||
$defaults = $this->getDefaultSettings();
|
||||
$yearFrom = $this->getSetting('yearFrom');
|
||||
$yearTo = $this->getSetting('yearTo');
|
||||
$yearLock = (int) $this->getSetting('yearLock');
|
||||
|
||||
if(!$yearFrom) $yearFrom = $defaults['yearFrom'];
|
||||
if(!$yearTo) $yearTo = $defaults['yearTo'];
|
||||
|
||||
if($yearFrom > $yearTo) {
|
||||
list($yearFrom, $yearTo) = array($yearTo, $yearFrom);
|
||||
}
|
||||
|
||||
if($valueYear && !$yearLock) {
|
||||
// there is already a year value present
|
||||
$numYears = $yearTo > $yearFrom ? ceil(($yearTo - $yearFrom) / 2) : 1;
|
||||
if($valueYear > $yearTo || $valueYear < $yearFrom) {
|
||||
// year is before or after that accounted for in selectable range, so change the range
|
||||
$yearTo = $valueYear + $numYears;
|
||||
$yearFrom = $valueYear - $numYears;
|
||||
}
|
||||
}
|
||||
|
||||
return array($yearFrom, $yearTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ready
|
||||
*
|
||||
*/
|
||||
public function renderReady() {
|
||||
// "Multi" indicates multiple inputs constructing date/time
|
||||
$this->inputfield->addClass('InputfieldDatetimeSelect InputfieldDatetimeMulti', 'wrapClass');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
$name = $this->getAttribute('name');
|
||||
$value = $this->getAttribute('value');
|
||||
$valueYear = $value ? date('Y', $value) : 0;
|
||||
$yearLock = $this->getSetting('yearLock');
|
||||
$format = $this->getSetting('dateSelectFormat');
|
||||
$select = $this->modules->get('InputfieldSelect'); /** @var InputfieldSelect $select */
|
||||
$sanitizer = $this->wire('sanitizer'); /** @var Sanitizer $sanitizer */
|
||||
$monthLabel = $this->_('Month');
|
||||
$yearLabel = $this->_('Year');
|
||||
$dayLabel = $this->_('Day');
|
||||
$select->addClass('InputfieldSetWidth');
|
||||
|
||||
$months = clone $select;
|
||||
$months->attr('id+name', $name . '__m');
|
||||
$months->attr('title', $monthLabel);
|
||||
$months->addClass('InputfieldDatetimeMonth');
|
||||
$months->addOption('', $monthLabel);
|
||||
$abbreviate = strpos($format, 'M') === false;
|
||||
|
||||
for($n = 1; $n <= 12; $n++) {
|
||||
$monthFormat = $abbreviate ? '%b' : '%B';
|
||||
$monthLabel = $sanitizer->entities(strftime($monthFormat, mktime(0, 0, 0, $n)));
|
||||
$months->addOption($n, $monthLabel);
|
||||
}
|
||||
|
||||
list($yearFrom, $yearTo) = $this->getYearsRange($valueYear);
|
||||
|
||||
$years = clone $select;
|
||||
$years->attr('id+name', $name . '__y');
|
||||
$years->attr('title', $yearLabel);
|
||||
$years->attr('data-from-year', $yearFrom);
|
||||
$years->attr('data-to-year', $yearTo);
|
||||
$years->addClass('InputfieldDatetimeYear');
|
||||
$years->addOption('', $yearLabel);
|
||||
if(!$yearLock) $years->addOption("-", "< $yearFrom");
|
||||
|
||||
for($n = $yearFrom; $n <= $yearTo; $n++) {
|
||||
$years->addOption($n, $n);
|
||||
}
|
||||
|
||||
if(!$yearLock) $years->addOption("+", "> $yearTo");
|
||||
|
||||
$days = clone $select;
|
||||
$days->attr('id+name', $name . '__d');
|
||||
$days->attr('title', $dayLabel);
|
||||
$days->addClass('InputfieldDatetimeDay');
|
||||
$days->addOption('', $dayLabel);
|
||||
|
||||
for($n = 1; $n <= 31; $n++) {
|
||||
$days->addOption($n, $n);
|
||||
}
|
||||
|
||||
if($value) {
|
||||
$months->val(date('n', $value));
|
||||
$days->val(date('j', $value));
|
||||
$years->val($valueYear);
|
||||
}
|
||||
|
||||
$a = array();
|
||||
for($n = 0; $n < strlen($format); $n++) {
|
||||
switch(strtolower($format[$n])) {
|
||||
case 'm': $a[] = $months->render(); break;
|
||||
case 'y': $a[] = $years->render(); break;
|
||||
case 'd': $a[] = $days->render(); break;
|
||||
}
|
||||
}
|
||||
|
||||
$attrs = $this->inputfield->getAttributes();
|
||||
$attrs['type'] = 'hidden';
|
||||
$attrs['value'] = date(InputfieldDatetime::defaultDateInputFormat . ' ' . InputfieldDatetime::defaultTimeInputFormat, $value);
|
||||
unset($attrs['size'], $attrs['placeholder'], $attrs['class'], $attrs['required']);
|
||||
$attrs['class'] = 'InputfieldDatetimeValue';
|
||||
$attrStr = $this->inputfield->getAttributesString($attrs);
|
||||
$out = implode(' ', $a) . "<input $attrStr />"; // hidden input for dependencies if needed
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WireInputData $input
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function processInput(WireInputData $input) {
|
||||
|
||||
$name = $this->getAttribute('name');
|
||||
|
||||
$a = array(
|
||||
'second' => 0,
|
||||
'hour' => 0,
|
||||
'minute' => 0,
|
||||
'month' => (int) $input[$name . '__m'],
|
||||
'year' => (int) $input[$name . '__y'],
|
||||
'day' => (int) $input[$name . '__d'],
|
||||
);
|
||||
|
||||
if(!strlen(trim("$a[month]$a[day]$a[year]"))) {
|
||||
// empty value
|
||||
$this->setAttribute('value', '');
|
||||
return '';
|
||||
}
|
||||
|
||||
if(empty($a['month'])) $a['month'] = 1;
|
||||
if($a['month'] > 12) $a['month'] = 12;
|
||||
if(empty($a['year'])) $a['year'] = date('Y');
|
||||
if(empty($a['day'])) $a['day'] = 1;
|
||||
if($a['day'] > 31) $a['day'] = 31;
|
||||
|
||||
if((int) $this->getSetting('yearLock')) {
|
||||
list($yearFrom, $yearTo) = $this->getYearsRange($a['year']);
|
||||
if($a['year'] < $yearFrom || $a['year'] > $yearTo) {
|
||||
// year is outside selectable range
|
||||
$this->setAttribute('value', '');
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
$value = mktime($a['hour'], $a['minute'], $a['second'], $a['month'], $a['day'], $a['year']);
|
||||
|
||||
foreach($a as $k => $v) {
|
||||
if($k === 'year') continue;
|
||||
if(strlen("$v") === 1) $a[$k] = "0$v";
|
||||
}
|
||||
|
||||
$test1 = "$a[year]-$a[month]-$a[day]"; // $a[hour]:$a[minute]";
|
||||
$test2 = date('Y-m-d', $value);
|
||||
if($test1 !== $test2) {
|
||||
$this->inputfield->error(sprintf($this->_('Invalid date “%1$s” changed to “%2$s”'), $test1, $test2));
|
||||
}
|
||||
|
||||
if($value) $this->setAttribute('value', $value);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputfieldWrapper $inputfields
|
||||
*
|
||||
*/
|
||||
public function getConfigInputfields(InputfieldWrapper $inputfields) {
|
||||
|
||||
list($y, $d, $h, $hh, $i, $a) = explode(' ', date('Y d h H i A'));
|
||||
list($m, $mm) = explode(' ', strftime('%b %B'));
|
||||
$none = $this->_('None');
|
||||
if($m === $mm && $m === 'May') list($m, $mm) = array('Apr', 'April');
|
||||
|
||||
$dateOptions = array(
|
||||
'' => $none,
|
||||
'mdy' => "$m $d $y",
|
||||
'Mdy' => "$mm $d $y",
|
||||
'dmy' => "$d $m $y",
|
||||
'dMy' => "$d $mm $y",
|
||||
'ymd' => "$y $m $d",
|
||||
'yMd' => "$y $mm $d",
|
||||
// @todo: add options for 2-part dates (month year, day month, etc.)
|
||||
//'md_' => "$m $d",
|
||||
//'dm_' => "$d $m",
|
||||
//'my.' => "$m $y",
|
||||
//'ym.' => "$y $m",
|
||||
//'Md_' => "$mm $d",
|
||||
//'dM_' => "$d $mm",
|
||||
//'My.' => "$mm $y",
|
||||
//'yM.' => "$y $mm"
|
||||
);
|
||||
|
||||
if($mm === $m) {
|
||||
$abbrLabel = $this->_('(abbreviated)');
|
||||
foreach($dateOptions as $key => $value) {
|
||||
if(stripos($key, 'm') !== false) {
|
||||
$dateOptions[$key] .= " $abbrLabel";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @var InputfieldSelect $f */
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', 'dateSelectFormat');
|
||||
$f->label = $this->_('Date select format to use');
|
||||
$f->addOptions($dateOptions);
|
||||
$f->val($this->getSetting('dateSelectFormat'));
|
||||
$f->notes = $this->_('Month names are language/locale based');
|
||||
$inputfields->add($f);
|
||||
|
||||
// @todo add time select option
|
||||
//$f->columnWidth = 50;
|
||||
$timeOptions = array(
|
||||
'' => $none,
|
||||
'hia' => "$h:$i $a",
|
||||
'Hi' => "$hh:$i"
|
||||
);
|
||||
/*
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', 'timeSelectFormat');
|
||||
$f->label = $this->_('Time select format to use');
|
||||
$f->addOptions($timeOptions);
|
||||
$f->val($this->timeSelectFormat);
|
||||
$f->columnWidth = 50;
|
||||
$inputfields->add($f);
|
||||
*/
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
$f = $this->modules->get('InputfieldInteger');
|
||||
$f->attr('name', 'yearFrom');
|
||||
$f->label = $this->_('First selectable year');
|
||||
$f->val($this->getSetting('yearFrom'));
|
||||
$f->columnWidth = 33;
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
$f = $this->modules->get('InputfieldInteger');
|
||||
$f->attr('name', 'yearTo');
|
||||
$f->label = $this->_('Last selectable year');
|
||||
$f->val($this->getSetting('yearTo'));
|
||||
$f->columnWidth = 33;
|
||||
$inputfields->add($f);
|
||||
|
||||
/** @var InputfieldToggle $f */
|
||||
$f = $this->modules->get('InputfieldToggle');
|
||||
$f->attr('name', 'yearLock');
|
||||
$f->label = $this->_('Limit selection to these years?');
|
||||
$f->val((int) $this->getSetting('yearLock'));
|
||||
$f->columnWidth = 34;
|
||||
$inputfields->add($f);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,410 @@
|
||||
<?php namespace ProcessWire;
|
||||
|
||||
/**
|
||||
* Text date input types with optional jQuery UI datepicker
|
||||
*
|
||||
*/
|
||||
class InputfieldDatetimeText extends InputfieldDatetimeType {
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: None
|
||||
*
|
||||
*/
|
||||
const datepickerNo = 0;
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: Click button to show
|
||||
*
|
||||
*/
|
||||
const datepickerClick = 1;
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: Inline datepicker always visible (no timepicker support)
|
||||
*
|
||||
*/
|
||||
const datepickerInline = 2;
|
||||
|
||||
/**
|
||||
* jQuery UI datepicker: Show when input focused (recommend option when using datepicker)
|
||||
*
|
||||
*/
|
||||
const datepickerFocus = 3;
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getDefaultSettings() {
|
||||
|
||||
$a = array(
|
||||
'datepicker' => self::datepickerNo,
|
||||
'dateInputFormat' => InputfieldDatetime::defaultDateInputFormat,
|
||||
'timeInputFormat' => '',
|
||||
'timeInputSelect' => 0,
|
||||
'yearRange' => '',
|
||||
);
|
||||
|
||||
if($this->languages) {
|
||||
foreach($this->languages as $language) {
|
||||
/** @var Language $language */
|
||||
// account for alternate formats in other languages
|
||||
if($language->isDefault()) continue;
|
||||
$a["dateInputFormat$language"] = '';
|
||||
$a["timeInputFormat$language"] = '';
|
||||
}
|
||||
}
|
||||
|
||||
return $a;
|
||||
}
|
||||
|
||||
public function getTypeLabel() {
|
||||
return $this->_('Text input with jQuery UI datepicker');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render ready
|
||||
*
|
||||
*/
|
||||
public function renderReady() {
|
||||
|
||||
/** @var Config $config */
|
||||
$config = $this->wire('config');
|
||||
|
||||
// this method only needs to run if datepicker is in use
|
||||
$datepicker = (int) $this->getSetting('datepicker');
|
||||
if(!$datepicker) return;
|
||||
|
||||
list($dateFormat, $timeFormat) = $this->getInputFormat(true);
|
||||
if($dateFormat) {} // not used here
|
||||
|
||||
$useTime = false;
|
||||
$language = $this->wire('languages') ? $this->wire('user')->language : null;
|
||||
|
||||
$this->wire('modules')->get('JqueryCore'); // Jquery Core required before Jquery UI
|
||||
$this->wire('modules')->get('JqueryUI');
|
||||
$this->inputfield->addClass("InputfieldDatetimeDatepicker InputfieldDatetimeDatepicker{$datepicker}");
|
||||
|
||||
if(strlen($timeFormat) && $datepicker != self::datepickerInline) {
|
||||
// add in the timepicker script, if applicable
|
||||
$useTime = true;
|
||||
$url = $config->urls->get('InputfieldDatetime');
|
||||
$config->scripts->add($url . 'timepicker/jquery-ui-timepicker-addon.min.js');
|
||||
$config->styles->add($url . 'timepicker/jquery-ui-timepicker-addon.min.css');
|
||||
}
|
||||
|
||||
if($language) {
|
||||
// include i18n support for the datepicker
|
||||
// note that the 'xx' in the filename is just a placeholder to indicate what should be replaced for translations, as that file doesn't exist
|
||||
$langFile = ltrim($this->_('/wire/modules/Jquery/JqueryUI/i18n/jquery.ui.datepicker-xx.js'), '/'); // Datepicker translation file // Replace 'xx' with jQuery UI language code or specify your own js file
|
||||
if(is_file($config->paths->root . $langFile)) {
|
||||
// add a custom language file
|
||||
$config->scripts->add($config->urls->root . $langFile);
|
||||
} else {
|
||||
// attempt to auto-find one based on the language name (which are often 2 char language codes)
|
||||
$langFile = "wire/modules/Jquery/JqueryUI/i18n/jquery.ui.datepicker-{$language->name}.js";
|
||||
if(is_file($config->paths->root . $langFile)) $config->scripts->add($config->urls->root . $langFile);
|
||||
}
|
||||
if($useTime) {
|
||||
$langFile = $this->_('timepicker/i18n/jquery-ui-timepicker-xx.js'); // Timepicker translation file // Replace 'xx' with jQuery UI language code or specify your own js file. Timepicker i18n files are located in /wire/modules/Inputfield/InputfieldDatetime/timepicker/i18n/.
|
||||
$path = $config->paths->get('InputfieldDatetime');
|
||||
$url = $config->urls->get('InputfieldDatetime');
|
||||
if(is_file($path . $langFile)) {
|
||||
// add a custom language file
|
||||
$config->scripts->add($url . $langFile);
|
||||
} else {
|
||||
// attempt to auto-find one based on the language name (which are often 2 char language codes)
|
||||
$langFile = str_replace('-xx.', "-$language->name.", $langFile);
|
||||
if(is_file($path . $langFile)) {
|
||||
$config->scripts->add($url . $langFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function render() {
|
||||
|
||||
/** @var Sanitizer $sanitizer */
|
||||
$sanitizer = $this->wire('sanitizer');
|
||||
|
||||
/** @var WireDateTime $datetime */
|
||||
$datetime = $this->wire('datetime');
|
||||
|
||||
$datepicker = (int) $this->getSetting('datepicker');
|
||||
|
||||
list($dateFormat, $timeFormat) = $this->getInputFormat(true);
|
||||
$useTime = false;
|
||||
if(strlen($timeFormat) && $datepicker && $datepicker != self::datepickerInline) $useTime = true;
|
||||
|
||||
$attrs = $this->inputfield->getAttributes();
|
||||
$value = $attrs['value'];
|
||||
$valueTS = (int) $value*1000; // TS=for datepicker/javascript, which uses milliseconds rather than seconds
|
||||
unset($attrs['value']);
|
||||
|
||||
if(!$value && $this->inputfield->getSetting('defaultToday')) {
|
||||
$value = date($dateFormat);
|
||||
if($timeFormat) $value .= ' ' . date($timeFormat);
|
||||
$valueTS = time()*1000;
|
||||
|
||||
} else if($value) {
|
||||
$value = trim(date($dateFormat . ' ' . $timeFormat, (int) $value));
|
||||
}
|
||||
|
||||
$value = $sanitizer->entities($value);
|
||||
|
||||
$dateFormatJS = $sanitizer->entities($datetime->convertDateFormat($dateFormat, 'js'));
|
||||
$timeFormatJS = $useTime ? $datetime->convertDateFormat($timeFormat, 'js') : '';
|
||||
|
||||
if(strpos($timeFormatJS, 'h24') !== false) {
|
||||
// 24 hour format
|
||||
$timeFormatJS = str_replace(array('hh24', 'h24'), array('HH', 'H'), $timeFormatJS);
|
||||
$ampm = 0;
|
||||
} else {
|
||||
$ampm = 1;
|
||||
}
|
||||
|
||||
if(strlen($timeFormatJS)) $timeFormatJS = $sanitizer->entities($timeFormatJS);
|
||||
if(empty($value)) $value = '';
|
||||
$yearRange = $sanitizer->entities($this->getSetting('yearRange'));
|
||||
$timeInputSelect = $this->getSetting('timeInputSelect');
|
||||
|
||||
$out =
|
||||
"<input " . $this->inputfield->getAttributesString($attrs) . " " .
|
||||
"value='$value' " .
|
||||
"data-dateformat='$dateFormatJS' " .
|
||||
"data-timeformat='$timeFormatJS' " .
|
||||
"data-timeselect='$timeInputSelect' " .
|
||||
"data-ts='$valueTS' " .
|
||||
"data-ampm='$ampm' " .
|
||||
(strlen($yearRange) ? "data-yearrange='$yearRange' " : '') .
|
||||
"/>";
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render value
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
*/
|
||||
public function renderValue() {
|
||||
$value = $this->getAttribute('value');
|
||||
$format = $this->getSetting('dateInputFormat') . ' ' . $this->getSetting('timeInputFormat');
|
||||
return $format && $value ? $this->wire('datetime')->formatDate($value, trim($format)) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WireInputData $input
|
||||
* @return int|string|bool
|
||||
*
|
||||
*/
|
||||
public function processInput(WireInputData $input) {
|
||||
return false; // tell InputfieldDatetime to process the input instead
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input format string for the user's language
|
||||
*
|
||||
* @param bool $getArray
|
||||
* @return string|array of dateInputFormat timeInputFormat
|
||||
*
|
||||
*/
|
||||
protected function getInputFormat($getArray = false) {
|
||||
|
||||
$inputFormats = array();
|
||||
$language = $this->wire('user')->language;
|
||||
$useLanguages = $this->wire('languages') && $language && !$language->isDefault();
|
||||
|
||||
foreach(array('date', 'time') as $type) {
|
||||
$inputFormat = '';
|
||||
if($useLanguages) {
|
||||
$inputFormat = trim($this->getSetting("{$type}InputFormat{$language->id}"));
|
||||
}
|
||||
if(!strlen($inputFormat)) {
|
||||
// fallback to default language
|
||||
$inputFormat = $this->getSetting("{$type}InputFormat");
|
||||
}
|
||||
$inputFormats[] = $inputFormat;
|
||||
}
|
||||
|
||||
if($getArray) return $inputFormats;
|
||||
|
||||
return trim(implode(' ', $inputFormats));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize value
|
||||
*
|
||||
* @param int|string $value
|
||||
* @return int|string
|
||||
*
|
||||
*/
|
||||
public function sanitizeValue($value) {
|
||||
// convert date string to unix timestamp
|
||||
$format = $this->getInputFormat();
|
||||
$value = $this->wire('datetime')->stringToTimestamp($value, $format);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InputfieldWrapper $inputfields
|
||||
*
|
||||
*/
|
||||
public function getConfigInputfields(InputfieldWrapper $inputfields) {
|
||||
|
||||
$languages = $this->wire('languages');
|
||||
$datetime = $this->wire('datetime');
|
||||
$dateInputFormat = $this->getSetting('dateInputFormat');
|
||||
$timeInputFormat = $this->getSetting('timeInputFormat');
|
||||
$timeInputSelect = (int) $this->getSetting('timeInputSelect');
|
||||
|
||||
/** @var InputfieldRadios $f */
|
||||
$f = $this->modules->get('InputfieldRadios');
|
||||
$f->label = $this->_('Date Picker');
|
||||
$f->setAttribute('name', 'datepicker');
|
||||
$f->addOption(self::datepickerNo, $this->_('No date/time picker'));
|
||||
$f->addOption(self::datepickerFocus, $this->_('Date/time picker on field focus') . ' ' .
|
||||
$this->_('(recommended)'));
|
||||
$f->addOption(self::datepickerClick, $this->_('Date/time picker on button click'));
|
||||
// @todo this datepickerInline option displays a datepicker that is too large, not fully styled
|
||||
$f->addOption(self::datepickerInline, $this->_('Inline date picker always visible (no time picker)'));
|
||||
$f->attr('value', (int) $this->getSetting('datepicker'));
|
||||
$inputfields->append($f);
|
||||
|
||||
/** @var InputfieldFieldset $fieldset */
|
||||
$fieldset = $this->modules->get('InputfieldFieldset');
|
||||
$fieldset->attr('name', '_dateTimeInputFormats');
|
||||
$fieldset->label = $this->_('Date/Time Input Formats');
|
||||
|
||||
/** @var InputfieldSelect $f */
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', '_dateInputFormat');
|
||||
$f->label = $this->_('Date Input Format');
|
||||
$f->description = $this->_('Select the format to be used for user input to this field. Your selection will populate the field below this, which you may customize further if needed.');
|
||||
$f->icon = 'calendar';
|
||||
$date = strtotime('2016-04-08 5:10:02 PM');
|
||||
foreach($datetime->getDateFormats() as $format) {
|
||||
$dateFormatted = $datetime->formatDate($date, $format);
|
||||
if($format == 'U') $dateFormatted .= " " . $this->_('(unix timestamp)');
|
||||
$f->addOption($format, $dateFormatted);
|
||||
if($dateInputFormat == $format) $f->attr('value', $format);
|
||||
}
|
||||
$f->attr('onchange', "$('#Inputfield_dateInputFormat').val($(this).val());");
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldSelect $f */
|
||||
$f = $this->modules->get('InputfieldSelect');
|
||||
$f->attr('name', '_timeInputFormat');
|
||||
$f->label = $this->_('Time Input Format');
|
||||
$f->addOption('', $this->_('None'));
|
||||
$f->description = $this->_('Select an optional time format to be used for input. If used, the calendar option will include a time picker.');
|
||||
$f->icon = 'clock-o';
|
||||
foreach($datetime->getTimeFormats() as $format) {
|
||||
if(strpos($format, '!') === 0) continue; // skip relative formats
|
||||
$timeFormatted = $datetime->formatDate($date, $format);
|
||||
$f->addOption($format, $timeFormatted);
|
||||
if($timeInputFormat == $format) $f->attr('value', $format);
|
||||
}
|
||||
$f->attr('onchange', "$('#Inputfield_timeInputFormat').val($(this).val());");
|
||||
$f->collapsed = Inputfield::collapsedBlank;
|
||||
$f->columnWidth = 50;
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldRadios $f */
|
||||
$f = $this->modules->get("InputfieldRadios");
|
||||
$f->attr('name', 'timeInputSelect');
|
||||
$f->label = $this->_('Time Input Type');
|
||||
$f->description = $this->_('Sliders (default) let the user slide controls to choose the time, where as Select lets the user select the time from a drop-down select.');
|
||||
$f->icon = 'clock-o';
|
||||
$f->addOption(0, $this->_('Sliders'));
|
||||
$f->addOption(1, $this->_('Select'));
|
||||
$f->optionColumns = 1;
|
||||
$f->columnWidth = 50;
|
||||
$f->showIf = "_timeInputFormat!='', datepicker!=" . self::datepickerNo;
|
||||
$f->attr('value', $timeInputSelect);
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->modules->get("InputfieldText");
|
||||
$f->attr('name', 'dateInputFormat');
|
||||
$f->attr('value', $dateInputFormat ? $dateInputFormat : InputfieldDatetime::defaultDateInputFormat);
|
||||
$f->attr('size', 20);
|
||||
$f->label = $this->_('Date Input Format Code');
|
||||
$f->description = $this->_('This is automatically built from the date select above, unless you modify it.');
|
||||
$f->icon = 'calendar';
|
||||
$notes = $this->_('See the [PHP date](http://www.php.net/manual/en/function.date.php) function reference for more information on how to customize these formats.');
|
||||
if($languages) $notes .= "\n" . $this->_('You may optionally specify formats for other languages here as well. Any languages left blank will inherit the default setting.');
|
||||
$f->notes = $notes;
|
||||
$f->collapsed = Inputfield::collapsedYes;
|
||||
$f1 = $f;
|
||||
$fieldset->add($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->modules->get("InputfieldText");
|
||||
$f->attr('name', 'timeInputFormat');
|
||||
$f->attr('value', $timeInputFormat ? $timeInputFormat : '');
|
||||
$f->attr('size', 20);
|
||||
$f->label = $this->_('Time Input Format Code');
|
||||
$f->description = $this->_('This is automatically built from the time select above, unless you modify it.');
|
||||
$f->icon = 'clock-o';
|
||||
$f->notes = $notes;
|
||||
$f->collapsed = Inputfield::collapsedYes;
|
||||
$f2 = $f;
|
||||
|
||||
if($languages) {
|
||||
$f1->useLanguages = true;
|
||||
$f2->useLanguages = true;
|
||||
foreach($languages as $language) {
|
||||
if($language->isDefault()) continue;
|
||||
$f1->set("value$language", (string) $this->getSetting("dateInputFormat$language"));
|
||||
$f2->set("value$language", (string) $this->getSetting("timeInputFormat$language"));
|
||||
}
|
||||
}
|
||||
|
||||
$fieldset->add($f1);
|
||||
$fieldset->add($f2);
|
||||
|
||||
$inputfields->add($fieldset);
|
||||
|
||||
/** @var InputfieldText $field */
|
||||
$f = $this->modules->get('InputfieldText');
|
||||
$f->setAttribute('name', 'placeholder');
|
||||
$f->label = $this->_('Placeholder Text');
|
||||
$f->setAttribute('value', $this->getAttribute('placeholder'));
|
||||
$f->description = $this->_('Optional placeholder text that appears in the field when blank.');
|
||||
$f->columnWidth = 50;
|
||||
$inputfields->append($f);
|
||||
|
||||
/** @var InputfieldInteger $f */
|
||||
$f = $this->modules->get('InputfieldInteger');
|
||||
$f->setAttribute('name', 'size');
|
||||
$f->label = $this->_('Size');
|
||||
$f->attr('value', $this->getAttribute('size'));
|
||||
$f->attr('size', 4);
|
||||
$f->description = $this->_('The displayed width of this field (in characters).');
|
||||
$f->columnWidth = 50;
|
||||
$inputfields->append($f);
|
||||
|
||||
/** @var InputfieldText $f */
|
||||
$f = $this->modules->get("InputfieldText");
|
||||
$f->attr('name', 'yearRange');
|
||||
$f->attr('value', $this->getSetting('yearRange'));
|
||||
$f->attr('size', 10);
|
||||
$f->label = $this->_('Year Range');
|
||||
$f->description =
|
||||
$this->_('When predefined year selection is possible, there is a range of plus or minus 10 years from the current year.') . ' ' .
|
||||
$this->_('To modify this range, specify number of years before and after current year in this format: `-30:+20`, which would show 30 years before now and 20 years after now.');
|
||||
$f->notes = $this->_('Default is `-10:+10` which shows a year range 10 years before and after now.');
|
||||
$f->icon = 'arrows-h';
|
||||
$f->collapsed = Inputfield::collapsedBlank;
|
||||
$inputfields->append($f);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
Reference in New Issue
Block a user