DEV: more reactive field data for form-kit (#29819)
A lot of the data of fields is decided at insertion time and is not dynamic afterwards, this commit attempts to solve this problem by making the fk-field-data a component with getters on the all the properties we need. It allows for example to implement a dynamic @disabled without having to pass @disabled everywhere. Generally speaking this solution limits props-drilling. @format has received the same treatment than @disabled.
This commit is contained in:
parent
2137f2bb74
commit
89ec7d8b98
|
@ -1,6 +1,6 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { concat } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { concat, fn } from "@ember/helper";
|
||||
import willDestroy from "@ember/render-modifiers/modifiers/will-destroy";
|
||||
import { eq } from "truth-helpers";
|
||||
import FKLabel from "discourse/form-kit/components/fk/label";
|
||||
import FKMeta from "discourse/form-kit/components/fk/meta";
|
||||
|
@ -9,12 +9,6 @@ import concatClass from "discourse/helpers/concat-class";
|
|||
import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class FKControlWrapper extends Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
this.args.field.setType(this.controlType);
|
||||
}
|
||||
|
||||
get controlType() {
|
||||
if (this.args.component.controlType === "input") {
|
||||
return this.args.component.controlType + "-" + (this.args.type || "text");
|
||||
|
@ -23,11 +17,6 @@ export default class FKControlWrapper extends Component {
|
|||
return this.args.component.controlType;
|
||||
}
|
||||
|
||||
@action
|
||||
setFieldType() {
|
||||
this.args.field.type = this.controlType;
|
||||
}
|
||||
|
||||
get error() {
|
||||
return (this.args.errors ?? {})[this.args.field.name];
|
||||
}
|
||||
|
@ -44,12 +33,13 @@ export default class FKControlWrapper extends Component {
|
|||
"form-kit__field"
|
||||
(concat "form-kit__field-" this.controlType)
|
||||
(if this.error "has-error")
|
||||
(if @disabled "is-disabled")
|
||||
(if (eq @format "full") "--full")
|
||||
(if @field.disabled "is-disabled")
|
||||
(if (eq @field.format "full") "--full")
|
||||
}}
|
||||
data-disabled={{@disabled}}
|
||||
data-disabled={{@field.disabled}}
|
||||
data-name={{@field.name}}
|
||||
data-control-type={{this.controlType}}
|
||||
{{willDestroy (fn @unregisterField @field.name)}}
|
||||
>
|
||||
{{#if @field.showTitle}}
|
||||
<FKLabel class="form-kit__container-title" @fieldId={{@field.id}}>
|
||||
|
@ -72,7 +62,7 @@ export default class FKControlWrapper extends Component {
|
|||
<div
|
||||
class={{concatClass
|
||||
"form-kit__container-content"
|
||||
(if @format (concat "--" @format))
|
||||
(if @field.format (concat "--" @field.format))
|
||||
}}
|
||||
>
|
||||
<@component
|
||||
|
@ -86,7 +76,6 @@ export default class FKControlWrapper extends Component {
|
|||
@after={{@after}}
|
||||
@height={{@height}}
|
||||
@selection={{@selection}}
|
||||
@disabled={{@disabled}}
|
||||
id={{@field.id}}
|
||||
name={{@field.name}}
|
||||
aria-invalid={{if this.error "true"}}
|
||||
|
@ -97,12 +86,7 @@ export default class FKControlWrapper extends Component {
|
|||
{{yield components}}
|
||||
</@component>
|
||||
|
||||
<FKMeta
|
||||
@disabled={{@disabled}}
|
||||
@value={{@value}}
|
||||
@field={{@field}}
|
||||
@error={{this.error}}
|
||||
/>
|
||||
<FKMeta @field={{@field}} @error={{this.error}} />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -9,21 +9,23 @@ export default class FKControlCheckbox extends Component {
|
|||
|
||||
@action
|
||||
handleInput() {
|
||||
this.args.field.set(!this.args.value);
|
||||
this.args.field.set(!this.args.field.value);
|
||||
}
|
||||
|
||||
<template>
|
||||
<FKLabel class="form-kit__control-checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={{eq @value true}}
|
||||
checked={{eq @field.value true}}
|
||||
class="form-kit__control-checkbox"
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
{{on "change" this.handleInput}}
|
||||
/>
|
||||
<span class="form-kit__control-checkbox-content">
|
||||
<span class="form-kit__control-checkbox-title">{{@field.title}}</span>
|
||||
<span class="form-kit__control-checkbox-title">
|
||||
{{@field.title}}
|
||||
</span>
|
||||
{{#if (has-block)}}
|
||||
<span class="form-kit__control-checkbox-description">{{yield}}</span>
|
||||
{{/if}}
|
||||
|
|
|
@ -7,7 +7,7 @@ import { escapeExpression } from "discourse/lib/utilities";
|
|||
export default class FKControlCode extends Component {
|
||||
static controlType = "code";
|
||||
|
||||
initialValue = this.args.value || "";
|
||||
initialValue = this.args.field.value || "";
|
||||
|
||||
@action
|
||||
handleInput(content) {
|
||||
|
@ -27,7 +27,7 @@ export default class FKControlCode extends Component {
|
|||
@content={{this.initialValue}}
|
||||
@onChange={{this.handleInput}}
|
||||
@mode={{@lang}}
|
||||
@disabled={{@disabled}}
|
||||
@disabled={{@field.disabled}}
|
||||
class="form-kit__control-code"
|
||||
style={{this.style}}
|
||||
...attributes
|
||||
|
|
|
@ -22,9 +22,9 @@ export default class FKControlComposer extends Component {
|
|||
|
||||
<template>
|
||||
<DEditor
|
||||
@value={{readonly @value}}
|
||||
@value={{readonly @field.value}}
|
||||
@change={{this.handleInput}}
|
||||
@disabled={{@disabled}}
|
||||
@disabled={{@field.disabled}}
|
||||
class="form-kit__control-composer"
|
||||
style={{this.style}}
|
||||
@textAreaId={{@field.id}}
|
||||
|
|
|
@ -13,14 +13,14 @@ export default class FKControlIcon extends Component {
|
|||
|
||||
<template>
|
||||
<IconPicker
|
||||
@value={{readonly @value}}
|
||||
@value={{readonly @field.value}}
|
||||
@onlyAvailable={{true}}
|
||||
@options={{hash
|
||||
maximum=1
|
||||
disabled=@disabled
|
||||
disabled=@field.disabled
|
||||
caretDownIcon="angle-down"
|
||||
caretUpIcon="angle-up"
|
||||
icons=@value
|
||||
icons=@field.value
|
||||
}}
|
||||
@onChange={{this.handleInput}}
|
||||
class="form-kit__control-icon"
|
||||
|
|
|
@ -18,7 +18,7 @@ export default class FKControlImage extends Component {
|
|||
}
|
||||
|
||||
get imageUrl() {
|
||||
return isBlank(this.args.value) ? null : this.args.value;
|
||||
return isBlank(this.args.field.value) ? null : this.args.field.value;
|
||||
}
|
||||
|
||||
<template>
|
||||
|
|
|
@ -66,13 +66,13 @@ export default class FKControlInput extends Component {
|
|||
|
||||
<input
|
||||
type={{this.type}}
|
||||
value={{@value}}
|
||||
value={{@field.value}}
|
||||
class={{concatClass
|
||||
"form-kit__control-input"
|
||||
(if @before "has-prefix")
|
||||
(if @after "has-suffix")
|
||||
}}
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
{{on "input" this.handleInput}}
|
||||
/>
|
||||
|
|
|
@ -23,11 +23,11 @@ export default class FKControlMenu extends Component {
|
|||
<DMenu
|
||||
@onRegisterApi={{this.registerMenuApi}}
|
||||
@triggerClass="form-kit__control-menu"
|
||||
@disabled={{@disabled}}
|
||||
@disabled={{@field.disabled}}
|
||||
@placement="bottom-start"
|
||||
@offset={{5}}
|
||||
id={{@field.id}}
|
||||
data-value={{@value}}
|
||||
data-value={{@field.value}}
|
||||
@modalForMobile={{true}}
|
||||
>
|
||||
<:trigger>
|
||||
|
|
|
@ -59,9 +59,9 @@ export default class FKControlInput extends Component {
|
|||
>
|
||||
<input
|
||||
type={{this.type}}
|
||||
value={{@value}}
|
||||
value={{@field.value}}
|
||||
class="form-kit__control-password"
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
{{on "input" this.handleInput}}
|
||||
{{this.focusState}}
|
||||
|
|
|
@ -22,9 +22,9 @@ export default class FKControlQuestion extends Component {
|
|||
name={{@field.name}}
|
||||
type="radio"
|
||||
value="true"
|
||||
checked={{eq @value true}}
|
||||
checked={{eq @field.value true}}
|
||||
class="form-kit__control-radio"
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
id={{uuid}}
|
||||
{{on "change" this.handleInput}}
|
||||
|
@ -44,9 +44,9 @@ export default class FKControlQuestion extends Component {
|
|||
name={{@field.name}}
|
||||
type="radio"
|
||||
value="false"
|
||||
checked={{eq @value false}}
|
||||
checked={{eq @field.value false}}
|
||||
class="form-kit__control-radio"
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
id={{uuid}}
|
||||
{{on "change" this.handleInput}}
|
||||
|
|
|
@ -15,12 +15,7 @@ export default class FKControlRadioGroup extends Component {
|
|||
>
|
||||
{{yield
|
||||
(hash
|
||||
Radio=(component
|
||||
FKControlRadioGroupRadio
|
||||
groupValue=@value
|
||||
field=@field
|
||||
disabled=@disabled
|
||||
)
|
||||
Radio=(component FKControlRadioGroupRadio value=@value field=@field)
|
||||
)
|
||||
}}
|
||||
</FKFieldset>
|
||||
|
|
|
@ -21,10 +21,10 @@ const FKControlRadioGroupRadio = <template>
|
|||
name={{@field.name}}
|
||||
type="radio"
|
||||
value={{@value}}
|
||||
checked={{eq @groupValue @value}}
|
||||
checked={{eq @field.value @value}}
|
||||
id={{uuid}}
|
||||
class="form-kit__control-radio"
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
{{on "change" (withEventValue @field.set)}}
|
||||
/>
|
||||
|
|
|
@ -19,13 +19,15 @@ export default class FKControlSelect extends Component {
|
|||
|
||||
<template>
|
||||
<select
|
||||
value={{@value}}
|
||||
disabled={{@disabled}}
|
||||
value={{@field.value}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
class="form-kit__control-select"
|
||||
{{on "input" this.handleInput}}
|
||||
>
|
||||
{{yield (hash Option=(component FKControlSelectOption selected=@value))}}
|
||||
{{yield
|
||||
(hash Option=(component FKControlSelectOption selected=@field.value))
|
||||
}}
|
||||
</select>
|
||||
</template>
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@ export default class FKControlTextarea extends Component {
|
|||
<textarea
|
||||
class="form-kit__control-textarea"
|
||||
style={{this.style}}
|
||||
disabled={{@disabled}}
|
||||
disabled={{@field.disabled}}
|
||||
...attributes
|
||||
{{on "input" this.handleInput}}
|
||||
>{{@value}}</textarea>
|
||||
>{{@field.value}}</textarea>
|
||||
</template>
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ export default class FKControlToggle extends Component {
|
|||
|
||||
<template>
|
||||
<DToggleSwitch
|
||||
@state={{@value}}
|
||||
disabled={{@disabled}}
|
||||
@state={{@field.value}}
|
||||
disabled={{@field.disabled}}
|
||||
{{on "click" this.handleInput}}
|
||||
class="form-kit__control-toggle"
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,237 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { action } from "@ember/object";
|
||||
import ValidationParser from "discourse/form-kit/lib/validation-parser";
|
||||
import Validator from "discourse/form-kit/lib/validator";
|
||||
import uniqueId from "discourse/helpers/unique-id";
|
||||
|
||||
/**
|
||||
* Represents a field in a form with validation, registration, and field data management capabilities.
|
||||
*/
|
||||
export default class FKFieldData extends Component {
|
||||
/**
|
||||
* Unique identifier for the field.
|
||||
* @type {string}
|
||||
*/
|
||||
id = uniqueId();
|
||||
|
||||
/**
|
||||
* Unique identifier for the field's error element.
|
||||
* @type {string}
|
||||
*/
|
||||
errorId = uniqueId();
|
||||
|
||||
/**
|
||||
* Type of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
type;
|
||||
|
||||
/**
|
||||
* Initializes the FKFieldData component.
|
||||
* Validates the presence of required arguments and registers the field.
|
||||
* @throws {Error} If `@title` is not provided.
|
||||
*/
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (!this.args.title?.length) {
|
||||
throw new Error("@title is required on `<form.Field />`.");
|
||||
}
|
||||
|
||||
this.args.registerField(this.name, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current value of the field.
|
||||
* @type {any}
|
||||
*/
|
||||
get value() {
|
||||
return this.args.data.get(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the validation rules for the field.
|
||||
* @type {Object|null}
|
||||
*/
|
||||
get rules() {
|
||||
return this.args.validation
|
||||
? ValidationParser.parse(this.args.validation)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value of the field and triggers revalidation.
|
||||
* @param {any} value - The new value for the field.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
@action
|
||||
async set(value) {
|
||||
if (this.args.onSet) {
|
||||
await this.args.onSet(value, {
|
||||
set: this.args.set,
|
||||
index: this.args.collectionIndex,
|
||||
});
|
||||
} else {
|
||||
await this.args.set(this.name, value, {
|
||||
index: this.args.collectionIndex,
|
||||
});
|
||||
}
|
||||
|
||||
this.args.triggerRevalidationFor(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Title of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
get title() {
|
||||
return this.args.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
get format() {
|
||||
return this.args.format;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the field is disabled.
|
||||
* Defaults to `false`.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get disabled() {
|
||||
return this.args.disabled ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Description of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
get description() {
|
||||
return this.args.description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether to show the field's title.
|
||||
* Defaults to `true`.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get showTitle() {
|
||||
return this.args.showTitle ?? true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to add errors to the field.
|
||||
* @type {Function}
|
||||
*/
|
||||
get addError() {
|
||||
return this.args.addError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the unique name for the field.
|
||||
* @type {string}
|
||||
* @throws {Error} If `name` is not a string or contains invalid characters.
|
||||
*/
|
||||
get name() {
|
||||
if (typeof this.args.name !== "string") {
|
||||
throw new Error(
|
||||
"@name is required and must be a string on `<form.Field />`."
|
||||
);
|
||||
}
|
||||
|
||||
if (this.args.name.includes(".") || this.args.name.includes("-")) {
|
||||
throw new Error("@name can't include `.` or `-`.");
|
||||
}
|
||||
|
||||
return (
|
||||
(this.args.collectionName ? `${this.args.collectionName}.` : "") +
|
||||
(this.args.collectionIndex !== undefined
|
||||
? `${this.args.collectionIndex}.`
|
||||
: "") +
|
||||
this.args.name
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation rules for the field.
|
||||
* @type {string|Object}
|
||||
*/
|
||||
get validation() {
|
||||
return this.args.validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom validation function.
|
||||
* @type {Function}
|
||||
*/
|
||||
get customValidate() {
|
||||
return this.args.validate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the field is required.
|
||||
* Derived from validation rules.
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get required() {
|
||||
return this.rules?.required ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximum length of the field value.
|
||||
* Derived from validation rules.
|
||||
* @type {number|null}
|
||||
* @readonly
|
||||
*/
|
||||
get maxLength() {
|
||||
return this.rules?.length?.max ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum length of the field value.
|
||||
* Derived from validation rules.
|
||||
* @type {number|null}
|
||||
* @readonly
|
||||
*/
|
||||
get minLength() {
|
||||
return this.rules?.length?.min ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the field value.
|
||||
* @param {string} name - The name of the field.
|
||||
* @param {any} value - The value of the field.
|
||||
* @param {Object} data - Additional data for validation.
|
||||
* @returns {Promise<Object>} The validation errors.
|
||||
*/
|
||||
async validate(name, value, data) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.customValidate?.(name, value, {
|
||||
data,
|
||||
type: this.type,
|
||||
addError: this.addError,
|
||||
});
|
||||
|
||||
const validator = new Validator(value, this.rules);
|
||||
const validationErrors = await validator.validate(this.type);
|
||||
validationErrors.forEach((message) => {
|
||||
let title = this.title;
|
||||
if (this.args.collectionIndex !== undefined) {
|
||||
title += ` #${this.args.collectionIndex + 1}`;
|
||||
}
|
||||
|
||||
this.addError(name, { title, message });
|
||||
});
|
||||
}
|
||||
|
||||
<template>
|
||||
{{yield this}}
|
||||
</template>
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { hash } from "@ember/helper";
|
||||
import FKControlCheckbox from "discourse/form-kit/components/fk/control/checkbox";
|
||||
import FKControlCode from "discourse/form-kit/components/fk/control/code";
|
||||
|
@ -16,6 +15,7 @@ import FKControlSelect from "discourse/form-kit/components/fk/control/select";
|
|||
import FKControlTextarea from "discourse/form-kit/components/fk/control/textarea";
|
||||
import FKControlToggle from "discourse/form-kit/components/fk/control/toggle";
|
||||
import FKControlWrapper from "discourse/form-kit/components/fk/control-wrapper";
|
||||
import FKFieldData from "discourse/form-kit/components/fk/field-data";
|
||||
import FKRow from "discourse/form-kit/components/fk/row";
|
||||
|
||||
const RowColWrapper = <template>
|
||||
|
@ -32,57 +32,6 @@ const EmptyWrapper = <template>
|
|||
</template>;
|
||||
|
||||
export default class FKField extends Component {
|
||||
@tracked field;
|
||||
@tracked name;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
|
||||
if (!this.args.title?.length) {
|
||||
throw new Error("@title is required on `<form.Field />`.");
|
||||
}
|
||||
|
||||
if (typeof this.args.name !== "string") {
|
||||
throw new Error(
|
||||
"@name is required and must be a string on `<form.Field />`."
|
||||
);
|
||||
}
|
||||
|
||||
if (this.args.name.includes(".") || this.args.name.includes("-")) {
|
||||
throw new Error("@name can't include `.` or `-`.");
|
||||
}
|
||||
|
||||
this.name =
|
||||
(this.args.collectionName ? `${this.args.collectionName}.` : "") +
|
||||
(this.args.collectionIndex !== undefined
|
||||
? `${this.args.collectionIndex}.`
|
||||
: "") +
|
||||
this.args.name;
|
||||
|
||||
this.field = this.args.registerField(this.name, {
|
||||
triggerRevalidationFor: this.args.triggerRevalidationFor,
|
||||
title: this.args.title,
|
||||
description: this.args.description,
|
||||
showTitle: this.args.showTitle,
|
||||
collectionIndex: this.args.collectionIndex,
|
||||
set: this.args.set,
|
||||
addError: this.args.addError,
|
||||
validate: this.args.validate,
|
||||
validation: this.args.validation,
|
||||
onSet: this.args.onSet,
|
||||
});
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
this.args.unregisterField(this.name);
|
||||
|
||||
super.willDestroy();
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.args.data.get(this.name);
|
||||
}
|
||||
|
||||
get wrapper() {
|
||||
if (this.args.size) {
|
||||
return RowColWrapper;
|
||||
|
@ -92,142 +41,134 @@ export default class FKField extends Component {
|
|||
}
|
||||
|
||||
<template>
|
||||
<this.wrapper @size={{@size}}>
|
||||
{{yield
|
||||
(hash
|
||||
Custom=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlCustom
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
<FKFieldData
|
||||
@name={{@name}}
|
||||
@data={{@data}}
|
||||
@triggerRevalidationFor={{@triggerRevalidationFor}}
|
||||
@title={{@title}}
|
||||
@description={{@description}}
|
||||
@showTitle={{@showTitle}}
|
||||
@collectionIndex={{@collectionIndex}}
|
||||
@set={{@set}}
|
||||
@addError={{@addError}}
|
||||
@validate={{@validate}}
|
||||
@validation={{@validation}}
|
||||
@onSet={{@onSet}}
|
||||
@registerField={{@registerField}}
|
||||
@format={{@format}}
|
||||
@disabled={{@disabled}}
|
||||
@collectionName={{@collectionName}}
|
||||
as |field|
|
||||
>
|
||||
<this.wrapper @size={{@size}}>
|
||||
{{yield
|
||||
(hash
|
||||
Custom=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlCustom
|
||||
field=field
|
||||
)
|
||||
Code=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlCode
|
||||
field=field
|
||||
)
|
||||
Question=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlQuestion
|
||||
field=field
|
||||
)
|
||||
Textarea=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlTextarea
|
||||
field=field
|
||||
)
|
||||
Checkbox=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlCheckbox
|
||||
field=field
|
||||
)
|
||||
Image=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlImage
|
||||
field=field
|
||||
)
|
||||
Password=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlPassword
|
||||
field=field
|
||||
)
|
||||
Composer=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlComposer
|
||||
field=field
|
||||
)
|
||||
Icon=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlIcon
|
||||
field=field
|
||||
)
|
||||
Toggle=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlToggle
|
||||
field=field
|
||||
)
|
||||
Menu=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlMenu
|
||||
field=field
|
||||
)
|
||||
Select=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlSelect
|
||||
field=field
|
||||
)
|
||||
Input=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlInput
|
||||
field=field
|
||||
)
|
||||
RadioGroup=(component
|
||||
FKControlWrapper
|
||||
unregisterField=@unregisterField
|
||||
errors=@errors
|
||||
component=FKControlRadioGroup
|
||||
field=field
|
||||
)
|
||||
errorId=field.errorId
|
||||
id=field.id
|
||||
name=field.name
|
||||
set=field.set
|
||||
value=field.value
|
||||
)
|
||||
Code=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlCode
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Question=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlQuestion
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Textarea=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlTextarea
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Checkbox=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlCheckbox
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Image=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlImage
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Password=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlPassword
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Composer=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlComposer
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Icon=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlIcon
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Toggle=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlToggle
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Menu=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlMenu
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Select=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlSelect
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
Input=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlInput
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
RadioGroup=(component
|
||||
FKControlWrapper
|
||||
errors=@errors
|
||||
disabled=@disabled
|
||||
component=FKControlRadioGroup
|
||||
value=this.value
|
||||
field=this.field
|
||||
format=@format
|
||||
)
|
||||
errorId=this.field.errorId
|
||||
id=this.field.id
|
||||
name=this.field.name
|
||||
set=this.field.set
|
||||
value=this.value
|
||||
)
|
||||
}}
|
||||
</this.wrapper>
|
||||
}}
|
||||
</this.wrapper>
|
||||
</FKFieldData>
|
||||
</template>
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import Row from "discourse/form-kit/components/fk/row";
|
|||
import FKSection from "discourse/form-kit/components/fk/section";
|
||||
import FKSubmit from "discourse/form-kit/components/fk/submit";
|
||||
import { VALIDATION_TYPES } from "discourse/form-kit/lib/constants";
|
||||
import FKFieldData from "discourse/form-kit/lib/fk-field-data";
|
||||
import FKFormData from "discourse/form-kit/lib/fk-form-data";
|
||||
import { i18n } from "discourse-i18n";
|
||||
|
||||
|
@ -151,10 +150,9 @@ class FKForm extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
const fieldModel = new FKFieldData(name, field);
|
||||
this.fields.set(name, fieldModel);
|
||||
this.fields.set(name, field);
|
||||
|
||||
return fieldModel;
|
||||
return field;
|
||||
}
|
||||
|
||||
@action
|
||||
|
|
|
@ -4,7 +4,7 @@ import FKErrors from "discourse/form-kit/components/fk/errors";
|
|||
|
||||
export default class FKMeta extends Component {
|
||||
get shouldRenderCharCounter() {
|
||||
return this.args.field.maxLength > 0 && !this.args.disabled;
|
||||
return this.args.field.maxLength > 0 && !this.args.field.disabled;
|
||||
}
|
||||
|
||||
get shouldRenderMeta() {
|
||||
|
@ -24,7 +24,7 @@ export default class FKMeta extends Component {
|
|||
|
||||
{{#if this.shouldRenderCharCounter}}
|
||||
<FKCharCounter
|
||||
@value={{@value}}
|
||||
@value={{@field.value}}
|
||||
@minLength={{@field.minLength}}
|
||||
@maxLength={{@field.maxLength}}
|
||||
/>
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
import ValidationParser from "discourse/form-kit/lib/validation-parser";
|
||||
import Validator from "discourse/form-kit/lib/validator";
|
||||
import uniqueId from "discourse/helpers/unique-id";
|
||||
|
||||
/**
|
||||
* Represents field data for a form.
|
||||
*/
|
||||
export default class FKFieldData {
|
||||
/**
|
||||
* Unique identifier for the field.
|
||||
* @type {string}
|
||||
*/
|
||||
id = uniqueId();
|
||||
|
||||
/**
|
||||
* Unique identifier for the field error.
|
||||
* @type {Function}
|
||||
*/
|
||||
errorId = uniqueId();
|
||||
|
||||
/**
|
||||
* Type of the field.
|
||||
* @type {string}
|
||||
*/
|
||||
type;
|
||||
|
||||
/**
|
||||
* Creates an instance of FieldData.
|
||||
* @param {string} name - The name of the field.
|
||||
* @param {Object} options - The options for the field.
|
||||
* @param {Function} options.set - The callback function for setting the field value.
|
||||
* @param {Function} options.onSet - The callback function for setting the custom field value.
|
||||
* @param {string} options.validation - The validation rules for the field.
|
||||
* @param {boolean} [options.disabled=false] - Indicates if the field is disabled.
|
||||
* @param {Function} [options.validate] - The custom validation function.
|
||||
* @param {Function} [options.title] - The custom field title.
|
||||
* @param {Function} [options.subtitle] - The custom field subtitle.
|
||||
* @param {Function} [options.description] - The custom field description.
|
||||
* @param {Function} [options.showTitle=true] - Indicates if the field title should be shown.
|
||||
* @param {Function} [options.triggerRevalidationFor] - The function to trigger revalidation.
|
||||
* @param {Function} [options.addError] - The function to add an error message.
|
||||
*/
|
||||
constructor(
|
||||
name,
|
||||
{
|
||||
set,
|
||||
onSet,
|
||||
validation,
|
||||
disabled = false,
|
||||
validate,
|
||||
title,
|
||||
subtitle,
|
||||
description,
|
||||
showTitle = true,
|
||||
triggerRevalidationFor,
|
||||
collectionIndex,
|
||||
addError,
|
||||
}
|
||||
) {
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
this.subtitle = subtitle;
|
||||
this.description = description;
|
||||
this.collectionIndex = collectionIndex;
|
||||
this.addError = addError;
|
||||
this.showTitle = showTitle;
|
||||
this.disabled = disabled;
|
||||
this.customValidate = validate;
|
||||
this.validation = validation;
|
||||
this.rules = this.validation ? ValidationParser.parse(validation) : null;
|
||||
this.set = (value) => {
|
||||
if (onSet) {
|
||||
onSet(value, { set, index: collectionIndex });
|
||||
} else {
|
||||
set(this.name, value, { index: collectionIndex });
|
||||
}
|
||||
|
||||
triggerRevalidationFor(name);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the field is required.
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get required() {
|
||||
return this.rules?.required ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the type of the field.
|
||||
*/
|
||||
setType(type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum length of the field value.
|
||||
* @type {number|null}
|
||||
* @readonly
|
||||
*/
|
||||
get maxLength() {
|
||||
return this.rules?.length?.max ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum length of the field value.
|
||||
* @type {number|null}
|
||||
* @readonly
|
||||
*/
|
||||
get minLength() {
|
||||
return this.rules?.length?.min ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the field value.
|
||||
* @param {string} name - The name of the field.
|
||||
* @param {any} value - The value of the field.
|
||||
* @param {Object} data - Additional data for validation.
|
||||
* @returns {Promise<Object>} The validation errors.
|
||||
*/
|
||||
async validate(name, value, data) {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.customValidate?.(name, value, {
|
||||
data,
|
||||
type: this.type,
|
||||
addError: this.addError,
|
||||
});
|
||||
|
||||
const validator = new Validator(value, this.rules);
|
||||
const validationErrors = await validator.validate(this.type);
|
||||
validationErrors.forEach((message) => {
|
||||
let title = this.title;
|
||||
if (this.collectionIndex !== undefined) {
|
||||
title += ` #${this.collectionIndex + 1}`;
|
||||
}
|
||||
|
||||
this.addError(name, { title, message });
|
||||
});
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue