Source: components/instant-search/instant-search-style/instant-search-mobile.js

import jQ from 'jquery';

import BaseComponent from '../../base-component';
import SearchInput from '../instant-search-element/search-input';
import Class from "../../../helpers/class";
import Settings from "../../../helpers/settings";
import Labels from "../../../helpers/labels";
import Selector from "../../../helpers/selector";
import Utils from "../../../helpers/utils";
import Globals from '../../../helpers/globals';

/**
 * Instant search on mobile
 * @extends BaseComponent
 */
class InstantSearchMobile extends BaseComponent {
	/**
	 * @constructs
	 */
	constructor() {
		super();
		this.data = '';
		this.isBoundEvents = false;
		this.isOpen = false;
		this.inputMobileId = Selector.searchBoxMobile.substr(1);
		this.searchBox = null;
		
		this.selector = {
			searchInput: Selector.searchBoxMobile,
			clearButton: '.' + Class.searchSuggestionBtnClearMobile,
			closebutton: '.' + Class.searchSuggestionBtnCloseMobile,
			submitButton: '.' + Class.searchSuggestionBtnSubmitMobile,
			topPanel: '.' + Class.searchSuggestion + '-mobile-top-panel',
			overlay: '.' + Class.searchSuggestion + '-mobile-overlay',
			searchInputs: 'input[name="' + Settings.getSettingValue('search.termKey') + '"]'
		};
	}

	/**
	 * Enum of Insatant search mobile templates (SEARCH_BTN, DEFAULT)
	 * @enum {Object}
	 */
	static get tempType() {
		return {
			SEARCH_BTN: 'search_btn',
			DEFAULT: 'default'
		};
	}

	/**
	 * Returns whether or not this instant search result mobile is used
	 */
	static isActive() {
		return true;
	}

	/**
	 * Get the raw HTML template of Search box on mobile
	 * @param {String} tempType The template type: 'default' or 'search_btn'
	 * @returns {String} The raw HTML template
	 */
	getTemplate(tempType) {
		switch (tempType) {
			case InstantSearchMobile.tempType.SEARCH_BTN:
				return `
					<a href="javascript:;" class="{{class.searchSuggestionBtnSubmitMobile}}"><span>Submit</span></a>
				`;
			default:
				return `
					<div class="{{class.searchSuggestion}}-mobile-overlay"></div>
					<div class="{{class.searchSuggestion}}-mobile-top-panel">
						<form action="/search" method="get">
							<button type="button" class="{{class.searchSuggestionBtnCloseMobile}}"><-</button>
							{{btnSearch}}
							<input type="text" name="{{searchTermKey}}" placeholder="{{searchBoxPlaceholder}}" id="{{searchId}}" />
							<button type="button" class="{{class.searchSuggestionBtnClearMobile}}">X</button>
						</form>
					</div>
				`;
		}
	}

	/**
	 * Replace the brackets in raw html template with proper values
	 * @returns {String} HTML string
	 */
	compileTemplate() {
		var submitBtn = '';
		// Show search button on mobile OR NOT
		if (Settings.getSettingValue('search.showSearchBtnMobile')) {
			submitBtn = this.getTemplate(InstantSearchMobile.tempType.SEARCH_BTN);
		}

		return this.getTemplate()
			.replace(/{{btnSearch}}/g, submitBtn)
			.replace(/{{searchTermKey}}/g, Settings.getSettingValue('search.termKey'))
			.replace(/{{searchBoxPlaceholder}}/g, Labels.suggestion.searchBoxPlaceholder)
			.replace(/{{searchId}}/g, this.inputMobileId)
			.replace(/{{class.searchSuggestion}}/g, Class.searchSuggestion)
			.replace(/{{class.searchSuggestionBtnSubmitMobile}}/g, Class.searchSuggestionBtnSubmitMobile)
			.replace(/{{class.searchSuggestionBtnCloseMobile}}/g, Class.searchSuggestionBtnCloseMobile)
			.replace(/{{class.searchSuggestionBtnClearMobile}}/g, Class.searchSuggestionBtnClearMobile);
	}

