form.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. (function ($) {
  2. /*
  3. *
  4. * provides feedback form for zammad
  5. *
  6. <button id="feedback-form">Feedback</button>
  7. <script id="zammad_form_script" src="http://localhost:3000/assets/form/form.js"></script>
  8. <script>
  9. $(function() {
  10. $('#feedback-form').ZammadForm({
  11. messageTitle: 'Feedback Form', // optional
  12. messageSubmit: 'Submit', // optional
  13. messageThankYou: 'Thank you for your inquiry (#%s)! We\'ll contact you soon as possible.', // optional
  14. messageNoConfig: 'Unable to load form config from server. Maybe featrue is disabled.', // optional
  15. showTitle: true,
  16. lang: 'de', // optional, <html lang="xx"> will be used per default
  17. modal: true,
  18. attributes: [
  19. {
  20. display: 'Name',
  21. name: 'name',
  22. tag: 'input',
  23. type: 'text',
  24. placeholder: 'Your Name',
  25. },
  26. {
  27. display: 'Email',
  28. name: 'email',
  29. tag: 'input',
  30. type: 'email',
  31. placeholder: 'Your Email',
  32. },
  33. {
  34. display: 'Message',
  35. name: 'body',
  36. tag: 'textarea',
  37. placeholder: 'Your Message...',
  38. rows: 7,
  39. },
  40. {
  41. display: 'Attachments',
  42. name: 'file[]',
  43. tag: 'input',
  44. type: 'file',
  45. repeat: 3,
  46. },
  47. ]
  48. });
  49. });
  50. </script>
  51. */
  52. var pluginName = 'ZammadForm',
  53. defaults = {
  54. lang: undefined,
  55. debug: false,
  56. noCSS: false,
  57. prefixCSS: 'zammad-form-',
  58. showTitle: false,
  59. messageTitle: 'Zammad Form',
  60. messageSubmit: 'Submit',
  61. messageThankYou: 'Thank you for your inquiry! We\'ll contact you as soon as possible.',
  62. messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.',
  63. attributes: [
  64. {
  65. display: 'Name',
  66. name: 'name',
  67. tag: 'input',
  68. type: 'text',
  69. placeholder: 'Your Name',
  70. },
  71. {
  72. display: 'Email',
  73. name: 'email',
  74. tag: 'input',
  75. type: 'email',
  76. placeholder: 'Your Email',
  77. },
  78. {
  79. display: 'Message',
  80. name: 'body',
  81. tag: 'textarea',
  82. placeholder: 'Your Message...',
  83. rows: 7,
  84. },
  85. ],
  86. translations: {
  87. de: {
  88. 'Name': 'Name',
  89. 'Your Name': 'Ihr Name',
  90. 'Email': 'E-Mail',
  91. 'Your Email': 'Ihre E-Mail',
  92. 'Message': 'Nachricht',
  93. 'Your Message...': 'Ihre Nachricht...',
  94. },
  95. es: {
  96. 'Name': 'Nombre',
  97. 'Your Name': 'tu Nombre',
  98. 'Email': 'correo electrónico',
  99. 'Your Email': 'Tu correo electrónico',
  100. 'Message': 'Mensaje',
  101. 'Your Message...': 'tu Mensaje...',
  102. },
  103. fr: {
  104. 'Name': 'Prénom',
  105. 'Your Name': 'Votre nom',
  106. 'Email': 'Email',
  107. 'Your Email': 'Votre Email',
  108. 'Message': 'Message',
  109. 'Your Message...': 'Votre message...',
  110. },
  111. }
  112. };
  113. function Plugin(element, options) {
  114. this.element = element
  115. this.$element = $(element)
  116. this._defaults = defaults;
  117. this._name = pluginName;
  118. this._endpoint_config = '/api/v1/form_config'
  119. this._endpoint_submit = '/api/v1/form_submit'
  120. this._script_location = '/assets/form/form.js'
  121. this._css_location = '/assets/form/form.css'
  122. this._src = document.getElementById('zammad_form_script').src
  123. this.css_location = this._src.replace(this._script_location, this._css_location)
  124. this.endpoint_config = this._src.replace(this._script_location, this._endpoint_config)
  125. this.endpoint_submit = this._src.replace(this._script_location, this._endpoint_submit)
  126. this.options = $.extend({}, defaults, options)
  127. if (!this.options.lang) {
  128. this.options.lang = $('html').attr('lang')
  129. }
  130. if (this.options.lang) {
  131. this.options.lang = this.options.lang.replace(/-.+?$/, '')
  132. this.log('debug', "lang: " + this.options.lang)
  133. }
  134. this._config = {}
  135. this.init()
  136. }
  137. Plugin.prototype.init = function () {
  138. var _this = this,
  139. params = {}
  140. _this.log('debug', 'init', this._src)
  141. if (!_this.options.noCSS) {
  142. _this.loadCss(_this.css_location)
  143. }
  144. _this.log('debug', 'endpoint_config: ' + _this.endpoint_config)
  145. _this.log('debug', 'endpoint_submit: ' + _this.endpoint_submit)
  146. // load config
  147. if (this.options.test) {
  148. params.test = true
  149. }
  150. $.ajax({
  151. url: _this.endpoint_config,
  152. data: params
  153. }).done(function(data) {
  154. _this.log('debug', 'config:', data)
  155. _this._config = data
  156. }).fail(function(jqXHR, textStatus, errorThrown) {
  157. if (jqXHR.status == 401) {
  158. _this.log('error', 'Faild to load form config, feature is disabled!')
  159. }
  160. else {
  161. _this.log('error', 'Faild to load form config!')
  162. }
  163. _this.noConfig()
  164. });
  165. // show form
  166. if (!this.options.modal) {
  167. _this.render()
  168. }
  169. // bind form on call
  170. else {
  171. this.$element.off('click.zammad-form').on('click.zammad-form', function (e) {
  172. e.preventDefault()
  173. _this.render()
  174. return true
  175. })
  176. }
  177. }
  178. // load css
  179. Plugin.prototype.loadCss = function(filename) {
  180. if (document.createStyleSheet) {
  181. document.createStyleSheet(filename)
  182. }
  183. else {
  184. $('<link rel="stylesheet" type="text/css" href="' + filename + '" />').appendTo('head')
  185. }
  186. }
  187. // send
  188. Plugin.prototype.submit = function() {
  189. var _this = this
  190. // check min modal open time
  191. if (_this.modalOpenTime) {
  192. var currentTime = new Date().getTime()
  193. var diff = currentTime - _this.modalOpenTime.getTime()
  194. _this.log('debug', 'currentTime', currentTime)
  195. _this.log('debug', 'modalOpenTime', _this.modalOpenTime.getTime())
  196. _this.log('debug', 'diffTime', diff)
  197. if (diff < 1000*8) {
  198. alert('Sorry, you look like an robot!')
  199. return
  200. }
  201. }
  202. // disable form
  203. _this.$form.find('button').prop('disabled', true)
  204. $.ajax({
  205. method: 'post',
  206. url: _this.endpoint_submit,
  207. data: _this.getParams(),
  208. cache: false,
  209. contentType: false,
  210. processData: false,
  211. }).done(function(data) {
  212. // removed errors
  213. _this.$form.find('.has-error').removeClass('has-error')
  214. // set errors
  215. if (data.errors) {
  216. $.each(data.errors, function( key, value ) {
  217. _this.$form.find('[name=' + key + ']').closest('.form-group').addClass('has-error')
  218. })
  219. _this.$form.find('button').prop('disabled', false)
  220. return
  221. }
  222. // ticket has been created
  223. _this.thanks(data)
  224. }).fail(function() {
  225. _this.$form.find('button').prop('disabled', false)
  226. alert('Faild to submit form!')
  227. });
  228. }
  229. // get params
  230. Plugin.prototype.getParams = function() {
  231. var _this = this
  232. var formData = new FormData(_this.$form[0])
  233. if (!formData.has('title')) {
  234. formData.append('title', this.options.messageTitle)
  235. }
  236. if (this.options.test) {
  237. formData.append('test', true)
  238. }
  239. _this.log('debug', 'formData', formData)
  240. return formData
  241. }
  242. Plugin.prototype.closeModal = function() {
  243. if (this.$modal) {
  244. this.$modal.remove()
  245. }
  246. }
  247. // render form
  248. Plugin.prototype.render = function(e) {
  249. var _this = this
  250. _this.closeModal()
  251. _this.modalOpenTime = new Date()
  252. _this.log('debug', 'modalOpenTime:', _this.modalOpenTime)
  253. var element = "<div class=\"" + _this.options.prefixCSS + "modal\">\
  254. <div class=\"" + _this.options.prefixCSS + "modal-backdrop js-close\"></div>\
  255. <div class=\"" + _this.options.prefixCSS + "modal-body\">\
  256. <form class=\"zammad-form\"></form>\
  257. </div>\
  258. </div>"
  259. if (!this.options.modal) {
  260. element = '<div><form class="zammad-form"></form></div>'
  261. }
  262. var $element = $(element)
  263. var $form = $element.find('form')
  264. if (this.options.showTitle && this.options.messageTitle != '') {
  265. $form.append('<h2>' + this.options.messageTitle + '</h2>')
  266. }
  267. $.each(this.options.attributes, function(index, value) {
  268. var item = $('<div class="form-group"><label>' + _this.T(value.display) + '</label></div>')
  269. for (var i=0; i < (value.repeat ? value.repeat : 1); i++) {
  270. if (value.tag == 'input') {
  271. item.append('<input class="form-control" name="' + value.name + '" type="' + value.type + '" placeholder="' + _this.T(value.placeholder) + '">')
  272. }
  273. else if (value.tag == 'textarea') {
  274. item.append('<textarea class="form-control" name="' + value.name + '" placeholder="' + _this.T(value.placeholder) + '" rows="' + value.rows + '"></textarea>')
  275. }
  276. }
  277. $form.append(item)
  278. })
  279. $form.append('<button type="submit" class="btn">' + this.options.messageSubmit + '</button')
  280. this.$modal = $element
  281. this.$form = $form
  282. // bind on close
  283. $element.find('.js-close').off('click.zammad-form').on('click.zammad-form', function (e) {
  284. e.preventDefault()
  285. _this.closeModal()
  286. return true
  287. })
  288. // bind form submit
  289. $element.off('submit.zammad-form').on('submit.zammad-form', function (e) {
  290. e.preventDefault()
  291. _this.submit()
  292. return true
  293. })
  294. // show form
  295. if (!this.options.modal) {
  296. _this.$element.html($element)
  297. }
  298. // append modal to body
  299. else {
  300. $('body').append($element)
  301. }
  302. }
  303. // thanks
  304. Plugin.prototype.thanks = function(data) {
  305. var thankYou = this.options.messageThankYou
  306. if (data.ticket && data.ticket.number) {
  307. thankYou = thankYou.replace('%s', data.ticket.number)
  308. }
  309. var message = $('<div class="js-thankyou">' + thankYou + '</div>')
  310. this.$form.html(message)
  311. }
  312. // unable to load config
  313. Plugin.prototype.noConfig = function(e) {
  314. var message = $('<div class="js-noConfig">' + this.options.messageNoConfig + '</div>')
  315. if (this.$form) {
  316. this.$form.html(message)
  317. }
  318. this.$element.html(message)
  319. }
  320. // log method
  321. Plugin.prototype.log = function() {
  322. var args = Array.prototype.slice.call(arguments)
  323. var level = args.shift()
  324. if (!this.options.debug && level == 'debug') {
  325. return
  326. }
  327. args.unshift(this._name + '||' + level)
  328. console.log.apply(console, args)
  329. var logString = ''
  330. $.each( args, function(index, item) {
  331. logString = logString + ' '
  332. if (typeof item == 'object') {
  333. logString = logString + JSON.stringify(item)
  334. }
  335. else if (item && item.toString) {
  336. logString = logString + item.toString()
  337. }
  338. else {
  339. logString = logString + item
  340. }
  341. })
  342. $('.js-logDisplay').prepend('<div>' + logString + '</div>')
  343. }
  344. // translation method
  345. Plugin.prototype.T = function() {
  346. var string = arguments[0]
  347. var items = 2 <= arguments.length ? slice.call(arguments, 1) : []
  348. if (this.options.lang && this.options.lang !== 'en') {
  349. if (!this.options.translations[this.options.lang]) {
  350. this.log('debug', "Translation '" + this.options.lang + "' needed!")
  351. }
  352. else {
  353. translations = this.options.translations[this.options.lang]
  354. if (!translations[string]) {
  355. this.log('debug', "Translation needed for '" + this.options.lang + "' " + string + "'")
  356. }
  357. string = translations[string] || string
  358. }
  359. }
  360. if (items) {
  361. for (i = 0, len = items.length; i < len; i++) {
  362. item = items[i]
  363. string = string.replace(/%s/, item)
  364. }
  365. }
  366. return string
  367. }
  368. $.fn[pluginName] = function (options) {
  369. return this.each(function () {
  370. var instance = $.data(this, 'plugin_' + pluginName)
  371. if (instance) {
  372. instance.$element.empty()
  373. $.data(this, 'plugin_' + pluginName, undefined)
  374. }
  375. $.data(
  376. this, 'plugin_' + pluginName,
  377. new Plugin(this, options)
  378. );
  379. });
  380. }
  381. }(jQuery));