PageSourceSearch

https://ancor.kz/local/templates/.default/js/VacancyFilter.js?173081176211653

js ancor.kz collected 2026-09-30 19:11:47 UTC 11,653 bytes, 369 lines download raw bytes

1/**
2 * Фильтр вакансий
3 * @param {{counterSelector: string = '.i-vacancy-page-items', loadUri: string = './', timeoutInMs: number = 60, itemsContainerSelector: string = '', form: Object, preloaderSelector: string = ''}} options
4 * @constructor
5 */
6function VacancyFilter(options) {
7    // ALLOWED OPTIONS
8
9    /**
10     * Cелектор списка элементов
11     * @type {string}
12     */
13    this.itemsContainerSelector = options.itemsContainerSelector || '.i-vacancy-page-items';
14    /**
15     * Cелектор количества записей
16     * @type {string}
17     */
18    this.counterSelector = options.counterSelector || '';
19    /**
20     * URI для ajax запроса
21     * @type {string}
22     */
23    this.loadUri = options.loadUri || './';
24    /**
25     * Минимальное время перерыва между запросами
26     * @type {number}
27     */
28    this.timeoutInMs = options.timeoutInMs || 60;
29    /**
30     * Объекст формы
31     * @type {Object}
32     */
33    this.form = options.form || {};
34    /**
35     * Селектор прелоадера
36     * @type {string}
37     */
38    this.preloaderSelector = options.preloaderSelector || '';
39
40    // <-- ALLOWED OPTIONS
41
42    /**
43     * Список элементов
44     * @type {jQuery}
45     */
46    this.$itemsListObject = $(this.itemsContainerSelector);
47    /**
48     * Счётчик элементов
49     * @type {jQuery|undefined}
50     */
51    this.$counterObject = this.counterSelector.length > 0 ? $(this.counterSelector) : undefined;
52    /**
53     * Требуется ли интеграция с ShowMoreComponent
54     * @type {boolean}
55     */
56    this.isWithShowMoreComponent = false;
57
58    this.Preloader = this.createPreloaderInstance(this.preloaderSelector);
59    this.Filter = this.createFilterInstance();
60    this.FilterRequest = this.createFilterRequestInstance(this.timeoutInMs);
61}
62
63VacancyFilter.prototype.removeParamsFromUrl = function() {
64    if (typeof window.history.replaceState === 'function') {
65        // window.history.replaceState('', document.title, './');
66    }
67};
68
69/** @param {*} filter */
70VacancyFilter.prototype.setFilter = function(filter) {
71    this.Filter.set(filter);
72};
73
74VacancyFilter.prototype.saveToBrowserHistory = function(url) {
75    function queryStringToObject (queryString) {
76        let a = queryString.split('&')
77        let so = {}
78        a.forEach(item => {
79            let p = item.split('=')
80            if (p.length === 2) {
81                so[p[0]] = so[p[0]] === undefined ? [] : so[p[0]]
82                so[p[0]].push(p[1])
83            }
84        })
85
86        return so
87    }
88    function objectToQueryString (p) {
89        const d = []
90        for (const k in p) {
91            if (Array.isArray(p[k])) {
92                p[k].forEach(v => {
93                    d.push(k + '=' + v)
94                })
95            } else {
96                d.push(k + '=' + p[k])
97            }
98        }
99
100        return d.join('&')
101    }
102
103    const $form = $('<form></form>')
104    $('.i-filterSearch input, .i-filterSearch select').each(function() {
105        const name = $(this).attr('name')
106        const value = $(this).val()
107        if (name) {
108            if (typeof value === 'object' && value.length) {
109                const c = $(this).clone()
110                c.appendTo($form)
111                c.val($(this).val())
112            } else if (value) {
113                const c = $(this).clone()
114                c.appendTo($form)
115                c.val($(this).val())
116            }
117        }
118    })
119    const up = url.split('?')
120    const n = queryStringToObject($form.serialize())
121
122    if (typeof n[encodeURIComponent('cityId[]')] === 'undefined') {
123        // n['nocity'] = '1'
124    } else if (typeof n['nocity'] !== 'undefined') {
125        // delete n['nocity']
126    }
127    if (up.length === 2) {
128        const a = queryStringToObject(up[1])
129        for (var l in a) {
130            if (typeof n[l] === 'undefined' && l)
131                n[l] = a[l]
132        }
133    }
134
135    window.history.pushState(null, null, up[0] + '?' + objectToQueryString(n));
136}
137
138/**
139 * @param {{customLoadUri: string = '', saveItemsList: boolean = false, ignoreEmptyFilter: boolean = false}} options
140 * @returns {boolean}
141 */
142VacancyFilter.prototype.loadByFilter = function(options = {}) {
143    var customLoadUri = options.customLoadUri || undefined;
144    var ignoreEmptyFilter = options.ignoreEmptyFilter || false;
145    var saveItemsList = options.saveItemsList || false;
146
147    if (!this.FilterRequest.canSend()) {
148        return false;
149    }
150
151    if (!ignoreEmptyFilter && this.Filter.isEmpty()) {
152        return true;
153    }
154
155    this.Preloader.show();
156    this.FilterRequest.start();
157    this.removeParamsFromUrl();
158
159    var currentPostData = this.Filter.getHttpRequestData();
160    this.Filter.clear();
161
162    var globalThis = this;
163    var intervalId;
164
165    let url = customLoadUri || this.loadUri;
166
167
168    $.ajax({
169        url: url,
170        data: currentPostData,
171        type: 'POST',
172        success: function(data) {
173            if (typeof window.history.pushState === 'function') {
174                globalThis.saveToBrowserHistory(url)
175            }
176
177            var $data = $(data);
178            var $counter = $data.find(globalThis.counterSelector);
179            var $itemsList = $data.find(globalThis.itemsContainerSelector);
180
181            if (!saveItemsList) {
182                globalThis.$itemsListObject.empty();
183            }
184            if ($counter.length > 0) {
185                globalThis.setCounterValue($counter.html());
186            }
187            globalThis.$itemsListObject.append($itemsList.execInit().children());
188
189            if (globalThis.isWithShowMoreComponent) {
190                globalThis.withShowMoreComponent({
191                    $loadedData: $data,
192                });
193            }
194
195            globalThis.FilterRequest.cancel();
196            globalThis.Preloader.hide();
197
198            intervalId = setInterval(function() {
199                if (globalThis.loadByFilter({ customLoadUri: customLoadUri })) {
200                    clearInterval(intervalId);
201                }
202            }, globalThis.timeoutInMs);
203        },
204    });
205
206    return true;
207};
208
209/** @param {*} counterValue */
210VacancyFilter.prototype.setCounterValue = function(counterValue) {
211    if (typeof this.$counterObject !== undefined) {
212        this.$counterObject.val(counterValue)
213    }
214};
215
216/**
217 * Переназначает события элементов пагинации компонента ShowMoreComponent.
218 * Необходимо при создании объекта VacancyFilter вызвать этот метод,
219 * благодаря этому после каждого ajax запроса ShowMoreComponent
220 * будет переинициализирован этим методом.
221 *
222 * @returns {VacancyFilter}
223 */
224VacancyFilter.prototype.withShowMoreComponent = function(options = {}) {
225    // user options
226    this.paginationContainer = options.paginationContainer || this.paginationContainer || '';
227    if (this.paginationContainer.length === 0) {
228        return this;
229    }
230
231    // component options (inside VacancyFilter)
232    var $loadedData = options.$loadedData || undefined;
233    this.isWithShowMoreComponent = true;
234
235    var $paginationContainer = $(this.paginationContainer);
236    var $loadedPaginationContainer = undefined;
237    if ($loadedData !== undefined) {
238        $loadedPaginationContainer = $loadedData.find(this.paginationContainer);
239    }
240
241    if ($loadedPaginationContainer !== undefined) {
242        var html = $loadedPaginationContainer.length > 0 ? $loadedPaginationContainer.html() : '';
243        $paginationContainer.html(html);
244        $paginationContainer.execInit();
245    }
246
247    var $showMoreBtn = $paginationContainer.find('.i-showMore');
248    var $pageNavItems = $paginationContainer.find('.i-pageNavigation-item');
249    var globalThis = this;
250
251    $showMoreBtn.off().on('click', function(e) {
252        e.preventDefault();
253        globalThis.setCurrentFilter();
254        globalThis.loadByFilter({
255            customLoadUri: $showMoreBtn.data('href') || '',
256            ignoreEmptyFilter: true,
257            saveItemsList: true,
258        });
259    });
260
261    $pageNavItems.each(function() {
262        var $currentItem = $(this);
263        $currentItem.on('click', function(e) {
264            e.preventDefault();
265            globalThis.setCurrentFilter();
266            globalThis.loadByFilter({
267                customLoadUri: $currentItem.attr('href'),
268                ignoreEmptyFilter: true,
269            });
270        });
271    });
272
273    return this;
274};
275
276/**
277 * Устанавливает корректный фильтр со страницы
278 */
279VacancyFilter.prototype.setCurrentFilter = function () {
280    this.setFilter(this.form.serializeArray());
281};
282
283/**
284 * Создаёт новый объект фильтра
285 * @returns {{getHttpRequestData: (function(): {}), set: set, get: (function(): []), currentFilter: [], clear: clear, isEmpty: (function(): boolean)}}
286 */
287VacancyFilter.prototype.createFilterInstance = function() {
288    return {
289        currentFilter: [],
290
291        set: function(filter) {
292            this.currentFilter = filter;
293        },
294        get: function() {
295            return this.currentFilter;
296        },
297        clear: function() {
298            this.currentFilter = [];
299        },
300        isEmpty: function() {
301            return this.currentFilter.length === 0;
302        },
303        getHttpRequestData: function() {
304            var data = {};
305            this.get().forEach(function(filterItem) {
306                if (filterItem.name.endsWith('[]')) {
307                    if (data[filterItem.name] === undefined) {
308                        data[filterItem.name] = [];
309                    }
310                    data[filterItem.name].push(filterItem.value);
311                } else {
312                    data[filterItem.name] = filterItem.value;
313                }
314            });
315
316            return data;
317        },
318    }
319};
320
321/**
322 * Создаёт новый объект запроса фильтра
323 * @param timeoutInMs - минимальное время перерыва между запросами в миллисекундах
324 * @returns {{cancel: cancel, blocked: boolean, start: start, lastRequestWas: number, canSend: (function(): boolean)}}
325 */
326VacancyFilter.prototype.createFilterRequestInstance = function(timeoutInMs = 0) {
327    return {
328        blocked: false,
329        lastRequestWas: Date.now(),
330
331        start: function() {
332            this.blocked = true;
333        },
334        cancel: function() {
335            this.lastRequestWas = Date.now();
336            this.blocked = false;
337        },
338        canSend: function() {
339            if (timeoutInMs > 0 && Date.now() - this.lastRequestWas < timeoutInMs) {
340                return false;
341            }
342            return this.blocked === false;
343        },
344    }
345};
346
347/**
348 * Создаёт новый объект прелоадера
349 * @param {string} preloaderSelector
350 * @returns {{hide: (function(): undefined), show: (function(): undefined), $instance: (*|jQuery|HTMLElement|undefined)}}
351 */
352VacancyFilter.prototype.createPreloaderInstance = function(preloaderSelector) {
353    return {
354        $instance: preloaderSelector.length > 0 ? $(preloaderSelector) : undefined,
355
356        show: function() {
357            if (this.$instance === undefined) {
358                return;
359            }
360            this.$instance.css('display', 'block');
361        },
362        hide: function() {
363            if (this.$instance === undefined) {
364                return;
365            }
366            this.$instance.css('display', 'none');
367        },
368    }
369}

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.