Source: components/filter/filter-tree/filter-option-item/filter-option-item-multi-level-collections.js

import jQ from 'jquery';

import FilterOptionItem from './filter-option-item';
import Class from '../../../../helpers/class';
import Utils from '../../../../helpers/utils';
import FilterApi from "../../../../api/filter-api";
import Globals from "../../../../helpers/globals";
import Navigation from "../../../../helpers/navigation";
import FilterTreeEnum from "../../../../enum/filter-tree-enum";
import FilterOptionEnum from "../../../../enum/filter-option-enum";
import Settings from "../../../../helpers/settings";

/**
 * Filter option item for displayType = 'multi_level_collections'
 * @extends FilterOptionItem
 */
class FilterOptionItemMultiLevelCollections extends FilterOptionItem {
	constructor(filterTreeType) {
		super(filterTreeType);
		this.$element = null;
		this.$itemElement = null;
		this.$childContainerElement = null;
	}

	static getLevelOpenStateData(filterItem) {
		var filterOptionId = filterItem.filterOption.filterOptionId;
		var collectionId = filterItem.collectionId;
		var tagValue = filterItem.tag;
		var combineKey = filterOptionId + ":" + collectionId + (tagValue ? (":" + tagValue) : '');
		return levelOpenStateData.get(combineKey);
	}

	static setLevelOpenStateData(filterItem, isOpen) {
		var filterOptionId = filterItem.filterOption.filterOptionId;
		var collectionId = filterItem.collectionId;
		var tagValue = filterItem.tag;
		var combineKey = filterOptionId + ":" + collectionId + (tagValue ? (":" + tagValue) : '');
		levelOpenStateData.set(combineKey, isOpen);
	}

	init() {
		var selectType = this.filterOption ? this.filterOption.selectType : this.parent.selectType;
		this.requestInstantly = this.filterTreeType == FilterTreeEnum.FilterTreeType.VERTICAL
			|| selectType == FilterOptionEnum.SelectType.SINGLE
			|| Settings.getSettingValue('general.requestInstantly');
	}

	getTemplate() {
		return `
			<li class="boost-pfs-filter-option-multi-level-item boost-pfs-filter-option-first-level-item">
				<div class="{{class.filterOptionItem}} {{class.filterOptionLabel}}">
					<a class="{{class.button}}" data-action="select-filter-item" href="{{href}}">
						<span class="boost-pfs-filter-option-value">{{label}}</span>
					</a>
					{{arrow}}
				</div>
				<div class="boost-pfs-filter-option-multi-level-list boost-pfs-filter-option-second-level-list">
					{{childItems}}
				</div>
			</li>
		`;
	}

	getArrowTemplate() {
		return `
			<button class="{{class.button}} {{class.button}}-arrow" data-action="expand-filter-item" aria-label="{{label.ada.toggleMultiLevel}}">
				<span class="boost-pfs-arrow"></span>
			</button>
		`;
	}

	compileArrowTemplate() {
		if (this.level != 3 && this.children && this.children.length > 0) {
			return this.getArrowTemplate()
				.replace(/{{label.ada.toggleMultiLevel}}/g, Labels.ada.toggleMultiLevel.replace(/{{filterItem}}/g, this.label));
		} else {
			return '';
		}
	}

	compileTemplate() {
		return this.getTemplate()
			.replace(/{{class.filterOptionItem}}/g, Class.filterOptionItem)
			.replace(/{{class.filterOptionLabel}}/g, Class.filterOptionLabel)
			.replace(/{{label}}/g, this.label)
			.replace(/{{href}}/g, this.href)
			.replace(/{{arrow}}/g, this.compileArrowTemplate())
			.replace(/{{class.button}}/g, Class.button)
			.replace(/{{childItems}}/g, '');
	}

	render() {
		if (!this.$element) {
			this.$element = jQ(this.compileTemplate());
			this.$itemElement = this.$element.find('> .' + Class.filterOptionItem);
			this.$itemClickElement = this.$itemElement.find('[data-action="select-filter-item"]');
			this.$toggleClickElement = this.$itemElement.find('[data-action="expand-filter-item"]');
			this.$childContainerElement = this.$element.find('> .boost-pfs-filter-option-multi-level-list');
			if (this.$childContainerElement && this.children && this.children.length > 0) {
				this.children.forEach(child => {
					if (child.$element) {
						this.$childContainerElement.append(child.$element);
					}
				})
			}
		}

		if (this.$itemElement) {
			if (this.isSelected || this.isSelectedChild) {
				this.$itemElement.addClass('selected');
			} else {
				this.$itemElement.removeClass('selected');
			}
			var isOpen = FilterOptionItemMultiLevelCollections.getLevelOpenStateData(this);
			var openLevelByDefault = Settings.getSettingValue('general.openMultiLevelByDefault');
			if (isOpen
				|| (Array.isArray(openLevelByDefault) && openLevelByDefault.includes(this.level))
				|| (isOpen == null && this.isSelectedChild)) {
				this.$itemElement.addClass('boost-pfs-open');
				FilterOptionItemMultiLevelCollections.setLevelOpenStateData(this, true);
			} else {
				FilterOptionItemMultiLevelCollections.setLevelOpenStateData(this, false);
			}
		}
	}

