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

Auro Form PR #1 ( #215

Open
wants to merge 2 commits into
base: dhook/auro-form
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/datepicker/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
<script src="https://cdn.jsdelivr.net/npm/@aurodesignsystem/auro-button@latest/dist/auro-button__bundled.js" type="module"></script>

<script type="module" data-demo-script="true">
import { initExamples } from "./index.min.js";
import { initExamples } from "./index.js";

initExamples();
</script>
Expand Down
2 changes: 2 additions & 0 deletions components/form/demo/registerDemoDeps.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {AuroInput} from "@auro-formkit/auro-input";
import {AuroDatePicker} from "@auro-formkit/auro-datepicker";

AuroInput.register();
AuroDatePicker.register();
29 changes: 27 additions & 2 deletions components/form/demo/working.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,23 @@
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/design-tokens@latest/dist/tokens/CSSCustomProperties.css">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/webcorestylesheets@latest/dist/demoWrapper.css" />
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@aurodesignsystem/webcorestylesheets@latest/dist/elementDemoStyles.css" />
<script src="https://cdn.jsdelivr.net/npm/@aurodesignsystem/[email protected]/dist/auro-button__bundled.js" type="module"></script>
</head>
<body class="auro-markdown">
<main>
<style>
.submitBlock {
margin-top: 1rem;
display: flex;
justify-content: flex-end;
gap: 1rem;
}

.datepickerBlock {
margin-top: 1rem;
}
</style>

<h2>auro-form testing</h2>
<auro-form>
<auro-input id="first-name" name="firstName" required>
Expand All @@ -46,11 +60,22 @@ <h2>auro-form testing</h2>
</div>
</div>

<button type="submit">Submit</button>
<div class="datepickerBlock">
<h4>Pick a cool date</h4>
<auro-datepicker id="date-example" name="dateExample" required>
<span slot="fromLabel">Choose a date</span>
<span slot="mobileDateLabel">Choose a date</span>
</auro-datepicker>
</div>

<div class="submitBlock">
<auro-button type="reset">Reset</auro-button>
<auro-button type="submit">Submit</auro-button>
</div>
</auro-form>
</main>

<script type="module" data-demo-script="true" src="./index.min.js"></script>
<script type="module" data-demo-script="true" src="./index.js"></script>
<script type="module" data-demo-script="true" src="./registerDemoDeps.js"></script>
<!--<script type="module" data-demo-script="true" src="~@auro-formkit/auro-input/dist/index.min.js"></script>-->

