PageSourceSearch

https://support.almabetter.com/assets/form/form.js

js almabetter.com collected 2026-10-02 10:20:03 UTC 21,663 bytes, 711 lines download raw bytes

1(function ($) {
2
3/*
4*
5*  provides feedback form for zammad
6*
7
8<button id="zammad-feedback-form">Feedback</button>
9
10<script id="zammad_form_script" src="http://localhost:3000/assets/form/form.js"></script>
11<script>
12$(function() {
13  $('#zammad-feedback-form').ZammadForm({
14    messageTitle: 'Feedback Form', // optional
15    messageSubmit: 'Submit', // optional
16    messageThankYou: 'Thank you for your inquiry (#%s)! We\'ll contact you as soon as possible.', // optional
17    messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.', // optional
18    showTitle: true,
19    lang: 'de', // optional, <html lang="xx"> will be used per default
20    modal: true,
21    attachmentSupport: false,
22    attributes: [
23      {
24        display: 'Name',
25        name: 'name',
26        tag: 'input',
27        type: 'text',
28        placeholder: 'Your Name',
29        defaultValue: '',
30      },
31      {
32        display: 'Email',
33        name: 'email',
34        tag: 'input',
35        type: 'email',
36        required: true,
37        placeholder: 'Your Email',
38        defaultValue: function () {return User.email;},
39      },
40      {
41        display: 'Message',
42        name: 'body',
43        tag: 'textarea',
44        required: true,
45        placeholder: 'Your Message…',
46        defaultValue: '',
47        rows: 7,
48      },
49      {
50        display: 'Attachments',
51        name: 'file[]',
52        tag: 'input',
53        type: 'file',
54        repeat: 3,
55      },
56    ]
57  });
58});
59</script>
60
61*/
62
63  var pluginName = 'ZammadForm',
64  defaults = {
65    lang: undefined,
66    debug: false,
67    noCSS: false,
68    prefixCSS: 'zammad-form-',
69    showTitle: false,
70    messageTitle: 'Zammad Form',
71    messageSubmit: 'Submit',
72    messageThankYou: 'Thank you for your inquiry! We\'ll contact you as soon as possible.',
73    messageNoConfig: 'Unable to load form config from server. Maybe feature is disabled.',
74    attachmentSupport: false,
75    attributes: [
76      {
77        display: 'Name',
78        name: 'name',
79        tag: 'input',
80        type: 'text',
81        id: 'zammad-form-name',
82        required: true,
83        placeholder: 'Your Name',
84        defaultValue: '',
85      },
86      {
87        display: 'Email',
88        name: 'email',
89        tag: 'input',
90        type: 'email',
91        id: 'zammad-form-email',
92        required: true,
93        placeholder: 'Your Email',
94        defaultValue: '',
95      },
96      {
97        display: 'Message',
98        name: 'body',
99        tag: 'textarea',
100        id: 'zammad-form-body',
101        required: true,
102        placeholder: 'Your Message…',
103        defaultValue: '',
104        rows: 7,
105      },
106    ],
107    translations: {
108    // ZAMMAD_TRANSLATIONS_START
109      'cs': {
110        'Attachments': 'Přílohy',
111        'Email': 'Email',
112        'Message': 'Zpráva',
113        'Name': 'Jméno',
114        'Your Email': 'Váš e-mail',
115        'Your Message…': 'Vaše zpráva…',
116        'Your Name': 'Vaše jméno',
117      },
118      'de': {
119        'Attachments': 'Anhänge',
120        'Email': 'E-Mail',
121        'Message': 'Nachricht',
122        'Name': 'Name',
123        'Your Email': 'Ihre E-Mail',
124        'Your Message…': 'Ihre Nachricht…',
125        'Your Name': 'Ihr Name',
126      },
127      'es': {
128        'Attachments': 'Adjuntos',
129        'Email': 'Correo electrónico',
130        'Message': 'Mensaje',
131        'Name': 'Nombre',
132        'Your Email': 'Tu correo electrónico',
133        'Your Message…': 'Su mensaje…',
134        'Your Name': 'tu Nombre',
135      },
136      'et': {
137        'Attachments': 'Manused',
138        'Email': 'E-post',
139        'Message': 'Teade',
140        'Name': 'Nimi',
141        'Your Email': 'Sinu Meiliaadress',
142        'Your Message…': 'Sinu Teade…',
143        'Your Name': 'Sinu Nimi',
144      },
145      'fa': {
146        'Attachments': 'ضمایم',
147        'Email': 'رایانامه',
148        'Message': 'پیام',
149        'Name': 'نام',
150        'Your Email': 'ایمیل شما',
151        'Your Message…': 'پیام شما…',
152        'Your Name': 'نام شما',
153      },
154      'fr': {
155        'Attachments': 'Pièces jointes',
156        'Email': 'E-mail',
157        'Message': 'Message',
158        'Name': 'Nom',
159        'Your Email': 'Votre email',
160        'Your Message…': 'Votre message…',
161        'Your Name': 'Votre nom',
162      },
163      'hr': {
164        'Attachments': 'Privici',
165        'Email': 'E-Mail',
166        'Message': 'Poruka',
167        'Name': 'Ime',
168        'Your Email': 'Vaš e-mail',
169        'Your Message…': 'Vaša poruka…',
170        'Your Name': 'Vaše ime',
171      },
172      'hu': {
173        'Attachments': 'Csatolmányok',
174        'Email': 'E-mail',
175        'Message': 'Üzenet',
176        'Name': 'Név',
177        'Your Email': 'Az Ön e-mail címe',
178        'Your Message…': 'Az Ön üzenete…',
179        'Your Name': 'Az Ön neve',
180      },
181      'it': {
182        'Attachments': 'Allegati',
183        'Email': 'Email',
184        'Message': 'Messaggio',
185        'Name': 'Nome',
186        'Your Email': 'Il tuo indirizzo e-mail',
187        'Your Message…': 'Il tuo messaggio…',
188        'Your Name': 'Il tuo nome',
189      },
190      'lt': {
191        'Attachments': 'Prisegtukai',
192        'Email': 'El. paštas',
193        'Message': 'Žinutė',
194        'Name': 'Vardas',
195        'Your Email': 'Jūsų el. paštas',
196        'Your Message…': 'Jūsų žinutė…',
197        'Your Name': 'Jūsų vardas',
198      },
199      'nl': {
200        'Attachments': 'Bijlagen',
201        'Email': 'E-mail',
202        'Message': 'Bericht',
203        'Name': 'Naam',
204        'Your Email': 'Je e-mailadres',
205        'Your Message…': 'Je bericht…',
206        'Your Name': 'Je naam',
207      },
208      'pl': {
209        'Attachments': 'Załączniki',
210        'Email': 'E-mail',
211        'Message': 'Wiadomość',
212        'Name': 'Nazwa',
213        'Your Email': 'Adres e-mail',
214        'Your Message…': 'Twoja wiadomość…',
215        'Your Name': 'Imię i nazwisko',
216      },
217      'pt-br': {
218        'Attachments': 'Anexos',
219        'Email': 'Email',
220        'Message': 'Mensagem',
221        'Name': 'Nome',
222        'Your Email': 'Seu Email',
223        'Your Message…': 'Sua Mensagem…',
224        'Your Name': 'Seu nome',
225      },
226      'ro': {
227        'Attachments': 'Atașamente',
228        'Email': 'E-mail',
229        'Message': 'Mesaj',
230        'Name': 'Nume',
231        'Your Email': 'Adresă de e-mail',
232        'Your Message…': 'Mesajul tău…',
233        'Your Name': 'Numele tău',
234      },
235      'ru': {
236        'Attachments': 'Вложения',
237        'Email': 'Электронная почта',
238        'Message': 'Сообщение',
239        'Name': 'Имя',
240        'Your Email': 'Ваша почта',
241        'Your Message…': 'Ваше сообщение…',
242        'Your Name': 'Ваше имя',
243      },
244      'sk': {
245        'Attachments': 'Prílohy',
246        'Email': 'E-mail',
247        'Message': 'Správa',
248        'Name': 'Meno',
249        'Your Email': 'Váš e-mail',
250        'Your Message…': 'Vaša správa…',
251        'Your Name': 'Vaše meno',
252      },
253      'sr': {
254        'Attachments': 'Прилози',
255        'Email': 'Имејл',
256        'Message': 'Порука',
257        'Name': 'Назив',
258        'Your Email': 'Ваш имејл',
259        'Your Message…': 'Ваша порука…',
260        'Your Name': 'Ваше име',
261      },
262      'sr-latn-rs': {
263        'Attachments': 'Prilozi',
264        'Email': 'Imejl',
265        'Message': 'Poruka',
266        'Name': 'Naziv',
267        'Your Email': 'Vaš imejl',
268        'Your Message…': 'Vaša poruka…',
269        'Your Name': 'Vaše ime',
270      },
271      'sv': {
272        'Attachments': 'Bilagor',
273        'Email': 'E-post',
274        'Message': 'Meddelande',
275        'Name': 'Namn',
276        'Your Email': 'Din mejl',
277        'Your Message…': 'Ditt meddelande…',
278        'Your Name': 'Ditt namn',
279      },
280      'tr': {
281        'Attachments': 'Ekler',
282        'Email': 'Eposta',
283        'Message': 'Mesaj',
284        'Name': 'Isim',
285        'Your Email': 'E-posta adresiniz',
286        'Your Message…': 'Mesajınız…',
287        'Your Name': 'Adınız',
288      },
289      'uk': {
290        'Attachments': 'Вкладення',
291        'Email': 'Email',
292        'Message': 'Повідомлення',
293        'Name': 'Ім\'я',
294        'Your Email': 'Ваша електронна пошта',
295        'Your Message…': 'Ваше повідомлення…',
296        'Your Name': 'Ваше ім\'я',
297      },
298      'zh-cn': {
299        'Attachments': '附件',
300        'Email': '邮件地址',
301        'Message': '消息',
302        'Name': '名称',
303        'Your Email': '您的邮件地址',
304        'Your Message…': '',
305        'Your Name': '您的尊姓大名',
306      },
307      'zh-tw': {
308        'Attachments': '附件',
309        'Email': '電子郵件',
310        'Message': '訊息',
311        'Name': '名稱',
312        'Your Email': '請留下您的電子郵件地址',
313        'Your Message…': '請寫下您的留言…',
314        'Your Name': '您的尊姓大名',
315      },
316    // ZAMMAD_TRANSLATIONS_END
317    }
318  };
319
320  function Plugin(element, options) {
321    this.element  = element
322    this.$element = $(element)
323
324    this._defaults = defaults;
325    this._name     = pluginName;
326
327    this._endpoint_config = '/api/v1/form_config'
328    this._endpoint_submit = '/api/v1/form_submit'
329    this._script_location = '/assets/form/form.js'
330    this._css_location    = '/assets/form/form.css'
331
332    this._src = document.getElementById('zammad_form_script').src
333    this.css_location = this._src.replace(this._script_location, this._css_location)
334    this.endpoint_config = this._src.replace(this._script_location, this._endpoint_config)
335    this.endpoint_submit = this._src.replace(this._script_location, this._endpoint_submit)
336
337    this.options = $.extend(true, {}, defaults, options)
338    if (!this.options.lang) {
339      this.options.lang = $('html').attr('lang')
340    }
341    if (this.options.lang) {
342      this.options.lang = this.options.lang.replace(/-.+?$/, '')
343      this.log('debug', "lang: " + this.options.lang)
344    }
345
346    this._config = {}
347    this._token = ''
348
349    this.init()
350  }
351
352  Plugin.prototype.init = function () {
353    var _this = this,
354      params = {}
355
356    _this.log('debug', 'init', this._src)
357
358    if (!_this.options.noCSS) {
359      _this.loadCss(_this.css_location)
360    }
361    if (_this.options.attachmentSupport === true || _this.options.attachmentSupport === 'true') {
362      var attachment = {
363        display: 'Attachments',
364        name: 'file[]',
365        tag: 'input',
366        type: 'file',
367        repeat: 1,
368      }
369      _this.options.attributes.push(attachment)
370    }
371    if (_this.options.agreementMessage) {
372      var agreement = {
373        display: _this.options.agreementMessage,
374        name: 'agreement',
375        tag: 'input',
376        type: 'checkbox',
377        id: 'zammad-form-agreement',
378        required: true,
379        defaultValue: '',
380      }
381      _this.options.attributes.push(agreement)
382    }
383
384    _this.log('debug', 'endpoint_config: ' + _this.endpoint_config)
385    _this.log('debug', 'endpoint_submit: ' + _this.endpoint_submit)
386
387    // load config
388    if (this.options.test) {
389      params.test = true
390    }
391
392    params.fingerprint = this.fingerprint()
393
394    $.ajax({
395      method: 'post',
396      url: _this.endpoint_config,
397      cache: false,
398      processData: true,
399      data: params
400    }).done(function(data) {
401      _this.log('debug', 'config:', data)
402      _this._config = data
403    }).fail(function(jqXHR, textStatus, errorThrown) {
404      if (jqXHR.status == 401) {
405        _this.log('error', 'Faild to load form config, wrong authentication data!')
406      }
407      else if (jqXHR.status == 403) {
408        _this.log('error', 'Faild to load form config, feature is disabled or request is wrong!')
409      }
410      else {
411        _this.log('error', 'Faild to load form config!')
412      }
413      _this.noConfig()
414    });
415
416    // show form
417    if (!this.options.modal) {
418      _this.render()
419    }
420
421    // bind form on call
422    else {
423      this.$element.off('click.zammad-form').on('click.zammad-form', function (e) {
424        e.preventDefault()
425        _this.render()
426        return true
427      })
428    }
429  }
430
431  // load css
432  Plugin.prototype.loadCss = function(filename) {
433    if (document.createStyleSheet) {
434      document.createStyleSheet(filename)
435    }
436    else {
437      $('<link rel="stylesheet" type="text/css" href="' + filename + '" />').appendTo('head')
438    }
439  }
440
441  // send
442  Plugin.prototype.submit = function() {
443    var _this = this
444
445    // check min modal open time
446    if (_this.modalOpenTime) {
447      var currentTime = new Date().getTime()
448      var diff = currentTime - _this.modalOpenTime.getTime()
449      _this.log('debug', 'currentTime', currentTime)
450      _this.log('debug', 'modalOpenTime', _this.modalOpenTime.getTime())
451      _this.log('debug', 'diffTime', diff)
452      if (diff < 1000*10) {
453        alert('Sorry, you look like a robot!')
454        return
455      }
456    }
457
458    // disable form
459    _this.$form.find('button').prop('disabled', true)
460
461    $.ajax({
462      method: 'post',
463      url: _this.endpoint_submit,
464      data: _this.getParams(),
465      cache: false,
466      contentType: false,
467      processData: false,
468    }).done(function(data) {
469
470      // Remove the errors from the form.
471      _this.$form.find('.zammad-form-group--has-error').removeClass('zammad-form-group--has-error')
472      // Deprecated code, can be removed in future versions:
473      _this.$form.find('.has-error').removeClass('has-error')
474
475      // set errors
476      if (data.errors) {
477        $.each(data.errors, function( key, value ) {
478          _this.$form.find('[name=' + key + ']').closest('.'+ _this.options.prefixCSS +'group').addClass('zammad-form-group--has-error')
479          // Deprecated code, can be removed in future versions:
480          _this.$form.find('[name=' + key + ']').closest('.form-group').addClass('has-error')
481        })
482        if (data.errors.token) {
483          alert(data.errors.token)
484        }
485        _this.$form.find('button').prop('disabled', false)
486        return
487      }
488
489      // ticket has been created
490      _this.thanks(data)
491
492    }).fail(function() {
493      _this.$form.find('button').prop('disabled', false)
494      alert('The form could not be submitted!')
495    });
496  }
497
498  // get params
499  Plugin.prototype.getParams = function() {
500    var _this = this
501
502    var formData = new FormData(_this.$form[0])
503
504    /* unfortunaly not working in safari and some IEs - https://developer.mozilla.org/en-US/docs/Web/API/FormData
505    if (!formData.has('title')) {
506      formData.append('title', this.options.messageTitle)
507    }
508    */
509    if (!_this.$form.find('[name=title]').val()) {
510      formData.append('title', this.options.messageTitle)
511    }
512
513    if (this.options.test) {
514      formData.append('test', true)
515    }
516    formData.append('token', this._config.token)
517
518    formData.append('fingerprint', this.fingerprint())
519    _this.log('debug', 'formData', formData)
520
521    return formData
522  }
523
524  Plugin.prototype.closeModal = function() {
525    if (this.$modal) {
526      this.$modal.remove()
527    }
528  }
529
530  // render form
531  Plugin.prototype.render = function(e) {
532    var _this = this
533    _this.closeModal()
534    _this.modalOpenTime = new Date()
535    _this.log('debug', 'modalOpenTime:', _this.modalOpenTime)
536
537    var element = "<div class=\"" + _this.options.prefixCSS + "modal\">\
538      <div class=\"" + _this.options.prefixCSS + "modal-backdrop js-zammad-form-modal-backdrop\"></div>\
539      <div class=\"" + _this.options.prefixCSS + "modal-body js-zammad-form-modal-body\">\
540        <form class=\"zammad-form\"></form>\
541      </div>\
542    </div>"
543
544    if (!this.options.modal) {
545      element = '<div><form class="zammad-form"></form></div>'
546    }
547
548    var $element = $(element)
549    var $form = $element.find('form')
550    if (this.options.showTitle && this.options.messageTitle != '') {
551      $form.append('<h2>' + this.options.messageTitle + '</h2>')
552    }
553    $.each(this.options.attributes, function(index, value) {
554      var valueId = _this.options.modal ? value.id + '-modal' : value.id + '-inline'
555      var item
556      if (value.type == 'checkbox'){
557        item = $('<div class="form-group '+ _this.options.prefixCSS +'group"></div>');
558      } else {
559        // Deprecated class "form-group" can be removed in future versions.
560        item = $('<div class="form-group '+ _this.options.prefixCSS +'group"><label for="' + valueId +'"> ' + _this.T(value.display) + '</label></div>');
561      }
562      var defaultValue = (typeof value.defaultValue === 'function') ? value.defaultValue() : value.defaultValue;
563      for (var i=0; i < (value.repeat ? value.repeat : 1); i++) {
564        if (value.tag === 'input') {
565          if (value.type === 'checkbox'){
566            var label = $('<label for="' + valueId + '"><input type="' + value.type + '" name="' + value.name + '" id="' + valueId + '" class="' + _this.options.prefixCSS + 'checkbox" ' + (value.required === true ? ' required' : '') + '>' + _this.T(value.display) + '</label>')
567            item.append(label)
568          } else {
569            // Deprecated class "form-control" can be removed in future versions.
570            item.append('<input class="form-control '+ _this.options.prefixCSS +'control" id="' + valueId + '" name="' + value.name + '" type="' + value.type + '" placeholder="' + _this.T(value.placeholder) + '" value="' + (defaultValue || '') + '"' + (value.required === true ? ' required' : '') + '>')
571          }
572        }
573        else if (value.tag == 'textarea') {
574          // Deprecated class "form-control" can be removed in future versions.
575          item.append('<textarea class="form-control '+ _this.options.prefixCSS +'control" id="' + valueId + '" name="' + value.name + '" placeholder="' + _this.T(value.placeholder) + '" rows="' + value.rows + '"' + (value.required === true ? ' required' : '') + '>' + (defaultValue || '') + '</textarea>')
576        }
577      }
578      $form.append(item)
579    })
580    $form.append('<button type="submit" class="btn">' + this.options.messageSubmit + '</button')
581
582    this.$modal = $element
583    this.$form  = $form
584
585    // bind on close
586    $element.find('.js-zammad-form-modal-backdrop').off('click.zammad-form').on('click.zammad-form', function (e) {
587      e.preventDefault()
588      _this.closeModal()
589      return true
590    })
591
592    // bind form submit
593    $element.off('submit.zammad-form').on('submit.zammad-form', function (e) {
594      e.preventDefault()
595      _this.submit()
596      return true
597    })
598
599    // show form
600    if (!this.options.modal) {
601      _this.$element.html($element)
602    }
603
604    // append modal to body
605    else {
606      $('body').append($element)
607    }
608
609  }
610
611  // thanks
612  Plugin.prototype.thanks = function(data) {
613    var thankYou = this.options.messageThankYou
614    if (data.ticket && data.ticket.number) {
615      thankYou = thankYou.replace('%s', data.ticket.number)
616    }
617    var message = $('<div class="js-thankyou zammad-form-thankyou">' + thankYou + '</div>')
618    this.$form.html(message)
619  }
620
621  // unable to load config
622  Plugin.prototype.noConfig = function(e) {
623    var message = $('<div class="js-noConfig">' + this.options.messageNoConfig + '</div>')
624    if (this.$form) {
625      this.$form.html(message)
626    }
627    this.$element.html(message)
628  }
629
630  // log method
631  Plugin.prototype.log = function() {
632    var args = Array.prototype.slice.call(arguments)
633    var level = args.shift()
634    if (!this.options.debug && level == 'debug') {
635      return
636    }
637    args.unshift(this._name + '||' + level)
638    console.log.apply(console, args)
639
640    var logString = ''
641    $.each( args, function(index, item) {
642      logString = logString + ' '
643      if (typeof item == 'object') {
644        logString = logString + JSON.stringify(item)
645      }
646      else if (item && item.toString) {
647        logString = logString + item.toString()
648      }
649      else {
650        logString = logString + item
651      }
652    })
653    $('.js-logDisplay').prepend('<div>' + logString + '</div>')
654  }
655
656  // translation method
657  Plugin.prototype.T = function() {
658    var string = arguments[0]
659    var items = 2 <= arguments.length ? slice.call(arguments, 1) : []
660    if (this.options.lang && this.options.lang !== 'en') {
661      if (!this.options.translations[this.options.lang]) {
662        this.log('debug', "Translation '" + this.options.lang + "' needed!")
663      }
664      else {
665        translations = this.options.translations[this.options.lang]
666        if (!translations[string]) {
667          this.log('debug', "Translation needed for '" + this.options.lang + "' " + string + "'")
668        }
669        string = translations[string] || string
670      }
671    }
672    if (items) {
673      for (i = 0, len = items.length; i < len; i++) {
674        item = items[i]
675        string = string.replace(/%s/, item)
676      }
677    }
678    return string
679  }
680
681  Plugin.prototype.fingerprint = function () {
682    var canvas = document.createElement('canvas')
683    var ctx = canvas.getContext('2d')
684    var txt = 'https://zammad.com'
685    ctx.textBaseline = 'top'
686    ctx.font = '12px \'Arial\''
687    ctx.textBaseline = 'alphabetic'
688    ctx.fillStyle = '#f60'
689    ctx.fillRect(125,1,62,20)
690    ctx.fillStyle = '#069'
691    ctx.fillText(txt, 2, 15)
692    ctx.fillStyle = 'rgba(100, 200, 0, 0.7)'
693    ctx.fillText(txt, 4, 17)
694    return canvas.toDataURL()
695  }
696
697  $.fn[pluginName] = function (options) {
698    return this.each(function () {
699      var instance = $.data(this, 'plugin_' + pluginName)
700      if (instance) {
701        instance.$element.empty()
702        $.data(this, 'plugin_' + pluginName, undefined)
703      }
704      $.data(
705        this, 'plugin_' + pluginName,
706        new Plugin(this, options)
707      );
708    });
709  }
710
711}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.