* @link http://www.pradosoft.com/
* @copyright Copyright © 2005-2014 PradoSoft
* @license http://www.pradosoft.com/license/
* @package Prado\Web\UI\WebControls
*/
namespace Prado\Web\UI\WebControls;
use Prado\Exceptions\TNotSupportedException;
use Prado\Prado;
use Prado\TPropertyValue;
use Prado\Web\Javascripts\TJavaScript;
use Prado\Web\UI\TControl;
/**
*
* TDatePicker class.
*
* TDatePicker displays a text box for date input purpose.
* When the text box receives focus, a calendar will pop up and users can
* pick up from it a date that will be automatically entered into the text box.
* The format of the date string displayed in the text box is determined by
* the DateFormat property. Valid formats are the combination of the
* following tokens,
*
*
* Character Format Pattern (en-US)
* -----------------------------------------
* d day digit
* dd padded day digit e.g. 01, 02
* M month digit
* MM padded month digit
* MMMM localized month name, e.g. March, April
* yy 2 digit year
* yyyy 4 digit year
* -----------------------------------------
*
*
* TDatePicker has four Mode to show the date picker popup.
*
* # Basic -- Only shows a text input, focusing on the input shows the
* date picker. This way you can access the popup using only
* the keyboard. Note that because of this, TAB-bing through
* this control will automatically select the current date if
* no previous date was selected. If you close the popup (eg.
* pressing the ESC key) you'll need to un-focus and re-focus
* the control again for the popup to reappear.
* # Clickable -- Only shows a text input, clicking on the input shows the
* date picker. This mode solves the two small problems of the
* Basic mode. It was first introduced in Prado 3.2.
* # Button -- Shows a button next to the text input, clicking on the
* button shows the date, button text can be by the
* ButtonText property
* # ImageButton -- Shows an image next to the text input, clicking on
* the image shows the date picker, image source can be
* change through the ButtonImageUrl property.
*
* The CssClass property can be used to override the css class name
* for the date picker panel. CalendarStyle property sets the packages
* styles available. E.g. default.
*
* The InputMode property can be set to "TextBox" or "DropDownList" with
* default as "TextBox".
* In DropDownList mode, in addition to the popup date picker, three
* drop down list (day, month and year) are presented to select the date .
*
* The PositionMode property can be set to "Top" or "Bottom" with default
* as "Bottom". It specifies the position of the calendar popup, relative to the
* input field.
*
* @author Wei Zhuo
* @author Carl G. Mathisen
* @package Prado\Web\UI\WebControls
* @since 3.0
*/
class TDatePicker extends TTextBox
{
/**
* Script path relative to the TClientScriptManager::SCRIPT_PATH
*/
const SCRIPT_PATH = 'prado/datepicker';
/**
* @var TDatePickerClientScript validator client-script options.
*/
private $_clientScript;
/**
* AutoPostBack is not supported.
*/
public function setAutoPostBack($value)
{
throw new TNotSupportedException('tdatepicker_autopostback_unsupported',
get_class($this));
}
/**
* @return string the format of the date string
*/
public function getDateFormat()
{
return $this->getViewState('DateFormat','dd-MM-yyyy');
}
/**
* Sets the format of the date string.
* @param string the format of the date string
*/
public function setDateFormat($value)
{
$this->setViewState('DateFormat',$value,'dd-MM-yyyy');
}
/**
* @return boolean whether the calendar window should pop up when the control receives focus
*/
public function getShowCalendar()
{
return $this->getViewState('ShowCalendar',true);
}
/**
* Sets whether to pop up the calendar window when the control receives focus
* @param boolean whether to show the calendar window
*/
public function setShowCalendar($value)
{
$this->setViewState('ShowCalendar',TPropertyValue::ensureBoolean($value),true);
}
/**
* Gets the current culture.
* @return string current culture, e.g. en_AU.
*/
public function getCulture()
{
return $this->getViewState('Culture', '');
}
/**
* Sets the culture/language for the date picker.
* @param string a culture string, e.g. en_AU.
*/
public function setCulture($value)
{
$this->setViewState('Culture', $value, '');
}
/**
* @param TDatePickerInputMode input method of date values
*/
public function setInputMode($value)
{
$this->setViewState('InputMode', TPropertyValue::ensureEnum($value, 'Prado\\Web\\UI\\WebControls\\TDatePickerInputMode'), TDatePickerInputMode::TextBox);
}
/**
* @return TDatePickerInputMode input method of date values. Defaults to TDatePickerInputMode::TextBox.
*/
public function getInputMode()
{
return $this->getViewState('InputMode', TDatePickerInputMode::TextBox);
}
/**
* @param TDatePickerMode calendar UI mode
*/
public function setMode($value)
{
$this->setViewState('Mode', TPropertyValue::ensureEnum($value, 'Prado\\Web\\UI\\WebControls\\TDatePickerMode'), TDatePickerMode::Basic);
}
/**
* @return TDatePickerMode current calendar UI mode.
*/
public function getMode()
{
return $this->getViewState('Mode', TDatePickerMode::Basic);
}
/**
* @param string the image url for "Image" UI mode.
*/
public function setButtonImageUrl($value)
{
$this->setViewState('ImageUrl', $value, '');
}
/**
* @return string the image url for "Image" UI mode.
*/
public function getButtonImageUrl()
{
return $this->getViewState('ImageUrl', '');
}
/**
* @param string set the calendar style
*/
public function setCalendarStyle($value)
{
$this->setViewState('CalendarStyle', $value, 'default');
}
/**
* @return string current calendar style
*/
public function getCalendarStyle()
{
return $this->getViewState('CalendarStyle', 'default');
}
/**
* Set the first day of week, with 0 as Sunday, 1 as Monday, etc.
* @param integer 0 for Sunday, 1 for Monday, 2 for Tuesday, etc.
*/
public function setFirstDayOfWeek($value)
{
$this->setViewState('FirstDayOfWeek', TPropertyValue::ensureInteger($value), 1);
}
/**
* @return integer first day of the week
*/
public function getFirstDayOfWeek()
{
return $this->getViewState('FirstDayOfWeek', 1);
}
/**
* @return string text for the date picker button. Default is "...".
*/
public function getButtonText()
{
return $this->getViewState('ButtonText', '...');
}
/**
* @param string text for the date picker button
*/
public function setButtonText($value)
{
$this->setViewState('ButtonText', $value, '...');
}
/**
* @param integer date picker starting year, default is 2000.
*/
public function setFromYear($value)
{
$this->setViewState('FromYear', TPropertyValue::ensureInteger($value), intval(@date('Y'))-5);
}
/**
* @return integer date picker starting year, default is -5 years
*/
public function getFromYear()
{
return $this->getViewState('FromYear', intval(@date('Y'))-5);
}
/**
* @param integer date picker ending year, default +10 years
*/
public function setUpToYear($value)
{
$this->setViewState('UpToYear', TPropertyValue::ensureInteger($value), intval(@date('Y'))+10);
}
/**
* @return integer date picker ending year, default +10 years
*/
public function getUpToYear()
{
return $this->getViewState('UpToYear', intval(@date('Y'))+10);
}
/**
* @param TDatePickerPositionMode calendar UI position
*/
public function setPositionMode($value)
{
$this->setViewState('PositionMode', TPropertyValue::ensureEnum($value, 'Prado\\Web\\UI\\WebControls\\TDatePickerPositionMode'), TDatePickerPositionMode::Bottom);
}
/**
* @return TDatePickerPositionMode current calendar UI position.
*/
public function getPositionMode()
{
return $this->getViewState('PositionMode', TDatePickerPositionMode::Bottom);
}
/**
* @return integer current selected date from the date picker as timestamp, NULL if timestamp is not set previously.
*/
public function getTimeStamp()
{
if(trim($this->getText())==='')
return null;
else
return $this->getTimeStampFromText();
}
/**
* Sets the date for the date picker using timestamp.
* @param float time stamp for the date picker
*/
public function setTimeStamp($value)
{
if($value===null || (is_string($value) && trim($value)===''))
$this->setText('');
else
{
$date = TPropertyValue::ensureFloat($value);
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter',$this->getDateFormat());
$this->setText($formatter->format($date));
}
}
/**
* Returns the timestamp selected by the user.
* This method is required by {@link \Prado\IDataRenderer}.
* It is the same as {@link getTimeStamp()}.
* @return integer the timestamp of the TDatePicker control.
* @see getTimeStamp
* @since 3.1.2
*/
public function getData()
{
return $this->getTimeStamp();
}
/**
* Sets the timestamp represented by this control.
* This method is required by {@link \Prado\IDataRenderer}.
* It is the same as {@link setTimeStamp()}.
* @param integer the timestamp of the TDatePicker control.
* @see setTimeStamp
* @since 3.1.2
*/
public function setData($value)
{
$this->setTimeStamp($value);
}
/**
* @return string the date string.
*/
public function getDate()
{
return $this->getText();
}
/**
* @param string date string
*/
public function setDate($value)
{
$this->setText($value);
}
/**
* Gets the TDatePickerClientScript to set the TDatePicker event handlers.
*
* The date picker on the client-side supports the following events.
* # OnDateChanged -- raised when the date is changed.
*
* You can attach custom javascript code to each of these events
*
* @return TDatePickerClientScript javascript validator event options.
*/
public function getClientSide()
{
if($this->_clientScript===null)
$this->_clientScript = $this->createClientScript();
return $this->_clientScript;
}
/**
* @return TDatePickerClientScript javascript validator event options.
*/
protected function createClientScript()
{
return new TDatePickerClientScript;
}
/**
* Returns the value to be validated.
* This methid is required by \Prado\Web\UI\IValidatable interface.
* @return integer the interger timestamp if valid, otherwise the original text.
*/
public function getValidationPropertyValue()
{
if(($text = $this->getText()) === '')
return '';
$date = $this->getTimeStamp();
return $date == null ? $text : $date;
}
/**
* Publish the date picker Css asset files.
*/
public function onPreRender($param)
{
parent::onPreRender($param);
$this->publishCalendarStyle();
$this->registerCalendarClientScriptPre();
}
/**
* Renders body content.
* This method overrides parent implementation by adding
* additional date picker button if Mode is Button or ImageButton.
* @param THtmlWriter writer
*/
public function render($writer)
{
if($this->getInputMode() == TDatePickerInputMode::TextBox)
{
parent::render($writer);
$this->renderDatePickerButtons($writer);
}
else
{
$this->renderDropDownListCalendar($writer);
if($this->hasDayPattern())
{
$this->renderClientControlScript($writer);
$this->renderDatePickerButtons($writer);
}
}
}
/**
* Renders the date picker popup buttons.
*/
protected function renderDatePickerButtons($writer)
{
if($this->getShowCalendar())
{
switch ($this->getMode())
{
case TDatePickerMode::Button:
$this->renderButtonDatePicker($writer);
break;
case TDatePickerMode::ImageButton :
$this->renderImageButtonDatePicker($writer);
break;
}
}
}
/**
* Loads user input data. Override parent implementation, when InputMode
* is DropDownList call getDateFromPostData to get date data.
* This method is primarly used by framework developers.
* @param string the key that can be used to retrieve data from the input data collection
* @param array the input data collection
* @return boolean whether the data of the component has been changed
*/
public function loadPostData($key,$values)
{
if($this->getInputMode() == TDatePickerInputMode::TextBox)
return parent::loadPostData($key, $values);
$value = $this->getDateFromPostData($key, $values);
if(!$this->getReadOnly() && $this->getText()!==$value)
{
$this->setText($value);
return true;
}
else
return false;
}
/**
* Loads date from drop down list data.
* @param string the key that can be used to retrieve data from the input data collection
* @param array the input data collection
* @return array the date selected
*/
protected function getDateFromPostData($key, $values)
{
$date = @getdate();
if(isset($values[$key.'$day']))
$day = intval($values[$key.'$day']);
else
$day = $date['mday'];
if(isset($values[$key.'$month']))
$month = intval($values[$key.'$month']) + 1;
else
$month = $date['mon'];
if(isset($values[$key.'$year']))
$year = intval($values[$key.'$year']);
else
$year = $date['year'];
$s = Prado::createComponent('System.Util.TDateTimeStamp');
$date = $s->getTimeStamp(0, 0, 0, $month, $day, $year);
//$date = @mktime(0, 0, 0, $month, $day, $year);
$pattern = $this->getDateFormat();
$pattern = str_replace(array('MMMM', 'MMM'), array('MM','MM'), $pattern);
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter', $pattern);
return $formatter->format($date);
}
/**
* Get javascript date picker options.
* @return array date picker client-side options
*/
protected function getDatePickerOptions()
{
$options['ID'] = $this->getClientID();
$options['InputMode'] = $this->getInputMode();
$options['Format'] = $this->getDateFormat();
$options['FirstDayOfWeek'] = $this->getFirstDayOfWeek();
if(($cssClass=$this->getCssClass())!=='')
$options['ClassName'] = $cssClass;
$options['CalendarStyle'] = $this->getCalendarStyle();
$options['FromYear'] = $this->getFromYear();
$options['UpToYear'] = $this->getUpToYear();
switch($this->getMode())
{
case TDatePickerMode::Basic:
break;
case TDatePickerMode::Clickable:
$options['TriggerEvent'] = "click";
break;
default:
$options['Trigger'] = $this->getDatePickerButtonID();
break;
}
$options['PositionMode'] = $this->getPositionMode();
$options = array_merge($options, $this->getCulturalOptions());
if($this->_clientScript!==null)
$options = array_merge($options,
$this->_clientScript->getOptions()->toArray());
return $options;
}
/**
* Get javascript localization options, e.g. month and weekday names.
* @return array localization options.
*/
protected function getCulturalOptions()
{
if($this->getCurrentCulture() == 'en')
return array();
$date = $this->getLocalizedCalendarInfo();
$options['MonthNames'] = $date->getMonthNames();
$options['AbbreviatedMonthNames'] = $date->getAbbreviatedMonthNames();
$options['ShortWeekDayNames'] = $date->getAbbreviatedDayNames();
return $options;
}
/**
* @return string the current culture, falls back to application if culture is not set.
*/
protected function getCurrentCulture()
{
$app = $this->getApplication()->getGlobalization(false);
return $this->getCulture() == '' ?
($app ? $app->getCulture() : 'en') : $this->getCulture();
}
/**
* @return DateTimeFormatInfo date time format information for the current culture.
*/
protected function getLocalizedCalendarInfo()
{
//expensive operations
$culture = $this->getCurrentCulture();
$info = Prado::createComponent('System.I18N.core.CultureInfo', $culture);
return $info->getDateTimeFormat();
}
/**
* Renders the drop down list date picker.
*/
protected function renderDropDownListCalendar($writer)
{
if($this->getMode() == TDatePickerMode::Basic)
$this->setMode(TDatePickerMode::ImageButton);
parent::addAttributesToRender($writer);
$writer->removeAttribute('name');
$writer->removeAttribute('type');
$writer->addAttribute('id', $this->getClientID());
if(strlen($class = $this->getCssClass()) > 0)
$writer->addAttribute('class', $class);
$writer->renderBeginTag('span');
$s = Prado::createComponent('System.Util.TDateTimeStamp');
$date = $s->getDate($this->getTimeStampFromText());
//$date = @getdate($this->getTimeStampFromText());
$this->renderCalendarSelections($writer, $date);
//render a hidden input field
$writer->addAttribute('name', $this->getUniqueID());
$writer->addAttribute('type', 'hidden');
$writer->addAttribute('value', $this->getText());
$writer->renderBeginTag('input');
$writer->renderEndTag();
$writer->renderEndTag();
}
protected function hasDayPattern()
{
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter',
$this->getDateFormat());
return ($formatter->getDayPattern()!==null);
}
/**
* Renders the calendar drop down list depending on the DateFormat pattern.
* @param THtmlWriter the Html writer to render the drop down lists.
* @param array the current selected date
*/
protected function renderCalendarSelections($writer, $date)
{
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter',
$this->getDateFormat());
foreach($formatter->getDayMonthYearOrdering() as $type)
{
if($type == 'day')
$this->renderCalendarDayOptions($writer,$date['mday']);
elseif($type == 'month')
$this->renderCalendarMonthOptions($writer,$date['mon']);
elseif($type == 'year')
$this->renderCalendarYearOptions($writer,$date['year']);
}
}
/**
* Gets the date from the text input using TSimpleDateFormatter
* @return integer current selected date timestamp
*/
protected function getTimeStampFromText()
{
$pattern = $this->getDateFormat();
$pattern = str_replace(array('MMMM', 'MMM'), array('MM','MM'), $pattern);
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter',$pattern);
return $formatter->parse($this->getText());
}
/**
* Renders a drop down lists.
* @param THtmlWriter the writer used for the rendering purpose
* @param array list of selection options
* @param mixed selected key.
*/
private function renderDropDownListOptions($writer,$options,$selected=null)
{
foreach($options as $k => $v)
{
$writer->addAttribute('value', $k);
if($k == $selected)
$writer->addAttribute('selected', 'selected');
$writer->renderBeginTag('option');
$writer->write($v);
$writer->renderEndTag();
}
}
/**
* Renders the day drop down list options.
* @param THtmlWriter the writer used for the rendering purpose
* @param mixed selected day.
*/
protected function renderCalendarDayOptions($writer, $selected=null)
{
$days = $this->getDropDownDayOptions();
$writer->addAttribute('id', $this->getClientID().TControl::CLIENT_ID_SEPARATOR.'day');
$writer->addAttribute('name', $this->getUniqueID().TControl::ID_SEPARATOR.'day');
$writer->addAttribute('class', 'datepicker_day_options');
if($this->getReadOnly() || !$this->getEnabled(true))
$writer->addAttribute('disabled', 'disabled');
$writer->renderBeginTag('select');
$this->renderDropDownListOptions($writer, $days, $selected);
$writer->renderEndTag();
}
/**
* @return array list of day options for a drop down list.
*/
protected function getDropDownDayOptions()
{
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter',
$this->getDateFormat());
$days = array();
$requiresPadding = $formatter->getDayPattern() === 'dd';
for($i=1;$i<=31;$i++)
{
$days[$i] = $requiresPadding ? str_pad($i, 2, '0', STR_PAD_LEFT) : $i;
}
return $days;
}
/**
* Renders the month drop down list options.
* @param THtmlWriter the writer used for the rendering purpose
* @param mixed selected month.
*/
protected function renderCalendarMonthOptions($writer, $selected=null)
{
$info = $this->getLocalizedCalendarInfo();
$writer->addAttribute('id', $this->getClientID().TControl::CLIENT_ID_SEPARATOR.'month');
$writer->addAttribute('name', $this->getUniqueID().TControl::ID_SEPARATOR.'month');
$writer->addAttribute('class', 'datepicker_month_options');
if($this->getReadOnly() || !$this->getEnabled(true))
$writer->addAttribute('disabled', 'disabled');
$writer->renderBeginTag('select');
$this->renderDropDownListOptions($writer,
$this->getLocalizedMonthNames($info), $selected-1);
$writer->renderEndTag();
}
/**
* Returns the localized month names that depends on the month format pattern.
* "MMMM" will return the month names, "MM" or "MMM" return abbr. month names
* and "M" return month digits.
* @param DateTimeFormatInfo localized date format information.
* @return array localized month names.
*/
protected function getLocalizedMonthNames($info)
{
$formatter = Prado::createComponent('\\Prado\\Util\\TSimpleDateFormatter',
$this->getDateFormat());
switch($formatter->getMonthPattern())
{
case 'MMM': return $info->getAbbreviatedMonthNames();
case 'MM':
$array = array();
for($i=1;$i<=12;$i++)
$array[$i-1] = $i < 10 ? '0'.$i : $i;
return $array;
case 'M':
$array = array(); for($i=1;$i<=12;$i++) $array[$i-1] = $i;
return $array;
default : return $info->getMonthNames();
}
}
/**
* Renders the year drop down list options.
* @param THtmlWriter the writer used for the rendering purpose
* @param mixed selected year.
*/
protected function renderCalendarYearOptions($writer, $selected=null)
{
$years = array();
for($i = $this->getFromYear(); $i <= $this->getUpToYear(); $i++)
$years[$i] = $i;
$writer->addAttribute('id', $this->getClientID().TControl::CLIENT_ID_SEPARATOR.'year');
$writer->addAttribute('name', $this->getUniqueID().TControl::ID_SEPARATOR.'year');
$writer->addAttribute('class', 'datepicker_year_options');
if($this->getReadOnly() || !$this->getEnabled(true))
$writer->addAttribute('disabled', 'disabled');
$writer->renderBeginTag('select');
$this->renderDropDownListOptions($writer, $years, $selected);
$writer->renderEndTag();
}
/**
* Gets the ID for the date picker trigger button.
* @return string unique button ID
*/
protected function getDatePickerButtonID()
{
return $this->getClientID().'button';
}
/**
* Adds an additional button such that when clicked it shows the date picker.
* @return THtmlWriter writer
*/
protected function renderButtonDatePicker($writer)
{
$writer->addAttribute('id', $this->getDatePickerButtonID());
$writer->addAttribute('type', 'button');
$writer->addAttribute('class', $this->getCssClass().' TDatePickerButton');
$writer->addAttribute('value',$this->getButtonText());
if(!$this->getEnabled(true))
$writer->addAttribute('disabled', 'disabled');
$writer->renderBeginTag("input");
$writer->renderEndTag();
}
/**
* Adds an additional image button such that when clicked it shows the date picker.
* @return THtmlWriter writer
*/
protected function renderImageButtonDatePicker($writer)
{
$url = $this->getButtonImageUrl();
$url = empty($url) ? $this->getAssetUrl('calendar.png') : $url;
$writer->addAttribute('id', $this->getDatePickerButtonID());
$writer->addAttribute('src', $url);
$writer->addAttribute('alt', ' ');
$writer->addAttribute('class', $this->getCssClass().' TDatePickerImageButton');
if(!$this->getEnabled(true))
$writer->addAttribute('disabled', 'disabled');
$writer->addAttribute('type', 'image');
$writer->addAttribute('onclick', 'return false;');
$writer->renderBeginTag('input');
$writer->renderEndTag();
}
/**
* @param string date picker asset file in the self::SCRIPT_PATH directory.
* @return string date picker asset url.
*/
protected function getAssetUrl($file='')
{
$base = $this->getPage()->getClientScript()->getPradoScriptAssetUrl();
return $base.'/'.self::SCRIPT_PATH.'/'.$file;
}
/**
* Publish the calendar style Css asset file.
* @return string Css file url.
*/
protected function publishCalendarStyle()
{
$url = $this->getAssetUrl($this->getCalendarStyle().'.css');
$cs = $this->getPage()->getClientScript();
if(!$cs->isStyleSheetFileRegistered($url))
$cs->registerStyleSheetFile($url, $url);
return $url;
}
/**
* Add the client id to the input textbox, and register the client scripts.
* @param THtmlWriter writer
*/
protected function addAttributesToRender($writer)
{
parent::addAttributesToRender($writer);
$writer->addAttribute('id',$this->getClientID());
}
/**
* Registers the javascript code to initialize the date picker.
*/
protected function registerCalendarClientScriptPre()
{
if($this->getShowCalendar())
{
$cs = $this->getPage()->getClientScript();
$cs->registerPradoScript("datepicker");
}
}
protected function renderClientControlScript($writer)
{
if($this->getShowCalendar())
{
$cs = $this->getPage()->getClientScript();
if(!$cs->isEndScriptRegistered('TDatePicker.spacer'))
{
$spacer = $this->getAssetUrl('spacer.gif');
$code = "Prado.WebUI.TDatePicker.spacer = '$spacer';";
$cs->registerEndScript('TDatePicker.spacer', $code);
}
$options = TJavaScript::encode($this->getDatePickerOptions());
$code = "new Prado.WebUI.TDatePicker($options);";
$cs->registerEndScript("prado:".$this->getClientID(), $code);
}
}
}