PageSourceSearch

https://hls.kz/wp-content/plugins/feedback-system/assets/js/frontend.js?ver=1.2.5

js hls.kz collected 2026-10-02 07:24:57 UTC 18,187 bytes, 473 lines download raw bytes

1/**
2 * Feedback System Frontend JavaScript
3 */
4
5(function($) {
6    'use strict';
7
8    // Класс для работы с формами обратной связи
9    class FeedbackForm {
10        constructor(formElement) {
11            this.form = $(formElement);
12            this.submitBtn = this.form.find('.feedback-form__submit');
13            this.messageDiv = this.form.siblings('.feedback-form__message');
14            this.type = this.form.data('type');
15            
16            this.init();
17        }
18
19        init() {
20            this.bindEvents();
21            this.setupValidation();
22        }
23
24        bindEvents() {
25            this.form.on('submit', (e) => this.handleSubmit(e));
26            
27            // Валидация в реальном времени
28            this.form.find('input, textarea').on('blur', (e) => this.validateField($(e.target)));
29            this.form.find('input, textarea').on('input', (e) => this.clearFieldError($(e.target)));
30        }
31
32        setupValidation() {
33            // Настройка правил валидации
34            this.validationRules = {
35                name: {
36                    required: true,
37                    minLength: 2
38                },
39                email: {
40                    required: true,
41                    email: true
42                },
43                message: {
44                    required: true,
45                    minLength: 10,
46                    maxLength: 5000
47                }
48            };
49        }
50
51        handleSubmit(e) {
52            e.preventDefault();
53
54            if (!this.validateForm()) {
55                this.showMessage(feedback_ajax.messages.required_fields, 'error');
56                return;
57            }
58
59            this.sendForm();
60        }
61
62        validateForm() {
63            let isValid = true;
64            const requiredFields = this.form.find('[required]');
65
66            requiredFields.each((index, field) => {
67                const $field = $(field);
68                if (!this.validateField($field)) {
69                    isValid = false;
70                }
71            });
72
73            return isValid;
74        }
75
76        validateField($field) {
77            const fieldName = $field.attr('name');
78            const value = $field.val().trim();
79            const rules = this.validationRules[fieldName];
80            
81            if (!rules) return true;
82
83            let isValid = true;
84            let errorMessage = '';
85
86            // Проверка обязательности
87            if (rules.required && !value) {
88                isValid = false;
89                errorMessage = this.getFieldLabel($field) + ' обязательно для заполнения';
90            }
91            // Проверка минимальной длины
92            else if (rules.minLength && value.length < rules.minLength) {
93                isValid = false;
94                errorMessage = this.getFieldLabel($field) + ` должно содержать не менее ${rules.minLength} символов`;
95            }
96            // Проверка максимальной длины
97            else if (rules.maxLength && value.length > rules.maxLength) {
98                isValid = false;
99                errorMessage = this.getFieldLabel($field) + ` должно содержать не более ${rules.maxLength} символов`;
100            }
101            // Проверка email
102            else if (rules.email && value && !this.isValidEmail(value)) {
103                isValid = false;
104                errorMessage = 'Пожалуйста, введите корректный email адрес';
105            }
106
107            if (isValid) {
108                this.markFieldValid($field);
109            } else {
110                this.markFieldInvalid($field, errorMessage);
111            }
112
113            return isValid;
114        }
115
116        markFieldValid($field) {
117            const $group = $field.closest('.feedback-form__group');
118            $group.removeClass('feedback-form__group--error');
119            $group.addClass('feedback-form__group--success');
120            $field.removeClass('feedback-form__input--error');
121            this.removeFieldError($field);
122        }
123
124        markFieldInvalid($field, message) {
125            const $group = $field.closest('.feedback-form__group');
126            $group.removeClass('feedback-form__group--success');
127            $group.addClass('feedback-form__group--error');
128            $field.addClass('feedback-form__input--error');
129            this.showFieldError($field, message);
130        }
131
132        clearFieldError($field) {
133            const $group = $field.closest('.feedback-form__group');
134            $group.removeClass('feedback-form__group--error feedback-form__group--success');
135            $field.removeClass('feedback-form__input--error');
136            this.removeFieldError($field);
137        }
138
139        showFieldError($field, message) {
140            this.removeFieldError($field);
141            const errorHtml = `<div class="feedback-form__field-error" style="color: #dc3545; font-size: 12px; margin-top: 4px;">${message}</div>`;
142            $field.after(errorHtml);
143        }
144
145        removeFieldError($field) {
146            $field.siblings('.feedback-form__field-error').remove();
147        }
148
149        getFieldLabel($field) {
150            const $label = $field.siblings('.feedback-form__label');
151            return $label.length ? $label.text().replace(' *', '') : $field.attr('name');
152        }
153
154        isValidEmail(email) {
155            const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
156            return emailRegex.test(email);
157        }
158
159        sendForm() {
160            this.setLoading(true);
161
162            const formData = {
163                action: 'submit_feedback',
164                nonce: feedback_ajax.nonce,
165                type: this.type,
166                name: this.form.find('[name="name"]').val(),
167                email: this.form.find('[name="email"]').val(),
168                subject: this.form.find('[name="subject"]').val(),
169                message: this.form.find('[name="message"]').val()
170            };
171
172            $.ajax({
173                url: feedback_ajax.ajax_url,
174                type: 'POST',
175                data: formData,
176                timeout: 30000
177            })
178            .done((response) => this.handleSuccess(response))
179            .fail((xhr, status, error) => this.handleError(xhr, status, error))
180            .always(() => this.setLoading(false));
181        }
182
183        handleSuccess(response) {
184            if (response.success) {
185                this.showMessage(response.data.message, 'success');
186                this.form[0].reset();
187                this.clearAllFieldErrors();
188                
189                // Отправить пользовательское событие
190                $(document).trigger('feedbackFormSuccess', {
191                    type: this.type,
192                    messageId: response.data.message_id
193                });
194            } else {
195                this.showMessage(response.data.message || feedback_ajax.messages.error, 'error');
196            }
197        }
198
199        handleError(xhr, status, error) {
200            let message = feedback_ajax.messages.error;
201            
202            if (status === 'timeout') {
203                message = 'Время ожидания истекло. Попробуйте еще раз.';
204            } else if (xhr.responseJSON && xhr.responseJSON.data && xhr.responseJSON.data.message) {
205                message = xhr.responseJSON.data.message;
206            }
207            
208            this.showMessage(message, 'error');
209            
210            // Отправить пользовательское событие об ошибке
211            $(document).trigger('feedbackFormError', {
212                type: this.type,
213                error: error,
214                status: status
215            });
216        }
217
218        clearAllFieldErrors() {
219            this.form.find('.feedback-form__group').removeClass('feedback-form__group--error feedback-form__group--success');
220            this.form.find('.feedback-form__input').removeClass('feedback-form__input--error');
221            this.form.find('.feedback-form__field-error').remove();
222        }
223
224        setLoading(isLoading) {
225            if (isLoading) {
226                this.submitBtn
227                    .prop('disabled', true)
228                    .addClass('feedback-form__submit--disabled')
229                    .text(feedback_ajax.messages.sending);
230                this.form.addClass('feedback-form__loading');
231            } else {
232                this.submitBtn
233                    .prop('disabled', false)
234                    .removeClass('feedback-form__submit--disabled')
235                    .text(this.getSubmitText());
236                this.form.removeClass('feedback-form__loading');
237            }
238        }
239
240        getSubmitText() {
241            return this.submitBtn.data('original-text') || 'Отправить';
242        }
243
244        showMessage(message, type = 'info') {
245            this.messageDiv
246                .removeClass('feedback-form__message--success feedback-form__message--error feedback-form__message--hidden')
247                .addClass(`feedback-form__message--${type}`)
248                .html(message)
249                .show();
250
251            // Автоматически скрыть сообщение через 10 секунд
252            setTimeout(() => {
253                this.messageDiv.fadeOut();
254            }, 10000);
255
256            // Прокрутить к сообщению
257            $('html, body').animate({
258                scrollTop: this.messageDiv.offset().top - 100
259            }, 500);
260        }
261    }
262
263    // Класс для работы с табами
264    class FeedbackTabs {
265        constructor(container) {
266            this.container = $(container);
267            this.tabs = this.container.find('.feedback-tabs__nav-button');
268            this.contents = this.container.find('.feedback-tabs__content');
269            
270            this.init();
271        }
272
273        init() {
274            this.bindEvents();
275        }
276
277        bindEvents() {
278            this.tabs.on('click', (e) => {
279                e.preventDefault();
280                const $tab = $(e.currentTarget);
281                const targetTab = $tab.data('tab');
282                this.switchTab(targetTab);
283            });
284
285            // Поддержка клавиатуры
286            this.tabs.on('keydown', (e) => {
287                if (e.key === 'Enter' || e.key === ' ') {
288                    e.preventDefault();
289                    $(e.currentTarget).click();
290                }
291            });
292        }
293
294        switchTab(targetTab) {
295            // Обновить активные кнопки
296            this.tabs.removeClass('feedback-tabs__nav-button--active');
297            this.tabs.filter(`[data-tab="${targetTab}"]`).addClass('feedback-tabs__nav-button--active');
298
299            // Обновить активный контент
300            this.contents.removeClass('feedback-tabs__content--active');
301            this.container.find(`#tab-${targetTab}`).addClass('feedback-tabs__content--active');
302
303            // Отправить событие смены таба
304            $(document).trigger('feedbackTabChanged', { tab: targetTab });
305        }
306    }
307
308    // Утилиты
309    const Utils = {
310        // Дебаунс функция
311        debounce(func, wait) {
312            let timeout;
313            return function executedFunction(...args) {
314                const later = () => {
315                    clearTimeout(timeout);
316                    func(...args);
317                };
318                clearTimeout(timeout);
319                timeout = setTimeout(later, wait);
320            };
321        },
322
323        // Троттлинг функция
324        throttle(func, limit) {
325            let inThrottle;
326            return function() {
327                const args = arguments;
328                const context = this;
329                if (!inThrottle) {
330                    func.apply(context, args);
331                    inThrottle = true;
332                    setTimeout(() => inThrottle = false, limit);
333                }
334            };
335        },
336
337        // Проверка поддержки localStorage
338        supportsLocalStorage() {
339            try {
340                return 'localStorage' in window && window['localStorage'] !== null;
341            } catch (e) {
342                return false;
343            }
344        },
345
346        // Сохранение черновика формы
347        saveDraft(formId, data) {
348            if (this.supportsLocalStorage()) {
349                localStorage.setItem(`feedback_draft_${formId}`, JSON.stringify(data));
350            }
351        },
352
353        // Загрузка черновика формы
354        loadDraft(formId) {
355            if (this.supportsLocalStorage()) {
356                const draft = localStorage.getItem(`feedback_draft_${formId}`);
357                return draft ? JSON.parse(draft) : null;
358            }
359            return null;
360        },
361
362        // Удаление черновика
363        removeDraft(formId) {
364            if (this.supportsLocalStorage()) {
365                localStorage.removeItem(`feedback_draft_${formId}`);
366            }
367        }
368    };
369
370    // Инициализация при загрузке DOM
371    $(document).ready(function() {
372        // Инициализация форм обратной связи
373        $('.feedback-form__inner').each(function() {
374            const form = new FeedbackForm(this);
375            
376            // Сохранить оригинальный текст кнопки
377            const submitBtn = $(this).find('.feedback-form__submit');
378            submitBtn.data('original-text', submitBtn.text());
379            
380            // Загрузить черновик если есть
381            const formId = $(this).closest('.feedback-form').attr('id');
382            if (formId) {
383                const draft = Utils.loadDraft(formId);
384                if (draft) {
385                    $(this).find('[name="name"]').val(draft.name || '');
386                    $(this).find('[name="email"]').val(draft.email || '');
387                    $(this).find('[name="subject"]').val(draft.subject || '');
388                    $(this).find('[name="message"]').val(draft.message || '');
389                }
390                
391                // Сохранять черновик при изменении
392                const debouncedSave = Utils.debounce(() => {
393                    const data = {
394                        name: $(this).find('[name="name"]').val(),
395                        email: $(this).find('[name="email"]').val(),
396                        subject: $(this).find('[name="subject"]').val(),
397                        message: $(this).find('[name="message"]').val()
398                    };
399                    Utils.saveDraft(formId, data);
400                }, 1000);
401                
402                $(this).find('input, textarea').on('input', debouncedSave);
403            }
404        });
405
406        // Инициализация табов
407        $('.feedback-tabs').each(function() {
408            new FeedbackTabs(this);
409        });
410
411        // Переключение полного текста сообщений
412        $(document).on('click', '.feedback-table__message-toggle', function(e) {
413            e.preventDefault();
414            const $toggle = $(this);
415            const $preview = $toggle.siblings('.feedback-table__message-preview');
416            const $full = $toggle.siblings('.feedback-table__message-full');
417            
418            if ($full.is(':visible')) {
419                $full.hide();
420                $preview.show();
421                $toggle.text('Показать полностью');
422            } else {
423                $preview.hide();
424                $full.show();
425                $toggle.text('Свернуть');
426            }
427        });
428
429        // Обработка успешной отправки формы
430        $(document).on('feedbackFormSuccess', function(e, data) {
431            // Удалить черновик после успешной отправки
432            const formId = $(e.target).closest('.feedback-form').attr('id');
433            if (formId) {
434                Utils.removeDraft(formId);
435            }
436            
437            // Можно добавить дополнительную логику, например, аналитику
438            if (typeof gtag !== 'undefined') {
439                gtag('event', 'feedback_submitted', {
440                    'event_category': 'feedback',
441                    'event_label': data.type
442                });
443            }
444        });
445
446        // Плавная прокрутка к якорям
447        $('a[href^="#feedback"]').on('click', function(e) {
448            e.preventDefault();
449            const target = $($(this).attr('href'));
450            if (target.length) {
451                $('html, body').animate({
452                    scrollTop: target.offset().top - 100
453                }, 800);
454            }
455        });
456
457        // Автоматическое изменение размера textarea
458        $('textarea').each(function() {
459            this.setAttribute('style', 'height:' + (this.scrollHeight) + 'px;overflow-y:hidden;');
460        }).on('input', function() {
461            this.style.height = 'auto';
462            this.style.height = (this.scrollHeight) + 'px';
463        });
464    });
465
466    // Экспорт для глобального использования
467    window.FeedbackSystem = {
468        FeedbackForm,
469        FeedbackTabs,
470        Utils
471    };
472
473})(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.