	bindEvents() {
		if (this.$itemClickElement) {
			this.$itemClickElement.on('click', this.onClick.bind(this));
		}
		if (this.$toggleClickElement) {
			this.$toggleClickElement.on('click', this.onClickToggle.bind(this));
		}
	}

	onClick(event) {
		if (event) {
			event.preventDefault();
		}
		this.setCollectionParams();
		this.clearAllTagParams();

		// Reset the page param
		FilterApi.setParam('page', 1);

		// Expand the children
		if (this.children && this.children.length > 0) {
			FilterOptionItemMultiLevelCollections.setLevelOpenStateData(this, true);
		}

		if (this.requestInstantly) {
			FilterApi.applyFilter('collection');
		}
	}

	onClickToggle() {
		this.$itemElement.toggleClass('boost-pfs-open');
		if (this.$itemElement.hasClass('boost-pfs-open')) {
			FilterOptionItemMultiLevelCollections.setLevelOpenStateData(this, true);
		} else {
			FilterOptionItemMultiLevelCollections.setLevelOpenStateData(this, false);
		}
	}

	isAppliedFilter() {
		return Globals.collectionId == this.collectionId;
	}

	setCollectionParams() {
		// Set address bar to know which collection is being selected
		// On search page: add param pf_c_collection
		if (Utils.isSearchPage()) {
			FilterApi.setParam(this.filterOption.filterOptionId, this.collectionId);

		// On collection page: change collection path
		} else {
			Navigation.setAddressBarPathAfterFilter(this.href);
			Navigation.setWindowTitleAfterFilter(this.label + ' - ' + Globals.shopName);

			// Set new sort order on collection pages
			FilterApi.setParam('sort', this.sortOrder);
		}
		Globals.collectionId = this.collectionId;
		FilterApi.setParam('collection_scope', this.collectionId);
	}

	clearAllTagParams() {
		// Clear the pf_ct_collection and collectionTags query
		var filterOptionTagId = this.filterOption.filterOptionId.replace(Globals.prefix + '_c', Globals.prefix + '_ct');
		FilterApi.setParam(filterOptionTagId, null);
		Globals.collectionTags = [];

		// Clear all filter option params except collection
		var currentFilterOptionIds = [];
		Object.keys(Globals.queryParams).forEach(queryParam => {
			if (queryParam.startsWith(Globals.prefix) && !queryParam.startsWith(Globals.prefix  + '_c')) {
				currentFilterOptionIds.push(queryParam);
			}
		});

		currentFilterOptionIds.forEach(filterOptionId => {
			FilterApi.setParam(filterOptionId, null);
		});
	}

	setData(data) {
		super.setData(data);
		this.filterOption = this.parent;
		this.level = 1;
		this.href = Utils.isSearchPage() ? 'javascript:void(0);' : '/collections/' + this.handle;
		this.sortOrder = data.sort_order ? data.sort_order : Globals.defaultSorting;

		this.children = [];
		if (Array.isArray(data.tags)) {
			data.tags.forEach(tagData => {
				if (tagData.tag) {
					var childItem = new FilterOptionItemSecondLevelCollections(this.filterTreeType);
					this.addComponent(childItem);
					childItem.setData(tagData);
				}
			})
		}

		this.isSelected = this.isAppliedFilter();
		this.isSelectedChild = this.children.some(x => x.isSelected || x.isSelectedChild);

		this.filterOption.allNestedFilterItems.set(this.collectionId, this);
	}
}

/**
 * The second level (tag) of the multi level collection.
 * This class is not exported.
 * @extends FilterOptionItemMultiLevelCollections
 */
class FilterOptionItemSecondLevelCollections extends FilterOptionItemMultiLevelCollections {

	getTemplate() {
		return `
			<div class="boost-pfs-filter-option-multi-level-item boost-pfs-filter-option-second-level-item">
				<div class="{{class.filterOptionItem}} {{class.filterOptionLabel}}">
					<a class="{{class.button}}" data-action="select-filter-item" href="{{href}}">
						<span class="boost-pfs-check-box"></span>
						<span class="boost-pfs-filter-option-value">{{label}}</span>
					</a>
					{{arrow}}
				</div>
				<div class="boost-pfs-filter-option-multi-level-list boost-pfs-filter-option-third-level-list">
					{{childItems}}
				</div>
			</div>
		`;
	}

	onClick(event) {
		if (event) {
			event.preventDefault();
		}

		// If clicking on tag from a different collection, clear all previous tags
		if (this.collectionId != Globals.collectionId || Settings.getSettingValue('general.multiLevelCollectionSelectType') == FilterOptionEnum.SelectType.SINGLE) {
			this.clearAllTagParams();
		}
		this.setCollectionParams();
		this.setTagParams();

		// Reset the page param
		FilterApi.setParam('page', 1);

		// Expand the children
		if (this.children && this.children.length > 0) {
			FilterOptionItemMultiLevelCollections.setLevelOpenStateData(this, true);
		}

		if (this.requestInstantly) {
			FilterApi.applyFilter('collection');
		}
	}

