PageSourceSearch

https://www.sanar-tver.ru/local/components/logicloud/forms/js/callback.js

js sanar-tver.ru collected 2026-09-28 22:46:15 UTC 22,500 bytes, 609 lines download raw bytes

1const defaultSettings = {
2  alerts: {
3    errorItem: undefined,
4    errorEmail: undefined,
5    errorPhone: undefined,
6    errorServer: undefined,
7  }
8}
9
10function Callback(selector, settings = defaultSettings) {
11  this.form = document.querySelector(selector);
12  this.ajaxUrl = this.form.action;
13  this.submit = (event) => {
14    event.preventDefault();
15    if (event.target.classList.contains('active'))
16      return;
17
18    this._ajax(this.ajaxUrl, this.load);
19  }
20  this.clearForm = () => {
21    for (let i = 0; i < this.form.length; i++) {
22      let item = this.form[i];
23      this._removeErrorClass(item);
24      if (item.type !== 'hidden') {
25        item.value = '';
26      }
27      if (item.type === 'textarea') {
28        item.removeAttribute('filled');
29      }
30      if (item.type === 'file') {
31        const labels = item.labels;
32        labels.forEach(label => {
33          const labelField = label.querySelector('.field-file__label--text');
34          if (labelField) {
35            if (labelField.dataset.defaultField) {
36              labelField.innerHTML = '';
37              labelField.insertAdjacentHTML('beforeend', labelField.dataset.defaultField);
38            }
39          } else {
40            if (label.dataset.defaultField) {
41              label.innerHTML = '';
42              label.insertAdjacentHTML('beforeend', label.dataset.defaultField);
43            }
44          }
45        });
46      }
47    }
48  }
49  this.update = () => {
50    this._getSettings();
51    this._addListener();
52    this._addMask();
53    this._addDragAndDrop();
54  }
55  this._checkError = () => {
56    for (let key in this.errors) {
57      if (this.errors[key]) {
58        return true;
59      }
60    }
61    return false;
62  }
63  this._addMask = () => {
64    let phoneInputs = this.form.querySelectorAll(this.phoneMaskSelector);
65    phoneInputs.forEach((e) => {
66      e.addEventListener("input", this._mask);
67      e.addEventListener("focus", this._mask);
68      e.addEventListener("blur", this._mask);
69    });
70  }
71  this._addDragAndDrop = () => {
72    const formElements = this.form.elements;
73    for (const element of formElements) {
74      if (element.type !== 'file')
75        continue;
76      const
77        labels = element.labels,
78        actionClearFiles = event => {
79          const target = event.target.closest('a.clear');
80
81          if (!target)
82            return;
83
84          event.preventDefault();
85
86          target.classList.remove('show');
87
88          element.value = '';
89          labels.forEach(label => {
90            let labelField = label.querySelector('.field-file__label--text');
91            if (labelField) {
92              labelField.textContent = labelField.dataset.defaultField;
93            } else {
94              label.textContent = label.dataset.defaultField;
95            }
96          });
97        },
98        actionDragEnterOver = event => {
99          event.preventDefault();
100          element.parentElement.parentElement.classList.add('active');
101        },
102        actionDragLeave = event => {
103          event.preventDefault();
104          element.parentElement.parentElement.classList.remove('active');
105        },
106        actionDragDrop = event => {
107          event.preventDefault();
108          element.files = event.dataTransfer.files;
109          element.parentElement.parentElement.classList.remove('active');
110          const clearButton = element.parentElement.parentElement.querySelector('a.clear');
111          if (clearButton) {
112            clearButton.classList.add('show');
113          }
114          labels.forEach(label => {
115            let labelField = label.querySelector('.field-file__label--text');
116            if (labelField) {
117              if (!labelField.dataset.defaultField) {
118                labelField.dataset.defaultField = labelField.textContent;
119              }
120              labelField.textContent = '';
121              for (const file of event.dataTransfer.files) {
122                labelField.textContent += file.name + ';';
123              }
124            } else {
125              if (!label.dataset.defaultField) {
126                label.dataset.defaultField = label.textContent;
127              }
128              label.textContent = '';
129              for (const file of event.dataTransfer.files) {
130                label.textContent += file.name + ';';
131              }
132            }
133          });
134        },
135        actionInputChange = () => {
136          labels.forEach(label => {
137            let labelField = label.querySelector('.field-file__label--text');
138            const clearButton = element.parentElement.querySelector('a.clear');
139            if (clearButton) {
140              clearButton.classList.add('show');
141            }
142            if (labelField) {
143              if (!labelField.dataset.defaultField) {
144                labelField.dataset.defaultField = labelField.textContent;
145              }
146              labelField.textContent = '';
147              for (const file of element.files) {
148                labelField.textContent += file.name + ';\n';
149              }
150              if (element.files.length < 1) {
151                labelField.textContent = labelField.dataset.defaultField;
152                if (clearButton) {
153                  clearButton.classList.remove('show');
154                }
155              }
156            } else {
157              if (!label.dataset.defaultField) {
158                label.dataset.defaultField = label.textContent;
159              }
160              label.textContent = '';
161              for (const file of element.files) {
162                label.textContent += file.name + ';';
163              }
164              if (element.files.length < 1) {
165                label.textContent = label.dataset.defaultField;
166                if (clearButton) {
167                  clearButton.classList.remove('show');
168                }
169              }
170
171            }
172          });
173        };
174      element.addEventListener('change', actionInputChange);
175      labels.forEach(label => {
176        label.addEventListener('dragenter', actionDragEnterOver);
177        label.addEventListener('dragover', actionDragEnterOver);
178        label.addEventListener('dragleave', actionDragLeave);
179        label.addEventListener('drop', actionDragDrop);
180        label.parentElement.addEventListener('click', actionClearFiles);
181      });
182    }
183  }
184  this._setCursorPosition = function (pos, elem) {
185    elem.focus();
186    if (elem.setSelectionRange) {
187      elem.setSelectionRange(pos, pos)
188    }
189  }
190  this._mask = (event) => {
191    let input = event.target;
192
193    let matrix;
194    if (window.location.host === 'alumica.by') {
195      matrix = '+375 (__) ___-__-__';
196    } else {
197      matrix = '+7 (___) ___-__-__';
198    }
199    let i = 0,
200      def = matrix.replace(/\D/g, ""),
201      val = input.value.replace(/\D/g, "");
202    if (def.length >= val.length)
203      val = def;
204    input.value = matrix.replace(/./g, function (a) {
205      return /[_\d]/.test(a) && i < val.length ? val.charAt(i++) : i >= val.length ? "" : a
206    });
207    if (event.type === "blur") {
208      if (input.value.length === 2)
209        input.value = ""
210    } else
211      this._setCursorPosition(input.value.length, input)
212  }
213
214  this._addPostSettings = () => {
215    for (let key in settings.POST) {
216      this.dataPOST.append(key, settings.POST[key]);
217    }
218  }
219  this._ajax = (ajaxUrl, load) => {
220    this._checkFill();
221    if (!this._checkError()) {
222      let _self = this;
223
224      const loader = _self.form.querySelector('.btn.button');
225      if (loader) {
226        loader.classList.add('active');
227      }
228      const action = () => {
229        _self.dataPOST = new FormData(_self.form);
230        _self._addPostSettings();
231
232        _self.alert.classList.add('inactive');
233        _self.alert.style.top = '';
234
235        _self.alert.classList.remove(_self.alertClasses.error);
236        _self.alert.querySelector('.alert-form__text').innerHTML = '';
237
238        let request = new XMLHttpRequest();
239        request.open("POST", ajaxUrl);
240        request.addEventListener("load", () => {
241          if (loader) {
242            loader.classList.remove('active');
243          }
244          if (request.response >= 1) {
245            if (load !== undefined) {
246              load(request);
247            }
248
249            _self.clearForm();
250            _self.alert.classList.add(_self.alertClasses.success);
251            setTimeout(() => {
252              _self.alert.querySelector('.alert-form__text').innerHTML = _self.alerts.success;
253              _self.alert.classList.remove('inactive');
254              _self.alert.style.top = ((_self.alert.clientHeight + 15) * -1) + 'px';
255            });
256            setTimeout(() => {
257              _self.alert.classList.add('inactive');
258              _self.alert.style.top = '';
259              _self.alert.querySelector('.alert-form__text').innerHTML = '';
260              _self.alert.classList.remove(_self.alertClasses.success);
261
262              const modalContent = this.form.closest('.modal');
263
264              if (modalContent) {
265                const modal = bootstrap.Modal.getOrCreateInstance(modalContent);
266                modal.hide();
267              }
268
269
270            }, 5000);
271
272            document.dispatchEvent(new CustomEvent('formSubmit', {
273              detail: {
274                id: request.response,
275                //  type: settings.eventType,
276              }
277            }));
278
279            // ym(window.metrikaCode, 'reachGoal', 'form_send_success');
280            // gtag("event", "form_send", {});
281          } else {
282            _self.alert.classList.add(_self.alertClasses.error);
283            setTimeout(() => {
284              _self.alert.querySelector('.alert-form__text').innerHTML = _self.alerts.error.server;
285              _self.alert.classList.remove('inactive');
286              _self.alert.style.top = ((_self.alert.clientHeight + 15) * -1) + 'px';
287            });
288          }
289
290        }, false);
291        request.send(_self.dataPOST);
292      }
293
294      if (settings.captchaPublicKey) {
295        grecaptcha.ready(function () {
296          grecaptcha.execute(settings.captchaPublicKey, {action: 'submit'}).then(function (token) {
297            let request = new XMLHttpRequest();
298            request.open('POST', settings.captchaCheck);
299            request.addEventListener('load', () => {
300              if (request.response === 'OK') {
301                action();
302              } else {
303                if (loader) {
304                  loader.classList.remove('active');
305                }
306
307                _self.errors['captcha'] = true;
308
309                let activeHtml = false;
310                const actionTransitionEnd = () => {
311                  _self.alert.removeEventListener('transitionend', actionTransitionEnd);
312                  for (let key in _self.errors) {
313                    if (_self.errors[key]) {
314                      if (!activeHtml) {
315                        activeHtml = true;
316                        _self.alert.querySelector('.alert-form__text').innerHTML = _self.alerts.error[key]
317                      } else {
318                        _self.alert.querySelector('.alert-form__text').innerHTML = _self.alert.querySelector('.alert-form__text').innerHTML + '<br><br>' + _self.alerts.error[key]
319                      }
320                    }
321                  }
322                  _self.alert.classList.add(_self.alertClasses.error);
323                  _self.alert.classList.remove('inactive');
324                  _self.alert.style.top = ((_self.alert.clientHeight + 15) * -1) + 'px';
325                };
326                if (!_self.alert.classList.contains('inactive')) {
327                  _self.alert.classList.add('inactive');
328                  _self.alert.style.top = '';
329                  _self.alert.addEventListener('transitionend', actionTransitionEnd);
330                } else {
331                  actionTransitionEnd();
332                }
333              }
334            });
335            const sendData = new FormData;
336            sendData.append('token', token.toString());
337            request.send(sendData);
338          });
339        });
340      } else if (settings.YCaptchaKey) {
341        const tokenContainer = this.form.querySelector('[name="smart-token"]');
342
343        //  console.log(tokenContainer);
344
345        if (!tokenContainer.value) {
346          window.smartCaptcha.execute(window.logicloud[settings.YCaptchaId]);
347
348          const YCaptchaAction = event => {
349            let request = new XMLHttpRequest();
350            request.open('POST', settings.YCaptchaCheck);
351            request.addEventListener('load', () => {
352              if (request.response === 'OK') {
353                window.smartCaptcha.reset(window.logicloud[settings.YCaptchaId]);
354                action();
355              } else {
356                const loader = _self.form.querySelector('.loader-container');
357
358                if (loader) {
359                  loader.classList.remove('active');
360                }
361
362                _self.errors['captcha'] = true;
363
364                let activeHtml = false;
365                const actionTransitionEnd = () => {
366                  _self.alert.removeEventListener('transitionend', actionTransitionEnd);
367                  for (let key in _self.errors) {
368                    if (_self.errors[key]) {
369                      if (!activeHtml) {
370                        activeHtml = true;
371                        _self.alert.querySelector('.alert-form__text').innerHTML = _self.alerts.error[key]
372                      } else {
373                        _self.alert.querySelector('.alert-form__text').innerHTML = _self.alert.querySelector('.alert-form__text').innerHTML + '<br><br>' + _self.alerts.error[key]
374                      }
375                    }
376                  }
377                  _self.alert.classList.add(_self.alertClasses.error);
378                  _self.alert.classList.remove('inactive');
379                  _self.alert.style.top = ((_self.alert.clientHeight + 15) * -1) + 'px';
380                };
381                if (!_self.alert.classList.contains('inactive')) {
382                  _self.alert.classList.add('inactive');
383                  _self.alert.style.top = '';
384                  _self.alert.addEventListener('transitionend', actionTransitionEnd);
385                } else {
386                  actionTransitionEnd();
387                }
388              }
389            });
390
391            const sendData = new FormData;
392            sendData.append('token', event.detail.token.toString());
393            request.send(sendData);
394
395            document.removeEventListener('YCaptchaExecute', YCaptchaAction);
396          }
397
398          document.addEventListener('YCaptchaExecute', YCaptchaAction);
399        } else {
400          let request = new XMLHttpRequest();
401          request.open('POST', settings.YCaptchaCheck);
402          request.addEventListener('load', () => {
403            if (request.response === 'OK') {
404              action();
405            } else {
406              const loader = _self.form.querySelector('.loader-container');
407
408              if (loader) {
409                loader.classList.remove('active');
410              }
411
412              _self.errors['captcha'] = true;
413
414              let activeHtml = false;
415              const actionTransitionEnd = () => {
416                _self.alert.removeEventListener('transitionend', actionTransitionEnd);
417                for (let key in _self.errors) {
418                  if (_self.errors[key]) {
419                    if (!activeHtml) {
420                      activeHtml = true;
421                      _self.alert.querySelector('.alert-form__text').innerHTML = _self.alerts.error[key]
422                    } else {
423                      _self.alert.querySelector('.alert-form__text').innerHTML = _self.alert.querySelector('.alert-form__text').innerHTML + '<br><br>' + _self.alerts.error[key]
424                    }
425                  }
426                }
427                _self.alert.classList.add(_self.alertClasses.error);
428                _self.alert.classList.remove('inactive');
429                _self.alert.style.top = ((_self.alert.clientHeight + 15) * -1) + 'px';
430              };
431              if (!_self.alert.classList.contains('inactive')) {
432                _self.alert.classList.add('inactive');
433                _self.alert.style.top = '';
434                _self.alert.addEventListener('transitionend', actionTransitionEnd);
435              } else {
436                actionTransitionEnd();
437              }
438            }
439          });
440          const sendData = new FormData;
441          sendData.append('token', tokenContainer.value.toString());
442          request.send(sendData);
443        }
444      } else {
445        action();
446      }
447
448    } else {
449
450      let activeHtml = false;
451      const actionTransitionEnd = () => {
452        this.alert.removeEventListener('transitionend', actionTransitionEnd);
453        for (let key in this.errors) {
454          if (this.errors[key]) {
455            if (!activeHtml) {
456              activeHtml = true;
457              this.alert.querySelector('.alert-form__text').innerHTML = this.alerts.error[key]
458            } else {
459              this.alert.querySelector('.alert-form__text').innerHTML = this.alert.querySelector('.alert-form__text').innerHTML + '<br><br>' + this.alerts.error[key]
460            }
461          }
462        }
463        this.alert.classList.add(this.alertClasses.error);
464        this.alert.classList.remove('inactive');
465        this.alert.style.top = ((this.alert.clientHeight + 15) * -1) + 'px';
466
467        setTimeout(() => {
468          this.alert.classList.add('inactive');
469          this.alert.style.top = '';
470          this.alert.querySelector('.alert-form__text').innerHTML = '';
471          this.alert.classList.remove(this.alertClasses.error);
472        }, 5000);
473      };
474      if (!this.alert.classList.contains('inactive')) {
475        this.alert.classList.add('inactive');
476        this.alert.style.top = '';
477        this.alert.addEventListener('transitionend', actionTransitionEnd);
478
479      } else {
480        actionTransitionEnd();
481      }
482
483    }
484  }
485  this._addListener = () => {
486    this.btnSubmit.addEventListener('click', this.submit);
487    const requiredInputs = this.form.querySelectorAll('input[required], textarea[required]');
488
489    requiredInputs.forEach(input => {
490      input.addEventListener('input', () => {
491        if (input.value === '') {
492          input.classList.add('error');
493        } else {
494          input.classList.remove('error');
495        }
496      })
497    })
498  }
499  this._checkFill = () => {
500    this.errors = {
501      item: false,
502      email: false,
503      phone: false,
504      personal: false,
505      captcha: false,
506    }
507    let checkedForm = this.form;
508    for (let i = 0; i < checkedForm.length; i++) {
509      let checkedItem = checkedForm[i];
510      let customCheckedTypes = ['email', 'tel', 'checkbox', 'hidden'];
511      let isClassicItemType = checkedItem.required && !customCheckedTypes.includes(checkedItem.type);
512      let isCustomItemType = checkedItem.required && customCheckedTypes.includes(checkedItem.type) || checkedItem.value !== '';
513      if (isClassicItemType) {
514        if (checkedItem.value !== '') {
515          this._removeErrorClass(checkedItem);
516        } else {
517          this._addErrorClass(checkedItem);
518          this.errors.item = true;
519        }
520      } else if (isCustomItemType) {
521
522        if (checkedItem.type === 'email') {
523          this._checkEmail(checkedItem);
524        } else if (checkedItem.type === 'tel') {
525
526          this._checkPhone(checkedItem);
527        } else if (checkedItem.type === 'checkbox') {
528          this._checkCheckbox(checkedItem);
529        }
530      }
531    }
532  }
533  this._checkCheckbox = (checkedItem) => {
534    if (!checkedItem.checked) {
535      this._addErrorClass(checkedItem);
536      this.errors.personal = true;
537    } else {
538      this._removeErrorClass(checkedItem);
539    }
540  }
541  this._checkPhone = (checkedItem) => {
542    if (checkedItem.value.length < 18) {
543      this._addErrorClass(checkedItem);
544      this.errors.phone = true;
545    } else {
546      this._removeErrorClass(checkedItem);
547    }
548  }
549  this._checkEmail = (checkedItem) => {
550    if (checkedItem.value !== '') {
551      if (checkedItem.value.length > 0 && (checkedItem.value.match(/.+?@.+/g) || []).length !== 1) {
552        this._addErrorClass(checkedItem);
553        this.errors.email = true;
554      } else {
555        this._removeErrorClass(checkedItem);
556      }
557    } else {
558      this._addErrorClass(checkedItem);
559      this.errors.email = true;
560    }
561  }
562  this._getSettings = () => {
563    if (settings.alert) {
564      this.alert = document.querySelector(settings.alert);
565    } else {
566      this.alert = this.form.querySelector('.alert-form');
567    }
568    if (settings.btnSubmit) {
569      this.btnSubmit = document.querySelector(settings.btnSubmit);
570    } else {
571      this.btnSubmit = this.form.querySelector('[type=submit]');
572    }
573    if (settings.load) {
574      this.load = settings.load;
575    }
576    if (settings.phoneMaskSelector) {
577      this.phoneMaskSelector = settings.phoneMaskSelector;
578    } else {
579      this.phoneMaskSelector = 'input.phone';
580    }
581    this.alerts = {
582      error: {
583        item: (settings?.alerts?.errorItem !== undefined) ? settings.alerts.errorItem : 'Заполните обязательные поля',
584        email: (settings?.alerts?.errorEmail !== undefined) ? settings.alerts.errorEmail : 'Введите корректный Email',
585        phone: (settings?.alerts?.errorPhone !== undefined) ? settings.alerts.errorPhone : 'Введите корректный телефон',
586        server: (settings?.alerts?.errorServer !== undefined) ? settings.alerts.errorServer : 'Ошибка сервера, попробуйте позже',
587        personal: (settings?.alerts?.personal !== undefined) ? settings.alerts.personal : 'Подтвердите согласие на обработку персональных данных',
588        captcha: (settings?.alerts?.captcha !== undefined) ? settings.alerts.captcha : 'Возможно вы робот'
589      },
590      success: (settings?.alerts?.success !== undefined) ? settings.alerts.success : 'Заявка успешно отправлена'
591    }
592    this.alertClasses = {
593      error: (settings?.alertClasses?.error !== undefined) ? settings.alertClasses.error : 'danger',
594      success: (settings?.alertClasses?.success !== undefined) ? settings.alertClasses.success : 'success'
595    }
596  }
597  this._addErrorClass = (item, className = 'error') => {
598    if (item !== undefined && typeof (item) === 'object') {
599      item.classList.add(className)
600    }
601  }
602  this._removeErrorClass = (item, className = 'error') => {
603    if (item !== undefined && typeof (item) === 'object') {
604      item.classList.remove(className)
605    }
606  }
607
608  this.update();
609}

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.