	/**
	 * Render the search box on mobile
	 */
	render() {
		jQ('body').append(this.compileTemplate());
	}

	/**
	 * Returns whether or not the events are bind on instant search mobile
	 */
	isBindEvents() {
		return !this.isBoundEvents;
	};

	/**
	 * Bind the events on Instant search mobile
	 */
	bindEvents() {
		this.$searchInput = jQ(this.selector.searchInput);
		this.$clearButtonElement = jQ(this.selector.clearButton);
		this.$closebuttonElement = jQ(this.selector.closebutton);
		this.$submitButtonElement = jQ(this.selector.submitButton);
		this.$topPanelElement = jQ(this.selector.topPanel);
		this.$overlayElement = jQ(this.selector.overlay);
		
		// apply autocomplete for search input
		this.searchBox = new SearchInput(this.inputMobileId, this.$searchInput);
		this.searchBox.refresh();

		// Add close button event
		this.$closebuttonElement.on('click', this.closeInstantSearchMobile.bind(this, true));
		// Add clear button event
		this.$clearButtonElement.on('click', this.clearInstantSearchMobile.bind(this));

		this.$searchInputs = jQ(this.selector.searchInputs);
		this.$searchInputs
			.on('click', this._onClickSearchBox.bind(this))
			.on('focus', this._onFocusSearchBox.bind(this))
			.on('keyup', this._onTypeSearchBoxEvent.bind(this));
		this.$targetInput = null;

		this.isBoundEvents = true;
	}

	/**
	 * Bind the click evnet on the search input to open the search box on mobile
	 * @param {Event} event DOM event
	 */
	_onClickSearchBox(event) {
		if (Utils.isFullWidthMobile()) {
			// Set search term for all search input
			if (this.$targetInput && this.$targetInput.val() !== '') {
				this.$searchInputs.val(this.$targetInput.val());
			}
			// Open search result box if the exist the search term
			if (this.$searchInput && this.$searchInput.val() !== '') {
				this.openSuggestionMobile();
			}
		}
	}

	/**
	 * Bind the focus event on the search input to open the search box on mobile
	 * @param {Event} event DOM Event
	 */
	_onFocusSearchBox(event) {
		if (Utils.isFullWidthMobile()) {
			// Open the instant search box when focus on the theme search input
			if (!jQ(event.target).is(this.$searchInput)) {
				this.$targetInput = jQ(event.target);
				this.showSearchBoxMobile();
				this.$searchInput.trigger('click');
			}
		}
	}

	/**
	 * Bind the typeahead event on the search input
	 * @param {*} event 
	 */
	_onTypeSearchBoxEvent(event) {
		if (Utils.InstantSearch.isFullWidthMobile()) {
			this.searchBox.instantSearchResult.$wrapper.show();
			if (event.target.value == '') {
				this.closeInstantSearchMobile();
				this.$clearButtonElement.hide();
			} else {
				this.$clearButtonElement.show();
			}
		}
	}

	/**
	 * Show search box in mobile
	 */
	showSearchBoxMobile() {
		this.isOpen = true;
		// Click outside suggestion and close the suggestion
		this.onClickOutsideSuggestionMobileEvent();
		// Close keyboard when scrolling on mobile
		this.scrollSuggestionMobileEvent();
		// Display or not the clear button
		if (this.$searchInput.val() == '') {
			this.$clearButtonElement.hide();
		} else {
			this.$clearButtonElement.show();
		}
		if (!this.$searchInput.is(':focus')) {
			// Display the Search box on Top
			this.$topPanelElement.show();
			this.$overlayElement.show();
			// Remove Lock focus on Menu mobile, Dialog,... which have tabindex=-1
			jQ('[tabindex=-1]').removeAttr('tabindex').addClass(Class.searchSuggestionNoTabIndex);
			if (Utils.isMobile() && jQ("[data-open=true]").length > 0) {
			  jQ("[data-open=true]").attr('data-open', false);
			}
			// Focus Search box
			setTimeout(() => {
				this.$searchInput.focus();
			}, 100);
			// After showing the Search box on top on mobile
			this.afterShowSearchBoxMobile();
			
		}
	}