	setTagParams() {
		// Change address bar to know which tag is being selected
		// On search page, or using OR condition for tags: add param pf_ct_collection
		if (Utils.isSearchPage() || Settings.getSettingValue('general.multiLevelCollectionSelectType') == FilterOptionEnum.SelectType.MULTIPLE) {
			if (!Array.isArray(Globals.collectionTags)) {
				Globals.collectionTags = [];
			}
			var tagIndex = Globals.collectionTags.indexOf(this.tag);
			if (tagIndex > -1) {
				Globals.collectionTags.splice(tagIndex, 1);
			} else {
				Globals.collectionTags.push(this.tag);
			}
			Settings.general.tagMode = '2';

			// Set address bar to know which tag is being selected: add pf_ct_collection=tag
			var filterOptionTagId = this.filterOption.filterOptionId.replace(Globals.prefix + '_c_', Globals.prefix + '_ct_');
			if (Array.isArray(Globals.collectionTags) && Globals.collectionTags.length > 0) {
				FilterApi.setParam(filterOptionTagId, Globals.collectionTags);
			} else {
				FilterApi.setParam(filterOptionTagId, null);
			}

		// On collection page and using AND condition for tags: use 'collection/tags' path (like shopify link list)
		} else {
			Globals.collectionTags = this.tag;
			Navigation.setAddressBarPathAfterFilter(this.href);
		}
	}

	isAppliedFilter() {
		var isIncludeTag = (Array.isArray(Globals.collectionTags) && Globals.collectionTags.includes(this.tag)) || Globals.collectionTags == this.tag;
		return Globals.collectionId == this.collectionId && isIncludeTag;
	}

	setData(data) {
		this.tag = data.tag;
		this.slugifyTag = Utils.slugify(this.tag);
		this.label = data.displayName ? data.displayName : data.tag;
		this.filterOption = this.parent.filterOption;
		this.requestInstantly = this.parent.requestInstantly;
		this.collectionId = this.parent.collectionId;
		this.handle = this.parent.handle;
		this.level = 2;
		this.href = (Utils.isSearchPage() || Settings.getSettingValue('general.multiLevelCollectionSelectType') == FilterOptionEnum.SelectType.MULTIPLE) ?
			'javascript:void(0);' : '/collections/' + this.handle + '/' + this.slugifyTag;

		this.sortOrder = this.parent.sortOrder ? this.parent.sortOrder : Globals.defaultSorting;

		this.children = [];
		if (Array.isArray(data.subTags)) {
			data.subTags.forEach(tagData => {
				if (tagData.tag) {
					var childItem = new FilterOptionItemThirdLevelCollections(this.filterTreeType);
					this.addComponent(childItem);
					childItem.setData(tagData);
				}
			})
		}

		// Format the label
		this.label = this.buildLabel();
		this.isSelected = this.isAppliedFilter();
		this.isSelectedChild = this.children.some(x => x.isSelected || x.isSelectedChild);

		this.filterOption.allNestedFilterItems.set(this.collectionId + ':' + this.tag, this);
	}
}

/**
 * The third level (subTag) of the multi level collection.
 * This class is not exported.
 * @extends FilterOptionItemSecondLevelCollections
 */
class FilterOptionItemThirdLevelCollections extends FilterOptionItemSecondLevelCollections {
	getTemplate() {
		return `
			<div class="boost-pfs-filter-option-multi-level-item boost-pfs-filter-option-third-level-item">
				<div class="{{class.filterOptionItem}} {{class.filterOptionLabel}}">
					<a class="{{class.button}}" data-action="select-filter-item" href="{{href}}">
						<span class="boost-pfs-check-box"></span>
						<span class="boost-pfs-filter-option-value">{{label}}</span>
					</a>
				</div>
			</div>
		`;
	}
	setData(data) {
		this.tag = data.tag;
		this.slugifyTag = Utils.slugify(this.tag);
		this.label = data.displayName ? data.displayName : data.tag;
		this.filterOption = this.parent.filterOption;
		this.requestInstantly = this.parent.requestInstantly;
		this.collectionId = this.parent.collectionId;
		this.handle = this.parent.handle;
		this.level = 3;
		this.href = (Utils.isSearchPage() || Settings.getSettingValue('general.multiLevelCollectionSelectType') == FilterOptionEnum.SelectType.MULTIPLE) ?
			'javascript:void(0);' : '/collections/' + this.handle + '/' + this.slugifyTag;

		this.filterOption.allNestedFilterItems.set(this.collectionId + ':' + this.tag, this);
		this.sortOrder = this.parent.sortOrder ? this.parent.sortOrder : Globals.defaultSorting;

		// Format the label
		this.label = this.buildLabel();
		this.isSelected = this.isAppliedFilter();
	}
}

var levelOpenStateData = new Map();

export default FilterOptionItemMultiLevelCollections;
export { FilterOptionItemSecondLevelCollections, FilterOptionItemThirdLevelCollections };