import jQ from "jquery";
import Settings from "../../../helpers/settings";
import Utils from "../../../helpers/utils";
import Class from "../../../helpers/class";
import InstantSearchEnum from "../../../enum/instant-search-enum";
import BaseComponent from "../../base-component";
import InstantSearchResultBlock from "../instant-search-block/instant-search-result-block";
import InstantSearchResultBlockViewAll from "../instant-search-block/instant-search-result-block-view-all";
import InstantSearchResultBlockLoading from "../instant-search-block/instant-search-result-block-loading";
import InstantSearchResultBlockEmpty from "../instant-search-block/instant-search-result-block-empty";
/**
* Instant search result
* @extends BaseComponent
*/
class InstantSearchResult extends BaseComponent {
/**
* @constructs
* @param {String} searchInputId The element ID of search input
* @param {Object} $searchInputElement The jQuery object of search input element
*/
constructor(searchInputId, $searchInputElement) {
super();
this.searchInputId = searchInputId;
this.isLoading = false;
this.isFirstLoad = true;
this.blocks = [];
this.loadingBlock = null;
// Set selector
var wrapperSelector = '.' + Class.searchSuggestionWrapper + '[data-search-box-id="' + this.searchInputId + '"]';
this.selector = {
wrapper: wrapperSelector,
popover: wrapperSelector + ' .' + Class.searchSuggestion + '-popover',
loading: wrapperSelector + ' .' + Class.searchSuggestion + '-loading',
}
this.$scriptInstantSearchNotFoundJson = jQ('#boost-pfs-instant-search-products-not-found-json');
this.$searchInputElement = $searchInputElement;
this.$instantSearchResult = null;
this.$wrapper = null;
this.$popoverElement = null;
this.$loadingElement = null;
this.suggestionDirection = this._getSuggestionDirection();
this.position = '';
this.settings = {
suggestionNoResult: Settings.getSettingValue('search.suggestionNoResult')
};
};
/**
* Returns whether or not this instant search result style is used
*/
static isActive() {
return true
}
/**
* Initialize the instant search result component
*/
init() {
// Loading block
this.loadingBlock = new InstantSearchResultBlockLoading();
this.addComponent(this.loadingBlock);
// Result block
this.blockSettings = Settings.getSettingValue('search.suggestionBlocks');
// Put the Product block to top for Style2
this._applyFilterBlockSettings();
this.blockSettings.forEach((blockSetting) => {
var block = new InstantSearchResultBlock(blockSetting);
this.addComponent(block);
this.blocks.push(block);
});
// View all block
var blockViewAll = new InstantSearchResultBlockViewAll();
this.addComponent(blockViewAll);
this.blockViewAll = blockViewAll;
// Empty block
var blockEmpty = new InstantSearchResultBlockEmpty();
this.addComponent(blockEmpty);
this.blockEmpty = blockEmpty;
};
/**
* Get the raw HTML template of instant search result
* @returns {String} Raw HTML string
*/
getTemplate() {
return `
<div class="{{class.searchSuggestionWrapper}}" data-search-box-id="{{searchInputId}}">
<div class="{{class.searchSuggestion}}-popover" data-direction="{{suggestionDirection}}"></div>
</div>
`;
};
/**
* Replace the brackets in raw html template with proper values
* @returns {String} HTML string
*/
compileTemplate() {
return this.getTemplate()
.replace(/{{searchInputId}}/g, this.searchInputId)
.replace(/{{suggestionDirection}}/g, this.suggestionDirection)
.replace(/{{class.searchSuggestionWrapper}}/g, Class.searchSuggestionWrapper)
.replace(/{{class.searchSuggestion}}/g, Class.searchSuggestion);
};
/**
* Sorting the blocks in result data
*/
_applyFilterBlockSettings() {}
/**
* Render the result template to DOM
*/
render() {
if (this.isFirstLoad) {
// Render initial suggestion container and append it to the dom
var suggestionHtml = this.compileTemplate();
this.appendToSelector = 'body';
this._applyFilterAutocompleteAppendElement();
jQ(this.appendToSelector).append(suggestionHtml);
this.$wrapper = jQ(this.selector.wrapper);
this.$popoverElement = jQ(this.selector.popover);
this.isFirstLoad = false;
} else {
// Show the suggestion popover
this.$instantSearchResult.siblings().show();
// Render suggestion blocks and items
if (this.isLoading) {
this._renderSuggestionLoading();
this._renderWrapper();
} else {
this._renderWrapper();
this._renderSuggestion();
}
}
}
/**
* Change the element selector where the instant search result append to
*/
_applyFilterAutocompleteAppendElement() {}
/**
* Setup Classes, Position for Instant search wrapper
*/
_renderWrapper() {
var mobileClass = Utils.InstantSearch.isFullWidthMobile() ? Class.searchSuggestionMobile : '';
if (mobileClass !== '') {
this.$wrapper.addClass(mobileClass);
}
// Set suggestion wrapper, popover, and ul position
var suggestionPosition = this._setSuggestionPosition();
suggestionPosition.setSuggetionPosition();
suggestionPosition.setSuggetionPopoverPosition();
// Set suggestion width
var width = '';
if (Settings.getSettingValue('search.suggestionWidth') == 'auto' || Utils.isMobile()) {
width = this.$searchInputElement.outerWidth();
} else {
width = Settings.getSettingValue('search.suggestionWidth');
}
this.$wrapper.outerWidth(width);
}
/**
* Setup position for Instant search dropdown
*/
_setSuggestionPosition() {
var topPosition = Utils.InstantSearch.isFullWidthMobile() ? 'top' : 'top+12';
if (this.suggestionDirection == 'left') {
this.$wrapper.position({
my: 'left ' + topPosition,
at: 'left bottom',
of: this.$searchInputElement[0]
});
var setSuggetionPosition = () => {
this.$instantSearchResult.position({
my: 'left ' + topPosition,
at: 'left bottom',
of: this.$searchInputElement[0]
})
};
var setSuggetionPopoverPosition = () => {
this.$popoverElement.position({
my: 'left+20 top-8' + topPosition,
at: 'left bottom',
of: this.$searchInputElement[0]
})
};
} else {
this.$wrapper.position({
my: 'right ' + topPosition,
at: 'right bottom',
of: this.$searchInputElement[0]
});
var setSuggetionPosition = () => {
this.$instantSearchResult.position({
my: 'right ' + topPosition,
at: 'right bottom',
of: this.$searchInputElement[0]
})
};
var setSuggetionPopoverPosition = () => {
this.$popoverElement.position({
my: 'right-20 top-8',
at: 'right bottom',
of: this.$searchInputElement[0]
})
};
}
return {
setSuggetionPosition: setSuggetionPosition,
setSuggetionPopoverPosition: setSuggetionPopoverPosition
}
}
/**
* Render the result blocks into Instant search dropdown
*/
_renderSuggestion() {
if (this.$instantSearchResult) {
this.$instantSearchResult.attr('data-search-box', this.id);
}
if (this.data) {
var suggesionChildElements = [];
var isAllEmpty = Utils.getValueInObjectArray(InstantSearchEnum.ResultType.ALL_EMPTY, this.data);
if (isAllEmpty) {
// If have search redirect and no result, hide the search suggestion
if (this.blockEmpty.$element) {
suggesionChildElements.push(this.blockEmpty.$element);
this.blocks.forEach((block) => {
suggesionChildElements.push(block.$element);
});
} else {
this.$wrapper.hide();
}
} else {
this.blocks.forEach((block) => {
suggesionChildElements.push(block.$element);
});
// Append view all block
suggesionChildElements.push(this.blockViewAll.$element);
}
// Append to DOM
this.$instantSearchResult.append(suggesionChildElements);
}
};
/**
* Render Instant search - Loading block
*/
_renderSuggestionLoading() {
// TODO: render loading block if it is enabled in settings and have not rendered yet
if (this.loadingBlock.$element && !jQ(this.selector.loading).length) {
// Hide all suggestion content
this.$instantSearchResult.children().hide();
// Add Loading to the first children of the suggestion content if it isn't existed
this.$instantSearchResult.prepend(this.loadingBlock.$element);
this.$loadingElement = jQ(this.selector.loading);
// Show Loading
this.$wrapper.addClass(Class.searchSuggestionOpen);
this.$instantSearchResult.show();
this.$loadingElement.show();
}
};
/**
* Get direction of instant search result box
* @returns {String} Return left OR right
*/
_getSuggestionDirection() {
var suggestionPosition = Settings.getSettingValue('search.suggestionPosition');
if (suggestionPosition != '') return suggestionPosition;
var middleScreenPosition = jQ(window).width() / 2;
var leftSearchBoxPosition = this.$searchInputElement.offset().left;
return leftSearchBoxPosition < middleScreenPosition ? 'left' : 'right';
};
/**
* Setup data for Instant search box
* @param {Object} $instantSearchResult jQuery selector object of Intant search rerult element
* @param {Object} data The Suggestion result data
* @param {Boolean} isLoading Render loading block OR result blocks
*/
setData($instantSearchResult, data, isLoading) {
this.$instantSearchResult = $instantSearchResult;
this.data = data;
this.isLoading = isLoading;
if (this.data) {
// TODO: Set data for Suggestion blocks
var isAllEmpty = Utils.getValueInObjectArray(InstantSearchEnum.ResultType.ALL_EMPTY, this.data);
var instantSearchNotFoundJson = [];
if (this.$scriptInstantSearchNotFoundJson.length) {
instantSearchNotFoundJson = JSON.parse(this.$scriptInstantSearchNotFoundJson.html());
}
this.blocks.forEach((block) => {
var blockData = Utils.getValueInObjectArray(block.type, this.data);
// Set the popular data for products block and sugestion block if return not found result
if (isAllEmpty) {
if (block.type == InstantSearchEnum.ResultType.PRODUCTS
&& instantSearchNotFoundJson.hasOwnProperty('products')
&& this.settings.suggestionNoResult.products.status
) {
this._preparePopularProducts(instantSearchNotFoundJson.products);
blockData = instantSearchNotFoundJson.products;
block.notFoundLabel = this.settings.suggestionNoResult.products.label;
} else if (block.type == InstantSearchEnum.ResultType.SUGGESTIONS
&& instantSearchNotFoundJson.hasOwnProperty('search_terms')
&& this.settings.suggestionNoResult.search_terms.status
) {
blockData = instantSearchNotFoundJson.search_terms;
block.notFoundLabel = this.settings.suggestionNoResult.search_terms.label;
}
}
block.setData(blockData, isAllEmpty);
});
// TODO: Set data for View all block
this.blockEmpty.setData(this.data);
// TODO: Set data for View all block
this.blockViewAll.setData(this.data);
}
}
/**
* Prepare the popular products for Not found block
* @param {Object} products - The popular product data
*/
_preparePopularProducts(products) {
products.forEach((product) => {
// Generate the images_info of product
var images_info = [];
product.media.forEach((media) => {
if (media.media_type == 'image') {
images_info.push({
id: media.id,
position: media.position,
src: media.src,
width: media.width,
height: media.height
});
}
});
product.images_info = images_info;
product.price /= 100;
product.price_min /= 100;
product.price_max /= 100;
product.compare_at_price /= 100;
product.compare_at_price_min /= 100;
product.compare_at_price_max /= 100;
});
return products;
}
}
export default InstantSearchResult;