	/**
	 * Close search box on mobile
	 * @param {Boolean} isClose Close the search box or just close the instant search result
	 */
	closeInstantSearchMobile(isClose) {
		// Close Suggestion box
		this.$searchInput.autocomplete('close');
		this.searchBox.instantSearchResult.$wrapper.hide()
		// Remove search box
		var isClose = typeof isClose !== 'undefined' ? isClose : false;
		if (isClose) {
			this.$topPanelElement.hide();
			this.$overlayElement.hide();
		}
		// Update current term for all search boxes
		this._setValueAllSearchBoxes();
		// Add back tabindex=-1
		jQ('.' + Class.searchSuggestionNoTabIndex).attr('tabindex', -1);
		// Return scrolling of body
		if (jQ('body').hasClass(Class.searchSuggestionMobileOpen)) {
			jQ('body').removeClass(Class.searchSuggestionMobileOpen);
		}
		
		this.afterCloseInstantSearchMobile(isClose);
	}

	/**
	 * Clear the current search term and instant search result
	 */
	clearInstantSearchMobile() {
		this.$clearButtonElement.hide();
		// Set blank for current term
		Globals.currentTerm = '';
		// Apply blank for all search boxes
		this._setValueAllSearchBoxes();
		// Close Instant search result
		this.closeInstantSearchMobile();
		// Focus again Search box
		this.$searchInput.focus();
	}

	/**
	 * After close the instant search mobile
	 * @param {String} isClose Close the search box or just close the instant search result
	 */
	afterCloseInstantSearchMobile(isClose) {
		// Overide this method for customization
	}

	/**
	 * Update the current search term to all search inputs
	 * @param {*} value 
	 */
	_setValueAllSearchBoxes(value) {
		var value = typeof value !== 'undefined' ? searchTerm : Globals.currentTerm;
		this.$searchInputs.val(value);
	}

	/**
	 * Bind the click event on outside of instant search mobile to close it.
	 */
	onClickOutsideSuggestionMobileEvent() {
		jQ(document).on('touchstart', (event) => {
			var contentContainer = jQ('.' + Class.searchSuggestion + '-mobile-top-panel');
			if (!contentContainer.is(event.target) 
				&& contentContainer.has(event.target).length == 0
				&& this.searchBox
				&& this.searchBox.instantSearchResult.$wrapper.has(event.target).length == 0) {
				this.closeInstantSearchMobile(true);
			}
		});
	}

	/**
	 * Bind the scroll event on instant search mobile
	 */
	scrollSuggestionMobileEvent() {
		jQ(document).on('touchmove', (e) => {
			if (this.$searchInput.is(':focus')) {
				this.$searchInput.blur();
			}
		});
	}

	/**
	 * After show the instant search mobile
	 */
	afterShowSearchBoxMobile() {
		// Override this method for customization
	}

	/**
	 * Open the instant search mobile
	 */
	openSuggestionMobile() {
		// Before opening suggestion
		this.beforeOpenSuggestionMobile();
		// Add a specific class to prevent body from scrolling
		if (!jQ('body').hasClass(Class.searchSuggestionMobileOpen)) {
			jQ('body').addClass(Class.searchSuggestionMobileOpen);
		}
		// Dsisplay the Search box on Top
		this.showSearchBoxMobile();
		this.$searchInput.autocomplete('search');
		this.searchBox.instantSearchResult.$wrapper.show();
		// Scroll to the top when clicking to the search box
		jQ('html,body').scrollTop();
		// After opening suggestion
		this.afterOpenSuggestionMobile();
	}

	beforeOpenSuggestionMobile() {
		// Override this method for customization
	}
	
	afterOpenSuggestionMobile() {
		// Override this method for customization
	}
}

export default InstantSearchMobile;