import { InputArrayInterface, FMAssignInterface } from './Interfaces'; import FMInput from "./FMInput" /*! * FormManager by DZEIO's team * MIT Licensed * https://dze.io */ /** * * `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` have the same `value` attribute the submitted value will be 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: `.fm-repeat` with a name attribute * template (in container): `.fmr-template` * element (DO NOT PLACE IT YOURSELF) (in container): `.fmr-element` * add button (in container): `.fmr-add` * delete button (in template): `.fmr-del` * * input *MUST* have `data-name` and *NOT* `name` attributes * if `data-default` or `data-autoset` contains `{x}` it will be replaced by the index * * ```html *
*
* * * *
* *
*
*
* * *
* * *
* ``` * * * TODO: * check if input has attribute `form` and that the value is the current form id * if so add it to current form */ /** * Manager for Forms * * @export * @class FormManager */ export default class FormManager { /** * List of inputs * * @private * @type {InputArrayInterface} * @memberof FormManager */ private inputs: InputArrayInterface = {} /** * List of interfaces * * @private * @type {FMAssignInterface[]} * @memberof FormManager */ private FMInputs: FMAssignInterface[] = [] private _form: HTMLFormElement /** * The Form Element of the FM * * @private * @type {HTMLFormElement} * @memberof FormManager */ 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 interface this.assign({ input: FMInput }) //Setup the system for basic inputs this.setupInputs() } /** * Add the the FM an Input Manager * * @param {FMAssignInterface} inter the interface used * @memberof FormManager */ public assign(inter: FMAssignInterface) { this.FMInputs.unshift(inter) } /** * Setup the differents inputs to be used with their interfaces * * @memberof FormManager */ public setupInputs() { this.form.querySelectorAll("[name]:not([data-name])").forEach((element: HTMLElement) => { let el = this.getInit(element) this.inputs[el.getName()] = el }); } /** * Retrieve a specific FMInput for a Specific Input * * @param {HTMLElement} element * @returns {FMInput} * @memberof FormManager */ 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 the inputs for errors * * If it return false you can use `fm.lastErroredInput` to get the `FMInput` that errored * * @returns {boolean} if the requirements are correct or not (it will stop checking at the first issue) * @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` in a JSON format * You can plug an XMLHttpRequest loadend event in the `callback` to recover the results * * @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) => void, 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 } /** * Fill the form from JSON * * Hint: _to see what the json is made of use `fm.getJSON`_ * * @param {*} json the JSON * @memberof FormManager */ 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 an `uri` * * the format MUST be `JSON` * * @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) => { for (const name in this.inputs) { if (this.inputs.hasOwnProperty(name)) { const input = this.inputs[name]; input.setToDefault() } } }) } } /** * TODO: FMFileInput * have a data-endpoint with an URI * on file set -> show button to upload * on file change -> show button "delete and upload" * on upload -> upload and create hidden field with the result ID * on delete -> show notif about it * if pic -> show preview * * * * * * work with an endpoint like this: * retrieve pic: /enpoint?get=pic-id * return {uri:"/static/pic-name.jpg"} if it exist * return {error:true,msg:"picture don't exist"} is pic dont exist * upload pic: /enpoint?upload * return {uploaded:true,id:2} * delete pic: /endpoint?del=pic-id * return {deleted=true} if deleted * return {error=true,msg="pic can't be deleted"} if error */