FormManager/src/FormManager.ts

333 lines
8.7 KiB
TypeScript

import AttributesManager from './AttributesManager';
import InputIdentity from './modules/Interfaces/InputIdentity';
import DefaultInput from './modules/DefaultInput';
import InputAbstract from './modules/InputAbstract';
import InputArray from "./modules/Interfaces/InputArray";
import AttributeListeners from './attributes/AttributeListeners';
/*!
* FormManager by DZEIO's team
* MIT Licensed
* https://dze.io
*/
/**
* Manager for Forms
*
* @export
* @class FormManager
*/
export default class FormManager {
/**
* List of inputs
*
* @private
* @type {InputArray}
* @memberof FormManager
*/
public inputs: InputArray = {}
/**
* List of interfaces
*
* @private
* @type {InputIdentity[]}
* @memberof FormManager
*/
private FMInputs: InputIdentity[] = []
/**
* The last verified `FMInput` that returned an error
*
* @type {FMInput}
* @memberof FormManager
*/
public lastErroredInput: InputAbstract|undefined
/**
* The Form Element of the FM
*
* @type {HTMLFormElement}
* @memberof FormManager
*/
public form: HTMLFormElement
public attributeManager: AttributesManager
/**
* Creates an instance of FormManager.
* @param {HTMLFormElement} form the form HTMLElement
* @memberof FormManager
*/
constructor(form: HTMLFormElement) {
this.form = form
this.attributeManager = new AttributesManager(this)
//Prevent default submit action
form.onsubmit = (e) => {
e.preventDefault()
}
//assign default form interface
this.assign(DefaultInput)
//Setup the system for basic inputs
this.setupInputs()
}
/**
* Add to the Manager an Input
*
* @param {InputIdentity[]} inter the interface used
* @memberof FormManager
*/
public assign(...inter: (typeof InputAbstract[])) {
for (const input of inter) {
this.FMInputs.unshift(input.identity)
}
}
/**
* Setup the differents inputs to be used with their interfaces
*
* @memberof FormManager
*/
public setupInputs() {
this.inputs = {};
(this.form.querySelectorAll("[name]:not([data-name])") as NodeListOf<HTMLElement>).forEach((element: HTMLElement) => {
let el = this.getInit(element)
if (el) this.inputs[el.getName()] = el
});
this.attributeManager.trigger(AttributeListeners.FORM_INIT)
}
/**
* Retrieve a specific FMInput for a Specific Input
*
* @param {HTMLElement} element
* @returns {FMInput}
* @memberof FormManager
*/
public getInit(element: HTMLElement): InputAbstract|void {
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];
const res = this.attributeManager.triggerElement(AttributeListeners.VERIFY, input) as boolean
if(!input.verify() || !res) {
console.log(input.verify(), res)
this.lastErroredInput = input
return false
}
}
}
this.lastErroredInput = undefined
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 toSend = this.getJSON()
let event = this.attributeManager.trigger(AttributeListeners.FORM_SUBMIT, toSend)
if (typeof event !== "boolean" && event) {
toSend = event
}
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(toSend))
return true
}
/**
* Return the JSON `{key: value}` sequence
*
* @memberof FormManager
*/
public getJSON(): {[key: string]: any} {
const jsonObject: any = {}
for (const name in this.inputs) {
if (this.inputs.hasOwnProperty(name)) {
const input = this.inputs[name];
const val = input.getValue()
if (val != undefined) jsonObject[name] = val
}
}
return jsonObject
}
/**
* Fill the form from JSON
*
* Hint: _to see what the json look like, 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`)
}
}
this.attributeManager.trigger(AttributeListeners.FORM_FILL)
}
/**
* fill form from an `uri`
*
* the format MUST be `JSON`
*
* @param {string} uri the URI
* @memberof FormManager
*/
public fillFromURI(uri: string, callback?: () => void) {
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)
if (callback != undefined) callback()
}
})
ajax.send()
}
public mode = FMMode.EditMode;
public setMode(mode: FMMode) {
this.mode = mode
if (mode == FMMode.ViewMode) {
for (const name in this.inputs) {
if (this.inputs.hasOwnProperty(name)) {
const input = this.inputs[name];
input.element.setAttribute("disabled", "")
}
}
}
if (mode == FMMode.EditMode) {
for (const name in this.inputs) {
if (this.inputs.hasOwnProperty(name)) {
const input = this.inputs[name];
input.element.removeAttribute("disabled")
}
}
}
this.attributeManager.trigger(AttributeListeners.MODE_SWITCH, mode)
}
public setModeForInput(mode: FMMode, inputName: string) {
if (mode == FMMode.ViewMode) {
if (this.inputs[inputName]) {
this.inputs[inputName].element.setAttribute("disabled", "")
}
return
}
if (mode == FMMode.EditMode) {
if (this.inputs[inputName]) {
this.inputs[inputName].element.removeAttribute("disabled")
}
}
}
/**
* Clear the fields in the form
*
* @memberof FormManager
*/
public clear() {
if (this.attributeManager.trigger(AttributeListeners.PRE_CLEAR) === false) return
(this.form.querySelectorAll("[name]") as NodeListOf<HTMLInputElement>).forEach((el: HTMLInputElement) => {
for (const name in this.inputs) {
if (this.inputs.hasOwnProperty(name)) {
const input = this.inputs[name];
input.setValue(undefined)
}
}
})
this.attributeManager.trigger(AttributeListeners.POST_CLEAR)
}
}
export enum FMMode {
EditMode,
ViewMode
}
/**
* TODO: FMFileInput
* have a data-type with an typeId linked to 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&type=x
* with type is a type id (to set a different location in the system)
* _default to type 1_
* 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
*/