Skip to content

Instantly share code, notes, and snippets.

@reciosonny
Last active October 4, 2021 04:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save reciosonny/c5c5054fef498af68264772c4d79cf37 to your computer and use it in GitHub Desktop.
Save reciosonny/c5c5054fef498af68264772c4d79cf37 to your computer and use it in GitHub Desktop.
Vue - Reused similar functionalities using mixins
<template>
<div class="plp-wrapper" :class="{'adjust-wrapper-background-scroll-plp': dockFacetMenuListToTop}">
<facet-menu-list-reflektion
:rfk-facets="rfkFacets"
:facet-selections="selectedFacets"
:preselected-facets="urlParams"
:rfk-page-config="rfkPageConfig"
:rfk-sort-options="rfkSortOptions"
id="facet-menu-list"
:class="{ 'ps-is-stuck' : !inView.stickyWrapper && (isTouchScreen || storeScrollPosition > minimumScrollPositionForFacetsSticky), 'adjust-with-warning-without-toolbar': stickyWarningOn && !inView.stickyWrapper, 'dock-facets-to-top': dockFacetMenuListToTop && !stickyWarningOn}"
class="facet-menu-list-bottom-box-shadow"
>
</facet-menu-list-reflektion>
<div class="product-viewport">
<div id="plp-header"></div>
<plp-products id="plp-products"
:is-loading="productsLoading"
:result-set="resultSet"
:last-response="lastResponse"
:root-category="rootCategory"
:ratings-by-id="ratingsByIdRfk"
:category-error="categoryError"
:selection-json="selectionJson"
:selected-facets="selectedFacets"
:loyalty-club="loyaltyClub"
:loyalty-club-pdp-redesign="loyaltyClubPdpRedesign"
:selected-category="selectedCategory" :load-products-attempted="loadProductsAttempted"
:rfk-page-config="rfkPageConfig"
:rfk-sort-options="rfkSortOptions"
:clear-facet="clearFacet"
:default-rfk-id="defaultRfkId"
>
</plp-products>
<div id="plp-footer"></div>
</div>
</div>
</template>
<script>
import _ from 'lodash';
import axios from 'axios';
import Commerce from '@/pointsource/commerce/currency/commerce';
import { EventBus } from '@/eventBus';
import GeneralUtilsSvc from '@/sei/js/services/generalUtils.service';
import SearchSvc from '@/sei/js/services/search.service';
import plpProducts from '@/sei/js/scriptPortlets/plp-reflektion/plp.products';
import spContextualDataStore from '@/sei/js/services/spContextualData.service';
import facetMenuListReflektion from '@/sei/js/components/facets/facetMenuListReflektion';
import productTileListReflektion from '@/sei/js/components/productTileListReflektion';
import reflektionContainerMixin from '@/sei/js/components/reflektion/reflektionContainerMixin';
import parseAuthoredDataSvc from '@/sei/js/services/parseAuthoredData.service';
import Dx from '@/sei/js/dx';
export default {
name: 'plpReflektion',
mixins: [reflektionContainerMixin],
components: { plpProducts, facetMenuListReflektion, productTileListReflektion
},
data: function () {
return {
rootCategory: {},
selectionJson: {},
rootCategoryName: '',
selectedCategory: {},
topLevelCategories: {},
categoryError: false,
lastResponse: {},
categoryViewUri: null,
defaultRfkId: Dx.getRfkIdCategory()
};
},
watch: {
isStuck: function () {
this.dummyResize();
},
facetSelections: function () {
this.selectedFacets = this.facetSelections;
},
},
created: function () {
this.isCategoryPage = true;
this.spContextualData = spContextualDataStore.getSPContextualData('plp-reflektion-vue');
this.selectionJson = this.spContextualData.selectionJson;
this.categoryViewUri = this.spContextualData.categoryViewUri;
this.loadLoyaltyClubAuthoredData();
//#region Event listeners
EventBus.$on('category.error', () => {
this.categoryError = true;
});
EventBus.$on('selected.category.changed', (newCategory) => {
this.selectedCategory = newCategory;
this.loadProducts();
});
EventBus.$on('onRfkDataLoaded', result => {
try {
const { reflektionIds } = this.spContextualData.authData;
const rfkIdPlpHeader = reflektionIds.shortText4;
const rfkIdPlpFooter = reflektionIds.shortText5;
if (rfkIdPlpHeader) {
const resultWidget = result.widgets[rfkIdPlpHeader];
if (resultWidget) {
const htmlContent = resultWidget.templates.html.devices.pc.content;
this.renderPlpHeaderContentCustom(htmlContent);
}
else { //fallback to default Header Content from WCM if no widget found for reflektion
this.renderPlpHeaderContentWCM();
}
}
if (rfkIdPlpFooter) {
const resultWidget = result.widgets[rfkIdPlpFooter];
if (resultWidget) {
const htmlContent = resultWidget.templates.html.devices.pc.content;
this.renderPlpFooterContentCustom(htmlContent);
}
else { //fallback to default Footer Content from WCM if no widget found for reflektion
this.renderPlpFooterContentWCM();
}
}
} catch (ex) {
console.error('unhandled error on loading RFK widget: ', ex);
this.renderPlpHeaderContentWCM();
this.renderPlpFooterContentWCM();
}
});
//fallback if RFK request for UI widgets failed
EventBus.$on('onRfkRequestWidgetError', () => {
this.renderPlpHeaderContentWCM();
this.renderPlpFooterContentWCM();
});
//#endregion
this.initializeCategoryName();
this.loadProducts();
},
mounted: async function () {
const { reflektionIds } = this.spContextualData.authData;
const rfkIdPlpHeader = reflektionIds.shortText4;
const rfkIdPlpFooter = reflektionIds.shortText5;
if (!rfkIdPlpHeader) {
this.renderPlpHeaderContentWCM();
}
if (!rfkIdPlpFooter) {
this.renderPlpFooterContentWCM();
}
const currentPage = `${window.location.pathname}${window.location.search}`;
const uuid = this.getRfkUidCookie();
try {
const { reflektionIds } = this.spContextualData.authData;
if (reflektionIds) {
let rfkUiWidgets = { widgets: {} };
const enableRfkWidgetCalls = _.get(reflektionIds, 'shortTextNumber1');
if(enableRfkWidgetCalls === 'true') {
rfkUiWidgets = await this.getRfkUIWidgets(currentPage, uuid);
}
EventBus.$emit('onRfkDataLoaded', rfkUiWidgets);
}
else {
console.error("Script Portlet data for reflektionIds doesn't exist. Please check so UI widgets can be rendered from RFK");
EventBus.$emit('onRfkRequestWidgetError');
}
} catch (ex) {
console.error('RFK UI widget request failed: ', ex);
EventBus.$emit('onRfkRequestWidgetError');
}
},
methods: {
renderPlpHeaderContentWCM: function () {
const plpCompEl = $(this.$el);
const plpSPEle = $('.product-list-page');
let renderedPlpHeaderContent = plpSPEle.find('#plp-header-content').html();
// append the header content from the script portlet (template)
plpCompEl.find('#plp-header').html(renderedPlpHeaderContent);
},
renderPlpFooterContentWCM: function() {
const plpCompEl = $(this.$el);
const plpSPEle = $('.product-list-page');
let renderedPlpFooterContent = plpSPEle.find('#plp-footer-content').html();
// append the footer content from the script portlet (template)
plpCompEl.find('#plp-footer').html(renderedPlpFooterContent);
},
renderPlpHeaderContentCustom: function (customHtml) {
const plpCompEl = $(this.$el);
plpCompEl.find('#plp-header').html(customHtml);
},
renderPlpFooterContentCustom: function (customHtml) {
const plpCompEl = $(this.$el);
plpCompEl.find('#plp-footer').html(customHtml);
},
getRfkUIWidgets: async function (targetUrl, uuid) {
let rfkBatchData = {};
const rfkRes = await SearchSvc.rfkGetUIWidgets(targetUrl, uuid);
if (rfkRes.data) {
if (rfkRes.data.batch) {
rfkBatchData = rfkRes.data.batch.reduce((obj, currVal) => {
if (!obj.widgets) obj.widgets = {};
// only get widgets with HTML templates
if(currVal.widget && currVal.appearance) {
const { widget } = currVal;
if(!obj.widgets[widget.rfkid]) obj.widgets[widget.rfkid] = {};
if(currVal.appearance.variables) { //contains CSS properties from reflektion widget
obj.widgets[widget.rfkid].cssVariables = currVal.appearance.variables;
}
if (currVal.appearance.templates) {
obj.widgets[widget.rfkid].templates = currVal.appearance.templates;
}
}
return obj;
}, {});
} else { //results goes here if rfk only returned 1 widget result
const { widget, appearance } = rfkRes.data;
rfkBatchData = {
widgets: {
[widget.rfkid]: {
...widget,
cssVariables: appearance.variables,
templates: appearance.templates
}
}
}
}
}
return rfkBatchData;
},
initializeCategoryName: function () {
const categoryName = this.selectionJson.category || this.selectionJson.rootCategory;
this.selectedCategory.name = _.startCase(_.toLower(_.trim(categoryName)));
this.rootCategory = {
name: _.startCase(this.selectionJson.rootCategory)
}
},
loadLoyaltyClubAuthoredData: function () {
const authData = parseAuthoredDataSvc.fetchData('commonAuthoredData');
this.hasPageCopyData = !!(authData && authData.loyaltyClub2);
if (this.hasPageCopyData) {
this.loyaltyClub = {
arialLabel: _.get(authData.loyaltyClub2, 'shortText1'),
xlinkRef: _.get(authData.loyaltyClub2, 'shortText2'),
linkText: _.get(authData.loyaltyClub2, 'shortText3'),
pointsEarnableText: _.get(authData.loyaltyClub2, 'shortText4'),
withGoalClubText: _.get(authData.loyaltyClub2, 'text1'),
mapPriceText: _.get(authData.loyaltyClub2, 'text4')
};
this.loyaltyClubPdpRedesign = {
goalClubBenefitsApplyLinkText: _.get(authData.loyaltyClub2, 'shortText5'),
withGoalClubText: _.get(authData.loyaltyClub2, 'text1'),
plusText: _.get(authData.loyaltyClub2, 'text2'),
learnMoreLinkText: _.get(authData.loyaltyClub2, 'text3'),
mapPriceText: _.get(authData.loyaltyClub2, 'text4'),
pointsText: _.get(authData.loyaltyClub2, 'text5')
};
}
}
}
}
</script>
import axios from 'axios';
import { EventBus } from '@/eventBus';
import _ from 'lodash';
import Dx from '@/sei/js/dx';
import ContinueShoppingCookieSvc from '@/sei/js/services/continueShopping.service';
import FacetInterpreterSvc from '@/sei/js/services/facetInterpreter.service';
import GeneralUtilsSvc from '@/sei/js/services/generalUtils.service';
import ProductRatingsSvc from '@/sei/js/services/productRatings.service';
import psWidgetHelper from '@/pointsource/widgets/js/psWidgetHelper';
import SearchSvc from '@/sei/js/services/search.service';
import * as Cookies from 'js-cookie';
const reflektionContainerMixin = {
data: function () {
return {
isCategoryPage: true, //set isCategory to `false` when this mixin is used in search-reflektion.vue
rfkDefaultPageConfig: {
pageSize: Number(Dx.getRfkSearchDefaultItemsPerPage()),
pageNumber: 1,
sort: {
value: []
}
},
rfkSearchConfig: Dx.getRfkSearchConfig(),
resultSet: {},
rfkPageConfig: {},
rfkPageNumber: null,
rfkTotalPages: null,
facetSelections: null,
selectedFacets: {},
dockFacetMenuListToTop: false,
minimumScrollPositionForFacetsSticky: 20,
storeScrollPosition: null,
stickyWarningOn: false,
contextualDataStoreName: '',
rfkProductResults: {},
ratingsByIdRfk: null,
hasPageCopyData: {},
rfkFacets: null,
rfkSortOptions: [],
urlParams: {},
spContextualData: null,
loyaltyClub: {},
loyaltyClubPdpRedesign: {},
query: '',
inView : {},
loadProductsAttempted: false,
isTouchScreen: false,
productsLoading: false,
applyDefaultSort: false, //set to true if user selected a sort option in dropdown. Note: also set to true based on WCM settings later. We can use this to determine if default sort is applied the first time page loads
cfgInitialized: false
}
},
created() {
this.isTouchScreen = psWidgetHelper.detectTouchScreen();
this.selectedFacets = this.facetSelections;
EventBus.$on('navBarShowsEvent', () => {
this.dockFacetMenuListToTop = false;
});
EventBus.$on('navBarHidesEvent', () => {
this.dockFacetMenuListToTop = true;
});
EventBus.$on('ps-inview.status.in', this.updateInviewStatus);
EventBus.$on('ps-inview.status.out', this.updateInviewStatus);
EventBus.$on('sticky.warning.on', () => {
this.stickyWarningOn = true;
});
EventBus.$on('sticky.warning.off', () => {
this.stickyWarningOn = false;
});
EventBus.$on('sort.order.changed', (optionObj) => {
this.rfkPageConfig.pageNumber = 1;
this.rfkPageConfig.orderBy = optionObj.value;
this.rfkPageConfig.sort.value = [];
this.rfkPageConfig.sort.value.push(optionObj.sort);
this.applyDefaultSort = true; //need to set to true when user selected a dropdown so that the sort options they selected will be applied
this.loadProducts();
});
EventBus.$on('items.per.page.changed', (itemsPerPage) => {
this.rfkPageConfig.pageNumber = 1;
this.rfkPageConfig.pageSize = itemsPerPage;
this.loadProducts();
});
EventBus.$on('selected.facets.changed', (newFacets) => {
if (!_.isEqual(newFacets, this.selectedFacets)) {
this.rfkPageConfig.pageNumber = 1;
}
this.loadProducts();
});
EventBus.$on('selected.facets.clear', this.clearFacet);
EventBus.$on('selected.page.changed', (newPageNumber) => {
this.rfkPageConfig.pageNumber = newPageNumber;
$(window).scrollTop(0);
this.loadProducts();
});
window.addEventListener('popstate', (event) => {
let oldState = event.state;
this.query = oldState.query; //only in search-reflektion
this.facetSelections = oldState.facetSelections;
this.processRfkPageConfig();
this.loadProducts();
});
},
methods: {
initializeRfkConfig: function () { //note: This should be called first before calling `updateAddressBar` to load querystring values like sort options
if (!this.cfgInitialized) {
// reflektion page config. Set it in order
this.setSortOptions();
this.updateRfkDefaultPageConfig();
this.rfkPageConfig = _.cloneDeep(this.rfkDefaultPageConfig);
this.processRfkPageConfig();
this.cfgInitialized = true;
}
},
clearFacet: function (facetPayload) {
var facetToClear = facetPayload.facetToClear;
var facetGroup = this.facetSelections[facetToClear.key];
// if only one facet remains, remove it
if (facetGroup) {
if (facetGroup.length === 1) {
delete this.facetSelections[facetToClear.key];
}
else {
_.remove(this.facetSelections[facetToClear.key], function (facet) {
return facetToClear.key === facet.key &&
facetToClear.label === facet.label &&
facetToClear.value === facet.value &&
facetToClear.filterName === facet.filterName;
});
}
}
this.selectedFacets = Object.assign({}, this.facetSelections);
if (facetPayload.lastFacet) {
EventBus.$emit('selected.facets.changed', facetToClear);
}
},
loadProducts: function () {
EventBus.$emit('products.loading');
this.initializeRfkConfig();
this.updateAddressBar(!this.isCategoryPage);
// put setTimeout to update addressbar first with queries like search or facets
setTimeout(() => {
const currentPage = `${window.location.pathname}${window.location.search}`;
if (this.lastRequestUrl !== currentPage) {
this.requestProducts(this.lastRequestUrl = currentPage);
}
else {
EventBus.$emit('products.loaded');
}
}, 150);
},
addressBarQueryBuilder: function (hasQuerySearch) {
let newPath = `${window.location.pathname}`;
let facetQuery = '';
const pgConfig = this.getPageConfigString(true);
if (this.selectedFacets) {
const facetString = FacetInterpreterSvc.getFacetStringRfk(this.selectedFacets, ':');
facetQuery = facetString;
}
let queryStringResults = '';
if (hasQuerySearch) {
queryStringResults += this.query && `query=${this.query}`;
queryStringResults += facetQuery && `&${facetQuery}`;
queryStringResults += pgConfig && `&${pgConfig}`;
}
else {
queryStringResults += pgConfig ? `${pgConfig}${facetQuery && `&${facetQuery}`}` : `${facetQuery}`;
}
if (queryStringResults.length > 0) {
newPath = `${newPath}?${queryStringResults}`;
}
return newPath;
},
updateAddressBar: function (hasQuerySearch) {
setTimeout(() => {
var oldPath = window.location.pathname + window.location.search;
const newPath = this.addressBarQueryBuilder(hasQuerySearch);
var historyObj = {
url: newPath,
query: this.query,
facetSelections: this.selectedFacets
};
if (decodeURIComponent(oldPath) !== newPath) {
window.history.pushState(historyObj, null, newPath);
}
else { // handles the initial page load
window.history.replaceState(historyObj, null, newPath);
}
// Update Continue Shopping Link used by Cart
ContinueShoppingCookieSvc.setContinueShoppingLink();
});
},
getCategoryString: function() {
return GeneralUtilsSvc.jsonToQueryString({
categoryId: this.selectedCategory.uniqueID
});
},
getPageConfigString: function (nonDefaultsOnly) {
// copy the rfkPageConfig
let currConfig = _.omit(_.cloneDeep(this.rfkPageConfig), ['catalogId', 'query', 'sort', 'filter', 'facets']);
let diffObj = nonDefaultsOnly ? {} : currConfig;
if (nonDefaultsOnly) {
// remove items that match defaults
_.forOwn(currConfig, (value, key) => {
if (value !== this.rfkDefaultPageConfig[key]) {
diffObj[key] = value;
}
});
}
// if orderBy is null, remove it so it won't be included in querystring
if (diffObj.orderBy === null) {
delete diffObj.orderBy;
}
return GeneralUtilsSvc.jsonToQueryString(diffObj);
},
finishLoading: function() {
this.productsLoading = false;
this.loadProductsAttempted = true;
EventBus.$emit('products.loaded');
},
initializeRfkFacets: function (facetData) {
this.rfkFacets = FacetInterpreterSvc.getVisibleFacetsRfk(facetData);
this.rfkFacets = this.updateRfkFacetsWithSolrFieldNames(this.rfkFacets);
this.groupSizeFacetsRfk();
},
getRfkUidCookie: function() {
return Cookies.get('__ruid') || {};
},
requestProducts: async function (targetUrl) {
this.rfkProductResults = {};
this.productsLoading = true;
setTimeout(async () => {
if (!_.isEmpty(this.selectedFacets)) {
Object.assign(this.rfkPageConfig, {query: this.query, facets: this.selectedFacets});
}
const uuid = this.getRfkUidCookie();
try {
const hasQuerySearch = !this.isCategoryPage;
const hasSortOption = this.rfkPageConfig.sort.value[0];
const pageConfig = _.cloneDeep(this.rfkPageConfig);
if (!hasSortOption) {
delete pageConfig.sort;
}
const rfkRes = await SearchSvc.rfkProductSearch(pageConfig, targetUrl, uuid, this.selectedFacets, hasQuerySearch);
if (rfkRes.status === 200) {
this.initializeRfkFacets(rfkRes.data.facet);
const totalItems = rfkRes.data.total_item;
if (Dx.showBvReviews()) {
// note: use `rfkProductResults` to display the content results from reflektion
this.rfkProductResults = rfkRes.data.content.product;
this.rfkPageNumber = rfkRes.data.page_number;
this.rfkTotalPages = rfkRes.data.total_page;
this.resultSet = { rfkProductResults: this.rfkProductResults, recordSetTotal: totalItems, recordSetPageNumber: this.rfkPageNumber, totalPages: this.rfkTotalPages };
}
else {
this.updateRatingsByIdRfk(rfkRes.data.content.product.value)
.then(() => {
this.rfkProductResults = rfkRes.data;
this.rfkPageNumber = rfkRes.data.page_number;
this.rfkTotalPages = rfkRes.data.total_page;
this.resultSet = { rfkProductResults: this.rfkProductResults, recordSetTotal: totalItems, recordSetPageNumber: this.rfkPageNumber, totalPages: this.rfkTotalPages };
});
}
}
} catch (ex) {
console.error(ex);
}
this.finishLoading();
}, 150);
},
updateRatingsByIdRfk: function (items) {
let itemIds = _.map(items, function (item) {
return item.commerceProductId;
});
return ProductRatingsSvc.getRatings(itemIds)
.then((ratingsMap) => {
this.ratingsByIdRfk = ratingsMap;
});
},
updateInviewStatus: function (inviewObj) {
var ctrl = this;
ctrl.$set(ctrl.inView, _.camelCase(inviewObj.id), inviewObj.inView);
},
updateRfkDefaultPageConfig: function () {
const urlParams = GeneralUtilsSvc.queryStringToJson(window.location.search);
if (urlParams.orderBy) {
this.applyDefaultSort = true; //if orderBy querystring is detected, we need to apply the sort the first page load.
}
//we can use this to set the default order of the list results. Null for no sort applied by default during page load
if (this.applyDefaultSort) {
this.rfkDefaultPageConfig.orderBy = _.get(this.spContextualData, 'defaultSort', 1); //default of 1 = Featured
}
else {
this.rfkDefaultPageConfig.orderBy = null;
}
},
setSortOptions: function () {
const getWcmRfkSortOptions = _.get(this.spContextualData, 'rfkSortOptions');
const makeSortOptions = [{label: 'Select Sort:', value: null}].concat(getWcmRfkSortOptions);
this.rfkSortOptions = makeSortOptions;
},
processRfkPageConfig: function () {
this.urlParams = GeneralUtilsSvc.queryStringToJson(window.location.search);
this.facetSelections = FacetInterpreterSvc.facetArrayToJson(this.urlParams.facet, true);
this.rfkPageConfig = _.merge(this.rfkPageConfig, this.urlParams);
this.rfkPageConfig.orderBy = this.urlParams.orderBy ? Number(this.urlParams.orderBy) : this.rfkDefaultPageConfig.orderBy;
this.rfkPageConfig.pageNumber = Number(this.urlParams.pageNumber) || this.rfkDefaultPageConfig.pageNumber;
this.rfkPageConfig.pageSize = Number(this.urlParams.pageSize) || this.rfkDefaultPageConfig.pageSize;
this.rfkPageConfig.sort.value = [];
let sortObj = _.find(this.rfkSortOptions, {'value': this.rfkPageConfig.orderBy});
if (typeof sortObj !== 'undefined') {
this.rfkPageConfig.sort.value.push(sortObj.sort);
}
else {
this.rfkPageConfig.sort.value.push(this.rfkDefaultPageConfig.sort.value[0]);
}
delete this.rfkPageConfig.facet;
},
groupSizeFacetsRfk: function () {
let groupedSizeFacets = {};
groupedSizeFacets.name = 'SIZE';
groupedSizeFacets.Entry = [];
groupedSizeFacets.value = 'SizesUrls';
if (this.rfkFacets && this.rfkFacets.length > 0) {
_.each(this.rfkFacets, function(facet) {
if (facet.name && (_.endsWith(facet.name.toLowerCase(), 'size') || _.startsWith(facet.filterName, 'footwear'))) {
if (facet.Entry && facet.Entry.length > 0) {
_.each(facet.Entry, function (entry) {
entry.originalFacetType = facet.value;
entry.urlValue = 'Size';
entry.filterName = facet.filterName;
groupedSizeFacets.Entry.push(entry);
});
}
}
});
}
_.remove(this.rfkFacets, function (facet) {
return _.endsWith(facet.name.toLowerCase(), 'size') || _.startsWith(facet.filterName, 'footwear');
});
if (groupedSizeFacets.Entry.length > 0) {
this.rfkFacets.unshift(groupedSizeFacets);
}
},
updateRfkFacetsWithSolrFieldNames: function(rfkFacets) {
if (rfkFacets && rfkFacets.length > 0) {
const result = _.map(rfkFacets, facet => {
const model = Object.assign({}, facet);
model.name = facet.display_name;
model.value = facet.display_name.replace(/ |\//g, '-');
model.Entry = facet.value.map(e => {
var newEntry = Object.assign({}, e);
delete newEntry.text;
newEntry.label = e.text;
newEntry.filterName = facet.filterName;
return newEntry;
});
return model;
});
return result;
}
},
}
};
export default reflektionContainerMixin;
<template>
<div class="plp-wrapper" :class="{'adjust-wrapper-background-scroll-search': dockFacetMenuListToTop}">
<div id="sticky-wrapper" class="sticky-wrapper" v-ps-inview="{ offset : { bottom : 163 } }">
<facet-menu-list-reflektion
:facet-selections="selectedFacets"
:rfk-facets="rfkFacets"
:preselected-facets="urlParams"
:rfk-page-config="rfkPageConfig" id="facet-menu-list"
:class="{ 'ps-is-stuck' : !inView.stickyWrapper && (isTouchScreen || storeScrollPosition > minimumScrollPositionForFacetsSticky), 'adjust-with-warning-without-toolbar': stickyWarningOn && !inView.stickyWrapper, 'dock-facets-to-top': dockFacetMenuListToTop && !stickyWarningOn}"
class="facet-menu-list-bottom-box-shadow">
</facet-menu-list-reflektion>
</div>
<div class="search-viewport">
<div class="small-12 columns selected-facets-area-margin-bottom">
<selected-facets
id="selected-facets"
:rfk-page-config="rfkPageConfig"
:rfk-sort-options="rfkSortOptions"
:query="query"
:resultsNumber="rfkProductResults.total_item" :facet-selections="selectedFacets"
:rfk-facets="rfkFacets" :clear-facet-fn="clearFacet" :is-rfk="true">
</selected-facets>
</div>
<div class="products-column small-12 columns"
:class="{'large-8': searchSource('blogs'), 'large-12': !searchSource('blogs')}"
v-if="searchSource('products')">
<div class="product-tileset-header clearfix">
<div class="small-12 medium-6 columns show-for-large">
<h2>Products
<span class="total-results">
{{ rfkProductResults.total_item > 0 ? rfkProductResults.total_item + ' results' : '&nbsp;' }}
</span>
</h2>
</div>
<div class="small-6 columns show-for-small-only">
<div class="header-pagination search-results-pagination-placement-small"
v-if="!productsLoading && rfkProductResults.total_item && rfkProductResults.total_item > 0">
<pagination class="noselect" :rfk-page-config="rfkPageConfig" :max-pages="8" :rfk-page-number="rfkPageNumber" :rfk-total-pages="rfkTotalPages" :is-rfk="true"></pagination>
</div>
</div>
<div class="small-6 columns">
<div class="result-metadata" v-if="!productsLoading">
<div class="page-size-select" ref="pageSizeSelect">
<items-per-page-select
v-if="!productsLoading && rfkProductResults && rfkProductResults.total_item && rfkProductResults.total_item > 0"
v-model="rfkPageConfig.pageSize"></items-per-page-select>
</div>
</div>
</div>
</div>
<div class="loader-wrapper" v-if="query && productsLoading">
<div class="loader"></div>
<div class="loader-text">Searching products...</div>
</div>
<h3 class="no-results"
v-if="!(productsLoading || (rfkProductResults.value && rfkProductResults.value.length))">
Search for "{{ query }}" yielded 0 products.
</h3>
<div class="header-pagination hide-for-small-only"
v-if="!productsLoading && rfkProductResults.total_item && rfkProductResults.total_item > 0">
<pagination class="noselect" :rfk-page-config="rfkPageConfig" :max-pages="8" :rfk-page-number="rfkPageNumber" :rfk-total-pages="rfkTotalPages" :is-rfk="true"></pagination>
</div>
<product-tile-list-reflektion id="product-tileset" class="product-tileset" v-if="!productsLoading"
:default-rfk-id="defaultRfkId"
:from-search="query"
:loyalty-club="loyaltyClub"
:loyalty-club-pdp-redesign="loyaltyClubPdpRedesign"
:item-set="rfkProductResults.value"
:ratings-by-id="ratingsByIdRfk"
>
</product-tile-list-reflektion>
<div class="footer-pagination"
v-if="!productsLoading && rfkProductResults.total_item && rfkProductResults.total_item > 0">
<pagination class="noselect" :rfk-page-config="rfkPageConfig" :max-pages="8" :rfk-page-number="rfkPageNumber" :rfk-total-pages="rfkTotalPages" :is-rfk="true"></pagination>
</div>
<div class="show-for-large modified-rfk-product-suggestions-header" :data-rfkid="rfkId"></div>
</div>
<div class="blog-column large-4 small-12 columns">
<div class="hide-for-large modified-rfk-product-suggestions-header" :data-rfkid="rfkId"></div>
<div v-if="searchSource('blogs')">
<div v-if="filteredTeams.length > 0">
<h2 class="other-results-header">
Teams
<span class="total-results">
{{ filteredTeams.length }} <span v-if="totalNumberOfFilteredTeamsResults> filteredTeams.length">/ {{totalNumberOfFilteredTeamsResults}}</span> results
</span>
<a v-if="searchLinks.teams1Text && searchLinks.teams1Text.length > 0" @click="goToTeamSearchPage()" class="see-all-clubs-teams-players-articles-link"> {{ searchLinks.teams1Text.toUpperCase() }} </a>
<a v-if="searchLinks.teams2Text && searchLinks.teams2Text.length > 0" @click="goToTeam2SearchPage()" class="see-all-clubs-teams-players-articles-link"> {{ searchLinks.teams2Text.toUpperCase() }} </a>
</h2>
<div class="team-entry-list">
<div class="teamEntry" v-for="entry in filteredTeams"
@click="goToTeamPage(entry.teamPath)">
<div>
<img class="other-search-results-club-team-image"
:src="entry.thumbnailUrl? entry.thumbnailUrl : spContextualData.teamPlaceholder"/>
<span
class="other-search-results-club-team-image-caption">{{ entry.teamName }}</span>
</div>
</div>
</div>
</div>
<div v-if="filteredClubs.length > 0">
<h2 class="other-results-header">
Clubs
<span class="total-results">{{ filteredClubs.length }} <span
v-if="totalNumberOfFilteredClubsResults > filteredClubs.length">/ {{totalNumberOfFilteredClubsResults}}</span> results</span>
<a @click="goToClubSearchPage()" class="see-all-clubs-teams-players-articles-link">{{searchLinks.clubText.toUpperCase()}}</a>
</h2>
<div class="club-entry-list">
<div class="clubEntry" v-for="entry in filteredClubs"
@click="goToClubPage(entry.clubIdentifier)">
<div>
<img class="other-search-results-club-team-image" :src="entry.imageUrl"
@error="adjustClubImageUrl(entry)"/>
<span class="other-search-results-club-team-image-caption">{{ entry.name }}</span>
</div>
</div>
</div>
</div>
<div v-if="filteredPlayers.length > 0">
<h2 class="other-results-header">
Players
<span class="total-results">
{{ filteredPlayers.length }} <span v-if="totalNumberOfFilteredPlayersResults > filteredPlayers.length">/ {{totalNumberOfFilteredPlayersResults}}</span> results
</span>
<a @click="goToPlayerSearchPage()" class="see-all-clubs-teams-players-articles-link">{{ searchLinks.playersText.toUpperCase() }}</a>
</h2>
<div class="player-entry-list">
<div class="playerEntry" v-for="entry in filteredPlayers"
@click="goToPlayerPage(entry.playerPath)">
<div>
<figure class="other-search-results-player-image">
<img
:src="entry.imageUrl ? entry.imageUrl : spContextualData.playerPlaceholder"/>
<figcaption class="other-search-results-player-image-caption">{{ entry.firstName }} {{ entry.lastName }}
</figcaption>
</figure>
</div>
</div>
</div>
</div>
<div v-if="blogResults.searchResults && blogResults.searchResults.length > 0">
<h2 class="other-results-header">
Articles
<span class="total-results">
{{ blogResults.searchResults.length }} results
</span>
<a @click="goToBlogSearchPage()" class="see-all-clubs-teams-players-articles-link">{{ searchLinks.blogText.toUpperCase() }}</a>
</h2>
<div class="loader-wrapper" v-if="query && blogLoading">
<div class="loader"></div>
<div class="loader-text">Searching articles...</div>
</div>
<div class="article-list" v-if="!blogLoading && blogResults.searchResults">
<div class="article" @click="goToBlogPage(entry.path)"
v-for="(entry,index) in blogResults.searchResults">
<div class="image" :class="entry.id"
:aria-labelledby="'Click to read article: '+entry.title" v-if="entry.image"
v-html="getBlogEntryStyle(entry)">
</div>
<div class="title" :aria-labelledby="'Click to read article: '+entry.title">
{{ entry.title }}
</div>
</div>
</div>
</div>
<h2 class="searching-for-something-text">Looking for Something?</h2>
<div class="check-out-other-sections-text">Check out these other sections of {{ storeTitle }}</div>
<button v-if="searchLinks.clubText && searchLinks.clubText.length > 0" class="button check-out-other-sections-button" @click="goToClubSearchPage()">{{searchLinks.clubText}}
</button>
<button v-if="searchLinks.teams1Text && searchLinks.teams1Text.length > 0" class="button check-out-other-sections-button" @click="goToTeamSearchPage()">{{searchLinks.teams1Text}}
</button>
<button v-if="searchLinks.teams2Text && searchLinks.teams2Text.length > 0" class="button check-out-other-sections-button" @click="goToTeam2SearchPage()">{{searchLinks.teams2Text}}
</button>
<button class="button check-out-other-sections-button" @click="goToPlayerSearchPage()">{{ searchLinks.playersText }}
</button>
<button class="button check-out-other-sections-button" @click="goToBlogSearchPage()">{{ searchLinks.blogText }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
/*
* Copyright (c) 2019-2020 SEI.
* All rights reserved.
*/
import dashboardUtils from '@/sei/js/services/dashboard-utils.service';
import Dx from '@/sei/js/dx';
import {EventBus} from '@/eventBus';
import facetMenuList from '@/sei/js/components/facets/facetMenuList';
import facetMenuListReflektion from '@/sei/js/components/facets/facetMenuListReflektion';
import GeneralUtilsSvc from '@/sei/js/services/generalUtils.service';
import itemsPerPageSelect from '@/sei/js/components/facets/itemsPerPageSelect';
import pagination from '@/sei/js/components/facets/pagination';
import parseAuthoredDataSvc from '@/sei/js/services/parseAuthoredData.service';
import PlayerpassDataSvc from '@/sei/js/scriptPortlets/playerpass/services/playerpassData.service';
import productTileListReflektion from '@/sei/js/components/productTileListReflektion';
import psWidget from '@/pointsource/widgets/js/psWidget';
import SearchSvc from '@/sei/js/services/search.service';
import selectedFacets from '@/sei/js/components/facets/selectedFacets';
import sortSelect from '@/sei/js/components/facets/sortSelect';
import spContextualDataStore from '@/sei/js/services/spContextualData.service';
import wcmFetchSvc from '@/sei/js/services/wcmFetch.service';
import reflektionContainerMixin from '@/sei/js/components/reflektion/reflektionContainerMixin';
export default {
name: 'searchReflektion',
mixins: [reflektionContainerMixin],
components: {
facetMenuList, facetMenuListReflektion, itemsPerPageSelect, pagination, productTileListReflektion, selectedFacets, sortSelect
},
data: function () {
return {
blogResults: {},
isStuck: false,
blogLoading: false,
fullTeamsList: [],
fullClubsList: [],
fullPlayersList: [],
storeTitle: null,
blogParams: {},
teamParams: {},
playerParams: {},
searchLinks: {},
totalNumberOfFilteredClubsResults: 0,
totalNumberOfFilteredPlayersResults: 0,
totalNumberOfFilteredTeamsResults: 0,
modalOpen: false,
rfkSearchConfig: Dx.getRfkSearchConfig(),
defaultRfkId: ''
};
},
watch: {
isStuck: function () {
this.dummyResize();
},
facetSelections: function () {
this.selectedFacets = this.facetSelections;
},
query: function () {
this.search(this);
}
},
computed: {
filteredTeams: function () {
if (this.query && this.fullTeamsList.length > 0) {
this.fullTeamsList = _.filter(this.fullTeamsList, function (team) {
return team.teamName;
});
this.fullTeamsList = _.sortBy(this.fullTeamsList, 'teamName');
var filteredTeamsVar = _.filter(this.fullTeamsList, (team) => {
return team.teamName.toLowerCase().indexOf(this.query.toLowerCase()) > -1;
});
this.totalNumberOfFilteredTeamsResults = filteredTeamsVar.length;
return filteredTeamsVar.splice(0, 4);
}
return [];
},
filteredPlayers: function () {
if (this.query && this.fullPlayersList.length > 0) {
this.fullPlayersList = _.filter(this.fullPlayersList, function (player) {
return player.firstName;
});
this.fullPlayersList = _.sortBy(this.fullPlayersList, 'lastName');
var filteredPlayersVar = _.filter(this.fullPlayersList, (player) => {
return (player.lastName.toLowerCase().indexOf(this.query.toLowerCase()) > -1) || (player.firstName.toLowerCase().indexOf(this.query.toLowerCase()) > -1);
});
this.totalNumberOfFilteredPlayersResults = filteredPlayersVar.length;
return filteredPlayersVar.splice(0, 4);
}
return [];
},
filteredClubs: function () {
if (this.query && this.fullClubsList.length > 0) {
var filteredClubsVar = _.filter(
this.fullClubsList,
(clubObj) => {
return _.includes(
_.lowerCase(clubObj.name),
_.lowerCase(this.query)
);
});
this.totalNumberOfFilteredClubsResults = filteredClubsVar.length;
_.each(filteredClubsVar, function (club) {
club.imageUrl = '//www.soccer.com/wcsstore/sasSOCsoccer/upload/images/club/SAP' + club.clubIdentifier + '_logo.png';
});
return filteredClubsVar.splice(0, 4);
}
return [];
}
},
created: function () {
this.defaultRfkId = _.get(this.rfkSearchConfig, 'urlConfig.rfkFullPageSearchWidget');
this.isCategoryPage = false;
this.spContextualData = spContextualDataStore.getSPContextualData('search-reflektion-vue');
this.storeTitle = Dx.getStoreTitle();
this.getClub = Dx.getShowPlayerPass();
this.urlParams = GeneralUtilsSvc.queryStringToJson(window.location.search);
if (this.urlParams.query) {
this.query = this.urlParams.query;
}
if (this.spContextualData) {
this.rfkId = _.get(this.spContextualData, 'search[0].rfkId');
this.teamParams = _.get(this.spContextualData, 'wcmParms2');
this.blogParams = _.get(this.spContextualData, 'wcmParms1');
this.playerParams = _.get(this.spContextualData, 'wcmParms3');
}
$(document).on('open.zf.reveal', '[data-reveal]', () => {
this.modalOpen = true;
});
$(document).on('closed.zf.reveal', '[data-reveal]', () => {
this.modalOpen = false;
});
EventBus.$on('selected.facets.changed', (newFacets) => {
if (_.isEqual(newFacets, this.selectedFacets)) {
this.checkPerformBlogSearch();
}
});
// note: unique to search-reflektion
if (Foundation.MediaQuery.atLeast('medium') && !this.isTouchScreen) {
const navBar = $('#sticky-nav');
$(window).on('scroll', _.throttle(() => {
if (!this.productsLoading && !this.modalOpen) {
if ($(window).scrollTop() > 0) {
if (!navBar.hasClass('hide-nav-categories')) {
EventBus.$emit('navBarHidesEvent');
}
}
else {
EventBus.$emit('navBarShowsEvent');
}
}
}, 50));
EventBus.$on('products.loading', () => {
this.storeScrollPosition = 0;
});
}
$(document).scroll(() => {
_.debounce(this.dummyResize, 250);
});
this.loadLoyaltyClubAuthoredData();
if (this.getClub) {
if (this.searchSource('blogs')) {
PlayerpassDataSvc.getClubList()
.then((clubList) => {
this.fullClubsList = clubList;
});
}
}
if (this.teamParams) {
wcmFetchSvc.componentFetch(null, this.teamParams.contentContext, this.teamParams.inlineSearchTarget, this.teamParams.componentId, null, null, null, this.teamParams.menu_co, null, this.teamParams.cacheId )
.then((response) => {
this.fullTeamsList = response.data;
});
}
if (this.playerParams) {
wcmFetchSvc.componentFetch(null, this.playerParams.contentContext, this.playerParams.inlineSearchTarget, this.playerParams.componentId, null, null, null, this.playerParams.menu_co, null, this.playerParams.cacheId)
.then((response) => {
this.fullPlayersList = response.data;
});
}
},
mounted: function () {
$('#query-text-trigger').addClass('modify-search-bar-mobile');
},
methods: {
loadLoyaltyClubAuthoredData: function () {
const authData = parseAuthoredDataSvc.fetchData('commonAuthoredData');
const loyaltyClubCopy = !!(authData && authData.loyaltyClub);
if (loyaltyClubCopy) {
this.loyaltyClub = {
arialLabel: _.get(authData.loyaltyClub2, 'shortText1'),
xlinkRef: _.get(authData.loyaltyClub2, 'shortText2'),
linkText: _.get(authData.loyaltyClub2, 'shortText3'),
pointsEarnableText: _.get(authData.loyaltyClub2, 'shortText4'),
withGoalClubText: _.get(authData.loyaltyClub2, 'text1'),
mapPriceText: _.get(authData.loyaltyClub2, 'text4')
};
this.loyaltyClubPdpRedesign = {
goalClubBenefitsApplyLinkText: _.get(authData.loyaltyClub2, 'shortText5'),
withGoalClubText: _.get(authData.loyaltyClub2, 'text1'),
plusText: _.get(authData.loyaltyClub2, 'text2'),
learnMoreLinkText: _.get(authData.loyaltyClub2, 'text3'),
mapPriceText: _.get(authData.loyaltyClub2, 'text4'),
pointsText: _.get(authData.loyaltyClub2, 'text5')
};
}
this.searchLinks = {
blogLink: _.get(authData.linkPaths, 'shortText1'),
clubLink: _.get(authData.linkPaths2, 'shortText1'),
teams1Link: _.get(authData.linkPaths, 'shortText3'),
teams2Link: _.get(authData.linkPaths, 'shortText4'),
playersLink: _.get(authData.linkPaths, 'shortText5'),
blogText: _.get(authData.linkPaths, 'text1'),
clubText: _.get(authData.linkPaths, 'text2'),
teams1Text: _.get(authData.linkPaths, 'text3'),
teams2Text: _.get(authData.linkPaths, 'text4'),
playersText: _.get(authData.linkPaths, 'text5'),
blogLabel: _.get(authData.linkPaths, 'shortTextNumber1'),
clubLabel: _.get(authData.linkPaths, 'shortTextNumber2'),
teams1Label: _.get(authData.linkPaths, 'shortTextNumber3'),
teams2Label: _.get(authData.linkPaths, 'shortTextNumber4'),
playersLabel: _.get(authData.linkPaths, 'shortTextNumber5'),
}
},
adjustClubImageUrl: function (entry) {
entry.imageUrl = _.get(this.spContextualData, 'clubPlaceholder');
this.$forceUpdate();
},
dummyResize: function () {
$(window).trigger('resize');
},
performSearch: function () {
this.loadProducts();
this.checkPerformBlogSearch();
this.lastQuery = this.query;
},
performBlogSearch: function () {
this.blogResults = {};
this.blogLoading = true;
SearchSvc.blogSearch(this.query, null, null, this.blogParams.contentContext, this.blogParams.inlineSearchTarget, this.blogParams.componentId)
.then((response) => {
if (response.status === 200) {
this.blogResults = response.data;
if (!_.isEmpty(this.blogResults)) {
this.blogResults.searchResults = _.slice(this.blogResults.searchResults, 0, 4);
}
this.blogLoading = false;
}
})
.catch((error) => {
if (error.status !== 1) {
this.blogLoading = false;
}
});
},
checkPerformBlogSearch: function () {
if (Dx.getShowBlog() && this.lastQuery !== this.query) {
this.performBlogSearch();
}
this.lastQuery = this.query;
},
searchSource: function (source) {
return _.includes(Dx.getSearchSources(), source);
},
goToBlogSearchPage: function () {
var queryString = GeneralUtilsSvc.jsonToQueryString({query: this.query});
window.location.href = psWidget.getPageURI(this.searchLinks.blogLink) + '?' + queryString;
},
goToBlogPage: function (target) {
window.location.href = psWidget.getPageURI(this.searchLinks.blogLabel, target);
},
goToClubSearchPage: function () {
sessionStorage.setItem('clubsSearch', this.query);
window.location.href = psWidget.getPageURI(this.searchLinks.clubLink);
},
goToClubPage: function (clubId) {
dashboardUtils.goToClub(clubId);
},
goToTeamSearchPage: function () {
sessionStorage.setItem('teamsSearch', this.query);
window.location.href = psWidget.getPageURI(this.searchLinks.teams1Link);
},
goToTeam2SearchPage: function () {
sessionStorage.setItem('teamsSearch', this.query);
window.location.href = psWidget.getPageURI(this.searchLinks.teams2Link);
},
goToTeamPage: function (target) {
window.location.href = psWidget.getPageURI('cat', target.toLowerCase());
},
goToPlayerSearchPage: function () {
sessionStorage.setItem('playersSearch', this.query);
window.location.href = psWidget.getPageURI(this.searchLinks.playersLink);
},
goToPlayerPage: function (target) {
window.location.href = psWidget.getPageURI(this.searchLinks.playersLabel, target.toLowerCase());
},
getBlogEntryStyle: function (blogEntry) {
var style = '<style>';
style += '.' + blogEntry.id + '{';
style += 'background : url(' + blogEntry.image.replace(/\/myconnect\//g, '/connect/') + ') no-repeat center center;';
style += 'background-size: cover;';
style += '}</style>';
return style;
},
// leading value changed to from 500 to 300 to help catch users that hit enter very quickly
// maxWait changed from 1500 to 3000 since some users were getting the wrong results since the search text was incomplete
// the net result is that the call could be fired a lot more but should result in the right page more often
search: _.debounce((vm) => {
vm.performSearch();
}, 300, {'maxWait': 3000})
}
};
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment