-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(validated-input): rewrite to glimmer and support nested changesets (
#581) * fix(validated-input): rewrite to glimmer and support nested changesets Previously the computed `_val` did not recompute if `name` was a nested property like `titleObject.de`. This is solved with using getters. Example twiddle: https://ember-twiddle.com/9cb7325ea5aa8983b5ea871366014d2d?openFiles=controllers.application%5C.js%2C#docs(): * test!: drop ember 3.16 and legacy-changeset tests BREAKING CHANGE: This drops support for Ember LTS 3.16 and `ember-changeset` < 3.0.0 and `ember-changeset-validations` < 3.0.0 * refactor(validated-input): refactor dynamic component call to angle-brackets * chore(*): drop node v10 support BREAKING CHANGE: drop node v10 support since v10 has reached EOL * fix(themed-component): convert array to string befor using in key path
- Loading branch information
Showing
9 changed files
with
194 additions
and
158 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,99 @@ | ||
import Component from "@ember/component"; | ||
import { computed, defineProperty } from "@ember/object"; | ||
import { v4 } from "uuid"; | ||
import { setComponentTemplate } from "@ember/component"; | ||
import { action, set, get } from "@ember/object"; | ||
import { guidFor } from "@ember/object/internals"; | ||
import Component from "@glimmer/component"; | ||
import { tracked } from "@glimmer/tracking"; | ||
|
||
import themedComponent from "../-private/themed-component"; | ||
import layout from "../templates/components/validated-input"; | ||
import template from "../templates/components/validated-input"; | ||
|
||
/** | ||
* This component wraps form inputs. | ||
* | ||
* It can be used in a two-way-binding style like | ||
* {{validated-input model=model name='firstName'}} (model will be updated) | ||
* <ValidatedInput @model={{model}} @name='firstName'/> (model will be updated) | ||
* | ||
* or in a one-way-binding style | ||
* {{validated-input model=model name='firstName' on-update=(action "update"}} | ||
* <ValidatedInput @model={{model}} @name='firstName' @on-update={{this.update}} | ||
* (update action is called, model is not updated) | ||
* | ||
* @class validated-input | ||
* @export default | ||
*/ | ||
export default Component.extend({ | ||
layout, | ||
tagName: "", | ||
dirty: false, | ||
required: false, | ||
type: "text", | ||
validateBeforeSubmit: true, | ||
|
||
init(...args) { | ||
this._super(...args); | ||
|
||
defineProperty( | ||
this, | ||
"_val", | ||
computed("value", `model.${this.name}`, "name", function () { | ||
return this.value || this.get(`model.${this.name}`); | ||
}) | ||
); | ||
}, | ||
export class ValidatedInput extends Component { | ||
inputId = guidFor(this); | ||
|
||
@tracked dirty; | ||
@tracked required; | ||
@tracked type; | ||
@tracked validateBeforeSubmit; | ||
|
||
@themedComponent("validated-input/render") renderComponent; | ||
@themedComponent("validated-input/label") labelComponent; | ||
@themedComponent("validated-input/hint") hintComponent; | ||
@themedComponent("validated-input/error") errorComponent; | ||
|
||
inputId: computed(function () { | ||
return v4(); | ||
}), | ||
constructor(...args) { | ||
super(...args); | ||
|
||
errors: computed("_val", "name", function () { | ||
const errors = this.get(`model.error.${this.name}.validation`) || []; | ||
this.dirty = this.args.dirty ?? false; | ||
this.required = this.args.required ?? false; | ||
this.type = this.args.type ?? "text"; | ||
this.validateBeforeSubmit = this.args.validateBeforeSubmit ?? true; | ||
|
||
this.renderComponent = this.args.renderComponent ?? this.renderComponent; | ||
this.labelComponent = this.args.labelComponent ?? this.labelComponent; | ||
this.hintComponent = this.args.hintComponent ?? this.hintComponent; | ||
this.errorComponent = this.args.errorComponent ?? this.errorComponent; | ||
} | ||
|
||
get _val() { | ||
return ( | ||
this.args.value ?? | ||
(this.args.model && | ||
this.args.name && | ||
get(this.args.model, this.args.name)) | ||
); | ||
} | ||
|
||
get errors() { | ||
const errors = | ||
(this.args.model && | ||
get(this.args.model, `error.${this.args.name}.validation`)) ?? | ||
[]; | ||
|
||
if (!Array.isArray(errors)) { | ||
return [errors]; | ||
} | ||
|
||
return errors; | ||
}), | ||
} | ||
|
||
isValid: computed("showValidity", "errors.[]", function () { | ||
get isValid() { | ||
return this.showValidity && !this.errors.length; | ||
}), | ||
} | ||
|
||
isInvalid: computed("showValidity", "errors.[]", function () { | ||
get isInvalid() { | ||
return this.showValidity && !!this.errors.length; | ||
}), | ||
|
||
renderComponent: themedComponent("validated-input/render"), | ||
labelComponent: themedComponent("validated-input/label"), | ||
hintComponent: themedComponent("validated-input/hint"), | ||
errorComponent: themedComponent("validated-input/error"), | ||
|
||
showValidity: computed( | ||
"validateBeforeSubmit", | ||
"dirty", | ||
"submitted", | ||
function () { | ||
return this.submitted || (this.validateBeforeSubmit && this.dirty); | ||
} | ||
|
||
get showValidity() { | ||
return this.args.submitted || (this.validateBeforeSubmit && this.dirty); | ||
} | ||
|
||
@action | ||
setDirty() { | ||
this.dirty = true; | ||
} | ||
|
||
@action | ||
update(value) { | ||
if (this["on-update"]) { | ||
this["on-update"](value, this.args.model); | ||
} else { | ||
set(this.args.model, this.args.name, value); | ||
} | ||
), | ||
|
||
actions: { | ||
setDirty() { | ||
this.set("dirty", true); | ||
}, | ||
|
||
update(value) { | ||
if (this["on-update"]) { | ||
this["on-update"](value, this.model); | ||
} else { | ||
this.set(`model.${this.name}`, value); | ||
} | ||
}, | ||
}, | ||
}); | ||
} | ||
} | ||
|
||
export default setComponentTemplate(template, ValidatedInput); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,76 @@ | ||
{{#if hasBlock}} | ||
{{component labelComponent label=label required=required isValid=isValid isInvalid=isInvalid inputId=inputId}} | ||
{{#if (has-block)}} | ||
{{#let (component this.labelComponent) as |LabelComponent|}} | ||
<LabelComponent | ||
@label={{@label}} | ||
@required={{this.required}} | ||
@isValid={{this.isValid}} | ||
@isInvalid={{this.isInvalid}} | ||
@inputId={{this.inputId}} | ||
/> | ||
{{/let}} | ||
|
||
{{yield (hash | ||
value=_val | ||
update=(action "update") | ||
setDirty=(action "setDirty") | ||
model=model | ||
name=name | ||
inputId=inputId | ||
isValid=isValid | ||
isInvalid=isInvalid | ||
)}} | ||
{{yield | ||
(hash | ||
value=this._val | ||
update=this.update | ||
setDirty=this.setDirty | ||
model=@model | ||
name=@name | ||
inputId=this.inputId | ||
isValid=this.isValid | ||
isInvalid=this.isInvalid | ||
) | ||
}} | ||
|
||
{{#if hint}} | ||
{{component hintComponent hint=hint}} | ||
{{#if @hint}} | ||
{{component this.hintComponent hint=@hint}} | ||
{{/if}} | ||
|
||
{{#if (and showValidity errors)}} | ||
{{component errorComponent errors=errors}} | ||
{{#if (and this.showValidity this.errors)}} | ||
{{component this.errorComponent errors=this.errors}} | ||
{{/if}} | ||
{{else}} | ||
{{component renderComponent | ||
type=type | ||
value=_val | ||
inputId=inputId | ||
options=options | ||
name=name | ||
inputName=inputName | ||
disabled=disabled | ||
autofocus=autofocus | ||
autocomplete=autocomplete | ||
rows=rows | ||
cols=cols | ||
model=model | ||
isValid=isValid | ||
isInvalid=isInvalid | ||
placeholder=placeholder | ||
class=class | ||
|
||
promptIsSelectable=promptIsSelectable | ||
optionLabelPath=optionLabelPath | ||
optionValuePath=optionValuePath | ||
optionTargetPath=optionTargetPath | ||
includeBlank=includeBlank | ||
multiple=multiple | ||
|
||
update=(action "update") | ||
setDirty=(action "setDirty") | ||
|
||
labelComponent=(component labelComponent label=label required=required isValid=isValid isInvalid=isInvalid inputId=inputId) | ||
hintComponent=(if hint (component hintComponent hint=hint)) | ||
errorComponent=(if (and showValidity errors) (component errorComponent errors=errors)) | ||
}} | ||
{{#let (component this.renderComponent) as |RenderComponent|}} | ||
<RenderComponent | ||
@type={{this.type}} | ||
@value={{this._val}} | ||
@inputId={{this.inputId}} | ||
@options={{@options}} | ||
@name={{@name}} | ||
@inputName={{@inputName}} | ||
@disabled={{@disabled}} | ||
@autofocus={{@autofocus}} | ||
@autocomplete={{@autocomplete}} | ||
@rows={{@rows}} | ||
@cols={{@cols}} | ||
@model={{@model}} | ||
@isValid={{this.isValid}} | ||
@isInvalid={{this.isInvalid}} | ||
@placeholder={{@placeholder}} | ||
@class={{@class}} | ||
@promptIsSelectable={{@promptIsSelectable}} | ||
@optionLabelPath={{@optionLabelPath}} | ||
@optionValuePath={{@optionValuePath}} | ||
@optionTargetPath={{@optionTargetPath}} | ||
@includeBlank={{@includeBlank}} | ||
@multiple={{@multiple}} | ||
@update={{this.update}} | ||
@setDirty={{this.setDirty}} | ||
@labelComponent={{ | ||
component | ||
this.labelComponent | ||
label=@label | ||
required=@required | ||
isValid=this.isValid | ||
isInvalid=this.isInvalid | ||
inputId=this.inputId | ||
}} | ||
@hintComponent={{if @hint (component this.hintComponent hint=@hint)}} | ||
@errorComponent={{ | ||
if | ||
(and this.showValidity this.errors) | ||
(component this.errorComponent errors=this.errors) | ||
}} | ||
/> | ||
{{/let}} | ||
{{/if}} |
Oops, something went wrong.