_application_controller_form.coffee 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. class App.ControllerForm extends App.Controller
  2. constructor: (params) ->
  3. super
  4. for key, value of params
  5. @[key] = value
  6. if !@handlers
  7. @handlers = []
  8. @handlers.push @showHideToggle
  9. @handlers.push @requiredMandantoryToggle
  10. if !@model
  11. @model = {}
  12. if !@attributes
  13. @attributes = []
  14. # set empty class attributes if needed
  15. if !@form
  16. @form = @formGen()
  17. # add alert placeholder
  18. @form.prepend('<div class="alert alert--danger js-alert hide" role="alert"></div>')
  19. # if element is given, prepend form to it
  20. if @el
  21. @el.prepend(@form)
  22. # trigger change to rebuild shown/hidden item and update sub selections
  23. if typeof @form is 'object'
  24. @form.find('input').trigger('change')
  25. @form.find('textarea').trigger('change')
  26. @form.find('select').trigger('change')
  27. # remove alert on input
  28. @form.on('input', @hideAlert)
  29. @finishForm = true
  30. @form
  31. showAlert: (message) =>
  32. @form.find('.alert').first().removeClass('hide').html(App.i18n.translateInline(message))
  33. hideAlert: =>
  34. @form.find('.alert').addClass('hide').html()
  35. html: =>
  36. @form.html()
  37. formGen: =>
  38. App.Log.debug 'ControllerForm', 'formGen', @model.configure_attributes
  39. # check if own fieldset should be generated
  40. if @noFieldset
  41. fieldset = @el
  42. else
  43. fieldset = $('<fieldset></fieldset>')
  44. return fieldset if _.isEmpty(@model)
  45. # collect form attributes
  46. @attributes = []
  47. if @model.attributesGet
  48. attributesClean = @model.attributesGet(@screen)
  49. else
  50. attributesClean = App.Model.attributesGet(@screen, @model.configure_attributes)
  51. for attributeName, attribute of attributesClean
  52. # ignore read only attributes
  53. if !attribute.readonly
  54. # check generic filter
  55. if @filter && !attribute.filter
  56. if @filter[ attributeName ]
  57. attribute.filter = @filter[ attributeName ]
  58. @attributes.push attribute
  59. attribute_count = 0
  60. className = @model.className + '_' + Math.floor( Math.random() * 999999 ).toString()
  61. for attribute in @attributes
  62. attribute_count = attribute_count + 1
  63. # add item
  64. item = @formGenItem(attribute, className, fieldset, attribute_count)
  65. item.appendTo(fieldset)
  66. # if password, add confirm password item
  67. if attribute.type is 'password'
  68. # set selected value passed on current params
  69. if @params
  70. if attribute.name of @params
  71. attribute.value = @params[attribute.name]
  72. # rename display and name to _confirm
  73. if !attribute.single
  74. attribute.display = attribute.display + ' (confirm)'
  75. attribute.name = attribute.name + '_confirm'
  76. item = @formGenItem(attribute, className, fieldset, attribute_count)
  77. item.appendTo(fieldset)
  78. if @fullForm
  79. if !@formClass
  80. @formClass = ''
  81. fieldset = $('<form class="' + @formClass + '"><button class="btn">' + App.i18n.translateContent('Submit') + '</button></form>').prepend( fieldset )
  82. # bind form events
  83. if @events
  84. for eventSelector, callback of @events
  85. do (eventSelector, callback) ->
  86. evs = eventSelector.split(' ')
  87. fieldset.find( evs[1] ).bind( evs[0], (e) -> callback(e) )
  88. # return form
  89. return fieldset
  90. ###
  91. # input text field with max. 100 size
  92. attribute_config = {
  93. name: 'subject'
  94. display: 'Subject'
  95. tag: 'input'
  96. type: 'text'
  97. limit: 100
  98. null: false
  99. default: defaults['subject']
  100. class: 'span7'
  101. }
  102. # colection as relation with auto completion
  103. attribute_config = {
  104. name: 'customer_id'
  105. display: 'Customer'
  106. tag: 'autocompletion'
  107. # auto completion params, endpoints, ui,...
  108. type: 'text'
  109. limit: 100
  110. null: false
  111. relation: 'User'
  112. autocapitalize: false
  113. help: 'Select the customer of the ticket or create one.'
  114. helpLink: '<a href="" class="customer_new">&raquo;</a>'
  115. callback: @userInfo
  116. class: 'span7'
  117. }
  118. # colection as relation
  119. attribute_config = {
  120. name: 'priority_id'
  121. display: 'Priority'
  122. tag: 'select'
  123. multiple: false
  124. null: false
  125. relation: 'TicketPriority'
  126. default: defaults['priority_id']
  127. translate: true
  128. class: 'medium'
  129. }
  130. # colection as options
  131. attribute_config = {
  132. name: 'priority_id'
  133. display: 'Priority'
  134. tag: 'select'
  135. multiple: false
  136. null: false
  137. options: [
  138. {
  139. value: 5
  140. name: 'very hight'
  141. selected: false
  142. disable: false
  143. },
  144. {
  145. value: 3
  146. name: 'normal'
  147. selected: true
  148. disable: false
  149. },
  150. ]
  151. default: 3
  152. translate: true
  153. class: 'medium'
  154. }
  155. ###
  156. formGenItem: (attribute_config, classname, form, attribute_count) ->
  157. attribute = clone(attribute_config, true)
  158. # create item id
  159. attribute.id = "#{classname}_#{attribute.name}"
  160. # set label class name
  161. attribute.label_class = @model.labelClass
  162. # set autofocus
  163. if @autofocus && attribute_count is 1
  164. attribute.autofocus = 'autofocus'
  165. # set required option
  166. if attribute.required is true
  167. attribute.null = false
  168. if !attribute.null
  169. attribute.required = 'required'
  170. else
  171. attribute.required = ''
  172. # set autocapitalize option
  173. if attribute.autocapitalize is undefined || attribute.autocapitalize
  174. attribute.autocapitalize = ''
  175. else
  176. attribute.autocapitalize = 'autocapitalize="off"'
  177. # set autocomplete option
  178. if attribute.autocomplete is undefined
  179. attribute.autocomplete = ''
  180. else
  181. attribute.autocomplete = 'autocomplete="' + attribute.autocomplete + '"'
  182. # set default values
  183. if attribute.value is undefined && 'default' of attribute
  184. attribute.value = attribute.default
  185. # set params value
  186. if @params
  187. # check if we have a references
  188. parts = attribute.name.split '::'
  189. if parts[0] && parts[1]
  190. if @params[ parts[0] ] && parts[1] of @params[ parts[0] ]
  191. attribute.value = @params[ parts[0] ][ parts[1] ]
  192. # set params value to default
  193. if attribute.name of @params
  194. attribute.value = @params[attribute.name]
  195. App.Log.debug 'ControllerForm', 'formGenItem-before', attribute
  196. if App.UiElement[attribute.tag]
  197. item = App.UiElement[attribute.tag].render(attribute, @params, @)
  198. else
  199. throw "Invalid UiElement.#{attribute.tag}"
  200. if attribute.only_shown_if_selectable
  201. count = Object.keys(attribute.options).length
  202. if !attribute.null && (attribute.nulloption && count is 2) || (!attribute.nulloption && count is 1)
  203. attribute.transparent = true
  204. attributesNew = clone(attribute)
  205. attributesNew.type = 'hidden'
  206. attributesNew.value = ''
  207. for item in attribute.options
  208. if item.value && item.value isnt ''
  209. attributesNew.value = item.value
  210. item = $( App.view('generic/input')( attribute: attributesNew ) )
  211. if @handlers
  212. item.bind('change', (e) =>
  213. params = App.ControllerForm.params( $(e.target) )
  214. for handler in @handlers
  215. handler(params, attribute, @attributes, classname, form, @)
  216. )
  217. # bind dependency
  218. if @dependency
  219. for action in @dependency
  220. # bind on element if name is matching
  221. if action.bind && action.bind.name is attribute.name
  222. ui = @
  223. do (action, attribute) ->
  224. item.bind('change', ->
  225. value = $(@).val()
  226. if !value
  227. value = $(@).find('select, input').val()
  228. # lookup relation if needed
  229. if action.bind.relation
  230. data = App[action.bind.relation].find(value)
  231. value = data.name
  232. # check if value is used in condition
  233. if _.contains( action.bind.value, value )
  234. if action.change.action is 'hide'
  235. ui.hide(action.change.name)
  236. else
  237. ui.show(action.change.name)
  238. )
  239. if !attribute.display || attribute.transparent
  240. # hide/show item
  241. #if attribute.hide
  242. # @.hide(attribute.name)
  243. return item
  244. else
  245. fullItem = $(
  246. App.view('generic/attribute')(
  247. attribute: attribute,
  248. item: '',
  249. bookmarkable: @bookmarkable
  250. )
  251. )
  252. fullItem.find('.controls').prepend( item )
  253. # hide/show item
  254. if attribute.hide
  255. @.hide(attribute.name, fullItem)
  256. return fullItem
  257. show: (name, el = @el) ->
  258. if !_.isArray(name)
  259. name = [name]
  260. for key in name
  261. el.find('[name="' + key + '"]').closest('.form-group').removeClass('hide')
  262. el.find('[name="' + key + '"]').removeClass('is-hidden')
  263. el.find('[data-name="' + key + '"]').closest('.form-group').removeClass('hide')
  264. el.find('[data-name="' + key + '"]').removeClass('is-hidden')
  265. # hide old validation states
  266. if el
  267. el.find('.has-error').removeClass('has-error')
  268. el.find('.help-inline').html('')
  269. hide: (name, el = @el) ->
  270. if !_.isArray(name)
  271. name = [name]
  272. for key in name
  273. el.find('[name="' + key + '"]').closest('.form-group').addClass('hide')
  274. el.find('[name="' + key + '"]').addClass('is-hidden')
  275. el.find('[data-name="' + key + '"]').closest('.form-group').addClass('hide')
  276. el.find('[data-name="' + key + '"]').addClass('is-hidden')
  277. mandantory: (name, el = @el) ->
  278. if !_.isArray(name)
  279. name = [name]
  280. for key in name
  281. el.find('[name="' + key + '"]').attr('required', true)
  282. el.find('[name="' + key + '"]').parents('.form-group').find('label span').html('*')
  283. optional: (name, el = @el) ->
  284. if !_.isArray(name)
  285. name = [name]
  286. for key in name
  287. el.find('[name="' + key + '"]').attr('required', false)
  288. el.find('[name="' + key + '"]').parents('.form-group').find('label span').html('')
  289. showHideToggle: (params, changedAttribute, attributes, classname, form, ui) ->
  290. for attribute in attributes
  291. if attribute.shown_if
  292. hit = false
  293. for refAttribute, refValue of attribute.shown_if
  294. if params[refAttribute]
  295. if _.isArray(refValue)
  296. for item in refValue
  297. if params[refAttribute].toString() is item.toString()
  298. hit = true
  299. else if params[refAttribute].toString() is refValue.toString()
  300. hit = true
  301. if hit
  302. ui.show(attribute.name)
  303. else
  304. ui.hide(attribute.name)
  305. requiredMandantoryToggle: (params, changedAttribute, attributes, classname, form, ui) ->
  306. for attribute in attributes
  307. if attribute.required_if
  308. hit = false
  309. for refAttribute, refValue of attribute.required_if
  310. if params[refAttribute]
  311. if _.isArray( refValue )
  312. for item in refValue
  313. if params[refAttribute].toString() is item.toString()
  314. hit = true
  315. else if params[refAttribute].toString() is refValue.toString()
  316. hit = true
  317. if hit
  318. ui.mandantory(attribute.name)
  319. else
  320. ui.optional(attribute.name)
  321. validate: (params) ->
  322. App.Model.validate(
  323. model: @model
  324. params: params
  325. screen: @screen
  326. )
  327. # get all params of the form
  328. @params: (form) ->
  329. param = {}
  330. lookupForm = @findForm(form)
  331. # get contenteditable
  332. for element in lookupForm.find('[contenteditable]')
  333. name = $(element).data('name')
  334. if name
  335. param[name] = $(element).ceg()
  336. # get form elements
  337. array = lookupForm.serializeArray()
  338. # array to names
  339. for key in array
  340. # check if item is-hidden and should not be used
  341. if lookupForm.find('[name="' + key.name + '"]').hasClass('is-hidden') || lookupForm.find('div[data-name="' + key.name + '"]').hasClass('is-hidden')
  342. delete param[key.name]
  343. continue
  344. # collect all params, push it to an array if already exists
  345. if param[key.name] isnt undefined
  346. if typeof param[key.name] is 'string'
  347. param[key.name] = [param[key.name], key.value.trim()]
  348. else
  349. param[key.name].push key.value.trim()
  350. else
  351. param[key.name] = key.value.trim()
  352. # data type conversion
  353. for key of param
  354. # get boolean
  355. if key.substr(0,9) is '{boolean}'
  356. newKey = key.substr(9, key.length)
  357. if param[key] && param[key].toString() is 'true'
  358. param[newKey] = true
  359. else
  360. param[newKey] = false
  361. delete param[key]
  362. # get {date}
  363. else if key.substr(0,6) is '{date}'
  364. newKey = key.substr(6, key.length)
  365. if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-hidden')
  366. param[newKey] = null
  367. else if param[key]
  368. try
  369. time = new Date( Date.parse( "#{param[key]}T00:00:00Z" ) )
  370. format = (number) ->
  371. if parseInt(number) < 10
  372. number = "0#{number}"
  373. number
  374. if time is 'Invalid Date'
  375. throw "Invalid Date #{param[key]}"
  376. param[newKey] = "#{time.getUTCFullYear()}-#{format(time.getUTCMonth()+1)}-#{format(time.getUTCDate())}"
  377. catch err
  378. param[newKey] = "invalid #{param[key]}"
  379. console.log('ERR', err)
  380. else
  381. param[newKey] = undefined
  382. delete param[key]
  383. # get {datetime}
  384. else if key.substr(0,10) is '{datetime}'
  385. newKey = key.substr(10, key.length)
  386. if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-hidden')
  387. param[newKey] = null
  388. else if param[key]
  389. try
  390. time = new Date( Date.parse( param[key] ) )
  391. if time is 'Invalid Datetime'
  392. throw "Invalid Datetime #{param[key]}"
  393. param[newKey] = time.toISOString().replace(/:\d\d\.\d\d\dZ$/, ':00.000Z')
  394. catch err
  395. param[newKey] = "invalid #{param[key]}"
  396. console.log('ERR', err)
  397. else
  398. param[newKey] = undefined
  399. delete param[key]
  400. # split :: fields, build objects
  401. inputSelectObject = {}
  402. for key of param
  403. parts = key.split '::'
  404. if parts[0] && parts[1]
  405. if parts[1] && !inputSelectObject[ parts[0] ]
  406. inputSelectObject[ parts[0] ] = {}
  407. if parts[2] && !inputSelectObject[ parts[0] ][ parts[1] ]
  408. inputSelectObject[ parts[0] ][ parts[1] ] = {}
  409. if parts[3] && !inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ]
  410. inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = {}
  411. if parts[3]
  412. inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ][ parts[3] ] = param[ key ]
  413. delete param[ key ]
  414. else if parts[2]
  415. inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = param[ key ]
  416. delete param[ key ]
  417. else if parts[1]
  418. inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ]
  419. delete param[ key ]
  420. # set new object params
  421. for key of inputSelectObject
  422. param[ key ] = inputSelectObject[ key ]
  423. # data type conversion
  424. for key of param
  425. # get {business_hours}
  426. if key.substr(0,16) is '{business_hours}'
  427. newKey = key.substr(16, key.length)
  428. if lookupForm.find("[data-name=\"#{newKey}\"]").hasClass('is-hidden')
  429. param[newKey] = null
  430. else if param[key]
  431. newParams = {}
  432. for day, value of param[key]
  433. newParams[day] = {}
  434. newParams[day].active = false
  435. if value.active is 'true'
  436. newParams[day].active = true
  437. newParams[day].timeframes = []
  438. if _.isArray(value.start)
  439. for pos of value.start
  440. newParams[day].timeframes.push [ value.start[pos], value.end[pos] ]
  441. else
  442. newParams[day].timeframes.push [ value.start, value.end ]
  443. param[newKey] = newParams
  444. else
  445. param[newKey] = undefined
  446. delete param[key]
  447. #App.Log.notice 'ControllerForm', 'formParam', form, param
  448. param
  449. @formId: ->
  450. formId = new Date().getTime() + Math.floor( Math.random() * 99999 )
  451. formId.toString().substr formId.toString().length-9, 9
  452. @findForm: (form) ->
  453. # check jquery event
  454. if form && form.target
  455. form = form.target
  456. # create jquery object if not already exists
  457. if form instanceof jQuery
  458. # do nothing
  459. else
  460. form = $(form)
  461. # get form
  462. if form.is('form') is true
  463. #console.log('direct from')
  464. return form
  465. else if form.find('form').is('form') is true
  466. #console.log('child from')
  467. return form.find('form')
  468. else if $(form).closest('form').is('form') is true
  469. #console.log('closest from')
  470. return form.closest('form')
  471. # use current content as form if form isn't already finished
  472. else if !@finishForm
  473. #console.log('finishForm')
  474. return form
  475. else
  476. App.Log.error 'ControllerForm', 'no form found!', form
  477. form
  478. @disable: (form) ->
  479. lookupForm = @findForm(form)
  480. if lookupForm
  481. if lookupForm.is('button, input, select, textarea, div, span')
  482. App.Log.debug 'ControllerForm', 'disable item...', lookupForm
  483. lookupForm.attr('readonly', true)
  484. lookupForm.attr('disabled', true)
  485. return
  486. App.Log.debug 'ControllerForm', 'disable form...', lookupForm
  487. # set forms to read only during communication with backend
  488. lookupForm.find('button, input, select, textarea').attr('readonly', true)
  489. # disable additionals submits
  490. lookupForm.find('button').attr('disabled', true)
  491. else
  492. App.Log.debug 'ControllerForm', 'disable item...', form
  493. form.attr('readonly', true)
  494. form.attr('disabled', true)
  495. @enable: (form) ->
  496. lookupForm = @findForm(form)
  497. if lookupForm
  498. if lookupForm.is('button, input, select, textarea, div, span')
  499. App.Log.debug 'ControllerForm', 'disable item...', lookupForm
  500. lookupForm.attr('readonly', false)
  501. lookupForm.attr('disabled', false)
  502. return
  503. App.Log.debug 'ControllerForm', 'enable form...', lookupForm
  504. # enable fields again
  505. lookupForm.find('button, input, select, textarea').attr('readonly', false)
  506. # enable submits again
  507. lookupForm.find('button').attr('disabled', false)
  508. else
  509. App.Log.debug 'ControllerForm', 'enable item...', form
  510. form.attr('readonly', false)
  511. form.attr('disabled', false)
  512. @validate: (data) ->
  513. lookupForm = @findForm(data.form)
  514. # remove all errors
  515. lookupForm.find('.has-error').removeClass('has-error')
  516. lookupForm.find('.help-inline').html('')
  517. # show new errors
  518. for key, msg of data.errors
  519. # generic validation
  520. itemGeneric = lookupForm.find('[name="' + key + '"]').closest('.form-group')
  521. itemGeneric.addClass('has-error')
  522. itemGeneric.find('.help-inline').html(msg)
  523. # use meta fields
  524. itemMeta = lookupForm.find('[data-name="' + key + '"]').closest('.form-group')
  525. itemMeta.addClass('has-error')
  526. itemMeta.find('.help-inline').html(msg)
  527. # use native fields
  528. itemGeneric = lookupForm.find('[name="' + key + '"]').closest('.form-control')
  529. itemGeneric.trigger('validate')
  530. itemMeta = lookupForm.find('[data-name="' + key + '"]').closest('.form-control')
  531. itemMeta.trigger('validate')
  532. # set autofocus by delay to make validation testable
  533. App.Delay.set(
  534. ->
  535. lookupForm.find('.has-error').find('input, textarea, select').first().focus()
  536. 200
  537. 'validate'
  538. )