Expand Down
10 changes: 6 additions & 4 deletions components/form/docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ The auro-form element provides users a way to ... (it would be great if you fill

## Methods

| Method | Type |
|---------------------|----------------------------|
| `getSubmitFunction` | `(): (event: any) => void` |
| `onSlotChange` | `(event: any): void` |
| Method | Type | Description |
|---------------------|------------------------------|--------------------------------------------------|
| `getSubmitFunction` | `(): (event: any) => void` | |
DukeFerdinand marked this conversation as resolved.
Show resolved Hide resolved
| `isFormElement` | `(tagName: string): boolean` | Check if the tag name is a form element.<br /><br />**tagName**: The tag name to check. |
| `isSubmitElement` | `(tagName: string): boolean` | Check if the tag name is a submit element.<br /><br />**tagName**: The tag name to check. |
| `onSlotChange` | `(event: any): void` | |
DukeFerdinand marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions components/form/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"@auro-formkit/build-tools": "*",
"@auro-formkit/config": "*",
"@auro-formkit/typescript": "*",
"@auro-formkit/auro-datepicker": "*",
"@auro-formkit/auro-input": "*",
"@aurodesignsystem/design-tokens": "^4.12.1",
"@aurodesignsystem/webcorestylesheets": "^5.1.2",
"@rollup/plugin-node-resolve": "^15.3.0",
Expand Down
142 changes: 109 additions & 33 deletions components/form/src/auro-form.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable no-underscore-dangle */

// Copyright (c) 2024 Alaska Airlines. All right reserved. Licensed under the Apache-2.0 license
// See LICENSE in the project root for license information.

Expand Down Expand Up @@ -38,14 +40,17 @@ import AuroLibraryRuntimeUtils from '@aurodesignsystem/auro-library/scripts/util

// build the component class
export class AuroForm extends LitElement {
static get properties() {
return {
_formState: { attribute: false },
};
}

constructor() {
super();

/** @type {FormState} */
this.formState = {};

/** @type {HTMLInputElement[]} */
this.formElements = [];
this._formState = {};
}

// Note: button is NOT considered a form element in this context
Expand All @@ -54,10 +59,36 @@ export class AuroForm extends LitElement {
return [
'auro-input',
'auro-select',
'auro-datepicker',
'auro-checkbox-group',
];
}

/**
* Check if the tag name is a form element.
* @param {string} tagName - The tag name to check.
* @returns {boolean}
*/
isFormElement(tagName) {
return AuroForm.formElementTags.includes(tagName.toLowerCase());
}

static get submitElementTags() {
return [
'button',
'auro-button',
];
}

/**
* Check if the tag name is a submit element.
* @param {string} tagName - The tag name to check.
* @returns {boolean}
*/
isSubmitElement(tagName) {
return AuroForm.submitElementTags.includes(tagName.toLowerCase());
}

static get styles() {
return [styleCss];
}
Expand All @@ -72,20 +103,40 @@ export class AuroForm extends LitElement {
* @returns {Record<string, string | number | boolean | string[] | null>} The form value.
*/
get value() {
return Object.keys(this.formState).reduce((acc, key) => {
acc[key] = this.formState[key].value;
return Object.keys(this._formState).reduce((acc, key) => {
acc[key] = this._formState[key].value;
return acc;
}, {});
}

get validity() {
// go through validity states and return the first invalid state (if any)
const invalidKey = Object.keys(this._formState).
find((key) => {
const formKey = this._formState[key];
return formKey.validity !== null && formKey.validity !== 'valid' && formKey.required;
});

return invalidKey ? 'invalid' : 'valid';
}

// Below is not implemented yet
// get isInitialState() {
// // return true if all keys are null
// return true;
// }

getSubmitFunction() {
// We return an arrow function here to ensure that the `this` context points at this same AuroForm context.
// Otherwise, submission tries to read `this.value` on the button element.
return (event) => {
event.preventDefault();

// eslint-disable-next-line no-console
console.log(`Form submitted -> ${JSON.stringify(this.value)}`);
// eslint-disable-next-line no-console,no-magic-numbers
// console.log(`Form internal state (not for public use) -> ${JSON.stringify(this._formState, null, 4)}`);

// eslint-disable-next-line no-console,no-magic-numbers
console.log(`Form submitted -> ${JSON.stringify(this.value, null, 4)}`);
DukeFerdinand marked this conversation as resolved.
Show resolved Hide resolved
};
}

Expand All @@ -112,52 +163,77 @@ export class AuroForm extends LitElement {
/** @type {HTMLInputElement} */
const eventTarget = event.target;
if (AuroForm.formElementTags.includes(eventTarget.tagName.toLowerCase())) {
this.formState[eventTarget.name].value = eventTarget.value;
if (!this._formState[eventTarget.getAttribute("name")]) {
this._formState[eventTarget.getAttribute("name")] = {
value: eventTarget.value,
validity: eventTarget.getAttribute("validity"),
required: eventTarget.hasAttribute('required'),
};
}

this._formState[eventTarget.getAttribute("name")].value = eventTarget.value;
}
});

slot.addEventListener('auroFormElement-validated', (event) => {
const oldValue = this._formState;

// eslint-disable-next-line no-console
console.log(`${event.target.getAttribute("name")} -> ${event.detail.validity}`);
this._formState[event.target.getAttribute("name")].validity = event.detail.validity;
this.requestUpdate('formState', oldValue);
});
}

onSlotChange(event) {
const slot = event.target;
const elements = slot.assignedElements();

const formElements = [];
elements.forEach((element) => {
// If one of the root elements is a form element, just push it to the formElements array
if (AuroForm.formElementTags.includes(element.tagName.toLowerCase())) {
formElements.push(element);
} else {
// If this root element is not a form element, query for form elements within it (note: NOT shadowRoot)
element.querySelectorAll(AuroForm.formElementTags.join(',')).forEach((formElement) => {
formElements.push(formElement);
});
}
});
// Clear current form state - maybe we should call a reset function instead?
this._formState = {};

for (const element of formElements) {
if (element.tagName.toLowerCase() !== 'button') {
this.formState[element.getAttribute('name')] = {
const tryAddToElements = (element) => {
// Form elements get added to the form state
if (this.isFormElement(element.tagName)) {
this._formState[element.getAttribute('name')] = {
value: element.getAttribute('value'),
// THIS IS PROBABLY NOT DONE CORRECTLY :)
validity: element.getAttribute('validity'),
// THIS IS NOT DONE CORRECTLY :)
required: element.getAttribute('required'),
required: element.hasAttribute('required'),
};
} else if (element.tagName.toLowerCase() === 'button' && element.type === 'submit') {
// eslint-disable-next-line no-console
console.log('Button element detected');

// Remove in case this button was already registered!
} else if (this.isSubmitElement(element.tagName) && element.getAttribute('type') === 'submit') {
element.removeEventListener('click', this.getSubmitFunction());
element.addEventListener('click', this.getSubmitFunction());
} else {
throw new Error(`Element ${element.tagName} is not a form element or a submit element`);
}
}
};

elements.forEach((element) => {
try {
tryAddToElements(element);
} catch (error) {
if (error instanceof Error && error.message.includes('not a form element')) {
// Not a form element, so we need to check if it has form elements inside (note: not shadow DOM)
const query = AuroForm.formElementTags.concat(AuroForm.submitElementTags.map((tag) => `${tag}[type=submit]`)).join(',');
element.querySelectorAll(query).forEach((formElement) => {
try {
tryAddToElements(formElement);
// eslint-disable-next-line no-unused-vars
} catch (_error) {
// Do nothing - we don't care about this error
}
});
}
}
});
}

// function that renders the HTML and CSS into the scope of the component
render() {
return html`
<form>
<p>Value: ${JSON.stringify(this.value)}</p>
<p>Validity: ${this.validity}</p>
<h3>Auro form example</h3>
<slot @slotchange="${this.onSlotChange}"></slot>
</form>
Expand Down
1 change: 1 addition & 0 deletions components/form/src/styles/style.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
width: 100%;
padding: 1rem;
border: 1px solid #2a2a2a;
border-radius: 1rem;
}
}
Loading