form.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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 as soon as possible.', // optional
  14. messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.', // optional
  15. showTitle: true,
  16. lang: 'de', // optional, <html lang="xx"> will be used per default
  17. modal: true,
  18. attachmentSupport: false,
  19. attributes: [
  20. {
  21. display: 'Name',
  22. name: 'name',
  23. tag: 'input',
  24. type: 'text',
  25. placeholder: 'Your Name',
  26. defaultValue: '',
  27. },
  28. {
  29. display: 'Email',
  30. name: 'email',
  31. tag: 'input',
  32. type: 'email',
  33. placeholder: 'Your Email',
  34. defaultValue: function () {return User.email;},
  35. },
  36. {
  37. display: 'Message',
  38. name: 'body',
  39. tag: 'textarea',
  40. placeholder: 'Your Message...',
  41. defaultValue: '',
  42. rows: 7,
  43. },
  44. {
  45. display: 'Attachments',
  46. name: 'file[]',
  47. tag: 'input',
  48. type: 'file',
  49. repeat: 3,
  50. },
  51. ]
  52. });
  53. });
  54. </script>
  55. */
  56. var pluginName = 'ZammadForm',
  57. defaults = {
  58. lang: undefined,
  59. debug: false,
  60. noCSS: false,
  61. prefixCSS: 'zammad-form-',
  62. showTitle: false,
  63. messageTitle: 'Zammad Form',
  64. messageSubmit: 'Submit',
  65. messageThankYou: 'Thank you for your inquiry! We\'ll contact you as soon as possible.',
  66. messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.',
  67. attachmentSupport: false,
  68. attributes: [
  69. {
  70. display: 'Name',
  71. name: 'name',
  72. tag: 'input',
  73. type: 'text',
  74. placeholder: 'Your Name',
  75. defaultValue: '',
  76. },
  77. {
  78. display: 'Email',
  79. name: 'email',
  80. tag: 'input',
  81. type: 'email',
  82. placeholder: 'Your Email',
  83. defaultValue: '',
  84. },
  85. {
  86. display: 'Message',
  87. name: 'body',
  88. tag: 'textarea',
  89. placeholder: 'Your Message...',
  90. defaultValue: '',
  91. rows: 7,
  92. },
  93. ],
  94. translations: {
  95. 'de': {
  96. 'Name': 'Name',
  97. 'Your Name': 'Ihr Name',
  98. 'Email': 'E-Mail',
  99. 'Your Email': 'Ihre E-Mail',
  100. 'Message': 'Nachricht',
  101. 'Attachments': 'Anhänge',
  102. 'Your Message...': 'Ihre Nachricht...',
  103. },
  104. 'es': {
  105. 'Name': 'Nombre',
  106. 'Your Name': 'tu Nombre',
  107. 'Email': 'correo electrónico',
  108. 'Your Email': 'Tu correo electrónico',
  109. 'Message': 'Mensaje',
  110. 'Attachments': 'archivos adjuntos',
  111. 'Your Message...': 'tu Mensaje...',
  112. },
  113. 'fr': {
  114. 'Name': 'Prénom',
  115. 'Your Name': 'Votre nom',
  116. 'Email': 'Email',
  117. 'Your Email': 'Votre Email',
  118. 'Message': 'Message',
  119. 'Attachments': 'Pièces jointes',
  120. 'Your Message...': 'Votre message...',
  121. },
  122. 'nl': {
  123. 'Name': 'Naam',
  124. 'Your Name': 'Uw naam',
  125. 'Email': 'Email adres',
  126. 'Your Email': 'Uw Email adres',
  127. 'Message': 'Bericht',
  128. 'Attachments': 'Bijlage',
  129. 'Your Message...': 'Uw bericht...',
  130. },
  131. 'it': {
  132. 'Name': 'Nome',
  133. 'Your Name': 'Il tuo nome',
  134. 'Email': 'E-mail',
  135. 'Your Email': 'Il tuo indirizzo e-mail',
  136. 'Message': 'Messaggio',
  137. 'Attachments': 'Allegati',
  138. 'Your Message...': 'Il tuo messaggio...',
  139. },
  140. 'pl': {
  141. 'Name': 'Nazwa',
  142. 'Your Name': 'Imię i nazwisko',
  143. 'Email': 'Email adres',
  144. 'Your Email': 'Adres e-mail',
  145. 'Message': 'Wiadomość',
  146. 'Attachments': 'Załączniki',
  147. 'Your Message...': 'Twoja wiadomość...',
  148. },
  149. 'zh-cn': {
  150. 'Name': '联系人',
  151. 'Your Name': '您的尊姓大名',
  152. 'Email': '电子邮件',
  153. 'Your Email': '您的邮件地址',
  154. 'Message': '留言',
  155. 'Attachments': '附件',
  156. 'Your Message...': '您的留言...',
  157. },
  158. 'zh-tw': {
  159. 'Name': '聯絡人',
  160. 'Your Name': '您的尊姓大名',
  161. 'Email': 'E-Mail',
  162. 'Your Email': '請留下您的電子郵件地址',
  163. 'Message': '留言',
  164. 'Attachments': '附檔',
  165. 'Your Message...': '請寫下您的留言...'
  166. },
  167. }
  168. };
  169. function Plugin(element, options) {
  170. this.element = element
  171. this.$element = $(element)
  172. this._defaults = defaults;
  173. this._name = pluginName;
  174. this._endpoint_config = '/api/v1/form_config'
  175. this._endpoint_submit = '/api/v1/form_submit'
  176. this._script_location = '/assets/form/form.js'
  177. this._css_location = '/assets/form/form.css'
  178. this._src = document.getElementById('zammad_form_script').src
  179. this.css_location = this._src.replace(this._script_location, this._css_location)
  180. this.endpoint_config = this._src.replace(this._script_location, this._endpoint_config)
  181. this.endpoint_submit = this._src.replace(this._script_location, this._endpoint_submit)
  182. this.options = $.extend({}, defaults, options)
  183. if (!this.options.lang) {
  184. this.options.lang = $('html').attr('lang')
  185. }
  186. if (this.options.lang) {
  187. this.options.lang = this.options.lang.replace(/-.+?$/, '')
  188. this.log('debug', "lang: " + this.options.lang)
  189. }
  190. this._config = {}
  191. this._token = ''
  192. this.init()
  193. }
  194. Plugin.prototype.init = function () {
  195. var _this = this,
  196. params = {}
  197. _this.log('debug', 'init', this._src)
  198. if (!_this.options.noCSS) {
  199. _this.loadCss(_this.css_location)
  200. }
  201. $.each(_this.options.attributes, function(index, item) {
  202. if (item.name == 'file[]') {
  203. _this.options.attributes.splice(index, 1);
  204. }
  205. })
  206. if (_this.options.attachmentSupport === true || _this.options.attachmentSupport === 'true') {
  207. var attachment = {
  208. display: 'Attachments',
  209. name: 'file[]',
  210. tag: 'input',
  211. type: 'file',
  212. repeat: 1,
  213. }
  214. _this.options.attributes.push(attachment)
  215. }
  216. _this.log('debug', 'endpoint_config: ' + _this.endpoint_config)
  217. _this.log('debug', 'endpoint_submit: ' + _this.endpoint_submit)
  218. // load config
  219. if (this.options.test) {
  220. params.test = true
  221. }
  222. params.fingerprint = this.fingerprint()
  223. $.ajax({
  224. method: 'post',
  225. url: _this.endpoint_config,
  226. cache: false,
  227. processData: true,
  228. data: params
  229. }).done(function(data) {
  230. _this.log('debug', 'config:', data)
  231. _this._config = data
  232. }).fail(function(jqXHR, textStatus, errorThrown) {
  233. if (jqXHR.status == 401) {
  234. _this.log('error', 'Faild to load form config, wrong authentication data!')
  235. }
  236. else if (jqXHR.status == 403) {
  237. _this.log('error', 'Faild to load form config, feature is disabled or request is wrong!')
  238. }
  239. else {
  240. _this.log('error', 'Faild to load form config!')
  241. }
  242. _this.noConfig()
  243. });
  244. // show form
  245. if (!this.options.modal) {
  246. _this.render()
  247. }
  248. // bind form on call
  249. else {
  250. this.$element.off('click.zammad-form').on('click.zammad-form', function (e) {
  251. e.preventDefault()
  252. _this.render()
  253. return true
  254. })
  255. }
  256. }
  257. // load css
  258. Plugin.prototype.loadCss = function(filename) {
  259. if (document.createStyleSheet) {
  260. document.createStyleSheet(filename)
  261. }
  262. else {
  263. $('<link rel="stylesheet" type="text/css" href="' + filename + '" />').appendTo('head')
  264. }
  265. }
  266. // send
  267. Plugin.prototype.submit = function() {
  268. var _this = this
  269. // check min modal open time
  270. if (_this.modalOpenTime) {
  271. var currentTime = new Date().getTime()
  272. var diff = currentTime - _this.modalOpenTime.getTime()
  273. _this.log('debug', 'currentTime', currentTime)
  274. _this.log('debug', 'modalOpenTime', _this.modalOpenTime.getTime())
  275. _this.log('debug', 'diffTime', diff)
  276. if (diff < 1000*10) {
  277. alert('Sorry, you look like an robot!')
  278. return
  279. }
  280. }
  281. // disable form
  282. _this.$form.find('button').prop('disabled', true)
  283. $.ajax({
  284. method: 'post',
  285. url: _this.endpoint_submit,
  286. data: _this.getParams(),
  287. cache: false,
  288. contentType: false,
  289. processData: false,
  290. }).done(function(data) {
  291. // removed errors
  292. _this.$form.find('.has-error').removeClass('has-error')
  293. // set errors
  294. if (data.errors) {
  295. $.each(data.errors, function( key, value ) {
  296. _this.$form.find('[name=' + key + ']').closest('.form-group').addClass('has-error')
  297. })
  298. if (data.errors.token) {
  299. alert(data.errors.token)
  300. }
  301. _this.$form.find('button').prop('disabled', false)
  302. return
  303. }
  304. // ticket has been created
  305. _this.thanks(data)
  306. }).fail(function() {
  307. _this.$form.find('button').prop('disabled', false)
  308. alert('Faild to submit form!')
  309. });
  310. }
  311. // get params
  312. Plugin.prototype.getParams = function() {
  313. var _this = this
  314. var formData = new FormData(_this.$form[0])
  315. /* unfortunaly not working in safari and some IEs - https://developer.mozilla.org/en-US/docs/Web/API/FormData
  316. if (!formData.has('title')) {
  317. formData.append('title', this.options.messageTitle)
  318. }
  319. */
  320. if (!_this.$form.find('[name=title]').val()) {
  321. formData.append('title', this.options.messageTitle)
  322. }
  323. if (this.options.test) {
  324. formData.append('test', true)
  325. }
  326. formData.append('token', this._config.token)
  327. formData.append('fingerprint', this.fingerprint())
  328. _this.log('debug', 'formData', formData)
  329. return formData
  330. }
  331. Plugin.prototype.closeModal = function() {
  332. if (this.$modal) {
  333. this.$modal.remove()
  334. }
  335. }
  336. // render form
  337. Plugin.prototype.render = function(e) {
  338. var _this = this
  339. _this.closeModal()
  340. _this.modalOpenTime = new Date()
  341. _this.log('debug', 'modalOpenTime:', _this.modalOpenTime)
  342. var element = "<div class=\"" + _this.options.prefixCSS + "modal\">\
  343. <div class=\"" + _this.options.prefixCSS + "modal-backdrop js-zammad-form-modal-backdrop\"></div>\
  344. <div class=\"" + _this.options.prefixCSS + "modal-body js-zammad-form-modal-body\">\
  345. <form class=\"zammad-form\"></form>\
  346. </div>\
  347. </div>"
  348. if (!this.options.modal) {
  349. element = '<div><form class="zammad-form"></form></div>'
  350. }
  351. var $element = $(element)
  352. var $form = $element.find('form')
  353. if (this.options.showTitle && this.options.messageTitle != '') {
  354. $form.append('<h2>' + this.options.messageTitle + '</h2>')
  355. }
  356. $.each(this.options.attributes, function(index, value) {
  357. var item = $('<div class="form-group"><label>' + _this.T(value.display) + '</label></div>');
  358. var defaultValue = (typeof value.defaultValue === 'function') ? value.defaultValue() : value.defaultValue;
  359. for (var i=0; i < (value.repeat ? value.repeat : 1); i++) {
  360. if (value.tag == 'input') {
  361. item.append('<input class="form-control" name="' + value.name + '" type="' + value.type + '" placeholder="' + _this.T(value.placeholder) + '" value="' + (defaultValue || '') + '">')
  362. }
  363. else if (value.tag == 'textarea') {
  364. item.append('<textarea class="form-control" name="' + value.name + '" placeholder="' + _this.T(value.placeholder) + '" rows="' + value.rows + '">' + (defaultValue || '') + '</textarea>')
  365. }
  366. }
  367. $form.append(item)
  368. })
  369. $form.append('<button type="submit" class="btn">' + this.options.messageSubmit + '</button')
  370. this.$modal = $element
  371. this.$form = $form
  372. // bind on close
  373. $element.find('.js-zammad-form-modal-backdrop').off('click.zammad-form').on('click.zammad-form', function (e) {
  374. e.preventDefault()
  375. _this.closeModal()
  376. return true
  377. })
  378. // bind form submit
  379. $element.off('submit.zammad-form').on('submit.zammad-form', function (e) {
  380. e.preventDefault()
  381. _this.submit()
  382. return true
  383. })
  384. // show form
  385. if (!this.options.modal) {
  386. _this.$element.html($element)
  387. }
  388. // append modal to body
  389. else {
  390. $('body').append($element)
  391. }
  392. }
  393. // thanks
  394. Plugin.prototype.thanks = function(data) {
  395. var thankYou = this.options.messageThankYou
  396. if (data.ticket && data.ticket.number) {
  397. thankYou = thankYou.replace('%s', data.ticket.number)
  398. }
  399. var message = $('<div class="js-thankyou">' + thankYou + '</div>')
  400. this.$form.html(message)
  401. }
  402. // unable to load config
  403. Plugin.prototype.noConfig = function(e) {
  404. var message = $('<div class="js-noConfig">' + this.options.messageNoConfig + '</div>')
  405. if (this.$form) {
  406. this.$form.html(message)
  407. }
  408. this.$element.html(message)
  409. }
  410. // log method
  411. Plugin.prototype.log = function() {
  412. var args = Array.prototype.slice.call(arguments)
  413. var level = args.shift()
  414. if (!this.options.debug && level == 'debug') {
  415. return
  416. }
  417. args.unshift(this._name + '||' + level)
  418. console.log.apply(console, args)
  419. var logString = ''
  420. $.each( args, function(index, item) {
  421. logString = logString + ' '
  422. if (typeof item == 'object') {
  423. logString = logString + JSON.stringify(item)
  424. }
  425. else if (item && item.toString) {
  426. logString = logString + item.toString()
  427. }
  428. else {
  429. logString = logString + item
  430. }
  431. })
  432. $('.js-logDisplay').prepend('<div>' + logString + '</div>')
  433. }
  434. // translation method
  435. Plugin.prototype.T = function() {
  436. var string = arguments[0]
  437. var items = 2 <= arguments.length ? slice.call(arguments, 1) : []
  438. if (this.options.lang && this.options.lang !== 'en') {
  439. if (!this.options.translations[this.options.lang]) {
  440. this.log('debug', "Translation '" + this.options.lang + "' needed!")
  441. }
  442. else {
  443. translations = this.options.translations[this.options.lang]
  444. if (!translations[string]) {
  445. this.log('debug', "Translation needed for '" + this.options.lang + "' " + string + "'")
  446. }
  447. string = translations[string] || string
  448. }
  449. }
  450. if (items) {
  451. for (i = 0, len = items.length; i < len; i++) {
  452. item = items[i]
  453. string = string.replace(/%s/, item)
  454. }
  455. }
  456. return string
  457. }
  458. Plugin.prototype.fingerprint = function () {
  459. var canvas = document.createElement('canvas')
  460. var ctx = canvas.getContext('2d')
  461. var txt = 'https://zammad.com'
  462. ctx.textBaseline = 'top'
  463. ctx.font = '12px \'Arial\''
  464. ctx.textBaseline = 'alphabetic'
  465. ctx.fillStyle = '#f60'
  466. ctx.fillRect(125,1,62,20)
  467. ctx.fillStyle = '#069'
  468. ctx.fillText(txt, 2, 15)
  469. ctx.fillStyle = 'rgba(100, 200, 0, 0.7)'
  470. ctx.fillText(txt, 4, 17)
  471. return canvas.toDataURL()
  472. }
  473. $.fn[pluginName] = function (options) {
  474. return this.each(function () {
  475. var instance = $.data(this, 'plugin_' + pluginName)
  476. if (instance) {
  477. instance.$element.empty()
  478. $.data(this, 'plugin_' + pluginName, undefined)
  479. }
  480. $.data(
  481. this, 'plugin_' + pluginName,
  482. new Plugin(this, options)
  483. );
  484. });
  485. }
  486. }(jQuery));