diff --git a/examples/datetime/DatetimePresenter.latte b/examples/datetime/DatetimePresenter.latte new file mode 100644 index 0000000..a58c89a --- /dev/null +++ b/examples/datetime/DatetimePresenter.latte @@ -0,0 +1,10 @@ + + + + + Datetime demo + + +{control form} + + diff --git a/examples/datetime/DatetimePresenter.php b/examples/datetime/DatetimePresenter.php new file mode 100644 index 0000000..9103d2b --- /dev/null +++ b/examples/datetime/DatetimePresenter.php @@ -0,0 +1,35 @@ +addSubmit('save', 'Send'); + $form->onSuccess[] = function ($form, $values) { + dump($values); + }; + return $form; + } + + + public function formatTemplateFiles(): array + { + return [__DIR__ . '/DatetimePresenter.latte']; + } +} diff --git a/examples/datetime/config.neon b/examples/datetime/config.neon new file mode 100644 index 0000000..6700f9e --- /dev/null +++ b/examples/datetime/config.neon @@ -0,0 +1,7 @@ +application: + scanDirs: false + mapping: + *: NextrasDemos\FormsComponents\Datetime\*Module\*Presenter + +services: + routing.router: Nette\Application\Routers\SimpleRouter('Datetime:default') diff --git a/examples/datetime/index.php b/examples/datetime/index.php new file mode 100644 index 0000000..83a5dd1 --- /dev/null +++ b/examples/datetime/index.php @@ -0,0 +1,19 @@ +enableDebugger(__DIR__ . '/log'); +$configurator->setTempDirectory(__DIR__ . '/temp'); +$configurator->createRobotLoader()->addDirectory(__DIR__)->register(); +$configurator->addConfig(__DIR__ . '/config.neon'); + +$container = $configurator->createContainer(); +$app = $container->getByType(Application::class); +$app->run(); diff --git a/readme.md b/readme.md index 96e1c80..1c709e0 100644 --- a/readme.md +++ b/readme.md @@ -14,8 +14,8 @@ Architecture components provide Nette Forms' BaseControl in two flavors: UI components: - *AutocompleteControl* - text input with support for autocomplete signal handling; -- *DatePicker* - date picker - text input returning `DateTimeImmutable` instance; -- *DateTimePicker* - date picker - text input returning `DateTimeImmutable` instance; +- *DateControl* - date picker - text input returning `DateTimeImmutable` instance; +- *DateTimeControl* - date tiime picker - text input returning `DateTimeImmutable` instance; ### Installation diff --git a/src/Controls/DateControl.php b/src/Controls/DateControl.php new file mode 100644 index 0000000..af34cbe --- /dev/null +++ b/src/Controls/DateControl.php @@ -0,0 +1,67 @@ +\d{1,2})[. -]\s*(?P\d{1,2})([. -]\s*(?P\d{4})?)?$#', $value, $matches)) { + return null; + } + + $dd = $matches['dd']; + $mm = $matches['mm']; + $yyyy = $matches['yyyy'] ?? date('Y'); + + if (!checkdate($mm, $dd, $yyyy)) { + return null; + } + + return (new DateTimeImmutable()) + ->setDate($yyyy, $mm, $dd) + ->setTime(0, 0, 0); + }; + } + + + /** + * @return DateTimeImmutable|null + */ + public function getValue() + { + $val = parent::getValue(); + // set the midnight so the limit dates (min & max) pass the :RANGE validation rule + if ($val !== null) { + return $val->setTime(0, 0, 0); + } + return $val; + } +} diff --git a/src/Controls/DateTimeControl.php b/src/Controls/DateTimeControl.php new file mode 100644 index 0000000..27d12b5 --- /dev/null +++ b/src/Controls/DateTimeControl.php @@ -0,0 +1,61 @@ +\d{1,2})[. -]\s*(?P\d{1,2})(?:[. -]\s*(?P\d{4})?)?(?:\s*[ @-]\s*(?P\d{1,2})[:.](?P\d{1,2})(?:[:.](?P\d{1,2}))?)?$#', $value, $matches)) { + return null; + } + + $dd = $matches['dd']; + $mm = $matches['mm']; + $yyyy = $matches['yyyy'] ?? date('Y'); + + if (!checkdate($mm, $dd, $yyyy)) { + return null; + } + + $hh = $matches['hh'] ?? 0; + $ii = $matches['ii'] ?? 0; + $ss = $matches['ss'] ?? 0; + + if (!($hh >= 0 && $hh < 24 && $ii >= 0 && $ii <= 59 && $ss >= 0 && $ss <= 59)) { + return null; + } + + return (new DateTimeImmutable()) + ->setDate($yyyy, $mm, $dd) + ->setTime($hh, $ii, $ss); + }; + } +} diff --git a/src/Controls/DateTimeControlPrototype.php b/src/Controls/DateTimeControlPrototype.php new file mode 100644 index 0000000..ba31871 --- /dev/null +++ b/src/Controls/DateTimeControlPrototype.php @@ -0,0 +1,146 @@ +type = $this->htmlType; + $control->addClass($this->htmlType); + + list($min, $max) = $this->extractRangeRule($this->getRules()); + if ($min instanceof DateTimeInterface) { + $control->min = $min->format($this->htmlFormat); + } + if ($max instanceof DateTimeInterface) { + $control->max = $max->format($this->htmlFormat); + } + $value = $this->getValue(); + if ($value instanceof DateTimeInterface) { + $control->value = $value->format($this->htmlFormat); + } + + return $control; + } + + + public function setValue($value) + { + return parent::setValue( + $value instanceof DateTimeInterface + ? $value->format($this->htmlFormat) + : $value + ); + } + + + /** + * @return DateTimeImmutable|null + */ + public function getValue() + { + if ($this->value instanceof DateTimeImmutable) { + return $this->value; + + } elseif ($this->value instanceof DateTime) { + return DateTimeImmutable::createFromMutable($this->value); + + } elseif (empty($this->value)) { + return null; + + } elseif (is_string($this->value)) { + $parsers = $this->parsers; + $parsers[] = $this->getDefaultParser(); + + foreach ($parsers as $parser) { + $value = call_user_func($parser, $this->value); + if ($value instanceof DateTimeImmutable) { + return $value; + } elseif ($value instanceof DateTime) { + return DateTimeImmutable::createFromMutable($value); + } + } + + // fall-through + } + + return null; + } + + + public function addParser(callable $parser) + { + $this->parsers[] = $parser; + return $this; + } + + + abstract protected function getDefaultParser(); + + + /** + * Finds minimum and maximum allowed dates. + * + * @return array 0 => DateTime|null $minDate, 1 => DateTime|null $maxDate + */ + protected function extractRangeRule(Rules $rules) + { + $controlMin = $controlMax = null; + /** @var Nette\Forms\Rule $rule */ + foreach ($rules as $rule) { + /** @var Rules|null $branch */ + $branch = $rule->branch; + if ($branch === null) { + if ($rule->validator === Form::RANGE && !$rule->isNegative) { + $ruleMinMax = $rule->arg; + } + + } else { + if ($rule->validator === Form::FILLED && !$rule->isNegative && $rule->control === $this) { + $ruleMinMax = $this->extractRangeRule($branch); + } + } + + if (isset($ruleMinMax)) { + list($ruleMin, $ruleMax) = $ruleMinMax; + if ($ruleMin !== null && ($controlMin === null || $ruleMin > $controlMin)) { + $controlMin = $ruleMin; + } + if ($ruleMax !== null && ($controlMax === null || $ruleMax < $controlMax)) { + $controlMax = $ruleMax; + } + $ruleMinMax = null; + } + } + return [$controlMin, $controlMax]; + } +} diff --git a/tests/cases/DateTimeControlTest.phpt b/tests/cases/DateTimeControlTest.phpt new file mode 100644 index 0000000..6a82eb4 --- /dev/null +++ b/tests/cases/DateTimeControlTest.phpt @@ -0,0 +1,94 @@ +setRequired(); + + $form = new Nette\Forms\Form; + $form->addComponent($dateTimePicker, 'dateTimePicker'); + + $rule = new Nette\Forms\Rules($dateTimePicker); + + Assert::notSame( + Nette\Forms\Helpers::exportRules($rule), + $dateTimePicker->getControl()->getAttribute('data-nette-rules') + ); + + $rule->setRequired(); + + Assert::same( + Nette\Forms\Helpers::exportRules($rule), + $dateTimePicker->getControl()->getAttribute('data-nette-rules') + ); + } + + public function testDateTimeReturn() + { + $dateTimePicker_emptyValue = new DateTimeControl(); + Assert::null($dateTimePicker_emptyValue->getValue()); + + $dateTimePicker_stringValue = new DateTimeControl(); + $dateTimePicker_stringValue->setValue('2018-01-01 06:00:00'); + + Assert::type(DateTimeImmutable::class, $dateTimePicker_stringValue->getValue()); + Assert::equal(new DateTimeImmutable('2018-01-01 06:00:00'), $dateTimePicker_stringValue->getValue()); + + $dateTimePicker_dateTimeValue = new DateTimeControl(); + $dateTimePicker_dateTimeValue->setValue(new DateTime('2018-01-01 06:00:00')); + + Assert::type(DateTimeImmutable::class, $dateTimePicker_dateTimeValue->getValue()); + Assert::equal(new DateTimeImmutable('2018-01-01 06:00:00'), $dateTimePicker_dateTimeValue->getValue()); + + $dateTimePicker_dateTimeImmutableValue = new DateTimeControl(); + $dateTimePicker_dateTimeImmutableValue->setValue(new DateTimeImmutable('2018-01-01 06:00:00')); + + Assert::type(DateTimeImmutable::class, $dateTimePicker_dateTimeImmutableValue->getValue()); + Assert::equal(new DateTimeImmutable('2018-01-01 06:00:00'), $dateTimePicker_dateTimeImmutableValue->getValue()); + } + + public function testDateReturn() + { + $datePicker_emptyValue = new DateControl(); + Assert::null($datePicker_emptyValue->getValue()); + + $datePicker_stringValue = new DateControl(); + $datePicker_stringValue->setValue('2018-01-01 06:00:00'); + + Assert::type(DateTimeImmutable::class, $datePicker_stringValue->getValue()); + Assert::equal(new DateTimeImmutable('2018-01-01 00:00:00'), $datePicker_stringValue->getValue()); + + $datePicker_dateTimeValue = new DateControl(); + $datePicker_dateTimeValue->setValue(new DateTime('2018-01-01 06:00:00')); + + Assert::type(DateTimeImmutable::class, $datePicker_dateTimeValue->getValue()); + Assert::equal(new DateTimeImmutable('2018-01-01 00:00:00'), $datePicker_dateTimeValue->getValue()); + + $datePicker_dateTimeImmutableValue = new DateControl(); + $datePicker_dateTimeImmutableValue->setValue(new DateTimeImmutable('2018-01-01 06:00:00')); + + Assert::type(DateTimeImmutable::class, $datePicker_dateTimeImmutableValue->getValue()); + Assert::equal(new DateTimeImmutable('2018-01-01 00:00:00'), $datePicker_dateTimeImmutableValue->getValue()); + } +} + + +$test = new DateTimeControlTest(); +$test->run();