From 2d867aa8e5f3db8bfa251fb384b96dfb477b3d8e Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Thu, 21 Mar 2024 15:33:38 +0800 Subject: [PATCH] DEV: Add validation message to float fields in theme object editor (#26285) Why this change? This is a continuation of a30d73f255879f3e4cc0edbb9de358c03f3aed94 In our schema, we support the `min` and `max` validation rules like so: ``` some_objects_setting type: objects schema: name: some_object properties: id: type: float validations: min: 5 max: 10 ``` While the validations used to validate the objects on the server side, we should also add client side validation for better UX. What does this change do? Since the integer and float input fields share very very similar logic in the component. This commit pulls the common logic into `admin/components/schema-theme-setting/number-field.gjs` which `admin/components/schema-theme-setting/types/integer.gjs` and `admin/components/schema-theme-setting/types/float.gjs` will inherit from. --- .../schema-theme-setting/number-field.gjs | 90 +++++++++++++++++++ .../schema-theme-setting/types/float.gjs | 26 ++---- .../schema-theme-setting/types/integer.gjs | 82 ++--------------- .../editor-test.gjs | 61 ++++++++++--- 4 files changed, 150 insertions(+), 109 deletions(-) create mode 100644 app/assets/javascripts/admin/addon/components/schema-theme-setting/number-field.gjs diff --git a/app/assets/javascripts/admin/addon/components/schema-theme-setting/number-field.gjs b/app/assets/javascripts/admin/addon/components/schema-theme-setting/number-field.gjs new file mode 100644 index 00000000000..fd5707802ea --- /dev/null +++ b/app/assets/javascripts/admin/addon/components/schema-theme-setting/number-field.gjs @@ -0,0 +1,90 @@ +import Component from "@glimmer/component"; +import { tracked } from "@glimmer/tracking"; +import { Input } from "@ember/component"; +import { on } from "@ember/modifier"; +import { action } from "@ember/object"; +import { and, not } from "truth-helpers"; +import I18n from "discourse-i18n"; +import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description"; + +export default class SchemaThemeSettingNumberField extends Component { + @tracked touched = false; + @tracked value = this.args.value; + min = this.args.spec.validations?.min; + max = this.args.spec.validations?.max; + required = this.args.spec.required; + + @action + onInput(event) { + this.touched = true; + let inputValue = event.currentTarget.value; + + if (isNaN(inputValue)) { + this.value = null; + } else { + this.value = this.parseValue(inputValue); + } + + this.args.onChange(this.value); + } + + /** + * @param {string} value - The value of the input field to parse into a number + * @returns {number} + */ + parseFunc() { + throw "Not implemented"; + } + + get validationErrorMessage() { + if (!this.touched) { + return; + } + + if (!this.value) { + if (this.required) { + return I18n.t("admin.customize.theme.schema.fields.required"); + } else { + return; + } + } + + if (this.min && this.value < this.min) { + return I18n.t("admin.customize.theme.schema.fields.number.too_small", { + count: this.min, + }); + } + + if (this.max && this.value > this.max) { + return I18n.t("admin.customize.theme.schema.fields.number.too_large", { + count: this.max, + }); + } + } + + +} diff --git a/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/float.gjs b/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/float.gjs index c7a797bc946..8988df55945 100644 --- a/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/float.gjs +++ b/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/float.gjs @@ -1,23 +1,9 @@ -import Component from "@glimmer/component"; -import { Input } from "@ember/component"; -import { on } from "@ember/modifier"; -import { action } from "@ember/object"; -import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description"; +import SchemaThemeSettingNumberField from "admin/components/schema-theme-setting/number-field"; -export default class SchemaThemeSettingTypeFloat extends Component { - @action - onInput(event) { - this.args.onChange(parseFloat(event.currentTarget.value)); +export default class SchemaThemeSettingTypeFloat extends SchemaThemeSettingNumberField { + step = 0.1; + + parseValue(value) { + return parseFloat(value); } - - } diff --git a/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/integer.gjs b/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/integer.gjs index d0bc6fdebd8..dfc260c97eb 100644 --- a/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/integer.gjs +++ b/app/assets/javascripts/admin/addon/components/schema-theme-setting/types/integer.gjs @@ -1,80 +1,10 @@ -import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; -import { Input } from "@ember/component"; -import { on } from "@ember/modifier"; -import { action } from "@ember/object"; -import { and, not } from "truth-helpers"; -import I18n from "discourse-i18n"; -import FieldInputDescription from "admin/components/schema-theme-setting/field-input-description"; +import SchemaThemeSettingNumberField from "admin/components/schema-theme-setting/number-field"; -export default class SchemaThemeSettingTypeInteger extends Component { - @tracked touched = false; - @tracked value = this.args.value; - min = this.args.spec.validations?.min; - max = this.args.spec.validations?.max; - required = this.args.spec.required; +export default class SchemaThemeSettingTypeInteger extends SchemaThemeSettingNumberField { + inputMode = "numeric"; + pattern = "[0-9]*"; - @action - onInput(event) { - this.touched = true; - let newValue = parseInt(event.currentTarget.value, 10); - - if (isNaN(newValue)) { - newValue = null; - } - - this.value = newValue; - this.args.onChange(newValue); + parseValue(value) { + return parseInt(value, 10); } - - get validationErrorMessage() { - if (!this.touched) { - return; - } - - if (!this.value) { - if (this.required) { - return I18n.t("admin.customize.theme.schema.fields.required"); - } else { - return; - } - } - - if (this.min && this.value < this.min) { - return I18n.t("admin.customize.theme.schema.fields.number.too_small", { - count: this.min, - }); - } - - if (this.max && this.value > this.max) { - return I18n.t("admin.customize.theme.schema.fields.number.too_large", { - count: this.max, - }); - } - } - - } diff --git a/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs b/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs index de9035e2476..1010c696d2d 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs +++ b/app/assets/javascripts/discourse/tests/integration/components/admin-schema-theme-setting/editor-test.gjs @@ -546,34 +546,69 @@ module( }); test("input fields of type float", async function (assert) { - const setting = schemaAndData(3); + const setting = ThemeSettings.create({ + setting: "objects_setting", + objects_schema: { + name: "something", + identifier: "id", + properties: { + id: { + type: "float", + required: true, + validations: { + max: 10.5, + min: 5.5, + }, + }, + }, + }, + value: [ + { + id: 6.5, + }, + ], + }); await render(); const inputFields = new InputFieldsFromDOM(); + + assert.dom(inputFields.fields.id.labelElement).hasText("id*"); + assert.dom(inputFields.fields.id.inputElement).hasValue("6.5"); + assert - .dom(inputFields.fields.float_field.labelElement) - .hasText("float_field"); - assert.dom(inputFields.fields.float_field.inputElement).hasValue(""); + .dom(inputFields.fields.id.inputElement) + .hasAttribute("type", "number"); - await fillIn(inputFields.fields.float_field.inputElement, "6934.24"); - - const tree = new TreeFromDOM(); - await click(tree.nodes[1].element); + await fillIn(inputFields.fields.id.inputElement, "100.0"); inputFields.refresh(); - assert.dom(inputFields.fields.float_field.inputElement).hasValue(""); + assert.dom(inputFields.fields.id.errorElement).hasText( + I18n.t("admin.customize.theme.schema.fields.number.too_large", { + count: 10.5, + }) + ); + + await fillIn(inputFields.fields.id.inputElement, "0.2"); + + inputFields.refresh(); + + assert.dom(inputFields.fields.id.errorElement).hasText( + I18n.t("admin.customize.theme.schema.fields.number.too_small", { + count: 5.5, + }) + ); + + await fillIn(inputFields.fields.id.inputElement, ""); - tree.refresh(); - await click(tree.nodes[0].element); inputFields.refresh(); assert - .dom(inputFields.fields.float_field.inputElement) - .hasValue("6934.24"); + .dom(inputFields.fields.id.errorElement) + .hasText(I18n.t("admin.customize.theme.schema.fields.required")); }); test("input fields of type boolean", async function (assert) {