Skip to content

Commit

Permalink
feat: add form associated custom elements
Browse files Browse the repository at this point in the history
  • Loading branch information
pskelin committed Nov 17, 2023
1 parent 592a10b commit dcf0a0b
Show file tree
Hide file tree
Showing 9 changed files with 273 additions and 67 deletions.
30 changes: 30 additions & 0 deletions packages/main/src/CheckBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,32 @@ let activeCb: CheckBox;
@event("change")

class CheckBox extends UI5Element implements IFormElement {
static formAssociated = true;
get form() {
return this.#internals.form;
}

formStateRestoreCallback(value: string) {
this.checked = Boolean(value);
this._setFormValue();
}

formAssociatedCallback() {
this._setFormValue();
}

_setFormValue() {
this.#internals.setFormValue(this.checked.toString());
}

connectedCallback(): Promise<void> {
return super.connectedCallback();
}

onEnterDOM(): void {
console.log();

Check failure on line 134 in packages/main/src/CheckBox.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected console statement

Check failure on line 134 in packages/main/src/CheckBox.ts

View workflow job for this annotation

GitHub Actions / check (base)

Unexpected console statement

Check failure on line 134 in packages/main/src/CheckBox.ts

View workflow job for this annotation

GitHub Actions / check (main:suite-1)

Unexpected console statement

Check failure on line 134 in packages/main/src/CheckBox.ts

View workflow job for this annotation

GitHub Actions / check (main:suite-2)

Unexpected console statement

Check failure on line 134 in packages/main/src/CheckBox.ts

View workflow job for this annotation

GitHub Actions / check (fiori)

Unexpected console statement
}

/**
* Receives id(or many ids) of the elements that label the component
* @type {string}
Expand Down Expand Up @@ -281,10 +307,12 @@ class CheckBox extends UI5Element implements IFormElement {

static i18nBundle: I18nBundle;
_deactivate: () => void;
#internals: ElementInternals;

constructor() {
super();

this.#internals = this.attachInternals();
this._deactivate = () => {
if (activeCb) {
activeCb.active = false;
Expand Down Expand Up @@ -368,13 +396,15 @@ class CheckBox extends UI5Element implements IFormElement {
this.checked = !this.checked;
}

this._setFormValue();
const changePrevented = !this.fireEvent("change", null, true);
// Angular two way data binding
const valueChagnePrevented = !this.fireEvent("value-changed", null, true);

if (changePrevented || valueChagnePrevented) {
this.checked = lastState.checked;
this.indeterminate = lastState.indeterminate;
this._setFormValue();
}
}
return this;
Expand Down
15 changes: 15 additions & 0 deletions packages/main/src/Input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ type InputSuggestionScrollEventDetail = {
},
})
class Input extends UI5Element implements SuggestionComponent, IFormElement {
static formAssociated = true;
get form() {
return this._internals.form;
}
formStateRestoreCallback(value: string) {
this.value = value;
}
/**
* Defines whether the component is in disabled state.
* <br><br>
Expand Down Expand Up @@ -642,9 +649,12 @@ class Input extends UI5Element implements SuggestionComponent, IFormElement {
_performTextSelection?: boolean;
_previewItem?: SuggestionListItem;
static i18nBundle: I18nBundle;
_internals: ElementInternals;

constructor() {
super();
this._internals = this.attachInternals();

// Indicates if there is selected suggestionItem.
this.hasSuggestionItemSelected = false;

Expand Down Expand Up @@ -1365,12 +1375,17 @@ class Input extends UI5Element implements SuggestionComponent, IFormElement {
this.valueBeforeItemPreview = inputValue;

if (isUserInput) { // input
this._setFormValue();
this.fireEvent<InputEventDetail>(INPUT_EVENTS.INPUT, { inputType: e.inputType });
// Angular two way data binding
this.fireEvent("value-changed");
}
}

_setFormValue() {
this._internals.setFormValue(this.value);
}

async getInputValue() {
const domRef = this.getDomRef();

Expand Down
2 changes: 1 addition & 1 deletion packages/main/src/Menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ class Menu extends UI5Element {
}
if (this.open) {
const rootNode = this.getRootNode() as Document;
const opener = this.opener instanceof HTMLElement ? this.opener : rootNode && rootNode.getElementById(this.opener);
const opener = this.opener instanceof HTMLElement ? this.opener : rootNode && typeof rootNode.getElementById === "function" && rootNode.getElementById(this.opener);

if (opener) {
this.showAt(opener);
Expand Down
58 changes: 58 additions & 0 deletions packages/main/src/MultiComboBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,31 @@ type MultiComboboxItemWithSelection = {
})

class MultiComboBox extends UI5Element {
static formAssociated = true;
private _internals: ElementInternals;
formStateValue?: FormData;

get form() {
return this._internals.form;
}


Check failure on line 251 in packages/main/src/MultiComboBox.ts

View workflow job for this annotation

GitHub Actions / check

More than 1 blank line not allowed

Check failure on line 251 in packages/main/src/MultiComboBox.ts

View workflow job for this annotation

GitHub Actions / check (base)

More than 1 blank line not allowed

Check failure on line 251 in packages/main/src/MultiComboBox.ts

View workflow job for this annotation

GitHub Actions / check (main:suite-1)

More than 1 blank line not allowed

Check failure on line 251 in packages/main/src/MultiComboBox.ts

View workflow job for this annotation

GitHub Actions / check (main:suite-2)

More than 1 blank line not allowed

Check failure on line 251 in packages/main/src/MultiComboBox.ts

View workflow job for this annotation

GitHub Actions / check (fiori)

More than 1 blank line not allowed
formStateRestoreCallback(formValue: FormData) {
this.formStateValue = formValue;
const [value, ...tokens] = formValue.getAll(this.getAttribute("name")!) as string[];
this.items.forEach(item => {
const idx = tokens.indexOf(item.text);
if (idx !== -1) {
item.selected = true;
tokens.splice(idx, 1);
} else {
item.selected = false;
}
});

this.value = value;
}

/**
* Defines the value of the component.
* <br><br>
Expand Down Expand Up @@ -490,6 +515,7 @@ class MultiComboBox extends UI5Element {
constructor() {
super();

this._internals = this.attachInternals();
this._filteredItems = [];
this._previouslySelectedItems = [];
this.selectedValues = [];
Expand All @@ -506,6 +532,9 @@ class MultiComboBox extends UI5Element {
}

onEnterDOM() {
if (this.formStateValue) {
this.formStateRestoreCallback(this.formStateValue);
}
ResizeHandler.register(this, this._handleResizeBound);
}

Expand Down Expand Up @@ -592,9 +621,34 @@ class MultiComboBox extends UI5Element {
}
}

this._internals.setFormValue(this.buildValue());
this.fireEvent("input");
}

buildValue() {
const fd = new FormData();

// value is always present and always first
fd.append(this.getAttribute("name")!, this.value);
// fd.set("value", this.value);

// tokens if any
this._getSelectedItems().forEach(token => {
// fd.append("tokens", token.text);
// eslint-disable-next-line prefer-template
fd.append(this.getAttribute("name")!, token.text);
});

// const obj = { value: this.value, tokens: this._getSelectedItems().map(t => t.text) };
// const blob = new Blob([JSON.stringify(obj, null, 2)], {
// type: "application/json",
// });
// // return blob;
// fd.set(this.getAttribute("name")!, blob);

return fd;
}

_tokenDelete(e: CustomEvent<TokenizerTokenDeleteEventDetail>) {
this._previouslySelectedItems = this._getSelectedItems();
const token: Token = e.detail.ref;
Expand Down Expand Up @@ -777,6 +831,7 @@ class MultiComboBox extends UI5Element {
});
} else {
this.value = pastedText;
this._internals.setFormValue(this.buildValue());
this.fireEvent("input");
}
}
Expand Down Expand Up @@ -1297,6 +1352,7 @@ class MultiComboBox extends UI5Element {
}
}

this._internals.setFormValue(this.buildValue());
this.fireEvent("input");
}

Expand All @@ -1314,6 +1370,7 @@ class MultiComboBox extends UI5Element {
}

fireSelectionChange() {
this._internals.setFormValue(this.buildValue());
const changePrevented = !this.fireEvent<MultiComboBoxSelectionChangeEventDetail>("selection-change", {
items: this._getSelectedItems(),
}, true);
Expand Down Expand Up @@ -1419,6 +1476,7 @@ class MultiComboBox extends UI5Element {
this._filteredItems.forEach(item => {
item.selected = this._previouslySelectedItems.includes(item);
});
this._internals.setFormValue(this.buildValue());
}

onBeforeRendering() {
Expand Down
2 changes: 2 additions & 0 deletions packages/main/src/MultiInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ type MultiInputTokenDeleteEventDetail = {
})

class MultiInput extends Input {
static formAssociated = true;

/**
* Determines whether a value help icon will be visualized in the end of the input.
* Pressing the icon will fire <code>value-help-trigger</code> event.
Expand Down
28 changes: 12 additions & 16 deletions packages/main/src/RadioButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ let activeRadio: RadioButton;
@event("change")

class RadioButton extends UI5Element implements IFormElement {
static formAssociated = true;

formStateRestoreCallback(value: string) {
this.checked = (this.value === value);
this._setFormValue();
}

formAssociatedCallback() {
this._setFormValue();
}

/**
* Defines whether the component is disabled.
* <br><br>
Expand Down Expand Up @@ -279,10 +290,6 @@ class RadioButton extends UI5Element implements IFormElement {
_checked!: boolean;
_internals: ElementInternals;

static get formAssociated() {
return true;
}

static i18nBundle: I18nBundle;

constructor() {
Expand All @@ -308,8 +315,6 @@ class RadioButton extends UI5Element implements IFormElement {

onBeforeRendering() {
this.syncGroup();

this._enableFormSupport();
}

onExitDOM() {
Expand Down Expand Up @@ -348,16 +353,6 @@ class RadioButton extends UI5Element implements IFormElement {
this._checked = this.checked;
}

_enableFormSupport() {
const formSupport = getFeature<typeof FormSupport>("FormSupport");

if (formSupport) {
this._setFormValue();
} else if (this.value) {
console.warn(`In order for the "value" property to have effect, you should also: import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";`); // eslint-disable-line
}
}

_setFormValue() {
this._internals.setFormValue(this.checked ? this.value : null);
}
Expand Down Expand Up @@ -450,6 +445,7 @@ class RadioButton extends UI5Element implements IFormElement {

if (!this.name) {
this.checked = !this.checked;
this._setFormValue();
this.fireEvent("change");
return this;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/main/src/RadioButtonGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ class RadioButtonGroup {
static _deselectRadio(radioBtn: RadioButton) {
if (radioBtn) {
radioBtn.checked = false;
radioBtn._setFormValue();
}
}

Expand All @@ -173,6 +174,7 @@ class RadioButtonGroup {
radioBtn.focus();
radioBtn.checked = true;
radioBtn._checked = true;
radioBtn._setFormValue();
radioBtn.fireEvent("change");
}
}
Expand Down
21 changes: 21 additions & 0 deletions packages/main/src/Select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,17 @@ interface IOption extends UI5Element {
*/
@event("close")
class Select extends UI5Element implements IFormElement {
static formAssociated = true;
formStateValue?: string;
get form() {
return this.#internals.form;
}
formStateRestoreCallback(value: string) {
this.formStateValue = value;
this.options.forEach(option => {
option.selected = option.value === value;
});
}
static i18nBundle: I18nBundle;

/**
Expand Down Expand Up @@ -436,10 +447,12 @@ class Select extends UI5Element implements IFormElement {
_onMenuChange: (e: CustomEvent<SelectMenuChange>) => void;
_attachMenuListeners: (menu: HTMLElement) => void;
_detachMenuListeners: (menu: HTMLElement) => void;
#internals: ElementInternals;

constructor() {
super();

this.#internals = this.attachInternals();
this._syncedOptions = [];
this._selectedIndexBeforeOpen = -1;
this._escapePressed = false;
Expand Down Expand Up @@ -789,6 +802,7 @@ class Select extends UI5Element implements IFormElement {
this.selectOptions[this._selectedIndex].selected = false;

if (this._selectedIndex !== index) {
this.#internals.setFormValue(this.selectOptions[index].value);
this.fireEvent<SelectLiveChangeEventDetail>("live-change", { selectedOption: this.selectOptions[index] });
}

Expand Down Expand Up @@ -1126,6 +1140,13 @@ class Select extends UI5Element implements IFormElement {
}
}

onEnterDOM(): void {
if (this.formStateValue) {
this.formStateRestoreCallback(this.formStateValue);
// TODO remove

Check warning on line 1146 in packages/main/src/Select.ts

View workflow job for this annotation

GitHub Actions / check

Unexpected 'todo' comment: 'TODO remove'

Check warning on line 1146 in packages/main/src/Select.ts

View workflow job for this annotation

GitHub Actions / check (base)

Unexpected 'todo' comment: 'TODO remove'

Check warning on line 1146 in packages/main/src/Select.ts

View workflow job for this annotation

GitHub Actions / check (main:suite-1)

Unexpected 'todo' comment: 'TODO remove'

Check warning on line 1146 in packages/main/src/Select.ts

View workflow job for this annotation

GitHub Actions / check (main:suite-2)

Unexpected 'todo' comment: 'TODO remove'

Check warning on line 1146 in packages/main/src/Select.ts

View workflow job for this annotation

GitHub Actions / check (fiori)

Unexpected 'todo' comment: 'TODO remove'
}
}

get selectedOptionIcon() {
return this.selectedOption && this.selectedOption.icon;
}
Expand Down
Loading

0 comments on commit dcf0a0b

Please sign in to comment.