Skip to content

Commit

Permalink
feat(ui5-switch): add required property (#7324)
Browse files Browse the repository at this point in the history
Introducing the `required` property for the Switch control.
We now also enable the Form Support feature within the control, which means now the Switch component, could be easily used and integrated within forms as well.
  • Loading branch information
hinzzx authored Jul 26, 2023
1 parent 5a7f41f commit 0a01918
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 4 deletions.
2 changes: 2 additions & 0 deletions packages/main/src/Switch.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
aria-label="{{ariaLabelText}}"
aria-checked="{{checked}}"
aria-disabled="{{effectiveAriaDisabled}}"
aria-required="{{required}}"
@click="{{_onclick}}"
@keyup="{{_onkeyup}}"
@keydown="{{_onkeydown}}"
Expand Down Expand Up @@ -47,4 +48,5 @@
</div>

<input type='checkbox' ?checked="{{checked}}" class="ui5-switch-input" data-sap-no-tab-ref/>
<slot name="formSupport"></slot>
</div>
72 changes: 69 additions & 3 deletions packages/main/src/Switch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js";
import property from "@ui5/webcomponents-base/dist/decorators/property.js";
import event from "@ui5/webcomponents-base/dist/decorators/event.js";
import slot from "@ui5/webcomponents-base/dist/decorators/slot.js";
import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";

import { isSpace, isEnter } from "@ui5/webcomponents-base/dist/Keys.js";
Expand All @@ -13,6 +14,9 @@ import { getEffectiveAriaLabelText } from "@ui5/webcomponents-base/dist/util/Ari
import "@ui5/webcomponents-icons/dist/accept.js";
import "@ui5/webcomponents-icons/dist/decline.js";
import "@ui5/webcomponents-icons/dist/less.js";
import { getFeature } from "@ui5/webcomponents-base/dist/FeaturesRegistry.js";
import type FormSupport from "./features/InputElementsFormSupport.js";
import type { IFormElement } from "./features/InputElementsFormSupport.js";
import Icon from "./Icon.js";
import SwitchDesign from "./types/SwitchDesign.js";

Expand Down Expand Up @@ -77,7 +81,7 @@ import switchCss from "./generated/themes/Switch.css.js";
* @event sap.ui.webc.main.Switch#change
*/
@event("change")
class Switch extends UI5Element {
class Switch extends UI5Element implements IFormElement {
/**
* Defines the component design.
* <br><br>
Expand Down Expand Up @@ -188,8 +192,70 @@ class Switch extends UI5Element {
@property()
tooltip!: string;

/**
* Defines whether the component is required.
*
* @type {boolean}
* @name sap.ui.webc.main.Switch.prototype.required
* @defaultvalue false
* @public
* @since 1.16.0
*/
@property({ type: Boolean })
required!: boolean;

/**
* Determines the name with which the component will be submitted in an HTML form.
*
* <br><br>
* <b>Important:</b> For the <code>name</code> property to have effect, you must add the following import to your project:
* <code>import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";</code>
*
* <br><br>
* <b>Note:</b> When set, a native <code>input</code> HTML element
* will be created inside the component so that it can be submitted as
* part of an HTML form. Do not use this property unless you need to submit a form.
*
* @type {string}
* @name sap.ui.webc.main.Switch.prototype.name
* @defaultvalue ""
* @public
* @since 1.16.0
*/
@property()
name!: string;

/**
* The slot is used to render native <code>input</code> HTML element within Light DOM to enable form submit, when <code>Switch</code> is a part of HTML form.
*
* @type {HTMLElement[]}
* @slot
* @private
* @since 1.16.0
*/
@slot()
formSupport!: Array<HTMLElement>;

static i18nBundle: I18nBundle;

onBeforeRendering() {
this._enableFormSupport();
}

_enableFormSupport() {
const formSupport = getFeature<typeof FormSupport>("FormSupport");
if (formSupport) {
formSupport.syncNativeHiddenInput(this, (element: IFormElement, nativeInput: HTMLInputElement) => {
const switchComponent = (element as Switch);
nativeInput.checked = !!switchComponent.checked;
nativeInput.disabled = !!switchComponent.disabled;
nativeInput.value = switchComponent.checked ? "on" : "";
});
} else if (this.name) {
console.warn(`In order for the "name" property to have effect, you should also: import "@ui5/webcomponents/dist/features/InputElementsFormSupport.js";`); // eslint-disable-line
}
}

get sapNextIcon() {
return this.checked ? "accept" : "less";
}
Expand Down Expand Up @@ -219,9 +285,9 @@ class Switch extends UI5Element {
this.checked = !this.checked;
const changePrevented = !this.fireEvent("change", null, true);
// Angular two way data binding;
const valueChagnePrevented = !this.fireEvent("value-changed", null, true);
const valueChangePrevented = !this.fireEvent("value-changed", null, true);

if (changePrevented || valueChagnePrevented) {
if (changePrevented || valueChangePrevented) {
this.checked = !this.checked;
}
}
Expand Down
29 changes: 29 additions & 0 deletions packages/main/test/pages/Switch.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,28 @@ <h3>Graphical Switch</h3>
<ui5-switch design="Graphical" checked text-on="Yes" text-off="No" disabled></ui5-switch>
</div>

<div class="switch2auto">
<form id="myForm" class="switch-form">
<h3 style="margin: 0 0 1rem 0">Switch in Registration form example</h3>
<div style="display: flex; flex-direction: column;">
<ui5-input required type="Email" placeholder="Email" value="[email protected]"></ui5-input>
<ui5-input required type="Password" placeholder="Password" value="[email protected]"></ui5-input>
</div>
<p style="margin: 1rem 0 0 0">Please accept the terms and conditions, in order to proceed.</p>
<ui5-switch id="requiredSwitch" style="margin: 0" name="switch" text-on="Yes" text-off="No" required></ui5-switch>
<br />
<ui5-button type="Submit" id="submitBtn">Submit Form</ui5-button>
</form>
</div>

<h3>Switch in form test</h3>
<form id="switchForm">
<ui5-switch id="switch1" checked></ui5-switch>
<ui5-switch id="requiredTestSwitch" required></ui5-switch>
<br><br>
<ui5-button id="switchSubmit" type="Submit">Submit</ui5-button>
</form>

<h3>Custom Switch</h3>
<div class="switch2auto">
<ui5-switch text-on="Accept" text-off="Reject" class="switch4auto"></ui5-switch>
Expand Down Expand Up @@ -83,6 +105,13 @@ <h3>sap_horizon</h3>
<script>
var counter = 0;

submitBtn.addEventListener("click", function(e) {
if (myForm.checkValidity()) {
e.preventDefault();
alert("Form submitted successfully!");
}
});

sw.addEventListener("ui5-change", function(e) {
lbl.innerHTML = e.target.checked + " : " + (++counter);
field.value = counter;
Expand Down
6 changes: 6 additions & 0 deletions packages/main/test/pages/styles/Switch.css
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,9 @@ ui5-switch {
width: 200px;
height: 200px;
}

.switch-form {
border: 1px solid var(--sapList_BorderColor);
border-radius: var(--sapElement_BorderCornerRadius);
padding: 1rem;
}
25 changes: 25 additions & 0 deletions packages/main/test/specs/Switch.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,29 @@ describe("Switch general interaction", async () => {
assert.strictEqual(await switchPrevented.getProperty("checked"), currentChecked, "The switch is not checked");
});

it("The 'required' attribute is propagated properly", async () => {
assert.strictEqual(await browser.$("#requiredSwitch").shadow$(".ui5-switch-root").getAttribute("aria-required"), "true", "The required attribute is set correctly");
assert.strictEqual(await browser.$("#switchprevented").shadow$(".ui5-switch-root").getAttribute("aria-required"), "false", "The required attribute is set correctly");
})

it("Form should submit only when the 'required' switch is checked", async () => {
const requiredSwitch = await browser.$("#requiredTestSwitch");

let formValidity = await browser.execute(() => {
const form = document.getElementById("switchForm");
return form.checkValidity();
});

assert.strictEqual(formValidity, false, "The form could be submitted successfuly, when the 'required' switch is not checked");

requiredSwitch.click();
await browser.pause(1000);

formValidity = await browser.execute(() => {
const form = document.getElementById("switchForm");
return form.checkValidity();
});

assert.strictEqual(formValidity, true, "The form could be submitted successfuly, because the 'required' switch is checked");
});
});
43 changes: 42 additions & 1 deletion packages/playground/_stories/main/Switch/Switch.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const Template: UI5StoryArgs<Switch, StoryArgsSlots> = (args) => html`<ui5-switc
design="${ifDefined(args.design)}"
?checked="${ifDefined(args.checked)}"
?disabled="${ifDefined(args.disabled)}"
?required="${ifDefined(args.required)}"
?name="${ifDefined(args.name)}"
text-on="${ifDefined(args.textOn)}"
text-off="${ifDefined(args.textOff)}"
accessible-name="${ifDefined(args.accessibleName)}"
Expand Down Expand Up @@ -60,4 +62,43 @@ export const Design = Template.bind({});
Design.args = {
design: SwitchDesign.Graphical,
accessibleName: "graphical",
};
};

export const RequiredInForm = Template.bind({});
RequiredInForm.args = {
required: true,
name: "termsAndConditions",
disabled: false,
design: SwitchDesign.Textual,
textOn: "Yes",
textOff: "No",
};
RequiredInForm.decorators = [
(story) => {
return html`
<style>
.switch-form {
max-width: fit-content;
border: 1px solid var(--sapList_BorderColor);
border-radius: 0.5rem;
padding: 1rem;
}
</style>
<form id="myForm" class="switch-form">
<h3 style="margin: 0 0 1rem 0">Switch in Registration form sample</h3>
<div style="display: flex; flex-direction: column;">
<ui5-input required type="Email" placeholder="Email" value="[email protected]"></ui5-input>
<ui5-input required type="Password" placeholder="Password" value="[email protected]"></ui5-input>
</div>
<div style="display: flex; flex-direction: column; justify-content: center;">
<ui5-label for="mySwitch" style="margin: 1rem 0 0 0">Please accept the terms and conditions, in order to proceed</ui5-label>
<div style="width: fit-content">
${story()}
</div>
</div>
<br>
<ui5-button type="Submit">Submit Form</ui5-button>
</form>`
}
];

0 comments on commit 0a01918

Please sign in to comment.