Form.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. import { httpClientFactory } from '@/services/httpClientFactory'
  2. import Errors from './FormErrors'
  3. class Form {
  4. /**
  5. * Create a new form instance.
  6. *
  7. * @param {Object} data
  8. */
  9. constructor (data = {}) {
  10. this.axios = httpClientFactory('web')
  11. this.isBusy = false
  12. this.isDisabled = false
  13. // this.successful = false
  14. this.errors = new Errors()
  15. this.originalData = this.deepCopy(data)
  16. Object.assign(this, data)
  17. }
  18. /**
  19. * Fill form data.
  20. *
  21. * @param {Object} data
  22. */
  23. fill (data) {
  24. this.keys().forEach(key => {
  25. this[key] = data[key]
  26. })
  27. }
  28. /**
  29. * Update original form data.
  30. */
  31. setOriginal () {
  32. Object.keys(this)
  33. .filter(key => !Form.ignore.includes(key))
  34. .forEach(key => {
  35. this.originalData[key] = this.deepCopy(this[key])
  36. })
  37. }
  38. /**
  39. * Fill form data.
  40. *
  41. * @param {Object} data
  42. */
  43. hasChanged () {
  44. return this.keys().some(key => this[key] !== this.originalData[key])
  45. }
  46. /**
  47. * Fill form data.
  48. *
  49. * @param {Object} data
  50. */
  51. fillWithKeyValueObject (data) {
  52. this.keys().forEach(key => {
  53. const keyValueObject = data.find(s => s.key === key.toString())
  54. if(keyValueObject != undefined) {
  55. this[key] = keyValueObject.value
  56. }
  57. })
  58. }
  59. /**
  60. * Get the form data.
  61. *
  62. * @return {Object}
  63. */
  64. data () {
  65. return this.keys().reduce((data, key) => (
  66. { ...data, [key]: this[key] }
  67. ), {})
  68. }
  69. /**
  70. * Get the form data keys.
  71. *
  72. * @return {Array}
  73. */
  74. keys () {
  75. return Object.keys(this)
  76. .filter(key => !Form.ignore.includes(key))
  77. }
  78. /**
  79. * Start processing the form.
  80. */
  81. startProcessing () {
  82. this.errors.clear()
  83. this.isBusy = true
  84. // this.successful = false
  85. }
  86. /**
  87. * Finish processing the form.
  88. */
  89. finishProcessing () {
  90. this.isBusy = false
  91. // this.successful = true
  92. }
  93. /**
  94. * Clear the form errors.
  95. */
  96. clear () {
  97. this.errors.clear()
  98. // this.successful = false
  99. }
  100. /**
  101. * Reset the form fields.
  102. */
  103. reset () {
  104. Object.keys(this)
  105. .filter(key => !Form.ignore.includes(key))
  106. .forEach(key => {
  107. this[key] = this.deepCopy(this.originalData[key])
  108. })
  109. }
  110. /**
  111. * Submit the form via a GET request.
  112. *
  113. * @param {String} url
  114. * @param {Object} config (axios config)
  115. * @return {Promise}
  116. */
  117. get (url, config = {}) {
  118. return this.submit('get', url, config)
  119. }
  120. /**
  121. * Submit the form via a POST request.
  122. *
  123. * @param {String} url
  124. * @param {Object} config (axios config)
  125. * @return {Promise}
  126. */
  127. post (url, config = {}) {
  128. return this.submit('post', url, config)
  129. }
  130. /**
  131. * Submit the form via a PATCH request.
  132. *
  133. * @param {String} url
  134. * @param {Object} config (axios config)
  135. * @return {Promise}
  136. */
  137. patch (url, config = {}) {
  138. return this.submit('patch', url, config)
  139. }
  140. /**
  141. * Submit the form via a PUT request.
  142. *
  143. * @param {String} url
  144. * @param {Object} config (axios config)
  145. * @return {Promise}
  146. */
  147. put (url, config = {}) {
  148. return this.submit('put', url, config)
  149. }
  150. /**
  151. * Submit the form via a DELETE request.
  152. *
  153. * @param {String} url
  154. * @param {Object} config (axios config)
  155. * @return {Promise}
  156. */
  157. delete (url, config = {}) {
  158. return this.submit('delete', url, config)
  159. }
  160. /**
  161. * Submit the form data via an HTTP request.
  162. *
  163. * @param {String} method (get, post, patch, put)
  164. * @param {String} url
  165. * @param {Object} config (axios config)
  166. * @return {Promise}
  167. */
  168. submit (method, url, config = {}) {
  169. this.startProcessing()
  170. const data = method === 'get'
  171. ? { params: this.data() }
  172. : this.data()
  173. return new Promise((resolve, reject) => {
  174. // (Form.axios || axios).request({ url: this.route(url), method, data, ...config })
  175. this.axios.request({ url: this.route(url), method, data, ...config })
  176. .then(response => {
  177. this.finishProcessing()
  178. resolve(response)
  179. })
  180. .catch(error => {
  181. this.isBusy = false
  182. if (error.response) {
  183. this.errors.set(this.extractErrors(error.response))
  184. }
  185. if (error.response?.status != 422) {
  186. reject(error)
  187. }
  188. })
  189. })
  190. }
  191. /**
  192. * Submit the form data via an HTTP request.
  193. *
  194. * @param {String} method (get, post, patch, put)
  195. * @param {String} url
  196. * @param {Object} config (axios config)
  197. * @return {Promise}
  198. */
  199. upload (url, config = {}) {
  200. this.startProcessing()
  201. return new Promise((resolve, reject) => {
  202. // https://www.npmjs.com/package/axios#-automatic-serialization-to-formdata
  203. this.axios.post(this.route(url), this.data(), { headers: {'Content-Type' : 'multipart/form-data'}, ...config })
  204. .then(response => {
  205. this.finishProcessing()
  206. resolve(response)
  207. })
  208. .catch(error => {
  209. this.isBusy = false
  210. if (error.response) {
  211. this.errors.set(this.extractErrors(error.response))
  212. }
  213. // if (error.response?.status != 422) {
  214. reject(error)
  215. // }
  216. })
  217. })
  218. }
  219. /**
  220. * Extract the errors from the response object.
  221. *
  222. * @param {Object} response
  223. * @return {Object}
  224. */
  225. extractErrors (response) {
  226. if (!response.data || typeof response.data !== 'object') {
  227. return { error: Form.errorMessage }
  228. }
  229. if (response.data.errors) {
  230. return { ...response.data.errors }
  231. }
  232. if (response.data.message) {
  233. return { error: response.data.message }
  234. }
  235. return { ...response.data }
  236. }
  237. /**
  238. * Get a named route.
  239. *
  240. * @param {String} name
  241. * @return {Object} parameters
  242. * @return {String}
  243. */
  244. route (name, parameters = {}) {
  245. let url = name
  246. if (Form.routes.hasOwnProperty(name)) {
  247. url = decodeURI(Form.routes[name])
  248. }
  249. if (typeof parameters !== 'object') {
  250. parameters = { id: parameters }
  251. }
  252. Object.keys(parameters).forEach(key => {
  253. url = url.replace(`{${key}}`, parameters[key])
  254. })
  255. return url
  256. }
  257. /**
  258. * Clear errors on keydown.
  259. *
  260. * @param {KeyboardEvent} event
  261. */
  262. onKeydown (event) {
  263. if (event.target.name) {
  264. this.errors.clear(event.target.name)
  265. }
  266. }
  267. /**
  268. * Deep copy the given object.
  269. *
  270. * @param {Object} obj
  271. * @return {Object}
  272. */
  273. deepCopy (obj) {
  274. if (obj === null || typeof obj !== 'object') {
  275. return obj
  276. }
  277. const copy = Array.isArray(obj) ? [] : {}
  278. Object.keys(obj).forEach(key => {
  279. copy[key] = this.deepCopy(obj[key])
  280. })
  281. return copy
  282. }
  283. }
  284. Form.routes = {}
  285. Form.errorMessage = 'Something went wrong. Please try again.'
  286. Form.ignore = ['isBusy', 'isDisabled', 'errors', 'originalData', 'axios']
  287. export default Form