A powerfull Form Manager https://dze.io
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

273 lines
8.0 KiB

  1. import { InputArrayInterface, FMAssignInterface } from './Interfaces';
  2. import FMInput from "./FMInput"
  3. /**
  4. *
  5. * `datalist` upgrade:
  6. * the value submitted won't be the `value` attribute but the `data-value` attribute
  7. * 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
  8. * ex:
  9. * ```html
  10. * <input name="listing" list="list" data-strict/>
  11. * <datalist id="list">
  12. * <option data-value="value submitted" value="shown value">value subtitle</option>
  13. * <option data-value="value submitted" value="shown valuee">value subtitle</option>
  14. * <option data-value="value submitted" value="shown valueq">value subtitle</option>
  15. * <option data-value="value submitted" value="shown valuea">value subtitle</option>
  16. * </datalist>
  17. * ```
  18. * **ATTENTION if multiple `option` has the same `value` attribute the `data-value` will be the one of the first one**
  19. *
  20. *
  21. * a `data-ignore` attribute:
  22. * the input with the data-ignore won't send datas to server _still need the `name` attribute_
  23. * awesome for usage with `data-autoset`
  24. *
  25. * a `data-regex` attribute:
  26. * when the element is getting verified it will be passed through the regex
  27. * ex:
  28. * ```html
  29. * <!-- value is set by the user -->
  30. * <input type="text" value="00" data-regex="\d" />
  31. * <!-- test passed form will submit -->
  32. * <input type="text" value="O0" data-regex="\d" />
  33. * <!-- error regex not corresponding form won't submit -->
  34. * ```
  35. * _please note that you still have to verify that server-side_
  36. *
  37. *
  38. * a `data-default` attribute:
  39. * if the value is not something controlable by html it will be set by here
  40. * depending on the input type different defaults are possibles
  41. * ex: `date` `datetime` `time` if it's set it will be the current time
  42. * ```html
  43. * <input type="date" name="date" data-default />
  44. * <!-- will result if today was the 2019-08-26 in -->
  45. * <input type="date" name="date" data-default value="2019-08-26"/>
  46. * ```
  47. *
  48. *
  49. * a `data-autoset` attribute:
  50. * this attribute will change it's value regarding other inputs
  51. * DON'T name any input `x` as it will break the repeat element
  52. * (you SHOULD use `readonly` or `disabled` attribute with it)
  53. * ex:
  54. * ```html
  55. * <input name="input"/>
  56. * <input name="test" readonly data-autoset="testing-autoset-{input}"/>
  57. * ```
  58. * the test input should always contain `testing-autoset-(the value of input)
  59. *
  60. *
  61. * a `repeat` type:
  62. * container: `.form-manager-repeat`
  63. * template (in container): `.form-manager-template`
  64. * add button (in container): `.form-manager-add-button`
  65. * delete button (in template): `.form-manager-delete-button`
  66. *
  67. * input MUST have data-name and not name attributes
  68. * if the script detect `[x]` it will be replaced by `[${index}]`
  69. * on item deletion all items are re-indexed
  70. * if `data-default` or `data-autoset` contains `{x}` it will be replaced by `${index}`
  71. *
  72. * check if input has attribute `form` and that the value is the current form id
  73. * if so add it to current form
  74. * ```html
  75. * <div class="fm-repeat" name="testName">
  76. * <div class="fmr-template">
  77. * <input data-input type="text"/>
  78. * <!-- if there is only one input the name will be `testName[x]` -->
  79. * <!-- if there is only multiple inputs the name will be `testName[x][index]` -->
  80. * <div class="fmr-del">
  81. * <button></button>
  82. * </div>
  83. * </div>
  84. * <!-- future content position -->
  85. * <div class="">
  86. * <input data-input type="text"/>
  87. * <div class="fmr-del">
  88. * <button></button>
  89. * </div>
  90. * </div>
  91. * <!-- future content position -->
  92. * <div class="fmr-add">
  93. * <button></button>
  94. * </div>
  95. * </div>
  96. * ```
  97. */
  98. /**
  99. * Manager for Forms
  100. *
  101. * @export
  102. * @class FormManager
  103. */
  104. export default class FormManager {
  105. private inputs: InputArrayInterface = {}
  106. private FMInputs: FMAssignInterface[] = []
  107. private _form: HTMLFormElement
  108. public set form(v : HTMLFormElement) {this._form = v}
  109. public get form(): HTMLFormElement {return this._form}
  110. /**
  111. * Creates an instance of FormManager.
  112. * @param {HTMLFormElement} form the form HTMLElement
  113. * @memberof FormManager
  114. */
  115. constructor(form: HTMLFormElement) {
  116. this.form = form
  117. //Prevent default submit action
  118. form.onsubmit = (e) => {
  119. e.preventDefault()
  120. }
  121. //assign default form field
  122. this.assign({
  123. input: FMInput
  124. })
  125. //Setup for basic items
  126. this.setupInputs()
  127. }
  128. public assign(inter: FMAssignInterface) {
  129. this.FMInputs.unshift(inter)
  130. }
  131. public setupInputs() {
  132. this.form.querySelectorAll("[name]:not([data-name])").forEach((element: HTMLElement) => {
  133. let el = this.getInit(element)
  134. this.inputs[el.getName()] = el
  135. });
  136. }
  137. public getInit(element: HTMLElement): FMInput {
  138. inputsLoop: for (const input of this.FMInputs) {
  139. if (input.classes != undefined) {
  140. let tmpList: string[]
  141. if (typeof input.classes == "object") tmpList = input.classes
  142. if (typeof input.classes === "string") tmpList = [input.classes]
  143. for (const classe of tmpList) {
  144. if(!element.classList.contains(classe)) continue inputsLoop
  145. }
  146. }
  147. if (input.attributes != undefined) {
  148. let tmpList: string[]
  149. if (typeof input.attributes == "object") tmpList = input.attributes
  150. if (typeof input.attributes === "string") tmpList = [input.attributes]
  151. for (const classe of tmpList) {
  152. if(!element.hasAttribute(classe)) continue inputsLoop
  153. }
  154. }
  155. if (input.type !== undefined) {
  156. if(element.getAttribute("type") !== input.type) continue
  157. }
  158. if (input.tagName !== undefined) {
  159. if (element.nodeName.toLowerCase() !== input.tagName.toLowerCase()) continue
  160. }
  161. return new (input.input)(element, this)
  162. }
  163. }
  164. /**
  165. * verify all the values
  166. *
  167. * @returns {boolean} if the requirements are correct or not
  168. * @memberof FormManager
  169. */
  170. public verify(): boolean {
  171. for (const name in this.inputs) {
  172. if (this.inputs.hasOwnProperty(name)) {
  173. const input = this.inputs[name];
  174. if(!input.verify()) return false
  175. }
  176. }
  177. return true
  178. }
  179. /**
  180. * submit the form to the `url`
  181. *
  182. * @param {string} url the url
  183. * @param {(this: XMLHttpRequest, ev: ProgressEvent) => any} [callback] callback of event `loadend`
  184. * @param {boolean} [verify=true] is the content verified beforehand (won't be sent if not correct)
  185. * @returns {boolean} return if the content was sent or not
  186. * @memberof FormManager
  187. */
  188. public submit(url: string, callback?: (this: XMLHttpRequest, ev: ProgressEvent) => any, verify: boolean = true): boolean {
  189. if (verify && !this.verify()) return false
  190. let ajax = new XMLHttpRequest
  191. ajax.open("POST", url, true)
  192. ajax.setRequestHeader("Content-Type", "application/json")
  193. if (callback != undefined) ajax.addEventListener("loadend", callback)
  194. ajax.send(JSON.stringify(this.getJSON()))
  195. return true
  196. }
  197. /**
  198. * return the JSON `{key: value}` sequence
  199. *
  200. * @memberof FormManager
  201. */
  202. public getJSON(): any {
  203. const jsonObject:any = {}
  204. for (const name in this.inputs) {
  205. if (this.inputs.hasOwnProperty(name)) {
  206. const input = this.inputs[name];
  207. jsonObject[name] = input.getValue()
  208. }
  209. }
  210. return jsonObject
  211. }
  212. public fillFromJSON(json: any) {
  213. for (const key in json) {
  214. if (json.hasOwnProperty(key)) {
  215. const element = json[key];
  216. if(this.inputs[key] !== undefined) this.inputs[key].setValue(element)
  217. else console.warn(`${key} is not a valid input name`)
  218. }
  219. }
  220. }
  221. /**
  222. * fill form from uri
  223. * future parameter will be the `type` of source datas (`JSON`, `XML`)
  224. *
  225. * @param {string} uri the URI
  226. * @memberof FormManager
  227. */
  228. public fillFromURI(uri: string) {
  229. let ajax = new XMLHttpRequest
  230. ajax.open("GET", uri, true)
  231. ajax.addEventListener("loadend", (e) => {
  232. if (ajax.readyState === 4 && ajax.status === 200) {
  233. let json = JSON.parse(ajax.responseText)
  234. this.fillFromJSON(json)
  235. }
  236. })
  237. ajax.send()
  238. }
  239. /**
  240. * Clear the fields in the form
  241. *
  242. * @memberof FormManager
  243. */
  244. public clear() {
  245. this.form.querySelectorAll("[name]").forEach((el: HTMLInputElement) => {
  246. el.value = ""
  247. el.removeAttribute("value")
  248. })
  249. }
  250. }