* @since 1.0 * @see http://www.malot.fr/bootstrap-datetimepicker/ */ class DateTimePicker extends InputWidget { /** * Calendar icon markup */ const CALENDAR_ICON = ''; /** * HTML native text input type */ const TYPE_INPUT = 1; /** * Bootstrap prepended input group addon type */ const TYPE_COMPONENT_PREPEND = 2; /** * Bootstrap appended input group addon type */ const TYPE_COMPONENT_APPEND = 3; /** * Datetimepicker rendered inline */ const TYPE_INLINE = 4; /** * Datetimepicker rendered as a button */ const TYPE_BUTTON = 5; /** * @var string the markup type of widget markup must be one of the TYPE constants. */ public $type = self::TYPE_COMPONENT_PREPEND; /** * @var string The size of the input - 'lg', 'md', 'sm', 'xs' */ public $size; /** * @var boolean whether to auto default timezone if not set. */ public $autoDefaultTimezone = true; /** * @var array the HTML attributes for the button that is rendered for [[DateTimePicker::TYPE_BUTTON]]. * Defaults to `['class'=>'btn btn-default']`. The following special options are recognized: * * - `label`: _string_, the button label. Defaults to `` */ public $buttonOptions = []; /** * @var array the HTML attributes for the input tag. */ public $options = []; /** * @var string the layout template to display the buttons (applicable only when [[type]] is one of * [[TYPE_COMPONENT_PREPEND]] or [[TYPE_COMPONENT_APPEND]]). The following tokens will be parsed and replaced: * - `{picker}`: will be replaced with the date picker button (rendered as a bootstrap input group addon). * - `{remove}`: will be replaced with the date clear/remove button (rendered as a bootstrap input group addon). * - `{input}`: will be replaced with the HTML input markup that stores the datetime. * The [[layout]] property defaults to the following value if not set: * - `{picker}{remove}{input}` for TYPE_COMPONENT_PREPEND * - `{input}{remove}{picker}` for TYPE_COMPONENT_APPEND */ public $layout; /** * @var string|array|boolean the calendar/time picker button configuration. * - if this is passed as a string, it will be displayed as is (will not be HTML encoded). * - if this is set to false, the picker button will not be displayed. * - if this is passed as an array (this is the DEFAULT) it will treat this as HTML attributes * for the button (to be displayed as a Bootstrap addon). The following special keys are recognized; * - `icon` - _string_, the bootstrap glyphicon name/suffix. Defaults to 'calendar'. * - `title` - _string_, the title to be displayed on hover. Defaults to 'Select date & time'. */ public $pickerButton = []; /** * @var string|array|boolean the calendar/time remove button configuration. * - if this is passed as a string, it will be displayed as is (will not be HTML encoded). * - if this is set to false, the remove button will not be displayed. * - if this is passed as an array (this is the DEFAULT) it will treat this as HTML attributes * for the button (to be displayed as a Bootstrap addon). The following special keys are recognized; * - `icon` - _string_, the bootstrap glyphicon name/suffix. Defaults to 'remove'. * - `title` - _string_, the title to be displayed on hover. Defaults to 'Clear field'. */ public $removeButton = []; /** * @inheritdoc */ public $pluginName = 'datetimepicker'; /** * @var array the HTML options for the DateTimePicker container */ private $_container = []; /** * @inheritdoc * @throws InvalidConfigException */ public function init() { $this->_msgCat = 'kvdtime'; parent::init(); if ($this->type < 1 || $this->type > 5 || !is_int($this->type)) { throw new InvalidConfigException( "Invalid value for the property 'type'. Must be an integer between 1 and 5." ); } if ($this->autoDefaultTimezone && empty($this->pluginOptions['timezone']) && !empty(Yii::$app->timezone)) { $this->pluginOptions['timezone'] = Yii::$app->timezone; } $dir = Yii::getAlias('@vendor/kartik-v/yii2-widget-datetimepicker'); $this->initI18N($dir); $s = DIRECTORY_SEPARATOR; $this->setLanguage('bootstrap-datetimepicker.', "{$dir}{$s}assets{$s}"); $this->parseDateFormat('datetime'); if (empty($this->_container['id'])) { $this->_container['id'] = $this->options['id'] . '-datetime'; } if (empty($this->layout)) { if ($this->type == self::TYPE_COMPONENT_PREPEND) { $this->layout = '{picker}{remove}{input}'; } if ($this->type == self::TYPE_COMPONENT_APPEND) { $this->layout = '{input}{remove}{picker}'; } } $this->registerAssets(); echo $this->renderInput(); } /** * Registers the needed assets */ public function registerAssets() { if ($this->disabled) { return; } $view = $this->getView(); if (!empty($this->_langFile)) { DateTimePickerAsset::register($view)->js[] = $this->_langFile; } else { DateTimePickerAsset::register($view); } if ($this->type == self::TYPE_INLINE) { $this->pluginOptions['linkField'] = $this->options['id']; if (!empty($this->pluginOptions['format'])) { $this->pluginOptions['linkFormat'] = $this->pluginOptions['format']; } } if ($this->type == self::TYPE_INPUT) { $this->registerPlugin($this->pluginName); } else { $this->registerPlugin($this->pluginName, 'jQuery("#' . $this->_container['id'] . '")'); } } /** * Renders the source input for the [[DateTimePicker]] plugin. Graceful fallback to a normal HTML text input - in * case JQuery is not supported by the browser */ protected function renderInput() { if ($this->type == self::TYPE_INLINE) { if (empty($this->options['readonly'])) { $this->options['readonly'] = true; } if (empty($this->options['class'])) { $this->options['class'] = 'form-control input-sm text-center'; } } else { Html::addCssClass($this->options, 'form-control'); } $input = $this->type == self::TYPE_BUTTON ? 'hiddenInput' : 'textInput'; return $this->parseMarkup($this->getInput($input)); } /** * Returns the addon for prepend or append. * * @param string|array $options the HTML attributes for the addon (if passed as an array) or the addon markup if * passed as a string * @param string $type whether the addon is the picker or remove * * @return string */ protected function renderAddon(&$options, $type = 'picker') { if ($options === false) { return ''; } if (is_string($options)) { return $options; } Html::addCssClass($options, 'input-group-addon'); $icon = ($type === 'picker') ? 'calendar' : 'remove'; $icon = ''; if (empty($options['title'])) { $title = ($type === 'picker') ? Yii::t('kvdtime', 'Select date & time') : Yii::t('kvdtime', 'Clear field'); if ($title != false) { $options['title'] = $title; } } return Html::tag('span', $icon, $options); } /** * Parses the input to render based on markup type * * @param string $input the input markup * * @return string */ protected function parseMarkup($input) { $css = $this->disabled ? ' disabled' : ''; $size = isset($this->size) ? "input-{$this->size} " : ''; switch ($this->type) { case self::TYPE_INPUT: Html::addCssClass($this->options, $size . $css); return $input; case self::TYPE_COMPONENT_PREPEND: case self::TYPE_COMPONENT_APPEND: $size = isset($this->size) ? "input-group-{$this->size} " : ''; Html::addCssClass($this->_container, "input-group {$size}date"); $out = strtr( $this->layout, [ '{picker}' => $this->renderAddon($this->pickerButton), '{remove}' => $this->renderAddon($this->removeButton, 'remove'), '{input}' => $input, ] ); return Html::tag('div', $out, $this->_container); case self::TYPE_BUTTON: Html::addCssClass($this->_container, 'date' . $css); $label = ArrayHelper::remove($this->buttonOptions, 'label', self::CALENDAR_ICON); if (!isset($this->buttonOptions['disabled'])) { $this->buttonOptions['disabled'] = $this->disabled; } if (empty($this->buttonOptions['class'])) { $this->buttonOptions['class'] = 'btn btn-default'; } $button = Html::button($label, $this->buttonOptions); Html::addCssStyle($this->_container, 'display:block'); return Html::tag('span', "{$input}{$button}", $this->_container); case self::TYPE_INLINE: Html::addCssClass($this->options, $size . $css); return Html::tag('div', '', $this->_container) . $input; default: return ''; } } }