Add date range form field
authorMarcel Werk <burntime@woltlab.com>
Thu, 7 Nov 2024 14:51:23 +0000 (15:51 +0100)
committerMarcel Werk <burntime@woltlab.com>
Tue, 12 Nov 2024 11:53:43 +0000 (12:53 +0100)
com.woltlab.wcf/templates/shared_dateRangeFormField.tpl [new file with mode: 0644]
ts/WoltLabSuite/Core/Form/Builder/Field/DateRange.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/DateRange.js [new file with mode: 0644]
wcfsetup/install/files/lib/system/form/builder/field/DateRangeFormField.class.php [new file with mode: 0644]

diff --git a/com.woltlab.wcf/templates/shared_dateRangeFormField.tpl b/com.woltlab.wcf/templates/shared_dateRangeFormField.tpl
new file mode 100644 (file)
index 0000000..b0507cd
--- /dev/null
@@ -0,0 +1,23 @@
+<input
+       type="{if $field->supportsTime()}datetime{else}date{/if}"
+       id="{$field->getPrefixedId()}_from"
+       name="{$field->getPrefixedId()}[from]"
+       value="{$field->getFromValue()}"
+       data-placeholder="{lang}wcf.date.period.start{/lang}"
+       {if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}
+       {if $field->isAutofocused()} autofocus{/if}
+       {if $field->isRequired()} required{/if}
+       {if $field->isImmutable()} disabled{/if}
+       {foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}
+>
+<input
+       type="{if $field->supportsTime()}datetime{else}date{/if}"
+       id="{$field->getPrefixedId()}_to"
+       name="{$field->getPrefixedId()}[to]"
+       value="{$field->getToValue()}"
+       data-placeholder="{lang}wcf.date.period.end{/lang}"
+       {if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}
+       {if $field->isRequired()} required{/if}
+       {if $field->isImmutable()} disabled{/if}
+       {foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}
+>
diff --git a/ts/WoltLabSuite/Core/Form/Builder/Field/DateRange.ts b/ts/WoltLabSuite/Core/Form/Builder/Field/DateRange.ts
new file mode 100644 (file)
index 0000000..3da0173
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * Data handler for a date range form builder field in an Ajax form.
+ *
+ * @author    Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since     6.2
+ */
+import Field from "./Field";
+import { FormBuilderData } from "../Data";
+import DatePicker from "../../../Date/Picker";
+
+class DateRange extends Field {
+  #fromField: HTMLElement | null;
+  #toField: HTMLElement | null;
+
+  constructor(fieldId: string) {
+    super(fieldId);
+
+    this.#fromField = document.getElementById(this._fieldId + "_from");
+    if (this.#fromField === null) {
+      throw new Error("Unknown field with id '" + this._fieldId + "'.");
+    }
+
+    this.#toField = document.getElementById(this._fieldId + "_to");
+    if (this.#toField === null) {
+      throw new Error("Unknown field with id '" + this._fieldId + "'.");
+    }
+  }
+
+  protected _getData(): FormBuilderData {
+    return {
+      [this._fieldId]: {
+        from: DatePicker.getValue(this.#fromField as HTMLInputElement),
+        to: DatePicker.getValue(this.#toField as HTMLInputElement),
+      },
+    };
+  }
+
+  protected _readField(): void {}
+}
+
+export = DateRange;
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/DateRange.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Form/Builder/Field/DateRange.js
new file mode 100644 (file)
index 0000000..b4d8d2d
--- /dev/null
@@ -0,0 +1,30 @@
+define(["require", "exports", "tslib", "./Field", "../../../Date/Picker"], function (require, exports, tslib_1, Field_1, Picker_1) {
+    "use strict";
+    Field_1 = tslib_1.__importDefault(Field_1);
+    Picker_1 = tslib_1.__importDefault(Picker_1);
+    class DateRange extends Field_1.default {
+        #fromField;
+        #toField;
+        constructor(fieldId) {
+            super(fieldId);
+            this.#fromField = document.getElementById(this._fieldId + "_from");
+            if (this.#fromField === null) {
+                throw new Error("Unknown field with id '" + this._fieldId + "'.");
+            }
+            this.#toField = document.getElementById(this._fieldId + "_to");
+            if (this.#toField === null) {
+                throw new Error("Unknown field with id '" + this._fieldId + "'.");
+            }
+        }
+        _getData() {
+            return {
+                [this._fieldId]: {
+                    from: Picker_1.default.getValue(this.#fromField),
+                    to: Picker_1.default.getValue(this.#toField),
+                },
+            };
+        }
+        _readField() { }
+    }
+    return DateRange;
+});
diff --git a/wcfsetup/install/files/lib/system/form/builder/field/DateRangeFormField.class.php b/wcfsetup/install/files/lib/system/form/builder/field/DateRangeFormField.class.php
new file mode 100644 (file)
index 0000000..96d8a26
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+
+namespace wcf\system\form\builder\field;
+
+use wcf\system\form\builder\field\validation\FormFieldValidationError;
+
+/**
+ * Implementation of a form field for a date range (with a time).
+ *
+ * @author      Marcel Werk
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since       6.2
+ */
+class DateRangeFormField extends AbstractFormField implements
+    IAttributeFormField,
+    IAutoFocusFormField,
+    ICssClassFormField,
+    IImmutableFormField,
+    INullableFormField
+{
+    use TAttributeFormField;
+    use TAutoFocusFormField;
+    use TCssClassFormField;
+    use TImmutableFormField;
+    use TNullableFormField;
+
+    /**
+     * is `true` if not only the date, but also the time can be set
+     * @var bool
+     */
+    protected $supportsTime = false;
+
+    /**
+     * @inheritDoc
+     */
+    protected $javaScriptDataHandlerModule = 'WoltLabSuite/Core/Form/Builder/Field/DateRange';
+
+    /**
+     * @inheritDoc
+     */
+    protected $templateName = 'shared_dateRangeFormField';
+
+    const DATE_FORMAT = 'Y-m-d';
+
+    const TIME_FORMAT = 'Y-m-d\TH:i:sP';
+
+    /**
+     * @inheritDoc
+     */
+    public function getSaveValue()
+    {
+        if ($this->getValue() === null && $this->isNullable()) {
+            return null;
+        }
+
+        return $this->getFromValue() . ';' . $this->getToValue();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readValue()
+    {
+        if (
+            $this->getDocument()->hasRequestData($this->getPrefixedId())
+            && \is_array($this->getDocument()->getRequestData($this->getPrefixedId()))
+        ) {
+            $this->value = $this->getDocument()->getRequestData($this->getPrefixedId());
+        }
+
+        return $this;
+    }
+
+    /**
+     * Sets if not only the date, but also the time can be set.
+     */
+    public function supportTime($supportsTime = true): static
+    {
+        if ($this->value !== null) {
+            throw new \BadFunctionCallException(
+                "After a value has been set, time support cannot be changed for field '{$this->getId()}'."
+            );
+        }
+
+        $this->supportsTime = $supportsTime;
+
+        return $this;
+    }
+
+    /**
+     * Returns `true` if not only the date, but also the time can be set, and
+     * returns `false` otherwise.
+     *
+     * By default, the time cannot be set.
+     */
+    public function supportsTime(): bool
+    {
+        return $this->supportsTime;
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function validate()
+    {
+        if ($this->isRequired() && (!$this->getFromValue() || !$this->getToValue())) {
+            $this->addValidationError(new FormFieldValidationError('empty'));
+        }
+
+        if ($this->getFromValue()) {
+            $dateTime = \DateTime::createFromFormat(
+                $this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT,
+                $this->getFromValue()
+            );
+            if ($dateTime === false) {
+                $this->addValidationError(new FormFieldValidationError('invalid'));
+            }
+        }
+
+        if ($this->getToValue()) {
+            $dateTime = \DateTime::createFromFormat(
+                $this->supportsTime() ? self::TIME_FORMAT : self::DATE_FORMAT,
+                $this->getToValue()
+            );
+            if ($dateTime === false) {
+                $this->addValidationError(new FormFieldValidationError('invalid'));
+            }
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function value($value)
+    {
+        $values = \explode(';', $value);
+        if (\count($values) !== 2) {
+            throw new \InvalidArgumentException(
+                "Given value does not match format for field '{$this->getId()}'."
+            );
+        }
+
+        $this->value = [
+            'from' => $values[0],
+            'to' => $values[1],
+        ];
+
+        return $this;
+    }
+
+    public function getFromValue(): string
+    {
+        return $this->value['from'] ?? '';
+    }
+
+    public function getToValue(): string
+    {
+        return $this->value['to'] ?? '';
+    }
+}