Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui5-switch): add required property #7324

Merged
merged 11 commits into from
Jul 26, 2023
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 })
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
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" : ""; // eslint-disable-line
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
});
} 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
23 changes: 23 additions & 0 deletions packages/main/test/pages/Switch.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ <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>Custom Switch</h3>
<div class="switch2auto">
<ui5-switch text-on="Accept" text-off="Reject" class="switch4auto"></ui5-switch>
Expand Down Expand Up @@ -83,6 +97,15 @@ <h3>sap_horizon</h3>
<script>
var counter = 0;

submitBtn.addEventListener("click", function(e) {
if (myForm.checkValidity()) {
e.preventDefault();
alert("Form submitted successfully!");
} else {
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
// Default behavior
}
});

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;
}
5 changes: 5 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,9 @@ 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");
})

});
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,
hinzzx marked this conversation as resolved.
Show resolved Hide resolved
name: "mySwitch",
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>`
}
];

Loading