Your IP : 216.73.216.86


Current Path : /var/www/homesaver/www/bitrix/components/bitrix/main.ui.filter/templates/.default/src/
Upload File :
Current File : /var/www/homesaver/www/bitrix/components/bitrix/main.ui.filter/templates/.default/src/filter.js

/* eslint-disable */
import { Loc, Type } from 'main.core';
import { UI } from 'ui.notification';
import { Presets } from './presets';

;(function() {
	'use strict';

	BX.namespace('BX.Main');


	/**
	 * General filter class
	 * @param {object} params Component params
	 * @param {object} options Extends BX.Filter.Settings
	 * @param {object} types Field types from Bitrix\Main\UI\Filter\Type
	 * @param types.STRING
	 * @param types.SELECT
	 * @param types.DATE
	 * @param types.CUSTOM_DATE
	 * @param types.MULTI_SELECT
	 * @param types.NUMBER
	 * @param types.DEST_SELECTOR
	 * @param types.ENTITY_SELECTOR
	 * @param types.CUSTOM_ENTITY
	 * @param types.CHECKBOX
	 * @param types.CUSTOM
	 * @param types.ENTITY
	 * @param {object} dateTypes Date field types from Bitrix\Main\UI\Filter\DateType
	 * @param dateTypes.NONE
	 * @param dateTypes.YESTERDAY
	 * @param dateTypes.CURRENT_DAY
	 * @param dateTypes.CURRENT_WEEK
	 * @param dateTypes.CURRENT_MONTH
	 * @param dateTypes.CURRENT_QUARTER
	 * @param dateTypes.LAST_7_DAYS
	 * @param dateTypes.LAST_30_DAYS
	 * @param dateTypes.LAST_60_DAYS
	 * @param dateTypes.LAST_90_DAYS
	 * @param dateTypes.MONTH
	 * @param dateTypes.QUARTER
	 * @param dateTypes.YEAR
	 * @param dateTypes.EXACT
	 * @param dateTypes.LAST_WEEK
	 * @param dateTypes.LAST_MONTH
	 * @param dateTypes.RANGE
	 * @param dateTypes.NEXT_DAYS
	 * @param dateTypes.PREV_DAYS
	 * @param dateTypes.TOMORROW
	 * @param dateTypes.NEXT_MONTH
	 * @param dateTypes.NEXT_WEEK
	 * @param {object} numberTypes Number field types from Bitrix\Main\UI\Filter\NumberType
	 * @memberOf {BX.Main}
	 */
	BX.Main.Filter = function(params, options, types, dateTypes, numberTypes, additionalDateTypes, additionalNumberTypes)
	{
		this.params = params;
		this.search = null;
		this.popup = null;
		this.checkboxListPopup = null;
		this.presets = null;
		this.fields = null;
		this.types = types;
		this.dateTypes = dateTypes;
		this.additionalDateTypes = additionalDateTypes;
		this.additionalNumberTypes = additionalNumberTypes;
		this.numberTypes = numberTypes;
		this.settings = new BX.Filter.Settings(options, this);
		this.filter = null;
		this.api = null;
		this.isAddPresetModeState = false;
		this.firstInit = true;
		this.analyticsLabel = null;
		this.emitter = new BX.Event.EventEmitter();
		this.emitter.setEventNamespace('BX.Filter.Field');
		this.emitter.subscribe = function(eventName, listener) {
			BX.Event.EventEmitter.subscribe(
				this.emitter,
				eventName.replace('BX.Filter.Field:', ''),
				listener
			);
		}.bind(this);
		this.enableFieldsSearch = null;
		this.enableHeadersSections = null;

		this.init();
	};

	/**
	 * Converts string to camel case
	 * @param {string} string
	 * @return {*}
	 */
	function toCamelCase(string)
	{
		if (BX.type.isString(string))
		{
			string = string.toLowerCase();
			string = string.replace(/[\-_\s]+(.)?/g, function(match, chr) {
				return chr ? chr.toUpperCase() : '';
			});
			return string.substr(0, 1).toLowerCase() + string.substr(1);
		}

		return string;
	}

	//noinspection JSUnusedGlobalSymbols
	BX.Main.Filter.prototype = {
		init: function()
		{
			BX.bind(document, 'mousedown', BX.delegate(this._onDocumentClick, this));
			BX.bind(document, 'keydown', BX.delegate(this._onDocumentKeydown, this));
			BX.bind(window, 'load', BX.delegate(this.onWindowLoad, this));
			BX.addCustomEvent('Grid::ready', BX.delegate(this._onGridReady, this));

			this.getSearch().updatePreset(this.getParam('CURRENT_PRESET'));

			this.enableFieldsSearch = this.getParam('ENABLE_FIELDS_SEARCH', false);
			this.enableHeadersSections = this.getParam('HEADERS_SECTIONS', false);

			if (this.isAppliedDefaultPreset())
			{
				this.setDefaultPresetAppliedState(true);
			}
		},

		getEmitter: function()
		{
			return this.emitter;
		},


		onWindowLoad: function()
		{
			this.settings.get('AUTOFOCUS') && this.adjustFocus();
		},


		/**
		 * Removes apply_filter param from url
		 */
		clearGet: function()
		{
			if ('history' in window)
			{
				var url = window.location.toString();
				var clearUrl = BX.util.remove_url_param(url, 'apply_filter');
				window.history.replaceState(null, '', clearUrl);
			}
		},


		/**
		 * Adjusts focus on search field
		 */
		adjustFocus: function()
		{
			this.getSearch().adjustFocus();
		},

		_onAddPresetKeydown: function(event)
		{
			if (BX.Filter.Utils.isKey(event, 'enter'))
			{
				this._onSaveButtonClick();
			}
		},

		_onDocumentKeydown: function(event)
		{
			if (BX.Filter.Utils.isKey(event, 'escape'))
			{
				if (this.getPopup().isShown())
				{
					BX.onCustomEvent(window, 'BX.Main.Filter:blur', [this]);
					this.closePopup();

					if (this.getParam('VALUE_REQUIRED_MODE'))
					{
						this.restoreRemovedPreset();
					}

					if (this.getParam('VALUE_REQUIRED'))
					{
						if (!this.getSearch().getSquares().length)
						{
							this.getPreset().applyPinnedPreset();
						}
					}
				}
			}
		},


		/**
		 * Gets BX.Filter.Api instance
		 * @return {BX.Filter.Api}
		 */
		getApi: function()
		{
			if (!(this.api instanceof BX.Filter.Api))
			{
				this.api = new BX.Filter.Api(this);
			}

			return this.api;
		},


		/**
		 * Adds sidebar item
		 * @param {string} id
		 * @param {string} name
		 * @param {boolean} [pinned = false]
		 */
		addSidebarItem: function(id, name, pinned)
		{
			var Presets = this.getPreset();
			var presetsContainer = Presets.getContainer();
			var sidebarItem = Presets.createSidebarItem(id, name, pinned);
			var preset = Presets.getPresetNodeById(id);

			if (BX.type.isDomNode(preset))
			{
				BX.remove(preset);
				presetsContainer.insertBefore(sidebarItem, Presets.getAddPresetField());

			}
			else
			{
				presetsContainer && presetsContainer.insertBefore(sidebarItem, Presets.getAddPresetField());
			}

			BX.bind(sidebarItem, 'click', BX.delegate(Presets._onPresetClick, Presets));
		},


		/**
		 * Saves user settings
		 * @param {boolean} [forAll = false]
		 */
		saveUserSettings: function(forAll)
		{
			var optionsParams = {'FILTER_ID': this.getParam('FILTER_ID'), 'GRID_ID': this.getParam('GRID_ID'), 'action': 'SET_FILTER_ARRAY'};
			var Presets = this.getPreset();
			var currentPresetId = Presets.getCurrentPresetId();
			var presetsSettings = {};

			this.params['PRESETS'] = BX.clone(this.editablePresets);
			presetsSettings.current_preset = currentPresetId;

			Presets.getPresets().forEach(function(current, index) {
				var presetId = Presets.getPresetId(current);

				if (presetId && presetId !== 'tmp_filter')
				{
					var presetData = Presets.getPreset(presetId);

					presetData.TITLE = BX.util.htmlspecialchars(BX.util.htmlspecialcharsback(presetData.TITLE));
					presetData.SORT = index;
					Presets.updatePresetName(current, presetData.TITLE);

					presetsSettings[presetId] = {
						sort: index,
						name: presetData.TITLE,
						fields: this.preparePresetSettingsFields(presetData.FIELDS),
						rows: presetData.FIELDS.map((field) => field.NAME),
						for_all: (
							(forAll && !BX.type.isBoolean(presetData.FOR_ALL)) ||
							(forAll && presetData.FOR_ALL === true)
						)
					}
				}
			}, this);

			this.saveOptions(presetsSettings, optionsParams, null, forAll);
		},


		/**
		 * Checks is for all
		 * @return {boolean}
		 */
		isForAll: function(forAll)
		{
			var checkbox = this.getForAllCheckbox();
			return (
				(BX.type.isBoolean(forAll) && forAll) ||
				(!!checkbox && !!checkbox.checked)
			);
		},


		/**
		 * Gets for all checkbox
		 * @return {?HTMLElement}
		 */
		getForAllCheckbox: function()
		{
			if (!this.forAllCheckbox)
			{
				this.forAllCheckbox = BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classForAllCheckbox);
			}

			return this.forAllCheckbox;
		},


		/**
		 * Prepares preset settings fields
		 * @param fields
		 * @return {?object}
		 */
		preparePresetSettingsFields: function(fields)
		{
			var result = {};
			var valuesKeys;

			(fields || []).forEach(function(current) {
				switch (current.TYPE)
				{
					case this.types.STRING : {
						result[current.NAME] = current.VALUE;
						break;
					}

					case this.types.TEXTAREA : {
						result[current.NAME] = current.VALUE;
						break;
					}

					case this.types.SELECT : {
						result[current.NAME] = 'VALUE' in current.VALUE ? current.VALUE.VALUE : '';
						break;
					}

					case this.types.MULTI_SELECT : {
						if (BX.type.isArray(current.VALUE) && current.VALUE.length)
						{
							current.VALUE.forEach(function(curr, index) {
								result[current.NAME] = BX.type.isPlainObject(result[current.NAME]) ? result[current.NAME] : {};
								result[current.NAME][index] = curr.VALUE;
							}, this);
						}
						break;
					}

					case this.types.CHECKBOX : {
						if (BX.type.isArray(current.VALUE) && current.VALUE.length)
						{
							current.VALUE.forEach(function(curr, index) {
								result[current.NAME] = BX.type.isPlainObject(result[current.NAME]) ? result[current.NAME] : {};
								result[current.NAME][index] = curr.VALUE;
							}, this);
						}
						break;
					}

					case this.types.DATE : {
						if (BX.type.isPlainObject(current.VALUES))
						{
							valuesKeys = Object.keys(current.VALUES);
							result[current.NAME + '_datesel'] = current.SUB_TYPE.VALUE;
							valuesKeys.forEach(function(curr) {
								result[current.NAME + curr] = current.VALUES[curr];
							}, this);
						}
						break;
					}

					case this.types.NUMBER : {
						if (BX.type.isPlainObject(current.VALUES))
						{
							valuesKeys = Object.keys(current.VALUES);
							result[current.NAME + '_numsel'] = current.SUB_TYPE.VALUE;
							valuesKeys.forEach(function(curr) {
								result[current.NAME + curr] = current.VALUES[curr];
							}, this);
						}
						break;
					}

					case this.types.DEST_SELECTOR : {
						if (BX.type.isPlainObject(current.VALUES))
						{
							result[current.NAME] = current.VALUES._value;
							result[current.NAME + '_label'] = current.VALUES._label;
						}
						break;
					}

					case this.types.DEST_SELECTOR:
					case this.types.ENTITY_SELECTOR:
					case this.types.CUSTOM_ENTITY: {
						if (BX.type.isPlainObject(current.VALUES))
						{
							result[current.NAME] = current.VALUES._value;
							result[current.NAME + '_label'] = current.VALUES._label;
						}
						break;
					}

					default : {
						break;
					}
				}
			}, this);

			return result;
		},


		/**
		 * Saves preset
		 */
		savePreset: function()
		{
			var presetId = 'filter_' + (+new Date());
			var presetName = BX.util.htmlspecialcharsback(this.getPreset().getAddPresetFieldInput().value);

			this.updatePreset(presetId, presetName, null, true, null, null, true);
			this.addSidebarItem(presetId, presetName);
			this.getPreset().applyPreset(presetId);
			this.getPreset().activatePreset(presetId);
			this.applyFilter();
		},


		/**
		 * Updates preset
		 * @param {string} presetId
		 * @param {?string} [presetName]
		 * @param {?boolean} [reset]
		 * @param {?boolean} [sort]
		 * @param {?function} [beforeLoad]
		 * @param {?function} [afterLoad]
		 * @param {boolean} [isNew]
		 * @return {BX.Promise}
		 */
		updatePreset: function(presetId, presetName, reset, sort, beforeLoad, afterLoad, isNew)
		{
			var fields = this.getFilterFieldsValues();
			var sourceFields = this.getPreset().getFields().map(function(curr) { return BX.data(curr, 'name'); });
			var preset = this.getPreset().getCurrentPresetData();
			var params = {'FILTER_ID': this.getParam('FILTER_ID'), 'GRID_ID': this.getParam('GRID_ID'), 'action': 'SET_FILTER'};
			var rows, value, tmpPresetNode, tmpPresetInput, presets;
			var data = {};

			data.additional = {};

			if (presetId !== 'tmp_filter' && presetId !== 'default_filter' && !isNew)
			{
				var additional = BX.type.isArray(preset.ADDITIONAL) ? preset.ADDITIONAL : [];

				additional.forEach(function(field) {
					Object.keys(fields).forEach(function(key) {
						if (key.indexOf(field.NAME) !== -1)
						{
							data.additional[key] = fields[key];
							delete fields[key];
						}
					});
				});
			}

			rows = Object.keys(fields);

			if (!reset)
			{
				data.apply_filter = 'Y';
			}
			else
			{
				data.clear_filter = 'Y';
			}

			data.save = 'Y';
			data.fields = fields;
			data.rows = sourceFields.join(',');

			data.preset_id = presetId || preset.ID;

			if (BX.type.isNotEmptyString(presetName))
			{
				data.name = BX.util.htmlspecialchars(presetName);
			}
			else
			{
				tmpPresetNode = this.getPreset().getPresetNodeById(data.preset_id);
				tmpPresetInput = this.getPreset().getPresetInput(tmpPresetNode);

				if (BX.type.isDomNode(tmpPresetInput) && BX.type.isNotEmptyString(tmpPresetInput.value))
				{
					data.name = tmpPresetInput.value;
				}
				else
				{
					data.name = preset.TITLE;
				}
			}

			if ((!('sort' in data) || !BX.type.isNumber(data.sort)) && sort)
			{
				presets = this.getParam('PRESETS');
				data.sort = presets.length + 2;
			}

			if (!reset)
			{
				rows.forEach(function(key) {
					if (BX.type.isArray(data.fields[key]))
					{
						value = data.fields[key].length ? {} : '';

						data.fields[key].forEach(function(val, index) {
							value[index] = val;
						}, this);

						if (value || BX.type.isNumber(value) || BX.type.isBoolean(value))
						{
							data.fields[key] = value;
						}
					}
				}, this);
			}

			if (data.preset_id === 'tmp_filter' || this.isAddPresetEnabled() || reset)
			{
				this.updateParams(data);
			}

			if (BX.type.isFunction(beforeLoad))
			{
				beforeLoad();
			}

			var promise = new BX.Promise(null, this);
			promise.setAutoResolve('fulfill', 0);

			promise.then(function() {
				var afterPromise = new BX.Promise(null, this);
				this.saveOptions(data, params, BX.proxy(afterPromise.fulfill, afterPromise));
				return afterPromise;
			})
			.then(function() {
				!!afterLoad && afterLoad();
			});

			return promise;
		},


		/**
		 * Saves fields sort
		 */
		saveFieldsSort: function()
		{
			var params = {'FILTER_ID': this.getParam('FILTER_ID'), 'GRID_ID': this.getParam('GRID_ID'), 'action': 'SET_FILTER'};
			var fields = this.getPreset().getFields();
			var data = {};

			data.preset_id = 'default_filter';

			if (BX.type.isArray(fields))
			{
				data.rows = fields.map(function(current) {
					return BX.data(current, 'name');
				});
				data.rows = data.rows.join(',');
			}

			this.updateParams(data);
			this.saveOptions(data, params);
		},


		/**
		 * Updates params
		 * @param {object} data
		 */
		updateParams: function(data)
		{
			var preset, presets;
			var fields = [];

			if (BX.type.isPlainObject(data) && 'preset_id' in data)
			{
				preset = this.getPreset().getPreset(data.preset_id);

				if (BX.type.isPlainObject(preset))
				{
					if ('name' in data && BX.type.isNotEmptyString(data.name))
					{
						preset.TITLE = data.name;
					}

					if ('rows' in data && !('fields' in data))
					{
						data.fields = {};

						data.rows.split(',').forEach(function(curr) {
							data.fields[curr] = '';
						});
					}

					if ('fields' in data)
					{
						preset.FIELDS = this.preparePresetFields(data.fields, data.rows);
					}

					if ('additional' in data && preset.ID !== 'tmp_filter')
					{
						preset.ADDITIONAL = this.preparePresetFields(data.additional, data.rows);
					}
				}
				else
				{
					presets = this.getParam('PRESETS');
					preset = {
						ID: data.preset_id,
						TITLE: data.name,
						SORT: (presets.length + 2),
						FIELDS: this.preparePresetFields(data.fields, data.rows)
					};

					presets.push(preset);
				}
			}
		},


		/**
		 * Prepares preset fields
		 * @param {object[]} dataFields
		 * @param rows
		 * @return {object[]}
		 */
		preparePresetFields: function(dataFields, rows)
		{
			var fieldKeys, field;
			var fields = [];

			if (BX.type.isPlainObject(dataFields))
			{
				rows = BX.type.isNotEmptyString(rows) ? rows.split(',') : [];
				fieldKeys = rows.length ? rows : Object.keys(dataFields);
				fieldKeys.forEach(function(current) {
					current = current
						.replace('_datesel', '')
						.replace('_numsel', '')
						.replace('_' + BX.Filter.AdditionalFilter.Type.IS_EMPTY, '')
						.replace('_' + BX.Filter.AdditionalFilter.Type.HAS_ANY_VALUE, '');
					field = BX.clone(this.getFieldByName(current));

					if (BX.type.isPlainObject(field))
					{
						field.ADDITIONAL_FILTER = BX.Filter.AdditionalFilter.fetchAdditionalFilter(current, dataFields);
						if (!BX.Type.isStringFilled(field.ADDITIONAL_FILTER))
						{
							if (field.TYPE === this.types.STRING)
							{
								field.VALUE = dataFields[current];
							}

							if (field.TYPE === this.types.TEXTAREA)
							{
								field.VALUE = dataFields[current];
							}

							if (field.TYPE === this.types.MULTI_SELECT)
							{
								field.VALUE = this.prepareMultiSelectValue(dataFields[current], field.ITEMS);
							}

							if (field.TYPE === this.types.SELECT || field.TYPE === this.types.CHECKBOX)
							{
								field.VALUE = this.prepareSelectValue(dataFields[current], field.ITEMS);
							}

							if (field.TYPE === this.types.DATE)
							{
								field.SUB_TYPE = this.prepareSelectValue(dataFields[current + '_datesel'], field.SUB_TYPES);

								field.VALUES = {
									'_from': dataFields[current + '_from'],
									'_to': dataFields[current + '_to'],
									'_days': dataFields[current + '_days'],
									'_month': dataFields[current + '_month'],
									'_quarter': dataFields[current + '_quarter'],
									'_year': dataFields[current + '_year'],
									'_allow_year': dataFields[current + '_allow_year']
								};
							}

							if (field.TYPE === this.types.CUSTOM_DATE)
							{
								field.VALUE = {
									'days': Object.keys(dataFields[current + '_days'] || {}).map(function(index) {
										return dataFields[current + '_days'][index];
									}),
									'months': Object.keys(dataFields[current + '_months'] || {}).map(function(index) {
										return dataFields[current + '_months'][index];
									}),
									'years': Object.keys(dataFields[current + '_years'] || {}).map(function(index) {
										return dataFields[current + '_years'][index];
									})
								};
							}

							if (field.TYPE === this.types.NUMBER)
							{
								field.SUB_TYPE = this.prepareSelectValue(dataFields[current + '_numsel'], field.SUB_TYPES);
								field.VALUES = {
									'_from': dataFields[current + '_from'],
									'_to': dataFields[current + '_to']
								};
							}

							if (
								field.TYPE === this.types.DEST_SELECTOR
								|| field.TYPE === this.types.ENTITY_SELECTOR
								||field.TYPE === this.types.CUSTOM_ENTITY
							)
							{
								if (typeof dataFields[current + '_label'] !== 'undefined')
								{
									field.VALUES._label = dataFields[current + '_label'];
								}

								if (typeof dataFields[current] !== 'undefined')
								{
									field.VALUES._value = dataFields[current];
								}
							}

							if (field.TYPE === this.types.CUSTOM)
							{
								field._VALUE = dataFields[current];
							}
						}

						fields.push(field);
					}
				}, this);
			}

			return fields;
		},


		/**
		 * Prepares select values
		 * @param value
		 * @param items
		 * @return {object}
		 */
		prepareSelectValue: function(value, items)
		{
			var result = {};
			var tmpResult;

			if (BX.type.isNotEmptyString(value) && BX.type.isArray(items))
			{
				tmpResult = this.prepareMultiSelectValue({0: value}, items);
				result = tmpResult.length > 0 ? tmpResult[0] : {};
			}
			else
			{
				result = items[0];
			}

			return result;
		},


		/**
		 * Prepares multiselect value
		 * @param values
		 * @param items
		 * @return {Array}
		 */
		prepareMultiSelectValue: function(values, items)
		{
			var result = [];

			if (BX.type.isPlainObject(values) && BX.type.isArray(items))
			{
				var valuesKeys = Object.keys(values);
				var valuesValues = valuesKeys.map(function(curr) { return values[curr]; });

				result = items.filter(function(current) {
					return valuesValues.some(function(val) { return val === current.VALUE});
				}, this);
			}

			return result;
		},


		/**
		 * Get field by name
		 * @param {string} name
		 * @return {?object}
		 */
		getFieldByName: function(name)
		{
			var fields = this.getParam('FIELDS');

			var field = fields.find(function(current) {
				return current.NAME === name;
			});

			if (field)
			{
				return field;
			}

			var node = this.getFieldListContainer()
				.querySelector('[data-name="' + name + '"]');

			field = BX.Filter.Field.instances.get(node);

			if (field)
			{
				return field.options;
			}

			return null;
		},


		/**
		 * @private
		 * @return {Promise}
		 */
		confirmSaveForAll: function()
		{
			return new Promise(function(resolve) {
				var action = {
					CONFIRM: true,
					CONFIRM_MESSAGE: this.getParam('MAIN_UI_FILTER__CONFIRM_MESSAGE_FOR_ALL'),
					CONFIRM_APPLY_BUTTON: this.getParam('MAIN_UI_FILTER__CONFIRM_APPLY_FOR_ALL'),
					CONFIRM_CANCEL_BUTTON: this.getParam('CONFIRM_CANCEL')
				};
				this.confirmDialog(action, resolve);
			}.bind(this));
		},


		/**
		 * Save options
		 * @param {object} data
		 * @param {object} [params]
		 * @param {function} [callback]
		 * @param {boolean} [forAll = false]
		 */
		saveOptions: function(data, params, callback, forAll)
		{
			params.action = toCamelCase(params.action);
			params.forAll = this.isForAll(forAll);
			params.commonPresetsId = this.getParam('COMMON_PRESETS_ID');
			params.apply_filter = data.apply_filter || "N";
			params.clear_filter = data.clear_filter || "N";
			params.with_preset = data.with_preset || "N";
			params.save = data.save || "N";
			params.isSetOutside = this.isSetOutside();

			var requestData = {
				params: params,
				data: data
			};

			delete data.apply_filter;
			delete data.save;
			delete data.clear_filter;
			delete data.with_preset;

			if (params.forAll && params.action === 'setFilterArray')
			{
				return this.confirmSaveForAll()
					.then(function() {
						return this.backend(params.action, requestData);
					}.bind(this))
					.then(function() {
						this.disableEdit();
						this.disableAddPreset();
					}.bind(this))
			}

			return this.backend(params.action, requestData)
				.then(function() {
					BX.removeClass(this.getFindButton(), this.settings.classWaitButtonClass);
					BX.type.isFunction(callback) && callback();
				}.bind(this));
		},


		/**
		 *
		 * @param {string} action
		 * @param data
		 */
		backend: function(action, data)
		{
			const analyticsLabel = this.analyticsLabel || {};
			this.analyticsLabel = {};

			return BX.ajax.runComponentAction(
				'bitrix:main.ui.filter',
				action,
				{
					mode: 'ajax',
					data: data,
					analyticsLabel: {
						FILTER_ID: this.getParam('FILTER_ID'),
						GRID_ID: this.getParam('GRID_ID'),
						PRESET_ID: data['data']['preset_id'],
						FIND: data['data'].hasOwnProperty('fields')
							&& data['data']['fields'].hasOwnProperty('FIND')
							&& !!data['data']['fields']['FIND'] ? "Y" : "N",
						ROWS: BX.Type.isObject(data['data']['additional'])
							&& Object.keys(data['data']['additional']).length == 0 ? "N" : "Y",
						...analyticsLabel
					}
				}
			);
		},

		/**
		 * Sends analytics when limit is enabled
		 */
		limitAnalyticsSend: function ()
		{
			BX.ajax.runComponentAction(
				'bitrix:main.ui.filter',
				'limitAnalytics',
				{
					mode: 'ajax',
					data: {},
					analyticsLabel: {
						FILTER_ID: this.getParam('FILTER_ID'),
						LIMIT: this.getParam('FILTER_ID')
					}
				}
			);
		},

		/**
		 * Prepares event.path
		 * @param event
		 * @return {*}
		 */
		prepareEvent: function(event)
		{
			var i, x;

			if (!('path' in event) || !event.path.length)
			{
				event.path = [event.target];
				i = 0;

				while ((x = event.path[i++].parentNode) !== null)
				{
					event.path.push(x);
				}
			}

			return event;
		},


		/**
		 * Restores removed preset values
		 * VALUE_REQUIRED_MODE = true only
		 */
		restoreRemovedPreset: function()
		{
			if (this.getParam('VALUE_REQUIRED_MODE'))
			{
				var currentPreset = this.getParam('CURRENT_PRESET');
				if (BX.type.isPlainObject(currentPreset))
				{
					var currentPresetId = currentPreset.ID;
					var presetNode = this.getPreset().getPresetNodeById(currentPresetId);
					this.getPreset().applyPreset(currentPresetId);
					this.getPreset().activatePreset(presetNode);
				}
			}
		},


		/**
		 * Checks that the event occurred on the scroll bar
		 * @param {MouseEvent} event
		 * @return {boolean}
		 */
		hasScrollClick: function(event)
		{
			var x = 'clientX' in event ? event.clientX : 'x' in event ? event.x : 0;
			return x >= document.documentElement.offsetWidth;
		},


		/**
		 * Checks whether to use common presets
		 * @return {boolean}
		 */
		isUseCommonPresets: function()
		{
			return !!this.getParam('COMMON_PRESETS_ID');
		},


		/**
		 * Checks whether event is inside filter
		 * @param {MouseEvent} event
		 * @returns {boolean}
		 */
		isInsideFilterEvent: function(event)
		{
			event = this.prepareEvent(event);
			return (event.path || []).some(function(current) {
				return (
					BX.type.isDomNode(current) && (
						BX.hasClass(current, this.settings.classFilterContainer) ||
						BX.hasClass(current, this.settings.classSearchContainer) ||
						BX.hasClass(current, this.settings.classDefaultPopup) ||
						BX.hasClass(current, this.settings.classPopupOverlay) ||
						BX.hasClass(current, this.settings.classSidePanelContainer)
					)
				);
			}, this);
		},

		_onDocumentClick: function(event)
		{
			var popup = this.getPopup();

			if (!this.isInsideFilterEvent(event) && !this.hasScrollClick(event))
			{
				if (popup && popup.isShown())
				{
					this.closePopup();

					if (this.getParam('VALUE_REQUIRED_MODE'))
					{
						this.restoreRemovedPreset();
					}

					if (this.getParam('VALUE_REQUIRED'))
					{
						if (!this.getSearch().getSquares().length)
						{
							this.getPreset().applyPinnedPreset();
						}
					}
				}

				BX.onCustomEvent(window, 'BX.Main.Filter:blur', [this]);
			}
		},

		_onAddFieldClick: function(event)
		{
			event.stopPropagation();
			event.preventDefault();

			if (this.getParam('USE_CHECKBOX_LIST_FOR_SETTINGS_POPUP'))
			{
				BX.Runtime.loadExtension('ui.dialogs.checkbox-list').then(() => {
					if (BX.UI && BX.Type.isFunction(BX.UI.CheckboxList))
					{
						this.showFieldsSettingsCheckboxList();

						return;
					}

					this.showFieldsSettingsPopup();
				});

				return;
			}

			this.showFieldsSettingsPopup();
		},

		showFieldsSettingsPopup: function(): void
		{
			const popup = this.getFieldsPopup();

			if (popup && !popup.isShown())
			{
				this.showFieldsPopup();
				this.syncFields();

				return;
			}

			this.closeFieldListPopup();
		},

		showFieldsSettingsCheckboxList: function(): void
		{
			if (this.checkboxListPopup)
			{
				this.checkboxListPopup.show();
				this.syncCheckboxFields();

				return;
			}

			this.getFieldsListPopupContent().then((content) => {
				const { sections, categories, options } = this.getPreparedCheckboxListData(content);
				const { enableFieldsSearch, enableHeadersSections } = this;
				const context = {
					parentType: 'filter',
				}

				this.checkboxListPopup = new BX.UI.CheckboxList({
					popupOptions: {
						width: this.settings.popupWidth,
					},
					lang: {
						title: Loc.getMessage('MAIN_UI_FILTER__FIELDS_SETTINGS_TITLE'),
						placeholder: Loc.getMessage('MAIN_UI_FILTER__FIELD_SEARCH_PLACEHOLDER'),
						emptyStateTitle: Loc.getMessage('MAIN_UI_FILTER__FIELD_EMPTY_STATE_TITLE'),
						emptyStateDescription: Loc.getMessage('MAIN_UI_FILTER__FIELD_EMPTY_STATE_DESCRIPTION'),
						allSectionsDisabledTitle: Loc.getMessage('MAIN_UI_FILTER__FIELD_ALL_SECTIONS_DISABLED'),
					},
					sections,
					categories,
					options,
					events: {
						onApply: (event) => this.onCheckboxListApply(event.data.fields),
					},
					params: {
						destroyPopupAfterClose: false,
						useSearch: enableFieldsSearch,
						useSectioning: enableHeadersSections,
					},
					context,
				});

				this.checkboxListPopup.show();
			});
		},

		syncCheckboxFields: function(): void
		{
			const fields = this.getPreset().getFields();
			const checkedFields = this.checkboxListPopup.getSelectedOptions();

			checkedFields.forEach((fieldName: string) => {
				if (!fields.some((field) => field.dataset.name === fieldName))
				{
					this.checkboxListPopup.handleOptionToggled(fieldName);
				}
			});
		},

		/**
		 * @param content
		 * @returns {{options: [], categories: [], sections: []}}
		 */
		getPreparedCheckboxListData: function(content: Object[]): Object
		{
			const defaultHeaderSection = this.getDefaultHeaderSection();
			const sectionIds: Set<string> = new Set();
			const headerSections = this.getHeadersSections();

			const sections = [];
			const categories = [];
			const options = [];

			const preset = this.getPreset();
			const checkedFields = preset.getFields();
			const defaultPresetFields = preset.parent.getParam('CURRENT_PRESET')?.FIELDS ?? [];
			const restrictedFields = this.getParam('RESTRICTED_FIELDS', []);

			content.forEach((item: Object) => {
				const sectionId = (item.sectionId.length ? item.sectionId : defaultHeaderSection?.id);
				if (this.enableHeadersSections && !sectionIds.has(sectionId))
				{
					const title = headerSections[sectionId].name;
					sectionIds.add(sectionId);
					sections.push({
						title,
						key: sectionId,
						value: true,
					});
					categories.push({
						title,
						sectionKey: sectionId,
						key: sectionId,
					});
				}

				const { name } = item;

				options.push({
					title: item.label,
					value: checkedFields.some((field: HTMLElement) => {
						return field.dataset.name === name;
					}),
					categoryKey: sectionId,
					defaultValue: defaultPresetFields.some((defaultField) => defaultField.NAME === name),
					id: name,
					locked: restrictedFields.includes(name),
				});
			});

			return {
				sections,
				categories,
				options,
			};
		},

		/**
		 * Synchronizes field list in popup and filter field list
		 * @param {?{cache: boolean}} [options]
		 */
		syncFields: function(options)
		{
			if (BX.type.isPlainObject(options))
			{
				if (options.cache === false)
				{
					this.fieldsPopupItems = null;
				}
			}

			var fields = this.getPreset().getFields();
			var items = this.getFieldsPopupItems();
			var currentId, isNeedCheck;

			if (BX.type.isArray(items) && items.length)
			{
				items.forEach(function(current) {
					currentId = BX.data(current, 'name').replace('_datesel', '').replace('_numsel', '');
					isNeedCheck = fields.some(function(field) {
						return BX.data(field, 'name') === currentId;
					});
					if (isNeedCheck)
					{
						BX.addClass(current, this.settings.classMenuItemChecked);
					}
					else
					{
						BX.removeClass(current, this.settings.classMenuItemChecked);
					}
				}, this);
			}
		},


		/**
		 * Gets items of popup window with a list of available fields
		 * @return {?HTMLElement[]}
		 */
		getFieldsPopupItems: function()
		{
			if (!BX.type.isArray(this.fieldsPopupItems))
			{
				var popup = this.getFieldsPopup();

				if ('contentContainer' in popup && BX.type.isDomNode(popup.contentContainer))
				{
					this.fieldsPopupItems = BX.Filter.Utils.getByClass(popup.contentContainer, this.settings.classMenuItem, true);
				}

				this.prepareAnimation();
			}

			return this.fieldsPopupItems;
		},


		/**
		 * Gets popup container class name by popup items count
		 * @param {int|string} itemsCount
		 * @return {string}
		 */
		getFieldListContainerClassName: function(itemsCount)
		{
			var popupColumnsCount = parseInt(this.settings.get('popupColumnsCount', 0), 10);
			if (popupColumnsCount > 0 && popupColumnsCount <= this.settings.maxPopupColumnCount)
			{
				return this.settings.get('classPopupFieldList' + popupColumnsCount + 'Column');
			}

			var containerClass = this.settings.classPopupFieldList1Column;

			if (itemsCount > 6 && itemsCount < 12)
			{
				containerClass = this.settings.classPopupFieldList2Column;
			}

			if (itemsCount > 12)
			{
				containerClass = this.settings.classPopupFieldList3Column;
			}

			return containerClass;
		},


		/**
		 * Prepares fields declarations
		 * @param {object[]} fields
		 * @return {object[]}
		 */
		prepareFieldsDecl: function(fields)
		{
			return (fields || []).map(function(item) {
				return {
					block: 'main-ui-filter-field-list-item',
					label: 'LABEL' in item ? item.LABEL : '',
					id: 'ID' in item ? item.ID : '',
					name: 'NAME' in item ? item.NAME : '',
					item: item,
					sectionId: 'SECTION_ID' in item ? item.SECTION_ID : '',
					onClick: BX.delegate(this._clickOnFieldListItem, this)
				};
			}, this);
		},


		/**
		 * Gets lazy load field list
		 * @return {BX.Promise}
		 */
		getLazyLoadFields: function()
		{
			const listUrl = this.getParam('LAZY_LOAD')['GET_LIST'];
			const p = new BX.Promise();

			if (BX.Type.isPlainObject(listUrl))
			{
				const { component, action, data } = listUrl;

				BX.ajax.runComponentAction(component, action, { mode : 'ajax', data })
					.then((response) => {
						p.fulfill(response.data.fields ?? []);
					})
				;
			}
			else
			{
				BX.ajax({
					method: 'GET',
					url: listUrl,
					dataType: 'json',
					onsuccess: (response) => p.fulfill(response),
				});
			}

			return p;
		},


		/**
		 * Gets fields list popup content
		 * @return {BX.Promise}
		 */
		getFieldsListPopupContent: function()
		{
			var p = new BX.Promise();
			var fields = this.getParam('FIELDS');
			var fieldsCount = BX.type.isArray(fields) ? fields.length : 0;

			if (this.getParam('LAZY_LOAD'))
			{
				const callback = function(response) {
					p.fulfill(this.getPopupContent(
						this.settings.classPopupFieldList,
						this.getFieldListContainerClassName(response.length),
						this.prepareFieldsDecl(response)
					));
				}.bind(this);

				if (BX.type.isNotEmptyObject(this.getParam('LAZY_LOAD')['CONTROLLER']))
				{
					var sourceComponentName = this.getParam('LAZY_LOAD')['CONTROLLER']['componentName'];
					var sourceComponentSignedParameters = this.getParam('LAZY_LOAD')['CONTROLLER']['signedParameters'];

					BX.ajax.runAction(this.getParam('LAZY_LOAD')['CONTROLLER']['getList'], {
						data: {
							filterId: this.getParam('FILTER_ID'),
							componentName: (BX.type.isNotEmptyString(sourceComponentName) ? sourceComponentName : ''),
							signedParameters: (BX.type.isNotEmptyString(sourceComponentSignedParameters) ? sourceComponentSignedParameters : '')
						}
					}).then(function(response) {
						callback(response.data);
					}.bind(this), function (response) {
					});
				}
				else
				{
					this.getLazyLoadFields().then(callback);
				}

				return p;
			}

			p.fulfill(this.getPopupContent(
				this.settings.classPopupFieldList,
				this.getFieldListContainerClassName(fieldsCount),
				this.prepareFieldsDecl(fields)
			));
			return p;
		},

		getPopupContent: function(block: string, mix: string, content: Object[]): HTMLElement
		{
			if (
				this.getParam('USE_CHECKBOX_LIST_FOR_SETTINGS_POPUP')
				&& BX.UI
				&& BX.Type.isFunction(BX.UI.CheckboxList)
			)
			{
				return content;
			}

			const wrapper = BX.Tag.render`<div></div>`;
			if (!this.enableHeadersSections)
			{
				const fieldsContent = BX.decl({
					content: content,
					block: block,
					mix: mix,
				});
				this.setPopupElementWidthFromSettings(fieldsContent);
				wrapper.appendChild(fieldsContent);

				if (this.enableFieldsSearch)
				{
					this.preparePopupContentHeader(wrapper);
				}

				return wrapper;
			}

			const defaultHeaderSection = this.getDefaultHeaderSection();
			const sections = {};

			content.forEach((item: Object) => {
				const sectionId = (item.sectionId.length ? item.sectionId : defaultHeaderSection.id);
				if (sections[sectionId] === undefined)
				{
					sections[sectionId] = [];
				}
				sections[sectionId].push(item);
			});

			this.preparePopupContentHeader(wrapper);
			this.preparePopupContentFields(wrapper, sections, block, mix);

			return wrapper;
		},

		async onCheckboxListApply(selectedFields: string[]): void
		{
			const presetFields = this.getPreset().getFields();
			const oldFields = [];

			presetFields.forEach((field) => {
				oldFields.push(field.dataset.name);
			});

			if (this.isFieldsChangePrevented(selectedFields, oldFields))
			{
				return;
			}

			const fieldsData = await this.fetchFields(selectedFields, oldFields);
			if (!Type.isArray(fieldsData))
			{
				if (Type.isPlainObject(fieldsData) && fieldsData?.ERROR)
				{
					UI.Notification.Center.notify({
						content: fieldsData.ERROR
					});
				}

				return;
			}

			fieldsData.forEach((field) => this.params.FIELDS.push(field));

			const fieldsForAdd = selectedFields.filter((field) => !oldFields.includes(field));
			const fieldsForRemove: string[] = oldFields.filter((field) => !selectedFields.includes(field));

			const disableSaveFieldsSort = true;

			fieldsForAdd.forEach((fieldId) => {
				const field = fieldsData.find((item) => item.NAME === fieldId);
				if (field)
				{
					this.getPreset().addField(field, disableSaveFieldsSort);

					// // @todo check this
					if (Type.isString(field.HTML))
					{
						const wrap = BX.create('div');
						this.getHiddenElement().appendChild(wrap);
						BX.html(wrap, field.HTML);
					}
				}
			});

			fieldsForRemove.forEach((fieldId: string) => {
				const field = fieldsData.find((item) => item.NAME === fieldId);
				if (field)
				{
					this.getPreset().removeField(field, disableSaveFieldsSort);
				}
			});

			this.saveFieldsSort();
		},

		async fetchFields(fields: string[], oldFields: string[]): Promise
		{
			if (!this.getParam('LAZY_LOAD'))
			{
				return this.getParam('FIELDS');
			}

			// @todo show loader ?

			const ids: string[] = [...new Set([...fields, ...oldFields])];
			const controller = this.getParam('LAZY_LOAD')['CONTROLLER'];
			if (controller)
			{
				const {
					componentName,
					signedParameters,
					getFields,
				} = controller;

				return new Promise((resolve) => {
					BX.ajax.runAction(
						getFields,
						{
							data: {
								filterId: this.getParam('FILTER_ID'),
								ids,
								componentName: (BX.type.isNotEmptyString(componentName) ? componentName : ''),
								signedParameters: (BX.type.isNotEmptyString(signedParameters) ? signedParameters : ''),
							},
						}).then((response) => resolve(response.data))
					;
				});
			}

			return this.getLazyLoadFieldsByIds(ids);
		},

		async getLazyLoadFieldsByIds(ids: string[]): Promise
		{
			const getFieldsUrl = this.getParam('LAZY_LOAD')['GET_FIELDS'];
			const url = BX.Uri.addParam(getFieldsUrl, { ids });

			return new Promise((resolve) => {
				BX.ajax({
					method: 'get',
					url,
					dataType: 'json',
					onsuccess: (response) => resolve(response),
				});
			});
		},

		isFieldsChangePrevented: function(fields: string[], oldFields: string[]): boolean
		{
			const event = new BX.Event.BaseEvent({
				data: {
					fields,
					oldFields,
				},
			});

			this.emitter.emit('onBeforeChangeFilterItems', event);

			return event.isDefaultPrevented();
		},

		preparePopupContentHeader: function(wrapper: HTMLElement): void
		{
			const headerWrapper = BX.Tag.render`
				<div class="main-ui-filter-popup-search-header-wrapper">
					<div class="ui-form-row-inline"></div>
				</div>
			`;

			wrapper.prepend(headerWrapper);

			this.preparePopupContentHeaderSections(headerWrapper);
			this.preparePopupContentHeaderSearch(headerWrapper);
		},

		preparePopupContentHeaderSections: function(headerWrapper): void
		{
			if (!this.enableHeadersSections)
			{
				return;
			}

			const headerSectionsWrapper = BX.Tag.render`
				<div class="ui-form-row">
					<div class="ui-form-content main-ui-filter-popup-search-section-wrapper"></div>
				</div>
			`;

			headerWrapper.firstElementChild.appendChild(headerSectionsWrapper);

			const headersSections = this.getHeadersSections();
			for (let key in headersSections)
			{
				const itemClass = this.settings.classPopupSearchSectionItemIcon
				 + (headersSections[key].selected ? ` ${this.settings.classPopupSearchSectionItemIconActive}` : '');

				const headerSectionItem = BX.Tag.render`
					<div class="main-ui-filter-popup-search-section-item" data-ui-popup-filter-section-button="${key}">
						<div class="${itemClass}">
							<div>
								${BX.Text.encode(headersSections[key].name)}
							</div>
						</div>
					</div>
				`;
				BX.bind(headerSectionItem, 'click', this.onFilterSectionClick.bind(this, headerSectionItem));

				headerSectionsWrapper.firstElementChild.appendChild(headerSectionItem);
			}
		},

		onFilterSectionClick: function(item: HTMLElement): void
		{
			const activeClass = this.settings.classPopupSearchSectionItemIconActive;
			const sectionId = item.dataset.uiPopupFilterSectionButton;
			const section = document.querySelectorAll("[data-ui-popup-filter-section='"+sectionId+"']");
			if (BX.Dom.hasClass(item.firstElementChild, activeClass))
			{
				BX.Dom.removeClass(item.firstElementChild, activeClass);
				BX.Dom.hide(section[0]);
			}
			else
			{
				BX.Dom.addClass(item.firstElementChild, activeClass);
				BX.Dom.show(section[0]);
			}
		},

		preparePopupContentHeaderSearch: function(headerWrapper: HTMLElement): void
		{
			if (!this.enableFieldsSearch)
			{
				return;
			}

			const searchForm = BX.Tag.render`
				<div class="ui-form-row">
					<div class="ui-form-content main-ui-filter-popup-search-input-wrapper">
						<div class="ui-ctl ui-ctl-textbox ui-ctl-before-icon ui-ctl-after-icon">
							<div class="ui-ctl-before ui-ctl-icon-search"></div>
							<button class="ui-ctl-after ui-ctl-icon-clear"></button>
							<input type="text" class="ui-ctl-element ${this.settings.classPopupSearchSectionItem}">
						</div>
					</div>
				</div>
			`;
			headerWrapper.firstElementChild.appendChild(searchForm);
			const inputs = searchForm.getElementsByClassName(this.settings.classPopupSearchSectionItem);
			if (inputs.length)
			{
				const input = inputs[0];
				BX.bind(input, 'input', this.onFilterSectionSearchInput.bind(this, input));
				BX.bind(input.previousElementSibling, 'click', this.onFilterSectionSearchInputClear.bind(this, input));
			}
		},

		preparePopupContentFields: function(wrapper: HTMLElement, sections, block: string, mix): void
		{
			if (!this.enableHeadersSections)
			{
				return;
			}

			const sectionsWrapper = BX.Tag.render`<div class="main-ui-filter-popup-search-sections-wrapper"></div>`;
			wrapper.appendChild(sectionsWrapper);

			for (let key in sections)
			{
				const sectionWrapper = BX.Tag.render`
					<div class="main-ui-filter-popup-section-wrapper" data-ui-popup-filter-section="${key}"></div>
				`;
				this.setPopupElementWidthFromSettings(sectionWrapper);

				if (!this.getHeadersSectionParam(key, 'selected'))
				{
					sectionWrapper.setAttribute('hidden', '');
				}

				const sectionTitle = BX.Tag.render`
					<h3 class="main-ui-filter-popup-title">
						${BX.Text.encode(this.getHeadersSectionParam(key, 'name'))}
					</h3>
				`;

				const fieldsBlock = BX.decl({
					block: block,
					mix: mix,
					content: sections[key]
				});

				sectionWrapper.appendChild(sectionTitle);
				sectionWrapper.appendChild(fieldsBlock);

				sectionsWrapper.appendChild(sectionWrapper);
			}
		},

		prepareAnimation: function(): void
		{
			if (this.enableFieldsSearch)
			{
				this.fieldsPopupItems.forEach(item =>
				{
					BX.bind(item, 'animationend', this.onAnimationEnd.bind(this, item));
				});
			}
		},

		onAnimationEnd: function(item: HTMLElement): void
		{
			item.style.display = (
				BX.Dom.hasClass(item, this.settings.classPopupSearchFieldListItemHidden)
				? 'none'
				: 'inline-block'
			);
		},

		onFilterSectionSearchInput: function(input: HTMLElement): void
		{
			let search = input.value;
			if (search.length)
			{
				search = search.toLowerCase();
			}

			this.getFieldsPopupItems().forEach(function (item){
				const title = item.innerText.toLowerCase();

				if (search.length && title.indexOf(search) === -1)
				{
					BX.Dom.removeClass(item,this.settings.classPopupSearchFieldListItemVisible);
					BX.Dom.addClass(item,this.settings.classPopupSearchFieldListItemHidden);
				}
				else
				{
					BX.Dom.removeClass(item, this.settings.classPopupSearchFieldListItemHidden);
					BX.Dom.addClass(item, this.settings.classPopupSearchFieldListItemVisible);
					item.style.display = 'inline-block';
				}
			}.bind(this));
		},

		onFilterSectionSearchInputClear: function(input: HTMLElement): void
		{
			if (input.value.length)
			{
				input.value = '';
				this.onFilterSectionSearchInput(input);
			}
		},

		getDefaultHeaderSection: function(): Object|null
		{
			const headersSections = this.getHeadersSections();

			for (let key in headersSections)
			{
				if ('selected' in headersSections[key] && headersSections[key].selected)
				{
					return headersSections[key];
				}
			}

			return null;
		},

		getHeadersSections: function(): Array
		{
			return this.getParam('HEADERS_SECTIONS');
		},

		getHeadersSectionParam: function(sectionId: string, paramName: string, defaultValue: any): any
		{
			if (
				this.getHeadersSections()[sectionId] !== undefined
				&& this.getHeadersSections()[sectionId][paramName] !== undefined
			)
			{
				return this.getHeadersSections()[sectionId][paramName];
			}
			return defaultValue;
		},

		/**
		 * Gets field loader
		 * @return {BX.Loader}
		 */
		getFieldLoader: function()
		{
			if (!this.fieldLoader)
			{
				this.fieldLoader = new BX.Loader({mode: "custom", size: 18, offset: {left: "5px", top: "5px"}});
			}

			return this.fieldLoader;
		},

		_clickOnFieldListItem: function(event)
		{
			var target = event.target;
			var data;

			if (!BX.hasClass(target, this.settings.classFieldListItem))
			{
				target = BX.findParent(target, {className: this.settings.classFieldListItem}, true, false);
			}

			if (BX.type.isDomNode(target))
			{
				try {
					data = JSON.parse(BX.data(target, 'item'));
				} catch (err) {}

				if (this.isFieldChangePrevented(
					data,
					BX.hasClass(target, this.settings.classMenuItemChecked)
				))
				{
					return;
				}

				var p = new BX.Promise();

				if (this.getParam("LAZY_LOAD"))
				{
					this.getFieldLoader().show(target);
					var label = target.querySelector(".main-ui-select-inner-label");

					if (label)
					{
						label.classList.add("main-ui-no-before");
					}

					var callback = function(response) {
						p.fulfill(response);
						this.getFieldLoader().hide();
						if (label)
						{
							label.classList.remove("main-ui-no-before");
						}
					}.bind(this);

					if (BX.type.isNotEmptyObject(this.getParam('LAZY_LOAD')['CONTROLLER']))
					{
						var sourceComponentName = this.getParam('LAZY_LOAD')['CONTROLLER']['componentName'];
						var sourceComponentSignedParameters = this.getParam('LAZY_LOAD')['CONTROLLER']['signedParameters'];

						BX.ajax.runAction(this.getParam('LAZY_LOAD')['CONTROLLER']['getField'], {
							data: {
								filterId: this.getParam('FILTER_ID'),
								id: data.NAME,
								componentName: (BX.type.isNotEmptyString(sourceComponentName) ? sourceComponentName : ''),
								signedParameters: (BX.type.isNotEmptyString(sourceComponentSignedParameters) ? sourceComponentSignedParameters : '')
							}
						}).then(function(response) {
							callback(response.data);
						}.bind(this), function (response) {
						});
					}
					else
					{
						this.getLazyLoadField(data.NAME).then(callback);
					}
				}
				else
				{
					p.fulfill(data);
				}

				p.then(function(response) {
					this.params.FIELDS.push(response);

					if (BX.hasClass(target, this.settings.classMenuItemChecked))
					{
						BX.removeClass(target, this.settings.classMenuItemChecked);
						this.getPreset().removeField(response);
					}
					else
					{
						if (BX.type.isPlainObject(response))
						{
							this.getPreset().addField(response);
							BX.addClass(target, this.settings.classMenuItemChecked);

							if (BX.type.isString(response.HTML))
							{
								var wrap = BX.create("div");
								this.getHiddenElement().appendChild(wrap);
								BX.html(wrap, response.HTML);
							}
						}
					}

					this.syncFields();
				}.bind(this));
			}
		},

		/**
		 * @return {boolean}
		 */
		isFieldChangePrevented: function(data, isChecked)
		{
			let eventParams;
			if (isChecked)
			{
				eventParams = {
					fields: [],
					oldFields: [data.NAME],
				};
			}
			else
			{
				eventParams = {
					fields: [data.NAME],
					oldFields: [],
				};
			}

			const event = new BX.Event.BaseEvent({
				data: eventParams,
			});

			this.emitter.emit('onBeforeChangeFilterItems', event);

			return event.isDefaultPrevented();
		},

		getHiddenElement: function()
		{
			if (!this.hiddenElement)
			{
				this.hiddenElement = BX.create("div");
				document.body.appendChild(this.hiddenElement);
			}

			return this.hiddenElement;
		},


		/**
		 * Gets lazy load fields
		 * @param id
		 * @return {BX.Promise}
		 */
		getLazyLoadField: function(id)
		{
			const fieldUrl = this.getParam('LAZY_LOAD')['GET_FIELD'];
			const p = new BX.Promise();

			if (BX.Type.isPlainObject(fieldUrl))
			{
				const { component, action, data } = fieldUrl;
				data.fieldId = id;

				BX.ajax.runComponentAction(component, action, { mode: 'ajax', data })
					.then((response) => {
						p.fulfill(response.data.field ?? []);
					})
				;
			}
			else
			{
				BX.ajax({
					method: 'get',
					url: BX.util.add_url_param(fieldUrl, { id }),
					dataType: 'json',
					onsuccess: (response) => p.fulfill(response),
				});
			}

			return p;
		},


		/**
		 * Shows fields list popup
		 */
		showFieldsPopup: function()
		{
			var popup = this.getFieldsPopup();
			this.adjustFieldListPopupPosition();
			popup.show();
		},


		/**
		 * Closes fields list popup
		 */
		closeFieldListPopup: function()
		{
			if (
				this.getParam('USE_CHECKBOX_LIST_FOR_SETTINGS_POPUP')
				&& BX.UI
				&& BX.Type.isFunction(BX.UI.CheckboxList)
			)
			{
				if (this.checkboxListPopup)
				{
					this.checkboxListPopup.destroy();
					this.checkboxListPopup = null;
				}

				return;
			}

			const popup = this.getFieldsPopup();
			popup.close();
		},


		/**
		 * Adjusts field list popup position
		 */
		adjustFieldListPopupPosition: function()
		{
			var popup = this.getFieldsPopup();
			var pos = BX.pos(this.getAddField());
			pos.forceBindPosition = true;
			popup.adjustPosition(pos);
		},


		/**
		 * Gets field list popup instance
		 * @return {BX.PopupWindow}
		 */
		getFieldsPopup: function()
		{
			var bindElement = (this.settings.get('showPopupInCenter', false) ? null : this.getAddField());

			if (!this.fieldsPopup)
			{
				this.fieldsPopup = new BX.PopupWindow(
					this.getParam('FILTER_ID') + '_fields_popup',
					bindElement,
					{
						autoHide : true,
						offsetTop : 4,
						offsetLeft : 0,
						lightShadow : true,
						closeIcon : (bindElement === null),
						closeByEsc : (bindElement === null),
						noAllPaddings: true,
						zIndex: 13
					}
				);

				this.fieldsPopupLoader = new BX.Loader({target: this.fieldsPopup.contentContainer});
				this.fieldsPopupLoader.show();
				this.setPopupElementWidthFromSettings(this.fieldsPopup.contentContainer);
				this.fieldsPopup.contentContainer.style.height = "330px";
				this.getFieldsListPopupContent().then(function(res) {
					this.fieldsPopup.contentContainer.removeAttribute("style");
					this.fieldsPopupLoader.hide();
					this.fieldsPopup.setContent(res);
					this.syncFields({cache: false});
					this.adjustFieldListPopupPosition();
				}.bind(this));
			}

			return this.fieldsPopup;
		},

		setPopupElementWidthFromSettings: function(element: HTMLElement): void
		{
			element.style.width = this.settings.popupWidth + 'px';
		},

		_onAddPresetClick: function()
		{
			this.enableAddPreset();
		},


		/**
		 * Enables shows wait spinner for button
		 * @param {HTMLElement} button
		 */
		enableWaitSate: function(button)
		{
			!!button && BX.addClass(button, this.settings.classWaitButtonClass);
		},


		/**
		 * Disables shows wait spinner for button
		 * @param {HTMLElement} button
		 */
		disableWaitState: function(button)
		{
			!!button && BX.removeClass(button, this.settings.classWaitButtonClass);
		},

		_onSaveButtonClick: function()
		{
			var forAll = !!this.getSaveForAllCheckbox() && this.getSaveForAllCheckbox().checked;
			var input = this.getPreset().getAddPresetFieldInput();
			var mask = input.parentNode.querySelector(".main-ui-filter-edit-mask");
			var presetName;

			function onAnimationEnd(event)
			{
				if (event.animationName === "fieldError")
				{
					event.currentTarget.removeEventListener("animationend", onAnimationEnd);
					event.currentTarget.removeEventListener("oAnimationEnd", onAnimationEnd);
					event.currentTarget.removeEventListener("webkitAnimationEnd", onAnimationEnd);
					event.currentTarget.classList.remove("main-ui-filter-error");
				}
			}

			function showLengthError(mask)
			{
				mask.addEventListener("animationend", onAnimationEnd);
				mask.addEventListener("oAnimationEnd", onAnimationEnd);
				mask.addEventListener("webkitAnimationEnd", onAnimationEnd);
				mask.classList.add("main-ui-filter-error");
				var promise = new BX.Promise();
				promise.fulfill(true);
				return promise;
			}

			this.enableWaitSate(this.getFindButton());

			if (this.isAddPresetEnabled() && !forAll)
			{
				presetName = input.value;

				if (presetName.length)
				{
					this.savePreset();
					this.disableAddPreset();
				}
				else
				{
					showLengthError(mask).then(function() {
						input.focus();
					});
				}
			}

			if (this.isEditEnabled())
			{
				var preset = this.getPreset();
				var currentPresetId = preset.getCurrentPresetId();
				var presetNode = preset.getPresetNodeById(currentPresetId);
				var presetNameInput = preset.getPresetInput(presetNode);

				if (
					presetNameInput.value.length === 0
					&& currentPresetId === 'default_filter'
				)
				{
					var currentPresetData = preset.getCurrentPresetData();
					if (currentPresetData)
					{
						BX.Dom.attr(presetNameInput, 'value', currentPresetData.TITLE);
					}
				}

				if (presetNameInput.value.length > 0)
				{
					preset.updateEditablePreset(currentPresetId);
					this.saveUserSettings(forAll);

					if (!forAll)
					{
						this.disableEdit();
					}
				}
				else
				{
					var presetMask = presetNode.querySelector(".main-ui-filter-edit-mask");
					showLengthError(presetMask).then(function() {
						presetNameInput.focus();
					});
				}
			}
		},

		_onCancelButtonClick: function()
		{
			this.setIsSetOutsideState(false);
			this.disableAddPreset();
			this.getPreset().clearAddPresetFieldInput();
			this.disableEdit();
			!!this.getSaveForAllCheckbox() && (this.getSaveForAllCheckbox().checked = null);
		},

		_onGridReady: function(grid)
		{
			if (!this.grid && grid.getContainerId() === this.getParam('GRID_ID'))
			{
				this.grid = grid;
			}
		},

		_onFilterMousedown: function(event)
		{
			var target = event.target;

			if (this.getFields().isDragButton(target))
			{
				var inputs = BX.Filter.Utils.getByTag(target.parentNode, 'input', true);

				(inputs || []).forEach(function(item) {
					BX.fireEvent(item, 'blur');
				});

				BX.fireEvent(this.getFilter(), 'click');
			}
		},

		_onFilterClick: function(event)
		{
			var Fields = this.getFields();
			var Presets = this.getPreset();
			var field;

			if (Fields.isFieldDelete(event.target))
			{
				field = Fields.getField(event.target);
				Presets.removeField(field);
			}

			if (Fields.isFieldValueDelete(event.target))
			{
				field = Fields.getField(event.target);
				Fields.clearFieldValue(field);
			}
		},


		/**
		 * Gets filter buttons container
		 * @return {?HTMLElement}
		 */
		getButtonsContainer: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classButtonsContainer);
		},


		/**
		 * Gets save button element
		 * @return {?HTMLElement}
		 */
		getSaveButton: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classSaveButton);
		},


		/**
		 * Gets cancel element
		 * @return {?HTMLElement}
		 */
		getCancelButton: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classCancelButton);
		},


		/**
		 * Gets find button element
		 * @return {?HTMLElement}
		 */
		getFindButton: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classFindButton);
		},


		/**
		 * Gets reset button element
		 * @return {?HTMLElement}
		 */
		getResetButton: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classResetButton);
		},


		/**
		 * Gets add preset button
		 * @return {?HTMLElement}
		 */
		getAddPresetButton: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classAddPresetButton);
		},


		/**
		 * Checks that add preset mode enabled
		 * @return {boolean}
		 */
		isAddPresetEnabled: function()
		{
			return this.isAddPresetModeState;
		},


		/**
		 * Enables add preset mode
		 */
		enableAddPreset: function()
		{
			var Preset = this.getPreset();
			var addPresetField = Preset.getAddPresetField();
			var addPresetFieldInput = Preset.getAddPresetFieldInput();
			var buttonsContainer = this.getButtonsContainer();

			BX.show(addPresetField);
			BX.show(buttonsContainer);
			BX.hide(this.getPresetButtonsContainer());
			this.hideForAllCheckbox();

			if (BX.type.isDomNode(addPresetFieldInput))
			{
				addPresetFieldInput.focus();
			}

			BX.addClass(this.getSidebarControlsContainer(), this.settings.classDisabled);

			this.isAddPresetModeState = true;
		},


		/**
		 * Disables add preset mode
		 */
		disableAddPreset: function()
		{
			var Preset = this.getPreset();
			var addPresetField = Preset.getAddPresetField();
			var buttonsContainer = this.getButtonsContainer();

			BX.hide(addPresetField);
			BX.hide(buttonsContainer);
			BX.show(this.getPresetButtonsContainer());
			this.showForAllCheckbox();

			Preset.getAddPresetFieldInput().value = '';

			BX.removeClass(this.getSidebarControlsContainer(), this.settings.classDisabled);

			this.isAddPresetModeState = false;
		},


		/**
		 * Gets control from field list
		 * @return {?HTMLElement[]}
		 */
		getControls: function()
		{
			var container = this.getFieldListContainer();
			var controls = null;

			if (BX.type.isDomNode(container))
			{
				controls = BX.Filter.Utils.getByClass(container, this.settings.classControl, true);
			}

			return controls;
		},


		/**
		 * Gets filter fields
		 * @return {?HTMLElement[]}
		 */
		getFilterFields: function()
		{
			var container = this.getFieldListContainer();
			var fields = [];
			var groups = [];

			if (BX.type.isDomNode(container))
			{
				fields = BX.Filter.Utils.getByClass(container, this.settings.classField, true);
				groups = BX.Filter.Utils.getByClass(container, this.settings.classFieldGroup, true);

				if (!BX.type.isArray(fields))
				{
					fields = [];
				}

				if (BX.type.isArray(groups))
				{
					groups.forEach(function(current) {
						fields.push(current);
					});
				}
			}

			return fields;
		},


		/**
		 * Gets filter fields values
		 * @return {object}
		 */
		getFilterFieldsValues: function()
		{
			var fields = this.getPreset().getFields();
			var Search = this.getSearch();
			var values = {};
			var type, name;

			values['FIND'] = Search.getInput().value;

			if (BX.type.isArray(fields) && fields.length)
			{
				fields.forEach(function(current) {
					var additionalFilter = BX.Filter.AdditionalFilter.getInstance().getFilter(current);
					if (additionalFilter)
					{
						Object.assign(values, additionalFilter);
						return;
					}

					type = BX.data(current, 'type');
					name = BX.data(current, 'name');

					switch (type) {
						case this.types.STRING : {
							this.prepareControlStringValue(values, current);
							break;
						}

						case this.types.TEXTAREA : {
							this.prepareControlTextareaValue(values, current);
							break;
						}

						case this.types.NUMBER : {
							this.prepareControlNumberValue(values, name, current);
							break;
						}

						case this.types.DATE : {
							this.prepareControlDateValue(values, name, current);
							break;
						}

						case this.types.CUSTOM_DATE : {
							this.prepareControlCustomDateValue(values, name, current);
							break;
						}

						case this.types.SELECT : {
							this.prepareControlSelectValue(values, name, current);
							break;
						}

						case this.types.MULTI_SELECT : {
							this.prepareControlMultiselectValue(values, name, current);
							break;
						}

						case this.types.DEST_SELECTOR:
						case this.types.CUSTOM_ENTITY:
						case this.types.ENTITY_SELECTOR: {
							this.prepareControlCustomEntityValue(values, name, current);
							break;
						}

						case this.types.CUSTOM : {
							this.prepareControlCustomValue(values, name, current);
							break;
						}

						default : {
							break;
						}
					}
				}, this);
			}

			return values;
		},


		/**
		 * @param values
		 * @param name
		 * @param field
		 */
		prepareControlCustomEntityValue: function(values, name, field)
		{
			var squares = this.fetchSquares(field);
			var squaresData = this.fetchSquaresData(squares);
			var isMultiple = BX.Main.ui.CustomEntity.isMultiple(field);

			values[name] = '';
			values[name + '_label'] = '';

			if (isMultiple)
			{
				values[name] = [];
				values[name + '_label'] = [];

				!!squaresData && squaresData.forEach(function(item) {
					values[name].push(item._value.toString());
					values[name + '_label'].push(item._label.toString());
				});
			}
			else
			{
				if (squaresData.length)
				{
					values[name] = squaresData[0]._value.toString();
					values[name + '_label'] = squaresData[0]._label.toString();
				}
			}
		},


		/**
		 * @param {HTMLElement} field
		 * @return {HTMLElement[]}
		 */
		fetchSquares: function(field)
		{
			return !!field ? BX.Filter.Utils.getByClass(field, this.settings.classSquare, true) : [];
		},


		/**
		 * @param {HTMLElement[]} squares
		 * @return {object[]}
		 */
		fetchSquaresData: function(squares)
		{
			return squares.map(function(square) {
				return JSON.parse(BX.data(square, 'item'));
			}, this);
		},


		/**
		 * @param {object} values
		 * @param {string} name
		 * @param {HTMLElement} field
		 */
		prepareControlCustomValue: function(values, name, field)
		{
			var stringFields = BX.Filter.Utils.getByTag(field, 'input', true);

			values[name] = '';

			if (BX.type.isArray(stringFields))
			{
				stringFields.forEach(function(current) {
					if (BX.type.isNotEmptyString(current.name))
					{
						values[current.name] = current.value;
					}
				});
			}
		},

		prepareControlMultiselectValue: function(values, name, field)
		{
			var select = BX.Filter.Utils.getByClass(field, this.settings.classMultiSelect);
			var value = JSON.parse(BX.data(select, 'value'));

			values[name] = '';

			if (BX.type.isArray(value) && value.length)
			{
				values[name] = {};
				value.forEach(function(current, index) {
					values[name][index] = current.VALUE;
				});
			}
		},

		prepareControlSelectValue: function(values, name, field)
		{
			var select = BX.Filter.Utils.getByClass(field, this.settings.classSelect);
			var value = JSON.parse(BX.data(select, 'value'));

			values[name] = value.VALUE;
		},

		prepareControlCustomDateValue: function(values, name, field)
		{
			var daysControl = field.querySelector("[data-name=\""+name + '_days'+"\"]");

			if (daysControl)
			{
				var daysValue = JSON.parse(daysControl.dataset.value);

				values[name + '_days'] = daysValue.map(function(item) {
					return item.VALUE;
				});
			}

			var monthsControl = field.querySelector("[data-name=\""+name + '_months'+"\"]");

			if (monthsControl)
			{
				var monthsValue = JSON.parse(monthsControl.dataset.value);

				values[name + '_months'] = monthsValue.map(function(item) {
					return item.VALUE;
				});
			}

			var yearsControl = field.querySelector("[data-name=\""+name + '_years'+"\"]");

			if (yearsControl)
			{
				var yearsValue = JSON.parse(yearsControl.dataset.value);

				values[name + '_years'] = yearsValue.map(function(item) {
					return item.VALUE;
				});
			}
		},

		prepareControlDateValue: function(values, name, field, withAdditional)
		{
			var additionalFieldsContainer = field.querySelector('.main-ui-filter-additional-fields-container');

			if (additionalFieldsContainer && !withAdditional)
			{
				BX.remove(additionalFieldsContainer);
			}

			var select = BX.Filter.Utils.getByClass(field, this.settings.classSelect);
			var yearsSwitcher = field.querySelector(".main-ui-select[data-name*=\"_allow_year\"]");
			var selectName = name + this.settings.datePostfix;
			var fromName = name + this.settings.fromPostfix;
			var toName = name + this.settings.toPostfix;
			var daysName = name + this.settings.daysPostfix;
			var monthName = name + this.settings.monthPostfix;
			var quarterName = name + this.settings.quarterPostfix;
			var yearName = name + this.settings.yearPostfix;
			var yearsSwitcherName = name + "_allow_year";
			var selectValue, stringFields, controls, controlName, yearsSwitcherValue;

			values[selectName] = '';
			values[fromName] = '';
			values[toName] = '';
			values[daysName] = '';
			values[monthName] = '';
			values[quarterName] = '';
			values[yearName] = '';

			var input = field.querySelector(".main-ui-date-input");

			if (input && input.dataset.isValid === "false")
			{
				return;
			}

			selectValue = JSON.parse(BX.data(select, 'value'));
			values[selectName] = selectValue.VALUE;

			if (yearsSwitcher)
			{
				yearsSwitcherValue = JSON.parse(BX.data(yearsSwitcher, 'value'));
				values[yearsSwitcherName] = yearsSwitcherValue.VALUE;
			}

			switch (selectValue.VALUE) {
				case this.dateTypes.EXACT : {
					stringFields = BX.Filter.Utils.getByClass(field, this.settings.classDateInput);
					values[fromName] = stringFields.value;
					values[toName] = stringFields.value;
					break;
				}

				case this.dateTypes.QUARTER : {
					controls = BX.Filter.Utils.getByClass(field, this.settings.classControl, true);

					if (BX.type.isArray(controls))
					{
						controls.forEach(function(current) {
							controlName = BX.data(current, 'name');

							if (controlName && controlName.indexOf('_quarter') !== -1)
							{
								values[quarterName] = JSON.parse(BX.data(current, 'value')).VALUE;
							}

							if (
								controlName
								&& controlName.endsWith('_year')
								&& !controlName.endsWith('_allow_year')
							)
							{
								values[yearName] = JSON.parse(BX.data(current, 'value')).VALUE;
							}
						}, this);
					}
					break;
				}

				case this.dateTypes.YEAR : {
					controls = BX.Filter.Utils.getByClass(field, this.settings.classControl, true);

					if (BX.type.isArray(controls))
					{
						controls.forEach(function(current) {
							controlName = BX.data(current, 'name');

							if (
								controlName
								&& controlName.endsWith('_year')
								&& !controlName.endsWith('_allow_year')
							)
							{
								values[yearName] = JSON.parse(BX.data(current, 'value')).VALUE;
							}
						}, this);
					}
					break;
				}

				case this.dateTypes.MONTH : {
					controls = BX.Filter.Utils.getByClass(field, this.settings.classControl, true);

					if (BX.type.isArray(controls))
					{
						controls.forEach(function(current) {
							controlName = BX.data(current, 'name');

							if (controlName && controlName.indexOf('_month') !== -1)
							{
								values[monthName] = JSON.parse(BX.data(current, 'value')).VALUE;
							}

							if (
								controlName
								&& controlName.endsWith('_year')
								&& !controlName.endsWith('_allow_year')
							)
							{
								values[yearName] = JSON.parse(BX.data(current, 'value')).VALUE;
							}
						}, this);
					}
					break;
				}

				case this.additionalDateTypes.PREV_DAY :
				case this.additionalDateTypes.NEXT_DAY :
				case this.additionalDateTypes.MORE_THAN_DAYS_AGO :
				case this.additionalDateTypes.AFTER_DAYS :
				case this.dateTypes.NEXT_DAYS :
				case this.dateTypes.PREV_DAYS : {
					var control = BX.Filter.Utils.getByClass(field, this.settings.classNumberInput);

					if (!!control && control.name === daysName)
					{
						values[daysName] = control.value;
					}

					break;
				}

				case this.dateTypes.RANGE : {
					stringFields = BX.Filter.Utils.getByClass(field, this.settings.classDateInput, true);
					stringFields.forEach(function(current) {
						if (current.name === fromName)
						{
							values[fromName] = current.value;
						}
						else if (current.name === toName)
						{
							values[toName] = current.value;
						}
					}, this);
					break;
				}

				case "CUSTOM_DATE" : {
					var customValues = {};
					this.prepareControlCustomDateValue(customValues, name, field);
					values[name + '_days'] = customValues[name + '_days'];
					values[monthName] = customValues[name + '_months'];
					values[yearName] = customValues[name + '_years'];
					break;
				}

				default : {
					break;
				}
			}

			if (additionalFieldsContainer && !withAdditional)
			{
				BX.append(additionalFieldsContainer, field);
			}

			var additionalFields = Array.from(
				field.querySelectorAll(
					'.main-ui-filter-additional-fields-container > [data-type="DATE"]',
				)
			);

			if (additionalFields)
			{
				additionalFields.forEach(function(additionalField) {
					var name = additionalField.dataset.name;
					this.prepareControlDateValue(values, name, additionalField, true);
				}, this);
			}
		},

		prepareControlNumberValue: function(values, name, field)
		{
			var stringFields = BX.Filter.Utils.getByClass(field, this.settings.classNumberInput, true);
			var select = BX.Filter.Utils.getByClass(field, this.settings.classSelect);
			var selectName = name + this.settings.numberPostfix;
			var fromName = name + this.settings.fromPostfix;
			var toName = name + this.settings.toPostfix;
			var selectValue;

			values[fromName] = '';
			values[toName] = '';

			selectValue = JSON.parse(BX.data(select, 'value'));
			values[selectName] = selectValue.VALUE;

			stringFields.forEach(function(current) {
				if (current.name.indexOf(this.settings.fromPostfix) !== -1)
				{
					values[fromName] = current.value || '';

					if (values[selectName] === 'exact')
					{
						values[toName] = current.value || '';
					}
				}
				else if (current.name.indexOf(this.settings.toPostfix) !== -1)
				{
					values[toName] = current.value || '';
				}
			}, this);
		},

		prepareControlStringValue: function(values, field)
		{
			var control = BX.Filter.Utils.getByClass(field, this.settings.classStringInput);
			var name;

			if (BX.type.isDomNode(control))
			{
				name = control.name;
				values[name] = control.value;
			}
		},

		prepareControlTextareaValue: function(values, field)
		{
			var control = BX.Filter.Utils.getByClass(field, this.settings.classStringInput);
			var name;

			if (BX.type.isDomNode(control))
			{
				name = control.name;
				values[name] = control.value;
			}
		},


		/**
		 * Shows grid animation
		 */
		showGridAnimation: function()
		{
			this.grid && this.grid.tableFade();
		},


		/**
		 * Hides grid animations
		 */
		hideGridAnimation: function()
		{
			this.grid && this.grid.tableUnfade();
		},

		/**
		 * @private
		 * @param {?Boolean} clear - is need reset filter
		 * @param {?Boolean} applyPreset - is need apply preset
		 * @return {String}
		 */
		getPresetId: function(clear, applyPreset)
		{
			var presetId = this.getPreset().getCurrentPresetId();

			if ((!this.isEditEnabled() && !this.isAddPresetEnabled() && !applyPreset) ||
				(presetId === 'default_filter' && !clear))
			{
				presetId = 'tmp_filter';
			}

			return presetId;
		},

		isAppliedUserFilter: function()
		{
			const presetOptions = this.getPreset().getCurrentPresetData();
			if (BX.Type.isPlainObject(presetOptions))
			{
				const hasFields = (
					BX.Type.isArrayFilled(presetOptions.FIELDS)
					&& presetOptions.FIELDS.some((field) => {
						return !this.getPreset().isEmptyField(field);
					})
				);

				const hasAdditional = (
					BX.Type.isArrayFilled(presetOptions.ADDITIONAL)
					&& presetOptions.ADDITIONAL.some((field) => {
						return !this.getPreset().isEmptyField(field);
					})
				);

				return (
					(
						!presetOptions.IS_PINNED
						&& (
							hasFields
							|| hasAdditional
						)
					)
					|| (
						presetOptions.IS_PINNED
						&& BX.Type.isArrayFilled(presetOptions.ADDITIONAL)
					)
					|| BX.Type.isStringFilled(this.getSearch().getSearchString())
				);
			}

			return false;
		},

		isAppliedDefaultPreset: function()
		{
			const presetData = this.getPreset().getCurrentPresetData();
			if (!presetData.IS_PINNED)
			{
				return false;
			}

			if (BX.Type.isArrayFilled(presetData.ADDITIONAL))
			{
				const hasAdditional = presetData.ADDITIONAL.some((field) => {
					return !this.getPreset().isEmptyField(field);
				});

				if (hasAdditional)
				{
					return false;
				}
			}

			if (BX.Type.isStringFilled(this.getSearch().getSearchString()))
			{
				return false;
			}

			return true;
		},

		/**
		 * Applies filter
		 * @param {?Boolean} [clear] - is need reset filter
		 * @param {?Boolean} [applyPreset] - is need apply preset
		 * @param {?Boolean} [isSetOutside] - is filter sets from outside
		 * @return {BX.Promise}
		 */
		applyFilter: function(clear, applyPreset, isSetOutside)
		{
			this.setIsSetOutsideState(isSetOutside);

			var filterId = this.getParam('FILTER_ID');
			var promise = new BX.Promise(null, this);
			var Preset = this.getPreset();
			var Search = this.getSearch();
			var applyParams = {autoResolve: !this.grid};
			var self = this;

			this.setDefaultPresetAppliedState(this.isAppliedDefaultPreset());

			if (this.isAppliedUserFilter())
			{
				BX.Dom.addClass(this.getSearch().container, 'main-ui-filter-search--active');
			}
			else
			{
				BX.Dom.removeClass(this.getSearch().container, 'main-ui-filter-search--active');
			}

			this.clearGet();
			this.showGridAnimation();

			var action = clear ? "clear" : "apply";

			BX.onCustomEvent(window, 'BX.Main.Filter:beforeApply', [filterId, {action: action}, this, promise]);
			// presetId defined  after `beforeApply` because current preset may be changed by the event's handlers
			const presetId = this.getPresetId(clear, applyPreset);

			this.updatePreset(presetId, null, clear, null).then(function() {
				Search.updatePreset(Preset.getPreset(presetId));

				if (self.getParam('VALUE_REQUIRED'))
				{
					if (!Search.getSquares().length)
					{
						self.lastPromise = Preset.applyPinnedPreset();
					}
				}
			}).then(function() {
				var params = {apply_filter: 'Y', clear_nav: 'Y'};
				var fulfill = BX.delegate(promise.fulfill, promise);
				var reject = BX.delegate(promise.reject, promise);
				self.grid && self.grid.reloadTable('POST', params, fulfill, reject);
				BX.onCustomEvent(window, 'BX.Main.Filter:apply', [filterId, {action: action}, self, promise, applyParams]);
				applyParams.autoResolve && promise.fulfill();
			});

			return promise;
		},


		/**
		 * Gets add field buttons
		 * @return {?HTMLElement}
		 */
		getAddField: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classAddField);
		},


		/**
		 * Gets fields list container
		 * @return {?HTMLElement}
		 */
		getFieldListContainer: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classFileldControlList);
		},


		/**
		 * @return {BX.Filter.Fields}
		 */
		getFields: function()
		{
			if (!(this.fields instanceof BX.Filter.Fields))
			{
				this.fields = new BX.Filter.Fields(this);
			}

			return this.fields;
		},


		getPreset: function(): Presets
		{
			if (!(this.presets instanceof Presets))
			{
				this.presets = new Presets(this);
			}

			return this.presets;
		},


		/**
		 * @param controlData
		 * @return {*}
		 */
		resetControlData: function(controlData)
		{
			if (BX.type.isPlainObject(controlData))
			{
				switch (controlData.TYPE)
				{
					case this.types.MULTI_SELECT : {
						controlData.VALUE = [];
						break;
					}

					case this.types.SELECT : {
						controlData.VALUE = controlData.ITEMS[0];
						break;
					}

					case this.types.DATE : {
						controlData.SUB_TYPE = controlData.SUB_TYPES[0];
						controlData.VALUES = {
							'_from': '',
							'_to': '',
							'_days': '',
							'_quarter': '',
							'_year': ''
						};
						break;
					}

					case this.types.CUSTOM_DATE : {
						controlData.VALUES = {
							'days': [],
							'months': [],
							'years': []
						};
						break;
					}

					case this.types.NUMBER : {
						controlData.SUB_TYPE = controlData.SUB_TYPES[0];
						controlData.VALUES = {
							'_from': '',
							'_to': ''
						};
						break;
					}

					case this.types.DEST_SELECTOR:
					case this.types.ENTITY_SELECTOR:
					case this.types.CUSTOM_ENTITY: {
						controlData.VALUES = {
							'_label': '',
							'_value': ''
						};
						break;
					}

					case this.types.CUSTOM : {
						controlData._VALUE = '';
						break;
					}

					default : {
						controlData.VALUE = '';
					}
				}
			}

			return controlData;
		},


		clearControl: function(name)
		{
			var control = this.getPreset().getField({NAME: name});
			var controlData, newControl;

			if (BX.type.isDomNode(control))
			{
				controlData = this.getFieldByName(name);
				controlData = this.resetControlData(controlData);

				newControl = this.getPreset().createControl(controlData);
				BX.insertAfter(newControl, control);
				BX.remove(control);
			}
		},

		clearControls: function(squareData)
		{
			if (BX.type.isArray(squareData))
			{
				squareData.forEach(function(item) {
					'name' in item && this.clearControl(item.name);
				}, this);
			}

			else if (BX.type.isPlainObject(squareData) && 'name' in squareData)
			{
				this.clearControl(squareData.name);
			}
		},


		/**
		 * Gets filter popup template
		 * @return {?string}
		 */
		getTemplate: function()
		{
			return BX.html(BX(this.settings.generalTemplateId));
		},

		isIe: function()
		{
			if (!BX.type.isBoolean(this.ie))
			{
				this.ie = BX.hasClass(document.documentElement, 'bx-ie');
			}

			return this.ie;
		},


		/**
		 * Closes filter popup
		 */
		closePopup: function()
		{
			var popup = this.getPopup();
			var popupContainer = popup.popupContainer;
			var configCloseDelay = this.settings.get('FILTER_CLOSE_DELAY');
			var closeDelay;

			BX.Dom.removeClass(this.getSearch().container, 'main-ui-filter-search--showed');

			setTimeout(BX.delegate(function() {

				if (!this.isIe())
				{
					BX.removeClass(popupContainer, this.settings.classAnimationShow);
					BX.addClass(popupContainer, this.settings.classAnimationClose);

					closeDelay = parseFloat(BX.style(popupContainer, 'animation-duration'));

					if (BX.type.isNumber(closeDelay))
					{
						closeDelay = closeDelay * 1000;
					}

					setTimeout(function() {
						popup.close();
					}, closeDelay);
				}
				else
				{
					popup.close();
				}
			}, this), configCloseDelay);

			if (this.getParam("LIMITS_ENABLED"))
			{
				BX.removeClass(this.getFilter(), this.settings.classLimitsAnimation);
			}

			this.closeFieldListPopup();
			this.adjustFocus();
		},


		/**
		 * Shows filter popup
		 */
		showPopup: function()
		{
			var popup = this.getPopup();
			var popupContainer;

			if (!popup.isShown())
			{
				BX.Dom.addClass(this.getSearch().container, 'main-ui-filter-search--showed');

				this.isOpened = true;
				var showDelay = this.settings.get('FILTER_SHOW_DELAY');

				if (this.getParam('LIMITS_ENABLED') === true)
				{
					this.limitAnalyticsSend();
				}

				setTimeout(BX.delegate(function() {
					popup.show();

					if (!this.isIe())
					{
						popupContainer = popup.popupContainer;
						BX.removeClass(popupContainer, this.settings.classAnimationClose);
						BX.addClass(popupContainer, this.settings.classAnimationShow);
						BX.onCustomEvent(window, "BX.Main.Filter:show", [this]);
					}

					var textareas = [].slice.call(
						this.getFieldListContainer().querySelectorAll('textarea')
					);

					textareas.forEach(function(item) {
						BX.style(item, 'height', item.scrollHeight + 'px');
					});
				}, this), showDelay);
			}
		},


		/**
		 * Gets save for all checkbox element
		 * @return {?HTMLInputElement}
		 */
		getSaveForAllCheckbox: function()
		{
			if (!this.saveForAllCheckbox && !!this.getSaveForAllCheckboxContainer())
			{
				this.saveForAllCheckbox = BX.Filter.Utils.getBySelector(this.getSaveForAllCheckboxContainer(), 'input[type="checkbox"]');
			}

			return this.saveForAllCheckbox;
		},


		/**
		 * Gets save for all checkbox container
		 * @return {?HTMLElement}
		 */
		getSaveForAllCheckboxContainer: function()
		{
			if (!this.saveForAllCheckboxContainer)
			{
				this.saveForAllCheckboxContainer = BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classForAllCheckbox);
			}

			return this.saveForAllCheckboxContainer;
		},


		/**
		 * Shows for all checkbox
		 */
		showForAllCheckbox: function()
		{
			!!this.getSaveForAllCheckboxContainer() &&
				BX.removeClass(this.getSaveForAllCheckboxContainer(), this.settings.classHide);
		},


		/**
		 * Hides for all checkbox
		 */
		hideForAllCheckbox: function()
		{
			!!this.getSaveForAllCheckboxContainer() &&
				BX.addClass(this.getSaveForAllCheckboxContainer(), this.settings.classHide);
		},


		/**
		 * Gets popup bind element
		 * @return {?HTMLElement}
		 */
		getPopupBindElement: function()
		{
			if (!this.popupBindElement)
			{
				var selector = this.settings.get('POPUP_BIND_ELEMENT_SELECTOR');
				var result = null;

				if (BX.type.isNotEmptyString(selector))
				{
					result = BX.Filter.Utils.getBySelector(document, selector);
				}

				this.popupBindElement = !!result ? result : this.getSearch().getContainer();
			}

			return this.popupBindElement;
		},


		/**
		 * Gets filter popup window instance
		 * @return {BX.PopupWindow}
		 */
		getPopup: function()
		{
			if (!(this.popup instanceof BX.PopupWindow))
			{
				this.popup =  new BX.PopupWindow(
					this.getParam('FILTER_ID') + this.settings.searchContainerPostfix,
					this.getPopupBindElement(),
					{
						autoHide : false,
						offsetTop : parseInt(this.settings.get('POPUP_OFFSET_TOP')),
						offsetLeft : parseInt(this.settings.get('POPUP_OFFSET_LEFT')),
						lightShadow : true,
						closeIcon : false,
						closeByEsc : false,
						noAllPaddings: true,
						zIndex: 12
					}
				);

				this.popup.setContent(this.getTemplate());
				BX.bind(this.getFieldListContainer(), 'keydown', BX.delegate(this._onFieldsContainerKeydown, this));
				BX.bind(this.getFilter(), 'click', BX.delegate(this._onFilterClick, this));
				BX.bind(this.getAddPresetButton(), 'click', BX.delegate(this._onAddPresetClick, this));
				BX.bind(this.getPreset().getAddPresetFieldInput(), 'keydown', BX.delegate(this._onAddPresetKeydown, this));
				BX.bind(this.getPreset().getContainer(), 'keydown', BX.delegate(this._onPresetInputKeydown, this));
				BX.bind(this.getSaveButton(), 'click', BX.delegate(this._onSaveButtonClick, this));
				BX.bind(this.getCancelButton(), 'click', BX.delegate(this._onCancelButtonClick, this));
				BX.bind(this.getFindButton(), 'click', BX.delegate(this._onFindButtonClick, this));
				BX.bind(this.getResetButton(), 'click', BX.delegate(this._onResetButtonClick, this));
				BX.bind(this.getAddField(), 'click', BX.delegate(this._onAddFieldClick, this));
				BX.bind(this.getEditButton(), 'click', BX.delegate(this._onEditButtonClick, this));
				BX.bind(this.getRestoreButton(), 'click', BX.delegate(this._onRestoreButtonClick, this));
				BX.bind(this.getRestoreFieldsButton(), 'click', BX.delegate(this._onRestoreFieldsButtonClick, this));
				this.getFilter().addEventListener('mousedown', BX.delegate(this._onFilterMousedown, this), true);
				this.getPreset().showCurrentPresetFields();
				this.getPreset().bindOnPresetClick();
			}

			return this.popup;
		},

		_onRestoreFieldsButtonClick: function()
		{
			this.restoreDefaultFields();
		},


		/**
		 * Restores default fields list
		 */
		restoreDefaultFields: function()
		{
			var defaultPreset = this.getPreset().getPreset('default_filter', true);
			var presets = this.getParam('PRESETS');
			var currentPresetId = this.getPreset().getCurrentPresetId();
			var params = {'FILTER_ID': this.getParam('FILTER_ID'), 'GRID_ID': this.getParam('GRID_ID'), 'action': 'SET_FILTER'};
			var fields = defaultPreset.FIELDS.map(function(curr) { return curr.NAME; });
			var rows = fields.join(',');

			presets.forEach(function(current, index) {
				if (current.ID === 'default_filter')
				{
					presets[index] = BX.clone(defaultPreset);
				}
			}, this);

			if (BX.type.isArray(this.editablePresets))
			{
				this.editablePresets.forEach(function(current, index) {
					if (current.ID === 'default_filter')
					{
						this.editablePresets[index] = BX.clone(defaultPreset);
					}
				}, this);
			}

			this.getPreset().applyPreset(currentPresetId);
			this.updatePreset(currentPresetId);
			this.saveOptions({preset_id: "default_filter", rows: rows, save: "Y", apply_filter: "N"}, params);
		},


		/**
		 * Gets restore default fields button
		 * @return {?HTMLElement}
		 */
		getRestoreFieldsButton: function()
		{
			if (!this.restoreFieldsButton)
			{
				this.restoreFieldsButton = BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classRestoreFieldsButton);
			}

			return this.restoreFieldsButton;
		},


		/**
		 * Restores filter
		 */
		restoreFilter: function()
		{
			var defaultPresets = this.getParam('DEFAULT_PRESETS');
			var allPresets = this.getParam('PRESETS');
			var isReplace = false;
			var replaceIndex, applyPresetId, presetNode;

			if (BX.type.isArray(defaultPresets))
			{
				defaultPresets.sort(function(a, b) {
					return a.SORT - b.SORT;
				});

				defaultPresets.forEach(function(defPreset) {
					isReplace = allPresets.some(function(current, index) {
						if (current.ID === defPreset.ID)
						{
							replaceIndex = index;
							return true;
						}
					});

					if (isReplace)
					{
						allPresets[replaceIndex] = BX.clone(defPreset);
					}
					else
					{
						allPresets.push(BX.clone(defPreset));
					}

					if (defPreset.ID !== 'default_filter')
					{
						this.addSidebarItem(defPreset.ID, defPreset.TITLE, defPreset.IS_PINNED);

						if (defPreset.IS_PINNED)
						{
							applyPresetId = defPreset.ID;
						}
					}
				}, this);
			}

			this.saveRestoreFilter();
			this.disableAddPreset();
			this.disableEdit();

			if (!applyPresetId)
			{
				applyPresetId = "default_filter";
			}

			presetNode = this.getPreset().getPresetNodeById(applyPresetId);

			if (presetNode)
			{
				BX.fireEvent(presetNode, 'click');
			}
		},

		saveRestoreFilter: function()
		{
			var params = {'FILTER_ID': this.getParam('FILTER_ID'), 'GRID_ID': this.getParam('GRID_ID'), 'action': 'RESTORE_FILTER'};
			var presets = this.getParam('PRESETS');
			var data = {};
			var rows;

			if (BX.type.isArray(presets))
			{
				presets.forEach(function(current) {
					rows = current.FIELDS.map(function(field) {
						return field.NAME;
					});
					rows = rows.join(',');
					data[current.ID] = {
						name: current.TITLE || null,
						sort: current.SORT,
						preset_id: current.ID,
						fields:  this.prepareFields(current.FIELDS),
						rows: rows,
						for_all: current.FOR_ALL
					};
				}, this);

				this.saveOptions(data, params);
			}
		},


		/**
		 * Prepares fields
		 * @param {object[]} fields
		 * @return {object}
		 */
		prepareFields: function(fields)
		{
			var result = {};
			var valuesKeys;

			if (BX.type.isArray(fields))
			{
				fields.forEach(function(current) {
					if (current.TYPE === this.types.SELECT)
					{
						result[current.NAME] = 'VALUE' in current.VALUE ? current.VALUE.VALUE : '';
					}

					if (current.TYPE === this.types.MULTI_SELECT)
					{
						current.VALUE.forEach(function(val, i) {
							result[current.NAME] = result[current.NAME] || {};
							result[current.NAME][i] = val.VALUE;
						});

						result[current.NAME] = result[current.NAME] || '';
					}

					if (current.TYPE === this.types.DATE ||
						current.TYPE === this.types.NUMBER)
					{
						valuesKeys = Object.keys(current.VALUES);

						valuesKeys.forEach(function(key) {
							result[current.NAME + key] = current.VALUES[key];
						});

						if (current.TYPE === this.types.DATE)
						{
							result[current.NAME + '_datesel'] = 'VALUE' in current.SUB_TYPE ?
								current.SUB_TYPE.VALUE : current.SUB_TYPES[0].VALUE;
						}

						if (current.TYPE === this.types.NUMBER)
						{
							result[current.NAME + '_numsel'] = 'VALUE' in current.SUB_TYPE ?
								current.SUB_TYPE.VALUE : current.SUB_TYPES[0].VALUE;
						}
					}

					if (
						current.TYPE === this.types.DEST_SELECTOR
						|| current.TYPE === this.types.ENTITY_SELECTOR
						|| current.TYPE === this.types.CUSTOM_ENTITY
					)
					{
						result[current.NAME + '_label'] = current.VALUES._label;
						result[current.NAME + '_value'] = current.VALUES._value;
					}
				}, this);
			}

			return result;
		},


		/**
		 * Gets restore button
		 * @return {?HTMLElement}
		 */
		getRestoreButton: function()
		{
			if (!BX.type.isDomNode(this.restoreButton))
			{
				this.restoreButton = BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classRestoreButton);
			}

			return this.restoreButton;
		},

		_onPresetInputKeydown: function(event)
		{
			if (BX.Filter.Utils.isKey(event, 'enter') && event.target.tagName === 'INPUT')
			{
				BX.fireEvent(this.getSaveButton(), 'click');
			}
		},

		_onFieldsContainerKeydown: function(event)
		{
			if (BX.Filter.Utils.isKey(event, 'enter') && event.target.tagName === 'INPUT')
			{
				BX.fireEvent(this.getFindButton(), 'click');
			}
		},

		_onFindButtonClick: function()
		{
			this.setIsSetOutsideState(false);
			var presets = this.getPreset();
			var currentPresetId = presets.getCurrentPresetId();
			var promise;

			if (
				currentPresetId !== 'tmp_filter'
				&& currentPresetId !== 'default_filter'
				&& !presets.isPresetValuesModified(currentPresetId)
			)
			{
				var preset = presets.getPreset(currentPresetId);
				var additional = presets.getAdditionalValues(currentPresetId);
				var rows = presets.getFields().map(function(current) {
					return BX.data(current, 'name');
				});

				preset.ADDITIONAL = this.preparePresetFields(additional, rows);
				preset.ADDITIONAL = preset.ADDITIONAL.filter(function(field) {
					return !this.getPreset().isEmptyField(field);
				}, this);

				promise = this.applyFilter(false, currentPresetId);
				this.closePopup();
			}
			else
			{
				presets.deactivateAllPresets();
				promise = this.applyFilter();
				this.closePopup();
			}

			return promise;
		},

		_onResetButtonClick: function()
		{
			if (this.getParam('VALUE_REQUIRED'))
			{
				var preset = this.getPreset().getCurrentPresetData();

				if (preset.ADDITIONAL.length)
				{
					this.closePopup();
				}

				BX.fireEvent(this.getSearch().getClearButton(), 'click');
			}
			else
			{
				if (this.getParam('RESET_TO_DEFAULT_MODE'))
				{
					this.getSearch().clearInput();
					this.getPreset().applyPinnedPreset();
				}
				else
				{
					this.resetFilter();
				}

				this.closePopup();
			}
		},


		/**
		 * @param withoutSearch
		 * @return {BX.Promise}
		 */
		resetFilter: function(withoutSearch)
		{
			var Search = this.getSearch();
			var Presets = this.getPreset();

			if (!withoutSearch)
			{
				Search.clearInput();
			}

			Search.removePreset();
			Presets.deactivateAllPresets();
			Presets.resetPreset(true);
			Search.hideClearButton();
			Search.adjustPlaceholder();
			return this.applyFilter(true, true);
		},

		_onEditButtonClick: function()
		{
			if (!this.isEditEnabled())
			{
				this.enableEdit();
			}
			else
			{
				this.disableEdit();
			}
		},


		/**
		 * Enables fields drag and drop
		 */
		enableFieldsDragAndDrop: function()
		{
			var fields = this.getPreset().getFields();

			this.fieldsList = [];

			if (BX.type.isArray(fields))
			{
				this.fieldsList = fields.map(this.registerDragItem, this);
			}
		},


		/**
		 * Register drag item
		 * @param {HTMLElement} item
		 * @return {HTMLElement}
		 */
		registerDragItem: function(item)
		{
			var dragButton = this.getDragButton(item);

			if (dragButton)
			{
				dragButton.onbxdragstart = BX.delegate(this._onFieldDragStart, this);
				dragButton.onbxdragstop = BX.delegate(this._onFieldDragStop, this);
				dragButton.onbxdrag = BX.delegate(this._onFieldDrag, this);
				jsDD.registerObject(dragButton);
				jsDD.registerDest(dragButton);
			}

			return item;
		},


		/**
		 * Unregister drag item
		 * @param {HTMLElement} item
		 */
		unregisterDragItem: function(item)
		{
			var dragButton = this.getDragButton(item);

			if (dragButton)
			{
				jsDD.unregisterObject(dragButton);
				jsDD.unregisterDest(dragButton);
			}
		},

		_onFieldDragStart: function()
		{
			this.dragItem = this.getFields().getField(jsDD.current_node);
			this.dragIndex = BX.Filter.Utils.getIndex(this.fieldsList, this.dragItem);
			this.dragRect = this.dragItem.getBoundingClientRect();
			this.offset = this.dragRect.height;
			this.dragStartOffset = (jsDD.start_y - (this.dragRect.top + BX.scrollTop(window)));

			BX.Filter.Utils.styleForEach(this.fieldsList, {'transition': '100ms'});
			BX.addClass(this.dragItem, this.settings.classPresetOndrag);
			BX.bind(document, 'mousemove', BX.delegate(this._onMouseMove, this));
		},

		_onFieldDragStop: function()
		{
			BX.unbind(document, 'mousemove', BX.delegate(this._onMouseMove, this));
			BX.removeClass(this.dragItem, this.settings.classPresetOndrag);

			BX.Filter.Utils.styleForEach(this.fieldsList, {'transition': '', 'transform': ''});
			BX.Filter.Utils.collectionSort(this.dragItem, this.targetItem);

			this.fieldsList = this.getPreset().getFields();

			this.saveFieldsSort();
		},

		_onFieldDrag: function()
		{
			var self = this;
			var currentRect, currentMiddle;

			this.dragOffset = (this.realY - this.dragRect.top - this.dragStartOffset);
			this.sortOffset = self.realY + BX.scrollTop(window);

			BX.Filter.Utils.styleForEach([this.dragItem], {
				'transition': '0ms',
				'transform': 'translate3d(0px, '+this.dragOffset+'px, 0px)'
			});

			this.fieldsList.forEach(function(current, index) {
				if (current)
				{
					currentRect = current.getBoundingClientRect();
					currentMiddle = currentRect.top + BX.scrollTop(window) + (currentRect.height / 2);

					if (index > self.dragIndex && self.sortOffset > currentMiddle &&
						current.style.transform !== 'translate3d(0px, '+(-self.offset)+'px, 0px)' &&
						current.style.transform !== '')
					{
						self.targetItem = current;
						BX.style(current, 'transform', 'translate3d(0px, '+(-self.offset)+'px, 0px)');
						BX.style(current, 'transition', '300ms');
					}

					if (index < self.dragIndex && self.sortOffset < currentMiddle &&
						current.style.transform !== 'translate3d(0px, '+(self.offset)+'px, 0px)' &&
						current.style.transform !== '')
					{
						self.targetItem = current;
						BX.style(current, 'transform', 'translate3d(0px, '+(self.offset)+'px, 0px)');
						BX.style(current, 'transition', '300ms');
					}

					if (((index < self.dragIndex && self.sortOffset > currentMiddle) ||
						(index > self.dragIndex && self.sortOffset < currentMiddle)) &&
						current.style.transform !== 'translate3d(0px, 0px, 0px)')
					{
						if (current.style.transform !== '')
						{
							self.targetItem = current;
						}

						BX.style(current, 'transform', 'translate3d(0px, 0px, 0px)');
						BX.style(current, 'transition', '300ms');
					}
				}
			});
		},


		/**
		 * Disables fields drag and drop
		 */
		disableFieldsDragAndDrop: function()
		{
			if (BX.type.isArray(this.fieldsList) && this.fieldsList.length)
			{
				this.fieldsList.map(this.unregisterDragItem, this);
			}
		},


		/**
		 * Enables presets drag and drop
		 */
		enablePresetsDragAndDrop: function()
		{
			var Preset, presets, dragButton, presetId;

			Preset = this.getPreset();
			presets = Preset.getPresets();
			this.presetsList = [];

			if (BX.type.isArray(presets) && presets.length)
			{
				presets.forEach(function(current) {
					presetId = Preset.getPresetId(current);

					if (!BX.hasClass(current, this.settings.classAddPresetField) &&
						presetId !== 'default_filter' &&
						!BX.hasClass(current, this.settings.classDefaultFilter))
					{
						dragButton = this.getDragButton(current);
						dragButton.onbxdragstart = BX.delegate(this._onDragStart, this);
						dragButton.onbxdragstop = BX.delegate(this._onDragStop, this);
						dragButton.onbxdrag = BX.delegate(this._onDrag, this);
						jsDD.registerObject(dragButton);
						jsDD.registerDest(dragButton);
						this.presetsList.push(current);
					}
				}, this);
			}
		},


		/**
		 * Gets drag button
		 * @param {HTMLElement} presetNode
		 * @return {?HTMLElement}
		 */
		getDragButton: function(presetNode)
		{
			return BX.Filter.Utils.getByClass(presetNode, this.settings.classPresetDragButton);
		},


		/**
		 * Disables presets drag and drop
		 */
		disablePresetsDragAndDrop: function()
		{
			if (BX.type.isArray(this.presetsList) && this.presetsList.length)
			{
				this.presetsList.forEach(function(current) {
					if (!BX.hasClass(current, this.settings.classAddPresetField))
					{
						jsDD.unregisterObject(current);
						jsDD.unregisterDest(current);
					}
				}, this);
			}
		},

		_onDragStart: function()
		{
			this.dragItem = this.getPreset().normalizePreset(jsDD.current_node);
			this.dragIndex = BX.Filter.Utils.getIndex(this.presetsList, this.dragItem);
			this.dragRect = this.dragItem.getBoundingClientRect();
			this.offset = this.dragRect.height;
			this.dragStartOffset = (jsDD.start_y - (this.dragRect.top + BX.scrollTop(window)));

			BX.Filter.Utils.styleForEach(this.list, {'transition': '100ms'});
			BX.addClass(this.dragItem, this.settings.classPresetOndrag);
			BX.bind(document, 'mousemove', BX.delegate(this._onMouseMove, this));
		},

		_onMouseMove: function(event)
		{
			this.realX = event.clientX;
			this.realY = event.clientY;
		},


		/**
		 * Gets drag offset
		 * @return {number}
		 */
		getDragOffset: function()
		{
			return (jsDD.x - this.startDragOffset - this.dragRect.left);
		},

		_onDragStop: function()
		{
			var Preset, presets;

			BX.unbind(document, 'mousemove', BX.delegate(this._onMouseMove, this));
			BX.removeClass(this.dragItem, this.settings.classPresetOndrag);

			BX.Filter.Utils.styleForEach(this.presetsList, {'transition': '', 'transform': ''});
			BX.Filter.Utils.collectionSort(this.dragItem, this.targetItem);

			Preset = this.getPreset();
			presets = Preset.getPresets();
			this.presetsList = [];

			if (BX.type.isArray(presets) && presets.length)
			{
				presets.forEach(function(current) {
					if (!BX.hasClass(current, this.settings.classAddPresetField) &&
						!BX.hasClass(current, this.settings.classDefaultFilter))
					{
						this.presetsList.push(current);
					}
				}, this);
			}

		},

		_onDrag: function()
		{
			var self = this;
			var currentRect, currentMiddle;

			this.dragOffset = (this.realY - this.dragRect.top - this.dragStartOffset);
			this.sortOffset = self.realY + BX.scrollTop(window);

			BX.Filter.Utils.styleForEach([this.dragItem], {
				'transition': '0ms',
				'transform': 'translate3d(0px, '+this.dragOffset+'px, 0px)'
			});

			this.presetsList.forEach(function(current, index) {
				if (current)
				{
					currentRect = current.getBoundingClientRect();
					currentMiddle = currentRect.top + BX.scrollTop(window) + (currentRect.height / 2);

					if (index > self.dragIndex && self.sortOffset > currentMiddle &&
						current.style.transform !== 'translate3d(0px, '+(-self.offset)+'px, 0px)' &&
						current.style.transform !== '')
					{
						self.targetItem = current;
						BX.style(current, 'transform', 'translate3d(0px, '+(-self.offset)+'px, 0px)');
						BX.style(current, 'transition', '300ms');
					}

					if (index < self.dragIndex && self.sortOffset < currentMiddle &&
						current.style.transform !== 'translate3d(0px, '+(self.offset)+'px, 0px)' &&
						current.style.transform !== '')
					{
						self.targetItem = current;
						BX.style(current, 'transform', 'translate3d(0px, '+(self.offset)+'px, 0px)');
						BX.style(current, 'transition', '300ms');
					}

					if (((index < self.dragIndex && self.sortOffset > currentMiddle) ||
						(index > self.dragIndex && self.sortOffset < currentMiddle)) &&
						current.style.transform !== 'translate3d(0px, 0px, 0px)')
					{
						if (current.style.transform !== '')
						{
							self.targetItem = current;
						}

						BX.style(current, 'transform', 'translate3d(0px, 0px, 0px)');
						BX.style(current, 'transition', '300ms');
					}
				}
			});
		},


		/**
		 * Gets sidebar controls container
		 * @return {?HTMLElement}
		 */
		getSidebarControlsContainer: function()
		{
			if (!BX.type.isDomNode(this.sidebarControlsContainer))
			{
				this.sidebarControlsContainer = BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classSidebarControlsContainer);
			}

			return this.sidebarControlsContainer;
		},


		/**
		 * Enables edit mode
		 */
		enableEdit: function()
		{
			var Preset = this.getPreset();
			var presets = Preset.getPresets();
			var presetId;

			if (BX.type.isArray(presets) && presets.length)
			{
				presets.forEach(function(current) {
					presetId = Preset.getPresetId(current);
					if (!BX.hasClass(current, this.settings.classAddPresetField) && presetId !== 'default_filter')
					{
						BX.addClass(current, this.settings.classPresetEdit);
					}
				}, this);
			}

			this.enablePresetsDragAndDrop();
			BX.show(this.getButtonsContainer());
			BX.hide(this.getPresetButtonsContainer());
			BX.addClass(this.getSidebarControlsContainer(), this.settings.classDisabled);
			this.editablePresets = BX.clone(this.getParam('PRESETS'));
			this.isEditEnabledState = true;
		},


		/**
		 * Disables edit mode
		 */
		disableEdit: function()
		{
			var Preset = this.getPreset();
			var presets = Preset.getPresets();

			if (BX.type.isArray(presets) && presets.length)
			{
				presets.forEach(function(current) {
					if (!BX.hasClass(current, this.settings.classAddPresetField))
					{
						BX.removeClass(current, this.settings.classPresetEdit);
						this.getPreset().disableEditPresetName(current);
					}
				}, this);
			}

			this.disablePresetsDragAndDrop();

			if (!this.isAddPresetEnabled())
			{
				BX.style(this.getButtonsContainer(), 'display', '');
			}

			BX.show(this.getPresetButtonsContainer());
			BX.removeClass(this.getSidebarControlsContainer(), this.settings.classDisabled);
			this.editablePresets = null;
			this.isEditEnabledState = false;
			this.applyFilter(null, true);
		},


		/**
		 * Get preset buttons container
		 * @return {?HTMLElement}
		 */
		getPresetButtonsContainer: function()
		{
			if (!BX.type.isDomNode(this.presetButtonsContainer))
			{
				this.presetButtonsContainer = BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classPresetButtonsContainer);
			}

			return this.presetButtonsContainer;
		},


		/**
		 * Checks is edit mode enabled
		 * @return {boolean}
		 */
		isEditEnabled: function()
		{
			return this.isEditEnabledState;
		},


		/**
		 * Gets edit button element
		 * @return {?HTMLElement}
		 */
		getEditButton: function()
		{
			return BX.Filter.Utils.getByClass(this.getFilter(), this.settings.classEditButton);
		},


		/**
		 * Gets component param by param name
		 * @param {string} paramName
		 * @param {*} [defaultValue] - Be returns if param with paramName not set
		 * @returns {*}
		 */
		getParam: function(paramName, defaultValue)
		{
			return paramName in this.params ? this.params[paramName] : defaultValue;
		},


		/**
		 * Gets container of filter popup
		 * @returns {HTMLElement|null}
		 */
		getFilter: function()
		{
			return BX.Filter.Utils.getByClass(this.getPopup().contentContainer, this.settings.classFilterContainer);
		},


		/**
		 * @returns {BX.Filter.Search}
		 */
		getSearch: function()
		{
			if (!(this.search instanceof BX.Filter.Search))
			{
				this.search = new BX.Filter.Search(this);
			}

			return this.search;
		},

		_onRestoreButtonClick: function()
		{
			var action = {
				CONFIRM: true,
				CONFIRM_MESSAGE: this.getParam('CONFIRM_MESSAGE'),
				CONFIRM_APPLY_BUTTON: this.getParam('CONFIRM_APPLY'),
				CONFIRM_CANCEL_BUTTON: this.getParam('CONFIRM_CANCEL')
			};

			this.confirmDialog(action, BX.delegate(this.restoreFilter, this));
		},


		/**
		 * Shows confirmation popup
		 * @param {object} action - Popup properties
		 * @param {boolean} action.CONFIRM - true If the user must confirm the action
		 * @param {string} action.CONFIRM_MESSAGE - Message of confirm popup
		 * @param {string} action.CONFIRM_APPLY_BUTTON - Text of apply button
		 * @param {string} action.CONFIRM_CANCEL_BUTTON - Text of cancel button
		 * @param {string} [action.CONFIRM_TITLE] - Title of confirm popup
		 * @param {function} then - Callback after a successful confirmation
		 * @param {function} [cancel] - callback after cancel confirmation
		 */
		confirmDialog: function(action, then, cancel)
		{
			if ('CONFIRM' in action && action.CONFIRM)
			{
				var dialogId = this.getParam('FILTER_ID') + '-confirm-dialog';
				var popupMessage = '<div class="main-ui-filter-confirm-content">'+action.CONFIRM_MESSAGE+'</div>';
				var popupTitle = 'CONFIRM_TITLE' in action ? action.CONFIRM_TITLE : '';

				var applyButton = new BX.PopupWindowButton({
					text: action.CONFIRM_APPLY_BUTTON,
					events: {
						click: function()
						{
							BX.type.isFunction(then) ? then() : null;
							this.popupWindow.close();
							this.popupWindow.destroy();
						}
					}
				});

				var cancelButton = new BX.PopupWindowButtonLink({
					text: action.CONFIRM_CANCEL_BUTTON,
					events: {
						click: function()
						{
							BX.type.isFunction(cancel) ? cancel() : null;
							this.popupWindow.close();
							this.popupWindow.destroy();
						}
					}
				});

				var dialog = new BX.PopupWindow(
					dialogId,
					null,
					{
						content: popupMessage,
						titleBar: popupTitle,
						autoHide: false,
						zIndex: 9999,
						overlay: 0.4,
						offsetTop: -100,
						closeIcon : true,
						closeByEsc : true,
						buttons: [applyButton, cancelButton]
					}
				);

				BX.addCustomEvent(dialog, 'onPopupClose', BX.delegate(function() {
					!!this.getSaveForAllCheckbox() && (this.getSaveForAllCheckbox().checked = null);
				}, this));

				if (!dialog.isShown())
				{
					dialog.show();
					var popupContainer = dialog.popupContainer;
					BX.removeClass(popupContainer, this.settings.classAnimationShow);
					BX.addClass(popupContainer, this.settings.classAnimationShow);
				}
			}
			else
			{
				BX.type.isFunction(then) ? then() : null;
			}
		},

		getInitialValue: function(name)
		{
			if (BX.type.isString(name))
			{
				var values = this.params.INITIAL_FILTER;

				if (BX.type.isPlainObject(values))
				{
					var filteredEntries = Object.entries(values).reduce(function(acc, item) {
						if (item[0].startsWith(name))
						{
							acc.push(item);
						}

						return acc;
					}, []);

					if (filteredEntries.length === 1)
					{
						return filteredEntries[0][1];
					}

					if (filteredEntries.length > 1)
					{
						return filteredEntries.reduce(function(acc, item) {
							acc[item[0].replace(name, '')] = item[1];
							return acc;
						}, {});
					}
				}
			}

			return '';
		},

		getField: function(name)
		{
			var node = this.getFieldListContainer()
				.querySelector('[data-name="' + name + '"]');

			return BX.Filter.Field.instances.get(node);
		},

		isSetOutside: function()
		{
			return BX.Text.toBoolean(this.isSetOutsideState);
		},

		setIsSetOutsideState: function(state)
		{
			this.isSetOutsideState = BX.Text.toBoolean(state);
			const searchContainer = this.getSearch().getContainer();
			if (this.isSetOutsideState)
			{
				BX.Dom.addClass(searchContainer, 'main-ui-filter-set-outside');
				BX.Dom.removeClass(searchContainer, 'main-ui-filter-set-inside');
			}
			else
			{
				BX.Dom.addClass(searchContainer, 'main-ui-filter-set-inside');
				BX.Dom.removeClass(searchContainer, 'main-ui-filter-set-outside');
			}
		},

		setDefaultPresetAppliedState: function(state)
		{
			this.isDefaultPresetAppliedState = BX.Text.toBoolean(state);
			const searchContainer = this.getSearch().getContainer();
			if (this.isDefaultPresetAppliedState)
			{
				BX.Dom.addClass(searchContainer, 'main-ui-filter-default-applied');
			}
			else
			{
				BX.Dom.removeClass(searchContainer, 'main-ui-filter-default-applied');
			}
		}
	};
})();


(function() {
	BX.Main.filterManager = {
		data: {},

		push: function(id, instance)
		{
			if (BX.type.isNotEmptyString(id) && instance)
			{
				this.data[id] = instance;
			}
		},

		getById: function(id)
		{
			var result = null;

			if (id in this.data)
			{
				result = this.data[id];
			}

			return result;
		},

		getList: function()
		{
			return Object.values(this.data);
		}
	};
})();