mirror of
https://github.com/dzeiocom/FormManager.git
synced 2025-04-22 19:02:15 +00:00
first commit
This commit is contained in:
commit
b33b45ba80
42
FMInput.ts
Normal file
42
FMInput.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
272
FormManager.ts
Normal file
272
FormManager.ts
Normal file
@ -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
|
||||||
|
* <input name="listing" list="list" data-strict/>
|
||||||
|
* <datalist id="list">
|
||||||
|
* <option data-value="value submitted" value="shown value">value subtitle</option>
|
||||||
|
* <option data-value="value submitted" value="shown valuee">value subtitle</option>
|
||||||
|
* <option data-value="value submitted" value="shown valueq">value subtitle</option>
|
||||||
|
* <option data-value="value submitted" value="shown valuea">value subtitle</option>
|
||||||
|
* </datalist>
|
||||||
|
* ```
|
||||||
|
* **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
|
||||||
|
* <!-- value is set by the user -->
|
||||||
|
* <input type="text" value="00" data-regex="\d" />
|
||||||
|
* <!-- test passed form will submit -->
|
||||||
|
* <input type="text" value="O0" data-regex="\d" />
|
||||||
|
* <!-- error regex not corresponding form won't submit -->
|
||||||
|
* ```
|
||||||
|
* _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
|
||||||
|
* <input type="date" name="date" data-default />
|
||||||
|
* <!-- will result if today was the 2019-08-26 in -->
|
||||||
|
* <input type="date" name="date" data-default value="2019-08-26"/>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
* <input name="input"/>
|
||||||
|
* <input name="test" readonly data-autoset="testing-autoset-{input}"/>
|
||||||
|
* ```
|
||||||
|
* 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
|
||||||
|
* <div class="fm-repeat" name="testName">
|
||||||
|
* <div class="fmr-template">
|
||||||
|
* <input data-input type="text"/>
|
||||||
|
* <!-- if there is only one input the name will be `testName[x]` -->
|
||||||
|
* <!-- if there is only multiple inputs the name will be `testName[x][index]` -->
|
||||||
|
* <div class="fmr-del">
|
||||||
|
* <button></button>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* <!-- future content position -->
|
||||||
|
* <div class="">
|
||||||
|
* <input data-input type="text"/>
|
||||||
|
* <div class="fmr-del">
|
||||||
|
* <button></button>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* <!-- future content position -->
|
||||||
|
* <div class="fmr-add">
|
||||||
|
* <button></button>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
23
Interfaces.ts
Normal file
23
Interfaces.ts
Normal file
@ -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
|
||||||
|
}
|
46
modules/FMDatalistInput.ts
Normal file
46
modules/FMDatalistInput.ts
Normal file
@ -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"
|
||||||
|
}
|
28
modules/FMDateInput.ts
Normal file
28
modules/FMDateInput.ts
Normal file
@ -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"
|
||||||
|
}
|
73
modules/FMRepeatInput.ts
Normal file
73
modules/FMRepeatInput.ts
Normal file
@ -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"
|
||||||
|
}
|
20
package.json
Normal file
20
package.json
Normal file
@ -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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user