mirror of
https://github.com/dzeiocom/FormManager.git
synced 2025-06-23 15:59:21 +00:00
Updated
This commit is contained in:
103
src/FMInput.ts
Normal file
103
src/FMInput.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import FormManager from "./FormManager"
|
||||
|
||||
export default class FMInput {
|
||||
|
||||
element: HTMLInputElement
|
||||
form: FormManager
|
||||
required: boolean
|
||||
|
||||
constructor(element: Element, form: FormManager) {
|
||||
this.element = element as HTMLInputElement
|
||||
this.form = form
|
||||
this.required = element.hasAttribute("required")
|
||||
|
||||
// Set element value to it's default one
|
||||
this.setToDefault()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the element Value
|
||||
*
|
||||
* @param {*} value
|
||||
* @memberof FMInput
|
||||
*/
|
||||
setValue(value: any) {
|
||||
this.element.value = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element value
|
||||
*
|
||||
* @returns {*} the value
|
||||
* @memberof FMInput
|
||||
*/
|
||||
getValue(): any {
|
||||
return this.formatValue(this.element.value)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format the value
|
||||
* ex: if the value is "1" it will return the value as a number 1
|
||||
*
|
||||
* @protected
|
||||
* @param {*} value
|
||||
* @returns {*}
|
||||
* @memberof FMInput
|
||||
*/
|
||||
protected formatValue(value: any): any {
|
||||
// if the value is a number return it as a number obj
|
||||
if (!isNaN(Number(value))) return Number(value)
|
||||
return value
|
||||
|
||||
}
|
||||
|
||||
getDefault(args?: string): any {
|
||||
// if arg is set and startsWith run: run the function in it
|
||||
if (args && args.startsWith("run:")) {
|
||||
args = args.split("run:")[1]
|
||||
return eval(args)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
setToDefault() {
|
||||
if (this.element.hasAttribute("data-default")) {
|
||||
return this.setValue(this.getDefault(this.element.dataset.default))
|
||||
}
|
||||
return this.setValue(this.getDefault(""))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the input name
|
||||
*
|
||||
* @returns {string}
|
||||
* @memberof FMInput
|
||||
*/
|
||||
getName(): string {
|
||||
// while we search for inputs containing [name] we search for the input real name in [name] or [data-name]
|
||||
// (Allow other inputs to play with inputs)
|
||||
let attr = this.element.getAttribute("name") || this.element.dataset.name;
|
||||
if (attr) return attr
|
||||
throw Error("Error: could not get input name!")
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the element is correct
|
||||
*
|
||||
* @returns {boolean}
|
||||
* @memberof FMInput
|
||||
*/
|
||||
verify(): boolean {
|
||||
let val: any = this.getValue()
|
||||
// if element is required and value is undefined retur false
|
||||
if (this.required && (val === undefined || val === null || val === "")) return false
|
||||
|
||||
// check regex
|
||||
const regex = this.element.dataset.regex
|
||||
if(regex) {
|
||||
return new RegExp(regex, 'g').test(val + "")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
416
src/FormManager.ts
Normal file
416
src/FormManager.ts
Normal file
@ -0,0 +1,416 @@
|
||||
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
|
||||
* <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` 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
|
||||
* <!-- 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: `.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
|
||||
* <div class="fm-repeat" name="testName">
|
||||
* <div class="fmr-template example-class">
|
||||
* <input data-input data-name="name" type="text"/>
|
||||
* <!-- if there is only one input the result will be an array of values -->
|
||||
* <!-- if there is only multiple inputs the result will be a named array of results -->
|
||||
* <div class="fmr-del">
|
||||
* <button></button>
|
||||
* </div>
|
||||
* </div>
|
||||
* <div class="example-class fmr-element">
|
||||
* <input data-input data-name="name" type="text"/>
|
||||
* <button class="fmr-del"></button>
|
||||
* </div>
|
||||
* <!-- future elements will always be placed before `.fmr-add` -->
|
||||
* <button class="fmr-add"></button>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* 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[] = []
|
||||
|
||||
/**
|
||||
* The last verified `FMInput` that returned an error
|
||||
*
|
||||
* @type {FMInput}
|
||||
* @memberof FormManager
|
||||
*/
|
||||
public lastErroredInput: FMInput|undefined
|
||||
|
||||
/**
|
||||
* The Form Element of the FM
|
||||
*
|
||||
* @type {HTMLFormElement}
|
||||
* @memberof FormManager
|
||||
*/
|
||||
public form: HTMLFormElement
|
||||
|
||||
|
||||
/**
|
||||
* 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()
|
||||
|
||||
setInterval(() => {
|
||||
(this.form.querySelectorAll("[data-autoset]") as NodeListOf<HTMLInputElement>).forEach((el: HTMLInputElement) => {
|
||||
let autosetStr = el.dataset.autoset
|
||||
if (autosetStr && autosetStr.startsWith("run:")) {
|
||||
let tmp = autosetStr.split("run:")[1]
|
||||
el.value = eval(tmp)
|
||||
}
|
||||
})
|
||||
}, 500)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.inputs = {}
|
||||
this.form.querySelectorAll("[name]:not([data-name])").forEach((element: Element) => {
|
||||
let el = this.getInit(element)
|
||||
if (el) this.inputs[el.getName()] = el
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific FMInput for a Specific Input
|
||||
*
|
||||
* @param {HTMLElement} element
|
||||
* @returns {FMInput}
|
||||
* @memberof FormManager
|
||||
*/
|
||||
public getInit(element: Element): FMInput|undefined {
|
||||
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()) {
|
||||
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 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, 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 setMode(mode: FMMode) {
|
||||
console.log(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")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public setModeForInput(mode: FMMode, inputName: string) {
|
||||
console.log(mode)
|
||||
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() {
|
||||
(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.setToDefault()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
23
src/Interfaces.ts
Normal file
23
src/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
|
||||
}
|
71
src/modules/FMDatalistInput.ts
Normal file
71
src/modules/FMDatalistInput.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { FMAssignInterface } from '../Interfaces';
|
||||
import FormManager from "../FormManager"
|
||||
import FMInput from "../FMInput"
|
||||
|
||||
/**
|
||||
* the upgraded datalist element
|
||||
* @class FMDatalistInput
|
||||
* @extends {FMInput}
|
||||
*/
|
||||
export default class FMDatalistInput extends FMInput {
|
||||
|
||||
datalist: HTMLDataListElement
|
||||
isStrict: boolean
|
||||
|
||||
constructor(element: HTMLInputElement, form: FormManager) {
|
||||
super(element, form)
|
||||
|
||||
// check if input is strict on inputs
|
||||
this.isStrict = this.element.hasAttribute("data-strict")
|
||||
|
||||
// get datalist id
|
||||
let id = this.element.getAttribute("list")
|
||||
if (!id) throw Error(`Error: your input "${this.getName()}" MUST have a list attribute`);
|
||||
|
||||
// get datalist
|
||||
this.datalist = document.getElementById(id) as HTMLDataListElement
|
||||
if (!this.datalist) throw Error(`Error: Datalist not found for ${this.getName()} input`)
|
||||
}
|
||||
|
||||
setValue(value: string) {
|
||||
// if value is "" set value to ""
|
||||
if (value == "") {
|
||||
this.element.value = ""
|
||||
return
|
||||
}
|
||||
// value is an object containing an id then set value to the id
|
||||
if ((value as any).id != undefined) {
|
||||
value = (value as any).id
|
||||
}
|
||||
|
||||
// get the option element containing the value
|
||||
let option = this.datalist.querySelector(`[data-value="${value}"]`)
|
||||
|
||||
// if it was set set the element value to the option value
|
||||
if (option != undefined) {
|
||||
this.element.value = (option as HTMLOptionElement).value
|
||||
return
|
||||
}
|
||||
|
||||
// if datalist is not strict set it to the value inputted
|
||||
if (!this.isStrict) {
|
||||
this.element.value = value
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
// if element value == option value return option data-value
|
||||
let option = this.datalist.querySelector(`[value="${this.element.value}"]`)
|
||||
if (option) return this.formatValue((option as HTMLOptionElement).dataset.value)
|
||||
|
||||
// if strict return undefined else return element value
|
||||
return this.isStrict ? undefined : this.formatValue(this.element.value)
|
||||
}
|
||||
}
|
||||
|
||||
export const FMDatalistAssignement: FMAssignInterface = {
|
||||
input: FMDatalistInput,
|
||||
attributes: "list",
|
||||
tagName: "input"
|
||||
}
|
35
src/modules/FMDateInput.ts
Normal file
35
src/modules/FMDateInput.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { FMAssignInterface } from '../Interfaces';
|
||||
import FMInput from "../FMInput"
|
||||
|
||||
/**
|
||||
*
|
||||
* @class FMDateInput
|
||||
* @extends {FMInput}
|
||||
*/
|
||||
export default class FMDateInput extends FMInput {
|
||||
|
||||
setValue(value: Date|string) {
|
||||
// if value is a string set value to the date of the string
|
||||
if (typeof(value) == "string") {
|
||||
value = new Date(value)
|
||||
}
|
||||
this.element.valueAsDate = value
|
||||
}
|
||||
|
||||
getValue(): Date|undefined {
|
||||
// if get date and if null return undefined else return value
|
||||
let date = this.element.valueAsDate
|
||||
return date == null ? undefined : date
|
||||
}
|
||||
|
||||
getDefault(args: string): Date {
|
||||
// if data-default is present return the current date
|
||||
return new Date
|
||||
}
|
||||
}
|
||||
|
||||
export const FMDateAssignement: FMAssignInterface = {
|
||||
input: FMDateInput,
|
||||
type: "date",
|
||||
tagName: "input"
|
||||
}
|
118
src/modules/FMFileInput.ts
Normal file
118
src/modules/FMFileInput.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { FMAssignInterface } from '../Interfaces';
|
||||
import FMInput from "../FMInput"
|
||||
import FormManager from '../FormManager';
|
||||
|
||||
/**
|
||||
*
|
||||
* @class FMFileInput
|
||||
* @extends {FMInput}
|
||||
*/
|
||||
export default class FMFileInput extends FMInput {
|
||||
|
||||
isUploaded = false
|
||||
|
||||
type = 1
|
||||
|
||||
|
||||
|
||||
button: HTMLButtonElement
|
||||
|
||||
constructor(element: HTMLInputElement, form: FormManager) {
|
||||
super(element, form)
|
||||
|
||||
this.type = this.element.dataset.uploadType ? parseInt(this.element.dataset.uploadType): 1
|
||||
|
||||
element.addEventListener("change", () => {
|
||||
console.log("pouet")
|
||||
let files = element.files
|
||||
if (files && element.parentElement && files.length > 0) {
|
||||
const name = element.parentElement.querySelector(".file-name")
|
||||
if (name) name.textContent = files[0].name
|
||||
}
|
||||
})
|
||||
|
||||
if (this.element.hasAttribute("data-button") && element.parentElement && element.dataset.button) {
|
||||
let btn = element.parentElement.querySelector(element.dataset.button)
|
||||
this.button = btn ? btn as HTMLButtonElement : undefined
|
||||
// this.button = element.parentElement.querySelector(element.dataset.button)
|
||||
}
|
||||
|
||||
if (this.button) {
|
||||
this.button.addEventListener("click", () => {
|
||||
if (!this.element.disabled) {
|
||||
console.log("pouet!")
|
||||
this.upload()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
upload() {
|
||||
// if (this.form.getJSON()["id"] == 0 || this.form.getJSON()["id"] == undefined) {
|
||||
// NotificationManager.getNotificationManager().add("Merci de sauvegarder l'offre au moins une fois !")
|
||||
// }
|
||||
let files = this.element.files
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0]
|
||||
const ajax = new XMLHttpRequest
|
||||
let form = new FormData
|
||||
form.append(this.getName(), file, file.name)
|
||||
ajax.open("POST", `/api/file?upload&type=${this.type}`)
|
||||
ajax.addEventListener("load", (ev) => {
|
||||
console.log(ev)
|
||||
})
|
||||
ajax.addEventListener("progress", (ev) => {
|
||||
console.log(ev)
|
||||
})
|
||||
ajax.addEventListener("loadstart", () => {
|
||||
if (this.button) this.button.classList.add("is-loading")
|
||||
if (!this.element.hasAttribute("disabled")) {
|
||||
this.element.setAttribute("disabled", "")
|
||||
this.element.setAttribute("data-uploading", "")
|
||||
}
|
||||
})
|
||||
ajax.addEventListener("loadend", () => {
|
||||
if (this.button) this.button.classList.remove("is-loading")
|
||||
if (this.element.hasAttribute("disabled") && this.element.hasAttribute("data-uploading")) {
|
||||
this.element.removeAttribute("disabled")
|
||||
this.element.removeAttribute("data-uploading")
|
||||
}
|
||||
if (this.button) this.button.innerText = "Uploaded!"
|
||||
this.element.dataset.id = JSON.parse(ajax.responseText).id
|
||||
ajax.responseText
|
||||
})
|
||||
ajax.send(form)
|
||||
}
|
||||
}
|
||||
|
||||
setValue(value: string|number) {
|
||||
if (value == "") {
|
||||
this.element.dataset.id = value + ""
|
||||
if (this.element.parentElement) {
|
||||
const name = this.element.parentElement.querySelector(".file-name")
|
||||
if (name) name.textContent = ""
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getValue(): number {
|
||||
return this.element.dataset.id ? parseInt(this.element.dataset.id):0
|
||||
}
|
||||
|
||||
verify() {
|
||||
if (this.element.hasAttribute("required")) {
|
||||
return this.isUploaded
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const FMFileAssignement: FMAssignInterface = {
|
||||
input: FMFileInput,
|
||||
type: "file",
|
||||
tagName: "input"
|
||||
}
|
149
src/modules/FMRepeatInput.ts
Normal file
149
src/modules/FMRepeatInput.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import { FMAssignInterface } from './../Interfaces';
|
||||
import FormManager from "../FormManager"
|
||||
import FMInput from "../FMInput"
|
||||
|
||||
/**
|
||||
*
|
||||
* @class FMRepeatInput
|
||||
* @extends {FMInput}
|
||||
*/
|
||||
export default class FMRepeatInput extends FMInput {
|
||||
|
||||
elements: FMInput[][] = []
|
||||
|
||||
private template: HTMLElement
|
||||
|
||||
private addBtn: HTMLElement
|
||||
|
||||
constructor(element: HTMLDivElement, form: FormManager) {
|
||||
super(element, form)
|
||||
|
||||
//fetch Template
|
||||
this.template = element.querySelector(".fmr-template") as HTMLElement
|
||||
if (!this.template) throw Error(`Error: your repeat input "${this.getName()}" MUST have a child with the class .fmr-template`);
|
||||
|
||||
this.template.style.display = "none"
|
||||
|
||||
//fetch add button
|
||||
this.addBtn = element.querySelector(".fmr-add") as HTMLElement
|
||||
if (!this.addBtn) throw Error(`Error: your repeat element "${this.getName()}" MUST have a child with the class .fmr-add`);
|
||||
|
||||
this.addBtn.addEventListener("click", () => {
|
||||
if (!this.addBtn.hasAttribute("disabled")) this.addLine()
|
||||
})
|
||||
|
||||
// Observer to handle attributes changes
|
||||
const observer = new MutationObserver((mutationList: any, observer: any) => {
|
||||
for (let mutation of mutationList) {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === "disabled") {
|
||||
(this.element.querySelectorAll(".fmr-add, .fmr-del") as NodeListOf<HTMLElement>).forEach((el: HTMLElement) => {
|
||||
if (this.element.hasAttribute("disabled")) el.style.display = "none"
|
||||
else el.style.display = ""
|
||||
})
|
||||
if (this.element.hasAttribute("disabled")) this.addBtn.setAttribute("disabled", "")
|
||||
else this.addBtn.removeAttribute("disabled")
|
||||
for (const iterator of this.elements) {
|
||||
for (const i2 of iterator) {
|
||||
if (this.element.hasAttribute("disabled")) {
|
||||
i2.element.setAttribute("disabled", "")
|
||||
continue
|
||||
}
|
||||
i2.element.removeAttribute("disabled")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
observer.observe(this.element, {attributes: true})
|
||||
}
|
||||
|
||||
addLine(values?: any[]|any) {
|
||||
// the new line
|
||||
let node = this.element.insertBefore(this.template.cloneNode(true), this.addBtn) as HTMLElement
|
||||
node.classList.remove("fmr-template")
|
||||
node.classList.add("fmr-element")
|
||||
node.style.display = ""
|
||||
|
||||
// loop through inputs ot init them
|
||||
let sub: FMInput[] = []
|
||||
node.querySelectorAll("[data-input]").forEach((el: Element) => {
|
||||
let input = this.form.getInit(el)
|
||||
if (this.element.hasAttribute("disabled")) {
|
||||
input.element.disabled = true
|
||||
}
|
||||
sub.push(input)
|
||||
// if values is a named array
|
||||
if (values != undefined && values[input.getName()] != undefined) {
|
||||
input.setValue(values[input.getName()])
|
||||
}
|
||||
// if value is a single string/number/etc
|
||||
if (typeof(values) != "object" && values != undefined) {
|
||||
input.setValue(values)
|
||||
}
|
||||
})
|
||||
this.elements.push(sub)
|
||||
|
||||
// get the delete button
|
||||
let del = node.querySelector(".fmr-del")
|
||||
if (del) del.addEventListener("click", () => {
|
||||
if (del && !del.hasAttribute("disabled")) {
|
||||
let id = this.element.querySelectorAll(".fmr-element").length-1
|
||||
this.elements.splice(id)
|
||||
node.remove()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
setValue(value: any[][]|string) {
|
||||
//remove every elements
|
||||
this.element.querySelectorAll(".fmr-element").forEach(el => {
|
||||
el.remove()
|
||||
this.elements = []
|
||||
})
|
||||
//ef string finish function
|
||||
if (typeof(value) == "string") return
|
||||
|
||||
//create each line
|
||||
for (const indexStr in value) {
|
||||
let index = parseInt(indexStr)
|
||||
if (value.hasOwnProperty(index)) {
|
||||
const el = value[index];
|
||||
|
||||
if (this.element.querySelectorAll(".fmr-element").length <= index) {
|
||||
//add element
|
||||
if ((el as any) == "") continue
|
||||
this.addLine(el)
|
||||
continue
|
||||
}
|
||||
// update element
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValue(): any[any] {
|
||||
let values = []
|
||||
for (const line of this.elements) {
|
||||
let lineArray: any = {}
|
||||
//one element repeat
|
||||
if (line.length == 1) {
|
||||
for (const col of line) {
|
||||
values.push(col.getValue())
|
||||
}
|
||||
continue
|
||||
}
|
||||
// multi elements repeat
|
||||
for (const col of line) {
|
||||
// if ()
|
||||
lineArray[col.getName()] = col.getValue()
|
||||
}
|
||||
values.push(lineArray)
|
||||
}
|
||||
return values
|
||||
}
|
||||
}
|
||||
|
||||
export const FMRepeatAssignment: FMAssignInterface = {
|
||||
input: FMRepeatInput,
|
||||
classes: "fm-repeat",
|
||||
tagName: "div"
|
||||
}
|
23
src/modules/FMSelectInput.ts
Normal file
23
src/modules/FMSelectInput.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { FMAssignInterface } from '../Interfaces';
|
||||
import FMInput from "../FMInput"
|
||||
|
||||
/**
|
||||
*
|
||||
* @class FMDateInput
|
||||
* @extends {FMInput}
|
||||
*/
|
||||
export default class FMSelectInput extends FMInput {
|
||||
|
||||
getDefault(): any {
|
||||
// check if element as a selected element and if true return it's value
|
||||
let def = this.element.querySelector("option[selected]")
|
||||
if (def) {
|
||||
return (def as HTMLOptionElement).value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const FMSelectAssignement: FMAssignInterface = {
|
||||
input: FMSelectInput,
|
||||
tagName: "select"
|
||||
}
|
Reference in New Issue
Block a user