From b33b45ba80de380015c3b01099a231a58139b821 Mon Sep 17 00:00:00 2001 From: Avior Date: Tue, 27 Aug 2019 11:30:29 +0200 Subject: [PATCH] first commit --- FMInput.ts | 42 ++++++ FormManager.ts | 272 +++++++++++++++++++++++++++++++++++++ Interfaces.ts | 23 ++++ modules/FMDatalistInput.ts | 46 +++++++ modules/FMDateInput.ts | 28 ++++ modules/FMRepeatInput.ts | 73 ++++++++++ package.json | 20 +++ 7 files changed, 504 insertions(+) create mode 100644 FMInput.ts create mode 100644 FormManager.ts create mode 100644 Interfaces.ts create mode 100644 modules/FMDatalistInput.ts create mode 100644 modules/FMDateInput.ts create mode 100644 modules/FMRepeatInput.ts create mode 100644 package.json diff --git a/FMInput.ts b/FMInput.ts new file mode 100644 index 0000000..8ae2aa6 --- /dev/null +++ b/FMInput.ts @@ -0,0 +1,42 @@ +import FormManager from "./FormManager" + +export default class FMInput { + + element: HTMLInputElement + form: FormManager + + constructor(element: HTMLElement, form: FormManager) { + this.element = element as HTMLInputElement + this.form = form + if (this.element.hasAttribute("data-default")) { + this.setValue(this.getDefault(this.element.dataset.default)) + } + } + + setValue(value: any) { + this.element.value = value + this.element.setAttribute("value", value) + } + getValue(): any { + return this.element.value + } + + getDefault(args: string): any { + return args + } + + getName(): string { + return this.element.getAttribute("name") + } + + verify(): boolean { + let val: string = this.getValue() + if(val == "" && this.element.hasAttribute("required")) { + return false + } + if(this.element.dataset.regex !== undefined) { + return new RegExp(this.element.dataset.regex, 'g').test(val as string) + } + return true + } +} diff --git a/FormManager.ts b/FormManager.ts new file mode 100644 index 0000000..3172161 --- /dev/null +++ b/FormManager.ts @@ -0,0 +1,272 @@ +import { InputArrayInterface, FMAssignInterface } from './Interfaces'; +import FMInput from "./FMInput" + +/** + * + * `datalist` upgrade: + * the value submitted won't be the `value` attribute but the `data-value` attribute + * a `data-strict` attribute if set will return undefined if input value is not from data-value else it will return the input value if not foudn in options + * ex: + * ```html + * + * + * + * + * + * + * + * ``` + * **ATTENTION if multiple `option` has the same `value` attribute the `data-value` will be the one of the first one** + * + * + * a `data-ignore` attribute: + * the input with the data-ignore won't send datas to server _still need the `name` attribute_ + * awesome for usage with `data-autoset` + * + * a `data-regex` attribute: + * when the element is getting verified it will be passed through the regex + * ex: + * ```html + * + * + * + * + * + * ``` + * _please note that you still have to verify that server-side_ + * + * + * a `data-default` attribute: + * if the value is not something controlable by html it will be set by here + * depending on the input type different defaults are possibles + * ex: `date` `datetime` `time` if it's set it will be the current time + * ```html + * + * + * + * ``` + * + * + * a `data-autoset` attribute: + * this attribute will change it's value regarding other inputs + * DON'T name any input `x` as it will break the repeat element + * (you SHOULD use `readonly` or `disabled` attribute with it) + * ex: + * ```html + * + * + * ``` + * the test input should always contain `testing-autoset-(the value of input) + * + * + * a `repeat` type: + * container: `.form-manager-repeat` + * template (in container): `.form-manager-template` + * add button (in container): `.form-manager-add-button` + * delete button (in template): `.form-manager-delete-button` + * + * input MUST have data-name and not name attributes + * if the script detect `[x]` it will be replaced by `[${index}]` + * on item deletion all items are re-indexed + * if `data-default` or `data-autoset` contains `{x}` it will be replaced by `${index}` + * + * check if input has attribute `form` and that the value is the current form id + * if so add it to current form + * ```html + *
+ *
+ * + * + * + *
+ * + *
+ *
+ * + *
+ * + *
+ * + *
+ *
+ * + *
+ * + *
+ *
+ * ``` + */ + + + + + +/** + * Manager for Forms + * + * @export + * @class FormManager + */ +export default class FormManager { + + private inputs: InputArrayInterface = {} + + private FMInputs: FMAssignInterface[] = [] + + private _form: HTMLFormElement + public set form(v : HTMLFormElement) {this._form = v} + public get form(): HTMLFormElement {return this._form} + + /** + * Creates an instance of FormManager. + * @param {HTMLFormElement} form the form HTMLElement + * @memberof FormManager + */ + constructor(form: HTMLFormElement) { + this.form = form + + //Prevent default submit action + form.onsubmit = (e) => { + e.preventDefault() + } + + //assign default form field + this.assign({ + input: FMInput + }) + + //Setup for basic items + this.setupInputs() + } + + public assign(inter: FMAssignInterface) { + this.FMInputs.unshift(inter) + } + + public setupInputs() { + this.form.querySelectorAll("[name]:not([data-name])").forEach((element: HTMLElement) => { + let el = this.getInit(element) + this.inputs[el.getName()] = el + }); + } + + public getInit(element: HTMLElement): FMInput { + inputsLoop: for (const input of this.FMInputs) { + if (input.classes != undefined) { + let tmpList: string[] + if (typeof input.classes == "object") tmpList = input.classes + if (typeof input.classes === "string") tmpList = [input.classes] + for (const classe of tmpList) { + if(!element.classList.contains(classe)) continue inputsLoop + } + } + if (input.attributes != undefined) { + let tmpList: string[] + if (typeof input.attributes == "object") tmpList = input.attributes + if (typeof input.attributes === "string") tmpList = [input.attributes] + for (const classe of tmpList) { + if(!element.hasAttribute(classe)) continue inputsLoop + } + } + if (input.type !== undefined) { + if(element.getAttribute("type") !== input.type) continue + } + if (input.tagName !== undefined) { + if (element.nodeName.toLowerCase() !== input.tagName.toLowerCase()) continue + } + return new (input.input)(element, this) + } + } + + /** + * verify all the values + * + * @returns {boolean} if the requirements are correct or not + * @memberof FormManager + */ + public verify(): boolean { + for (const name in this.inputs) { + if (this.inputs.hasOwnProperty(name)) { + const input = this.inputs[name]; + if(!input.verify()) return false + } + } + return true + } + + /** + * submit the form to the `url` + * + * @param {string} url the url + * @param {(this: XMLHttpRequest, ev: ProgressEvent) => any} [callback] callback of event `loadend` + * @param {boolean} [verify=true] is the content verified beforehand (won't be sent if not correct) + * @returns {boolean} return if the content was sent or not + * @memberof FormManager + */ + public submit(url: string, callback?: (this: XMLHttpRequest, ev: ProgressEvent) => any, verify: boolean = true): boolean { + if (verify && !this.verify()) return false + let ajax = new XMLHttpRequest + ajax.open("POST", url, true) + ajax.setRequestHeader("Content-Type", "application/json") + if (callback != undefined) ajax.addEventListener("loadend", callback) + ajax.send(JSON.stringify(this.getJSON())) + return true + } + + /** + * return the JSON `{key: value}` sequence + * + * @memberof FormManager + */ + public getJSON(): any { + const jsonObject:any = {} + for (const name in this.inputs) { + if (this.inputs.hasOwnProperty(name)) { + const input = this.inputs[name]; + jsonObject[name] = input.getValue() + } + } + return jsonObject + } + + public fillFromJSON(json: any) { + for (const key in json) { + if (json.hasOwnProperty(key)) { + const element = json[key]; + if(this.inputs[key] !== undefined) this.inputs[key].setValue(element) + else console.warn(`${key} is not a valid input name`) + } + } + } + + /** + * fill form from uri + * future parameter will be the `type` of source datas (`JSON`, `XML`) + * + * @param {string} uri the URI + * @memberof FormManager + */ + public fillFromURI(uri: string) { + let ajax = new XMLHttpRequest + ajax.open("GET", uri, true) + ajax.addEventListener("loadend", (e) => { + if (ajax.readyState === 4 && ajax.status === 200) { + let json = JSON.parse(ajax.responseText) + this.fillFromJSON(json) + } + }) + ajax.send() + } + + /** + * Clear the fields in the form + * + * @memberof FormManager + */ + public clear() { + this.form.querySelectorAll("[name]").forEach((el: HTMLInputElement) => { + el.value = "" + el.removeAttribute("value") + }) + } +} diff --git a/Interfaces.ts b/Interfaces.ts new file mode 100644 index 0000000..30bdad1 --- /dev/null +++ b/Interfaces.ts @@ -0,0 +1,23 @@ +import FMInput from "./FMInput" + +/** + * this interface is used for fetching and setting `name` to `FMInput` link + * + * @interface InputArrayInterface + */ +export interface InputArrayInterface { + [key:string]: FMInput +} + +/** + * this interface is used when adding a new `FMInput` class to the FormManager + * + * @interface FMAssignInterface + */ +export interface FMAssignInterface { + input: typeof FMInput + classes?: string[] | string + attributes?: string[] | string + type?: string + tagName?: string +} diff --git a/modules/FMDatalistInput.ts b/modules/FMDatalistInput.ts new file mode 100644 index 0000000..5e2a8ff --- /dev/null +++ b/modules/FMDatalistInput.ts @@ -0,0 +1,46 @@ +import { FMAssignInterface } from './../Interfaces'; +import FormManager from "../FormManager" +import FMInput from "../FMInput" + +/** + * the upgraded datalist element + * @priority 2 + * @class FMDatalistInput + * @extends {FMInput} + */ +export default class FMDatalistInput extends FMInput { + + datalist: HTMLDataListElement + isStrict: boolean + + constructor(element: HTMLInputElement, form: FormManager) { + super(element, form) + this.isStrict = this.element.hasAttribute("data-strict") + let id = this.element.getAttribute("list") + let tmpDatalist = document.getElementById(id) + this.datalist = tmpDatalist !== undefined ? tmpDatalist as HTMLDataListElement : undefined + } + + setValue(value: string) { + if (this.datalist) { + let option: HTMLOptionElement = this.datalist.querySelector(`[value="${value}"]`) + if (option || !this.isStrict) { + this.element.value = value + } + } + } + + getValue(): string { + if (this.datalist) { + let option: HTMLOptionElement = this.datalist.querySelector(`[value="${this.element.value}"]`) + if (option) return option.dataset.value + } + return this.isStrict ? undefined : this.element.value + } +} + +export const FMDatalistAssignement: FMAssignInterface = { + input: FMDatalistInput, + attributes: "list", + tagName: "input" +} diff --git a/modules/FMDateInput.ts b/modules/FMDateInput.ts new file mode 100644 index 0000000..b78acaa --- /dev/null +++ b/modules/FMDateInput.ts @@ -0,0 +1,28 @@ +import { FMAssignInterface } from './../Interfaces'; +import FMInput from "../FMInput" + +/** + * + * @class FMDateInput + * @extends {FMInput} + */ +export default class FMDateInput extends FMInput { + + setValue(value: Date) { + this.element.valueAsDate = value + } + + getValue(): Date { + return this.element.valueAsDate + } + + getDefault(args: string): Date { + return new Date + } +} + +export const FMDateInputAssignement: FMAssignInterface = { + input: FMDateInput, + type: "date", + tagName: "input" +} diff --git a/modules/FMRepeatInput.ts b/modules/FMRepeatInput.ts new file mode 100644 index 0000000..eb9711e --- /dev/null +++ b/modules/FMRepeatInput.ts @@ -0,0 +1,73 @@ +import { FMAssignInterface } from './../Interfaces'; +import FormManager from "../FormManager" +import FMInput from "../FMInput" + +/** + * + * @class FMRepeatInput + * @extends {FMInput} + */ +export default class FMRepeatInput extends FMInput { + + constructor(element: HTMLDivElement, form: FormManager) { + super(element, form) + + //fetch Template + let template: HTMLElement = element.querySelector(".fmr-template") + template.style.display = "none" + + //fetch add button + let addBtn = element.querySelector(".fmr-add") + addBtn.addEventListener("click", () => { + let node = element.insertBefore(template.cloneNode(true), addBtn) as HTMLElement + node.classList.remove("fmr-template") + node.classList.add("fmr-element") + node.style.display = "" + let del = node.querySelector(".fmr-del") + del.addEventListener("click", () => { + node.remove() + }) + }) + + } + + loopInputs(): FMInput[][] { + let inputs: FMInput[][] = [] + this.element.querySelectorAll(".fmr-element").forEach((pouet: HTMLElement) => { + let subElement: FMInput[] = [] + pouet.querySelectorAll("[data-input]").forEach((localElement: HTMLElement) => { + subElement.push(this.form.getInit(localElement)) + }); + inputs.push(subElement) + }) + return inputs + } + + setValue(value: any[][]) { + for (const index in value) { + if (value.hasOwnProperty(index)) { + const element = value[index]; + console.log(index,element, value) + } + } + } + + getValue(): any[][] { + let values: any[][] = [] + let elements = this.loopInputs() + for (const line of elements) { + let lineArray: any[] = [] + for (const col of line) { + lineArray.push(col.getValue()) + } + values.push(lineArray) + } + return values + } +} + +export const FMRepeatInputAssignment: FMAssignInterface = { + input: FMRepeatInput, + classes: "fm-repeat", + tagName: "div" +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..500b9f8 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "form-manager", + "version": "1.0.0", + "description": "A powerfull Form Manager", + "repository": { + "type": "git", + "url": "https://git.delta-wings.net/dzeio/FormManager" + }, + "bugs": { + "url": "https://git.delta-wings.net/dzeio/FormManager/issues", + "email": "contact@delta-wings.net" + }, + "author": { + "name": "Aviortheking", + "email": "florian.bouillon@delta-wings.net", + "url": "https://dze.io" + }, + "license": "MIT", + "private": false +}