Skip to content

Commit

Permalink
Add Form component & bind direct access (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
nizniz187 committed Sep 5, 2021
1 parent d75226b commit 5436393
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 16 deletions.
27 changes: 15 additions & 12 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,18 +53,21 @@
<div><a target="_blank" href="https://github.com/Padax/w-components">Donate</a></div>
</w-nav-part>
</w-nav>
<!-- CheckBox -->
<w-heading level="3" underlined>CheckBox</w-heading>
<w-checkbox>Un-Checked</w-checkbox>
<w-checkbox checked>Checked</w-checkbox>
<w-checkbox disabled>Un-Checked Disabled</w-checkbox>
<w-checkbox checked disabled>Checked Disabled</w-checkbox>
<!-- Radio -->
<w-heading level="3" underlined="true">Radio</w-heading>
<w-radio>Un-Checked</w-radio>
<w-radio checked>Checked</w-radio>
<w-radio disabled>Un-Checked Disabled</w-radio>
<w-radio checked disabled>Checked Disabled</w-radio>
<w-form name="form" action="test.html" method="get">
<!-- CheckBox -->
<w-heading level="3" underlined>CheckBox</w-heading>
<w-checkbox name="checkbox" value="1">Un-Checked</w-checkbox>
<w-checkbox name="checkbox" checked value="2">Checked</w-checkbox>
<w-checkbox name="checkbox" disabled value="3">Un-Checked Disabled</w-checkbox>
<w-checkbox name="checkbox" checked disabled>Checked Disabled</w-checkbox>
<input type="checkbox" name="native" value="5" /> Native Checkbox
<!-- Radio -->
<w-heading level="3" underlined="true">Radio</w-heading>
<w-radio>Un-Checked</w-radio>
<w-radio checked>Checked</w-radio>
<w-radio disabled>Un-Checked Disabled</w-radio>
<w-radio checked disabled>Checked Disabled</w-radio>
</w-form>
<w-spa-page path="/heading" id="heading-page">
<template>
<w-display-heading level="1" underlined>LONG LIVE W-COMPONENTS!</w-display-heading>
Expand Down
11 changes: 8 additions & 3 deletions wc/WComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class WComponent extends HTMLElement{
componentWillRender() {}
componentDidRender() {}


/**
* Set all properties from all observed attributes.
*/
Expand Down Expand Up @@ -67,15 +66,21 @@ class WComponent extends HTMLElement{
}
if(typeof this.constructor.attributes[name].parser !== 'function') {
// Default parser: return value as is.
return value => value;
return value => {
if(typeof value === 'string') {
return value;
} else {
return this.constructor.attributes[name].defaultValue;
}
};
}
return this.constructor.attributes[name].parser;
}
getDefaultValueByName(name) {
if(typeof name !== 'string') {
return undefined;
}
return this.constructor.attributes[name].defatulValue;
return this.constructor.attributes[name].defaultValue;
}
setStylesheet(stylesheet){
DOM.create('style', { props: { textContent: stylesheet } }, this.shadowRoot);
Expand Down
116 changes: 116 additions & 0 deletions wc/components/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import WComponent, { DOM, AttributeParser } from "../WComponent.js";

const stylesheet=`
`;

class Form extends WComponent{
static attributes = {
name: { name: 'name' },
action: { name: 'action' },
method: { name: 'method' }
};
static get observedAttributes() {
return this.getObservedAttributes(this.attributes);
}

constructor() {
super();
this.bindFormAccess();
this.bindFormElementAccess();
this.bindEvents();
}

attributeChangedCallback(name, oldValue, newValue) {
const form = this.shadowRoot.querySelector('form');
const value = this.getAttributeParserByName(name)(newValue, this.constructor.attributes[name]);
form[name] = value;

if(name === this.constructor.attributes.name.name) {
this.bindFormAccess(oldValue);
}
}
bindEvents() {
// Re-bind form access on slot change
const slot = this.shadowRoot.querySelector('slot');
slot.addEventListener('slotchange', () => this.bindFormAccess());
}
/**
* Bind direct form access from document by name.
*/
bindFormAccess(oldValue) {
if(typeof oldValue === 'string') { // Clean old assignment
document[oldValue] = undefined;
}
if(typeof this.name === 'string') {
document[this.name] = this;
}
}
/**
* Dynamically add getters & setters for form elements to get direct access by name.
*/
bindFormElementAccess() {
// Bind direct form elemnt access from form by name
this.querySelectorAll(FORM_ELEMENT_TYPES).forEach(elem => {
if(typeof elem.name === 'string'
&& !Object.getOwnPropertyDescriptor(this, elem.name)) {
// Dynamically add getter if there is none defined.
Object.defineProperty(this, elem.name, {
get: () => {
const list = Array.from(this.querySelectorAll(`[name='${elem.name}']`));
if(list.length > 1) {
const value = list.filter(item => item.checked).map(item => item.value);
list.value = value.length === 1 ? value[0] : value;
return list;
}
return list.length === 0 ? undefined : list[0];
}
});
}
});
}
render() {
const attrs = {
name: this.name,
action: this.action,
method: this.method
};
const form = DOM.create('form', { attrs }, this.shadowRoot);

DOM.create('slot', {}, form);
}

/**
* Get form data.
* @returns {FormData}
*/
get data() {
const formData = new FormData();
const elements = Array.from(this.querySelectorAll(FORM_ELEMENT_TYPES));
elements.forEach(elem => {
if(typeof elem.name === 'string' && elem.value !== null && elem.value !== undefined) {
if((elem.type === 'checkbox' || elem.type === 'radio') && !elem.checked) {
// Exclude unchecked checkbox & radio
return;
}
const existingValue = formData.get(elem.name);
if(existingValue === null) {
formData.set(elem.name, elem.value);
} else if(Array.isArray(existingValue)) {
existingValue.push(elem.value);
} else {
formData.set(elem.name, [existingValue, elem.value]);
}
}
});
return formData;
}

}
Form.prototype.stylesheet=stylesheet;

const FORM_ELEMENT_TYPES = [
'input', 'select', 'textarea',
'w-checkbox', 'w-radio'
];

export default Form;
2 changes: 1 addition & 1 deletion wc/components/checkable/Checkable.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Checkable extends WComponent{
value, attr.defaultValue
)
},
value: { name: 'value' },
value: { name: 'value', defaultValue: 'on' },
name: { name: 'name' }
};
static get observedAttributes() {
Expand Down
2 changes: 2 additions & 0 deletions wc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import ListItem from "./components/ListItem.js";
import Code from "./components/Code.js";
import CheckBox from "./components/checkable/Checkbox.js";
import Radio from "./components/checkable/Radio.js";
import Form from "./components/Form.js";
import SPALink from "./components/spa/SPALink.js";
import SPAPage from "./components/spa/SPAPage.js";

Expand All @@ -33,6 +34,7 @@ const wc={
defineCustomElement(prefix, 'code', Code);
defineCustomElement(prefix, 'checkbox', CheckBox);
defineCustomElement(prefix, 'radio', Radio);
defineCustomElement(prefix, 'form', Form);
defineCustomElement(prefix, 'spa-link', SPALink);
defineCustomElement(prefix, 'spa-page', SPAPage);
},
Expand Down

0 comments on commit 5436393

Please sign in to comment.