PageSourceSearch

https://4glaza.ru/local/templates/main/components/intervolga.custom/catalog.smart.filter/main/script.js

js 4glaza.ru collected 2026-10-02 10:11:47 UTC 20,170 bytes, 778 lines download raw bytes

1(function (window)
2{
3	'use strict';
4
5	window.IvSmartFilter = function (parameters)
6	{
7		this.expandedLocalStorage = 'catalog_filter_expanded_state';
8
9		this.catalogItemsBlockId = 'catalog_items';
10		this.catalogItems = BX(this.catalogItemsBlockId);
11
12		this.idTopDesciption = 'wd-top-description';
13		this.idBottomDesciption = 'wd-bottom-description';
14
15		this.classCatalog = 'catalog';
16		this.classCatalogContent = 'catalog__content';
17		this.classCatalogAppend = 'catalog__append';
18		this.classSeriesDesc = 'series__description';
19		this.classFilterItem = 'filter__item';
20		this.classFilterRange = 'filter-range';
21		this.classFilterCollapse = 'filter__body';
22		this.classFilterSection = 'filter__section';
23		this.classSeries = 'filter-check__item';
24		this.classFilterCount = 'count';
25		this.classSeriesContainer = 'catalog__prepend';
26		this.classSeriesTooltip = parameters.SERIES_TOOLTIP;
27
28		this.ajaxURL = parameters.AJAX_URL;
29		this.updateDelay = parameters.UPDATE_DELAY ?? 1000;
30
31		this.nodeCatalogContent = document.querySelector('div.' + this.classCatalogContent);
32		this.nodeSidebarFilter = BX(parameters.HTML_IDS.SIDEBAR_FILTER);
33
34		this.catalogTopFilterBlockId = parameters.HTML_IDS.CATALOG_TOP_FILTER;
35		this.nodeTopFilter = BX(parameters.HTML_IDS.CATALOG_TOP_FILTER);
36
37		this.seriesBlockId = parameters.HTML_IDS.FILTER_SERIES;
38		this.nodeSeriesFilter = BX(parameters.HTML_IDS.FILTER_SERIES);
39		this.btnReset = BX(parameters.HTML_IDS.FILTER_CONTROL_RESET);
40		this.nodeFounded = BX(parameters.HTML_IDS.FILTER_FOUNDED);
41		this.nodeFoundedCount = BX(parameters.HTML_IDS.FILTER_FOUNDED_COUNT);
42		this.nodeFoundedCountMobile = BX(parameters.HTML_IDS.FILTER_FOUNDED_COUNT_MOBILE);
43
44		this.cache = [];
45		this.cacheKey = BX.util.hashCode(JSON.stringify(this.getSelectedFilter()));
46		this.hashBrands = BX.util.hashCode(this.getSelectedBrands().toString());
47
48		this.filterTags = new window.IvSmartFilterTags({
49			filter: this,
50			idFilterTags: parameters.HTML_IDS.FILTER_TAGS,
51		});
52
53		this.updateSeriesBlock(this.getSelectedBrands());
54		this.initSeriesTooltip();
55		this.initCollapseFilters();
56		this.initAjaxNavigation();
57		this.initBind();
58	};
59
60	window.IvSmartFilter.prototype = {
61		initBind: function ()
62		{
63			this.bindEvent('change', 'change_filter', this.changeFilter, this.nodeSidebarFilter);
64			this.bindEvent('click', 'collapse_filter', this.collapseFilter, this.nodeSidebarFilter);
65			this.bindSeriesFilter();
66			this.bindInStockFilter();
67		},
68
69		bindEvent: function(eventType, dataEvent, eventFunction, targetNode)
70		{
71			let nodes = targetNode.querySelectorAll('[data-event="' + dataEvent + '"]');
72
73			for (let node of nodes)
74			{
75				if(node.getAttribute('data-event-type') === 'ajax')
76				{
77					$(node).on(eventType, BX.proxy(eventFunction, this))
78				}
79				else
80				{
81					BX.bind(node, eventType, BX.proxy(eventFunction, this));
82				}
83			}
84		},
85
86		bindInStockFilter: function()
87		{
88			if(this.nodeTopFilter)
89			{
90				this.bindEvent('click', 'change_filter', this.changeFilter, this.nodeTopFilter);
91			}
92		},
93
94		bindSeriesFilter: function()
95		{
96			if(this.nodeSeriesFilter)
97			{
98				this.bindEvent('click', 'change_series', this.changeSeries, this.nodeSeriesFilter);
99			}
100		},
101
102		changeSeries: function(event)
103		{
104			let nodeSeries = event.target.closest('.' + this.classSeries);
105
106			if(nodeSeries)
107			{
108				let checkbox = nodeSeries.querySelector('input[type="checkbox"]');
109				$(this.nodeSeriesFilter.querySelectorAll('input')).not(checkbox).prop('checked', false);
110
111				this.changeFilter();
112			}
113		},
114
115		updateSeriesBlock: function(selectedBrands)
116		{
117			let isShow = selectedBrands && selectedBrands.length === 1;
118			let nodeSeriesContainer = document.querySelector('.' + this.classCatalog + ' .' + this.classSeriesContainer);
119			if(nodeSeriesContainer)
120			{
121				if(isShow)
122				{
123					BX.show(nodeSeriesContainer);
124				}
125				else
126				{
127					BX.hide(nodeSeriesContainer);
128				}
129			}
130		},
131
132		clearFilterSeries: function()
133		{
134			if(this.nodeSeriesFilter)
135			{
136				$(this.nodeSeriesFilter.querySelectorAll('input')).prop('checked', false);
137			}
138		},
139
140		changeFilter: function(event)
141		{
142			if(!!this.timer)
143			{
144				clearTimeout(this.timer);
145			}
146			let input = null;
147			if(event && event.target)
148			{
149				input = event.target;
150			}
151			this.timer = setTimeout(BX.delegate(function(){
152				this.updateFilter(input);
153			}, this), this.updateDelay);
154		},
155
156		updateFilter: function(input)
157		{
158			this.curFilterinput = input;
159			let selectedBrands = this.getSelectedBrands();
160			let hashBrands = BX.util.hashCode(selectedBrands.toString());
161			if(hashBrands !== this.hashBrands)
162			{
163				this.clearFilterSeries();
164				this.updateSeriesBlock(selectedBrands);
165			}
166			let filterValues = this.getSelectedFilter();
167			let newCacheKey = BX.util.hashCode(JSON.stringify(filterValues));
168
169			if(newCacheKey === this.cacheKey)
170			{
171				return;
172			}
173
174			this.cacheKey = newCacheKey;
175			this.freezeElement(this.nodeSidebarFilter);
176            this.freezeElement(this.nodeTopFilter);
177			this.freezeElement(this.nodeSeriesFilter);
178
179			if (this.cache[this.cacheKey])
180			{
181				this.updateCatalog(this.cache[this.cacheKey], true);
182			}
183			else
184			{
185				BX.ajax.loadJSON(
186					this.ajaxURL,
187					filterValues,
188					BX.delegate(this.updateCatalog, this)
189				);
190			}
191		},
192
193		getSelectedFilter: function ()
194		{
195			let filterValues = {
196				'ajax': 'y',
197			};
198
199			let values = this.getSelectedValues();
200
201			for (let value of values)
202			{
203				filterValues[value.name] = value.value;
204			}
205
206			return filterValues;
207		},
208
209		getSelectedValues: function ()
210		{
211			let values = [];
212
213			this.gatherInputsValues(values, this.nodeSidebarFilter.querySelectorAll('input'));
214			if(this.nodeTopFilter)
215			{
216				this.gatherInputsValues(values, this.nodeTopFilter.querySelectorAll('input'));
217			}
218			if(this.nodeSeriesFilter)
219			{
220				this.gatherInputsValues(values, this.nodeSeriesFilter.querySelectorAll('input'));
221			}
222
223			return values.filter((value) => value.value);
224		},
225
226		gatherInputsValues: function (values, elements)
227		{
228			if(!elements)
229			{
230				return;
231			}
232			values = values || [];
233
234			for(let element of elements)
235			{
236				if (!element.dataset.type)
237				{
238					continue;
239				}
240
241				let value = {
242					id: element.id,
243					name: element.name,
244					value: element.value,
245					type: element.dataset.type,
246					text: element.dataset.tagValue,
247				};
248
249				switch(element.dataset.type.toLowerCase())
250				{
251					case 'checkbox':
252						if(element.checked)
253						{
254							values.push(value);
255							
256							// Для поддержки старых ссылок вида reference_parameter_1026-from-1-to-1
257							let filterNameMax = element.name.replace('_MIN', '_MAX');
258							if(filterNameMax !== element.name)
259							{
260								let valueMax = BX.clone(value);
261								valueMax.name = filterNameMax;
262								values.push(valueMax);
263							}
264						}
265						break;
266					case 'range':
267						let nodeRange = element.closest('.' + this.classFilterRange);
268						if(nodeRange)
269						{
270							let minValue = Number(nodeRange.dataset.min);
271							let maxValue = Number(nodeRange.dataset.max);
272							let filterValue = Number(element.value);
273
274							if((element.dataset.inputMin && minValue < filterValue) ||
275								(element.dataset.inputMax && maxValue > filterValue))
276							{
277								values.push(value);
278							}
279						}
280						break;
281					case 'radio':
282						if(element.checked)
283						{
284							value.name = element.dataset.name;
285							values.push(value);
286						}
287						break;
288					case 'text':
289					case 'textarea':
290					case 'password':
291					case 'hidden':
292					case 'select-one':
293					case 'select-multiple':
294					default:
295						break;
296				}
297			}
298		},
299
300		updateCatalog: function (result, fromCache)
301		{
302			this.unfreezeElement(this.nodeSidebarFilter);
303            this.unfreezeElement(this.nodeTopFilter);
304
305			if (!!result && !!result.ITEMS)
306			{
307				this.updateFilterItems(result);
308				this.updateControls(result);
309				this.showPopupMessage(result);
310				this.sendFilterResult(result);
311
312				this.freezeElement(this.nodeCatalogContent);
313				let url = this.updateBrowserUrl(result);
314
315				$.get(url, BX.proxy(this.onAfterCatalogLoad, this));
316			}
317
318			if (!fromCache && this.cacheKey)
319			{
320				this.cache[this.cacheKey] = result;
321			}
322
323			if(fromCache)
324			{
325				let params = {
326					url: this.ajaxURL + 'ajax=y'
327				};
328				BX.onCustomEvent('onAjaxSuccess', [result, params]);
329			}
330		},
331
332		sendFilterResult: function (result)
333		{
334			this.nodeSidebarFilter.dispatchEvent(new CustomEvent('IvAjaxFilterResult', {
335				bubbles: true,
336				detail: {
337					result: result,
338				},
339			}));
340		},
341
342		updateControls: function (result)
343		{
344			if (!!this.nodeFounded && !!this.nodeFoundedCount)
345			{
346				let elementCount = parseInt(result.ELEMENT_COUNT);
347				this.nodeFoundedCount.innerText = elementCount;
348				if(!!this.nodeFoundedCountMobile)
349				{
350					this.nodeFoundedCountMobile.innerText = ' (' + elementCount + ')';
351				}
352			}
353
354			if (result.JS_FILTER_PARAMS.FILTER_USED)
355			{
356				this.showElement(this.btnReset)
357			}
358			else
359			{
360				this.hideElement(this.btnReset)
361			}
362		},
363
364		updateFilterItems: function (result)
365		{
366			for(let filterItem of BX.util.array_values(result.ITEMS))
367			{
368				this.updateFilterItem(filterItem);
369			}
370		},
371
372		updateFilterItem: function (filterItem)
373		{
374			if (!filterItem.VALUES)
375			{
376				return;
377			}
378			for (let value of BX.util.array_values(filterItem.VALUES))
379			{
380				let control = BX(value.CONTROL_ID);
381
382				if (!!control)
383				{
384					if(!!value.DISABLED && !value.CHECKED)
385					{
386						BX.addClass(control, 'pseudo-disabled');
387						$(control).prop('disabled', true);
388					}
389					else
390					{
391						BX.removeClass(control, 'pseudo-disabled');
392						$(control).prop('disabled', false);
393					}
394
395					let controlContainer = control.closest('.' + this.classFilterItem);
396					if(controlContainer)
397					{
398						let counter = controlContainer.querySelector('.' + this.classFilterCount);
399						if (counter && value.hasOwnProperty('ELEMENT_COUNT'))
400						{
401							counter.innerHTML = '(' + value.ELEMENT_COUNT + ')';
402						}
403					}
404
405					if(filterItem.DISPLAY_TYPE == 'F')
406					{
407						control.checked = !!value.CHECKED;
408					}
409					else if(filterItem.DISPLAY_TYPE == 'CUSTOM_CHECKBOX')
410					{
411						if(value.CONTROL_ID.includes('MIN'))
412						{
413							control.checked = !!value.HTML_VALUE;
414							control.disabled = filterItem.DISABLED && !value.HTML_VALUE;
415						}
416					}
417					else if(control.dataset.type == 'range')
418					{
419						let controlValue = value.HTML_VALUE ?? value.VALUE;
420						if(control.value != controlValue)
421						{
422							this.setRangeFilterValues(control, controlValue);
423						}
424					}
425				}
426			}
427		},
428
429		setRangeFilterValues: function(rangeControl, value)
430		{
431			let nodeRange = rangeControl.closest('.' + this.classFilterRange);
432
433			if(rangeControl.dataset.inputMin)
434			{
435				nodeRange.FilterRange.updateValues({ valueMin: value ? value : nodeRange.dataset.min });
436			}
437			else if(rangeControl.dataset.inputMax)
438			{
439				nodeRange.FilterRange.updateValues({ valueMax: value ? value : nodeRange.dataset.max });
440			}
441		},
442
443		initAjaxNavigation: function()
444		{
445			this.setBrowserHistoryState({'BASE_URL' : this.ajaxURL});
446			window.addEventListener("hashchange", this.updateCurrentBrowserState.bind(this));
447			setTimeout(function(){
448				BX.bind(window, 'popstate', BX.proxy(this.applyBrowserHistoryState, this));
449			}.bind(this), 500);
450		},
451
452		updateBrowserUrl: function (result)
453		{
454			let url = BX.util.htmlspecialcharsback(result.FILTER_AJAX_URL)
455				|| BX.util.htmlspecialcharsback(result.SEF_DEL_FILTER_URL);
456
457			if(url !== window.location.pathname)
458			{
459				result.CACHE_KEY = this.cacheKey;
460				result.CUR_FILTER_INPUT = this.curFilterinput ? this.curFilterinput.id : null;
461				this.setBrowserHistoryState(result, url, true);
462			}
463			return url;
464		},
465
466		updateCurrentBrowserState: function()
467		{
468			if(window.history.state && Object.keys(window.history.state).length)
469			{
470				return;
471			}
472			let result = this.cache[this.cacheKey];
473			if(result != undefined)
474			{
475				result.CACHE_KEY = this.cacheKey;
476				result.CUR_FILTER_INPUT = this.curFilterinput ? this.curFilterinput.id : null;
477			}
478			else
479			{
480				result = {};
481			}
482			this.setBrowserHistoryState(result);
483		},
484
485		setBrowserHistoryState: function(state, url, pushState = false)
486		{
487			if(!state)
488			{
489				state = this.cache[this.cacheKey];
490				if(state != undefined)
491				{
492					state.CACHE_KEY = this.cacheKey;
493					state.CUR_FILTER_INPUT = this.curFilterinput ? this.curFilterinput.id : null;
494				}
495				else
496				{
497					state = {};
498				}
499			}
500			if(!url)
501			{
502				url = window.location.pathname+window.location.search+window.location.hash;
503			}
504			let currentState = history.state;
505			let currentUrl = window.location.pathname+window.location.search+window.location.hash;
506			setTimeout(function(){
507				if(pushState)
508				{
509					window.history.replaceState(currentState, '', currentUrl);
510					window.history.pushState(state, '', url);
511				}
512				else
513				{
514					window.history.replaceState(state, '', url);
515				}
516			}, 100); // webdebug.seo затирает текущий stage
517		},
518
519		applyBrowserHistoryState: function(data)
520		{
521			let state = data.state;
522			if(!state || !Object.keys(state).length)
523			{
524				return;
525			}
526			if(state.BASE_URL)
527			{
528				let currentUrl = window.location.pathname+window.location.search+window.location.hash;
529				if(state.BASE_URL == currentUrl)
530				{
531					window.location.href = state.BASE_URL;
532				}
533				return;
534			}
535			if(state.CACHE_KEY === this.cacheKey)
536			{
537				return;
538			}
539			this.cacheKey = state.CACHE_KEY;
540			if(this.curFilterinput)
541			{
542				let currentFilterContainer = this.curFilterinput.closest(`[data-toggle="filter-tooltip"]`);
543				if(currentFilterContainer)
544				{
545					currentFilterContainer.ins.scrollToTarget();
546				}
547			}
548			this.curFilterinput = BX(state.CUR_FILTER_INPUT);
549			this.updateCatalog(state, true);
550		},
551
552		onAfterCatalogLoad: function (html)
553		{
554			let updatedElements = [];
555			let catalogHtml = $(html).find('div.' + this.classCatalogContent).html();
556			let catalogAppendHtml = $(html).find('div.' + this.classCatalogAppend).html();
557			let catalogItemsHtml = $(html).find('#' + this.catalogItemsBlockId).html();
558			var catalogTopFilterHtml = $(html).find('#' + this.catalogTopFilterBlockId).html();
559
560			let topSeoDescription = $(html).find('#' + this.idTopDesciption).html();
561			let bottomSeoDescription = $(html).find('#' + this.idBottomDesciption).html();
562
563			catalogHtml = BX.processHTML(catalogHtml);
564			catalogAppendHtml = BX.processHTML(catalogAppendHtml);
565			catalogItemsHtml = BX.processHTML(catalogItemsHtml);
566			catalogTopFilterHtml = BX.processHTML(catalogTopFilterHtml);
567
568			$(this.catalogItems).html(catalogItemsHtml.HTML);
569			$('div.' + this.classCatalogAppend).html(catalogAppendHtml.HTML);
570
571			$('#' + this.idTopDesciption).html(topSeoDescription);
572			$('#' + this.idBottomDesciption).html(bottomSeoDescription);
573			$('#' + this.catalogTopFilterBlockId).html(catalogTopFilterHtml.HTML);
574
575			BX.ajax.processScripts(catalogHtml.SCRIPT);
576
577			if(this.nodeSeriesFilter)
578			{
579				let hashBrands = BX.util.hashCode(this.getSelectedBrands().toString());
580				let seriesChecked = $(html).find('#' + this.seriesBlockId + ' input:checked')[0] || {};
581				let currentSeriesChecked = this.nodeSeriesFilter.querySelector('input:checked') || {};
582				if(hashBrands !== this.hashBrands || (seriesChecked.id !== currentSeriesChecked.id))
583				{
584					this.nodeSeriesFilter.innerHTML = $(html).find('#'+this.seriesBlockId).html();
585					updatedElements['SERIES_FILTER'] = true;
586					this.hashBrands = hashBrands;
587				}
588
589				let nodeSeriesDesc = this.nodeSeriesFilter.querySelector('.' + this.classSeriesDesc);
590				if (nodeSeriesDesc)
591				{
592					let description = $(html).find('.' + this.classSeriesDesc).html() || '';
593					nodeSeriesDesc.innerHTML = description;
594					if (description.trim())
595					{
596						BX.show(nodeSeriesDesc);
597					} else
598					{
599						BX.hide(nodeSeriesDesc);
600					}
601				}
602			}
603
604			this.filterTags.initBlock().drawCurrentTags();
605			this.initAfterLoadedCatalog(updatedElements);
606			this.unfreezeElement(this.nodeCatalogContent);
607			this.unfreezeElement(this.nodeSeriesFilter);
608		},
609
610		initAfterLoadedCatalog: function (updatedElements)
611		{
612			this.initCatalogBlock();
613
614			if(updatedElements['SERIES_FILTER'])
615			{
616				this.initSeriesTooltip();
617				this.bindSeriesFilter();
618			}
619		},
620
621		showPopupMessage: function(result)
622		{
623			if(this.curFilterinput)
624			{
625				let currentFilterContainer = this.curFilterinput.closest(`[data-toggle="filter-tooltip"]`);
626				if(currentFilterContainer)
627				{
628					let countProducts = parseInt(result.ELEMENT_COUNT);
629					currentFilterContainer.dataset.title = BX.message('FILTER_FOUND_TOTAL').replace("#COUNT#", countProducts);
630					currentFilterContainer.dispatchEvent(new Event('iv.filter-tooltip.show'));
631				}
632			}
633		},
634
635		hideElement: function (element)
636		{
637			BX.hide(element);
638		},
639
640		showElement: function (element)
641		{
642			BX.show(element);
643		},
644
645		freezeElement: function (element)
646		{
647			if(element)
648			{
649				BX.addClass(element, 'loader');
650			}
651		},
652
653		unfreezeElement: function (element)
654		{
655			if(element)
656			{
657				BX.removeClass(element, 'loader');
658			}
659		},
660
661		getSelectedBrands: function ()
662		{
663			let inputBrands = this.nodeSidebarFilter.querySelectorAll('[data-is-brand="true"]');
664			let brandsSelected = [];
665			for (let inputBrand of inputBrands)
666			{
667				if (inputBrand.checked)
668				{
669					brandsSelected.push(inputBrand.id);
670				}
671			}
672
673			return brandsSelected;
674		},
675
676		collapseFilter: function (event)
677		{
678			let sectionFilterNode = event.target.closest('.' + this.classFilterSection);
679			let collapseNode = sectionFilterNode.querySelector('.' + this.classFilterCollapse);
680			this.saveCollapseState(sectionFilterNode.dataset.propId, !collapseNode.classList.contains('show'));
681		},
682
683		saveCollapseState: function (propertyId, state)
684		{
685			let collapseState = this.getCollapseState();
686			collapseState[propertyId] = state;
687			localStorage.setItem(this.expandedLocalStorage, JSON.stringify(collapseState));
688		},
689
690		getCollapseState: function ()
691		{
692			return JSON.parse(localStorage.getItem(this.expandedLocalStorage)) ?? {};
693		},
694
695		initCollapseFilters: function ()
696		{
697			let collapseState = this.getCollapseState();
698			let collapseNodes = this.nodeSidebarFilter.querySelectorAll('.' + this.classFilterSection + ' .' + this.classFilterCollapse);
699
700			for (let collapseNode of collapseNodes)
701			{
702				let sectionNode = collapseNode.closest('.' + this.classFilterSection);
703				if(sectionNode)
704				{
705					let propId = sectionNode.dataset.propId;
706					let isCheckedInput = !!sectionNode.querySelector('[checked]');
707					if(!isCheckedInput && collapseState.hasOwnProperty(propId))
708					{
709						let nodeFilterCollapse = sectionNode.querySelector('.' + this.classFilterCollapse);
710						if(collapseState[propId] === true)
711						{
712							this.openCollapse(nodeFilterCollapse);
713						}
714						else
715						{
716							this.closeCollapse(nodeFilterCollapse);
717						}
718					}
719				}
720			}
721		},
722
723		openCollapse: function (node)
724		{
725			if(node)
726			{
727				$(node).collapse('show')
728			}
729		},
730
731		closeCollapse: function (node)
732		{
733			if(node)
734			{
735				$(node).collapse('hide')
736			}
737		},
738
739		initSeriesTooltip: function ()
740		{
741			if(window.Block && window.Block.Tooltip)
742			{
743				let isMobile = BX.hasClass(document.documentElement, 'bx-touch');
744				if(!isMobile && this.nodeSeriesFilter)
745				{
746					let seriesElements = this.nodeSeriesFilter.getElementsByClassName(this.classSeriesTooltip);
747					for(let node of seriesElements)
748					{
749						node.dataset.toggle = "tooltip";
750					}
751					window.Block.Tooltip.initAllBlocks();
752				}
753			}
754		},
755
756		initCatalogBlock: function ()
757		{
758			if(window.Block.Catalog && window.Block.Catalog.getAllInstances().length > 0)
759			{
760				window.Block.Catalog.getAllInstances()[0].update();
761			}
762			if(window.Block.Popper)
763			{
764				window.Block.Popper.initAllBlocks();
765			}
766			if(window.Block.ProductOptions && window.Block.ProductOptions.getAllBlocks().length > 0)
767			{
768				window.Block.ProductOptions.initAllBlocks();
769			}
770			if (window.Block.SliderLine)
771			{
772				window.Block.SliderLine.initAllBlocks();
773			}
774            this.bindInStockFilter();
775		},
776
777	};
778})(window);

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.