Skip to content

Instantly share code, notes, and snippets.

@lostPixels
Last active February 25, 2017 17:16
Show Gist options
  • Save lostPixels/8d6c6dbf5ee2c6239cb7433558152e8c to your computer and use it in GitHub Desktop.
Save lostPixels/8d6c6dbf5ee2c6239cb7433558152e8c to your computer and use it in GitHub Desktop.
const React = require('react');
const ReactDOM = require('react-dom');
const URI = require('urijs');
const cssVariables = require('partials/_variables.scss');
const snappable = require('./modules/ui/components/snappable');
const scroll = require('./modules/ui/components/scroll-to-position');
const acceptCookieAlert = require('./modules/ui/bottom-message-alert');
const responsive = require('./modules/util/responsive');
const deepMerge = require('./modules/util/deep-merge');
const ProductGrid = require('./modules/categorysearch/components/product-grid');
const RefinementNavigation = require('./modules/categorysearch/components/refinement-navigation');
const BreadcrumbList = require('./modules/categorysearch/components/breadcrumb-list');
const RelaxerList = require('./modules/categorysearch/components/relaxer-list');
const Description = require('./modules/categorysearch/components/description');
const ViewAllDisplay = require('./modules/categorysearch/components/view-all-display');
const TopBanner = require('./modules/modular-react-banners/module-top-banner');
const InlineBanner = require('./modules/modular-react-banners/module-inline-banner');
const SortingOptionsList = require('./modules/categorysearch/components/sorting-options-list');
const ErrorMessage = require('./modules/categorysearch/components/error-message');
const RefinementVisibilityToggleButton = require('./modules/categorysearch/components/refinement-visibility-toggle-button');
let DEVELOPMENT = false;
let metaFlag = document.querySelector('meta[name="instance-type"]');
if(metaFlag && metaFlag.content === 'dev-staging') {
DEVELOPMENT = true;
}
if(!__PRODUCTION) {
const Perf = require('react-addons-perf');
window.Perf = Perf;
window.PerfDebug = {
start: Perf.start,
stop: () => {
Perf.stop();
let r = Perf.getLastMeasurements();
Perf.printInclusive(r);
Perf.printWasted(r)
PerfDebug.count();
},
count: () => {
let len = document.querySelectorAll('.product-tile').length;
console.warn(`Rendering ${len} product tiles.`);
}
}
}
////////////////////////////////////////////////////////////
//////////////// MAIN APPLICATION STATE. ///////////////////
let state = {
basePath: window.location.pathname,
infiniteScroll: false,
firstRun: true,
parseCommentsOutOfJSON: false, //Should we parse comments out of all JSON responses? This is required for when DW puts comments in JSON templates. Jerks.
data: window.__productResultBootstrap,
promotions: {
top: window.__categoryPromotionsBootstrap.top || {},
inline: window.__categoryPromotionsBootstrap.inline || {}
}
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// YO!!!! This is how you should update the Category global state. //
// Do not mutate it directly or call render(). //
// Uses a deep merge, much like Object.assign() //
function setState(newState, deepCopy = true) {
//console.log('old state: ',state)
//console.log('new state: ',newState)
if(deepCopy) {
state = deepMerge(state, newState);
}
else {
state = Object.assign(state, newState);
}
//console.log('merged state: ', state);
window.state = state;
render();
}
/////////////////
// Launch App! //
/////////////////
init();
function init() {
if(DEVELOPMENT) {
state.parseCommentsOutOfJSON = true;
}
if(window.location.hash !== '#noreact'){
render();
ajaxifyNavigation();
ajaxifySearch();
initViewToggleButtons();
initSnappableMobileNav();
$('#js-react-product-results').on('refined',(e, res ) => {
setURL(res.href);
loadData(res.href);
});
window.addEventListener('popstate', event => {
if (event.state) {
loadData(window.location.href, dataLoadedNoScroll);
}
}, false);
}
else{
$('.main-page-wrap').prepend('<div class="no-react-warning">REACT.JS IS INACTIVE</div>');
}
}
function ajaxifyNavigation() {
$('a[data-can-ajaxify-category]').on('click',e => {
if(!responsive.isMobile()) {
e.preventDefault();
$('.global-header').trigger('requestclose');
$('#js-react-product-results').trigger('refined',{href: $(e.currentTarget).attr('href') });
}
});
}
function ajaxifySearch() {
// $('form[data-can-ajaxify-search]').on('submit', e => {
// e.preventDefault();
// let endpoint = $(e.currentTarget).attr('action');
// let query = $(e.currentTarget).find('input[name="q"]').val();
// if(query) {
// $('.global-header').trigger('header.close');
// $('#js-react-product-results').trigger('refined',{href: `${endpoint}?q=${query}` });
// }
// })
}
function render(data = state.data) {
if(!data) {
updateErrorMessaging("Unable to render page, data is invalid.");
return false;
}
let hasHits = data.hits.length > 0;
let inlinePromotion = state.promotions && state.promotions.inline && state.promotions.inline.length ? state.promotions.inline[0] : {};
let topPromotion = state.promotions && state.promotions.top && state.promotions.top.length ? state.promotions.top[0] : {};
if(state.firstRun) {
state.firstRun = false;
}
else{
updatePageTitle(data.category);
}
updateViewToggleVisability(hasHits);
ReactDOM.render(<ProductGrid {...data} infiniteScroll={state.infiniteScroll} loadData={loadData} turnOnInfiniteScroll={turnOnInfiniteScroll} />, document.getElementById('js-react-product-results'));
ReactDOM.render(<RefinementNavigation refinements={data.refinements} relaxAll={data.meta.relaxAll} hasHits={hasHits}/>, document.getElementById('js-react-refinement-navigation'));
ReactDOM.render(<BreadcrumbList type={data.meta.type} breadcrumbs={data.breadcrumbs} searchPhrase={data.meta.searchPhrase}/>,document.getElementById('js-react-breadcrumbs'));
ReactDOM.render(<RelaxerList relaxers={data.refinementLaxativeGroups}/>,document.getElementById('js-react-relaxers'));
ReactDOM.render(<Description category={data.category} meta={data.meta} />, document.getElementById('js-react-description'));
ReactDOM.render(<ViewAllDisplay {...data.meta} canViewAll={!state.infiniteScroll} onClicked={turnOnInfiniteScroll} />, document.getElementById('js-react-product-view-all'));
ReactDOM.render(<SortingOptionsList options={data.sortingOptions} />, document.getElementById('js-react-sorting-options-desktop'));
ReactDOM.render(<SortingOptionsList options={data.sortingOptions} />, document.getElementById('js-react-sorting-options-mobile'));
ReactDOM.render(<InlineBanner {...inlinePromotion} />, document.getElementById('js-react-promotion-category-inline'));
ReactDOM.render(<TopBanner {...topPromotion} />, document.getElementById('js-react-promotion-category-top'));
ReactDOM.render(<RefinementVisibilityToggleButton closed={data.meta.shouldFiltersBeClosed} />, document.getElementById('js-react-refinement-visibility-togggle'));
}
function updateErrorMessaging(e) {
ReactDOM.render(<ErrorMessage error={e} />, document.getElementById('js-react-error-message'));
}
function dataLoaded(newData) {
setState({
infiniteScroll: false,
data: newData,
promotions: newData.promotions
}, false);
scrollNewResultsIntoView();
}
function dataLoadedNoScroll(newData) {
setState({
infiniteScroll: false,
data: newData,
promotions: newData.promotions
}, false);
}
function loadData(href, callback = dataLoaded, loadType = 'full-load') {
let endpoint = URI(href);
let type = state.parseCommentsOutOfJSON ? 'text' : 'json';
endpoint.addQuery("format", "ajax");
$('.product-results').addClass(`is-loading load-type-${loadType}`);
//Clear the error message.
updateErrorMessaging(false);
$.ajax(endpoint.toString(), {
dataType: 'text',
error: (e, status, error) => {
updateErrorMessaging(error);
},
success : data => {
$('.product-results').removeClass(`is-loading load-type-${loadType}`);
let resText = data,
resObj = {};
if(state.parseCommentsOutOfJSON){
try{
resText = resText.replace(/<!--[\s\S]*?-->/g, '');
resText = resText.replace(/<span class="dw-object[\s\S]*?span>/g,'');
}
catch(e) {
updateErrorMessaging('JSON comment remove error.' + e);
return false;
}
}
try {
resObj = JSON.parse(resText);
}
catch(e) {
updateErrorMessaging('Parsing JSON broke because '+e);
return false;
}
if(resObj.redirect) {
window.location = resObj.destination;
}
if(__metadata.site.siteID === 'Burton_EUR') {
acceptCookieAlert.init();
}
track(resObj);
callback(resObj);
}
})
}
function turnOnInfiniteScroll() {
setState({
infiniteScroll: true
});
}
function initViewToggleButtons() {
$('.js-toggles-mobile-grid-size').on('click',e => {
e.preventDefault();
$(e.currentTarget).toggleClass('half-width-active');
$('.product-grid').toggleClass('mobile-half-width-tiles');
});
}
function updateViewToggleVisability(shouldShow) {
if(shouldShow) {
$('.filter-toggle-wrap').show();
}
else{
$('.filter-toggle-wrap').hide();
}
}
function initSnappableMobileNav() {
snappable.create('.search-page .js-snaps-to-top', {
classWhenSnapped: 'el-is-snapped-mobile',
ocassionallyRecalcTrigger: true,
triggerY: function () {
return findMobileNavSnapPoint();
}
});
}
function scrollNewResultsIntoView() {
//Do we really want to scrolljack?
let scrollTargetPositon = document.querySelector('.search-page .promotion-category-top-wrap').offsetTop - 50;
// if(showDescriptionToo) {
// scrollTargetPositon = document.querySelector('.search-page category-description').offsetTop - 50;
// }
if(responsive.isMobile()) {
scrollTargetPositon = findMobileNavSnapPoint();
}
if(window.scrollY > scrollTargetPositon) {
scroll.to(scrollTargetPositon);
}
}
function findMobileNavSnapPoint() {
let mobileNavHeight = parseInt(cssVariables.mobileNavHeight);
return document.querySelector('.refinement-navigation-row').offsetTop - mobileNavHeight;
}
function updatePageTitle(categoryData) {
if(categoryData && categoryData.seoPageTitle) {
let title = categoryData.seoPageTitle;
document.querySelector('title').textContent = title;
}
}
function track(resObj) {
if(!resObj.redirect) {
$(document).trigger('viewed.page', [{
path: resObj.meta.base,
url: resObj.meta.currentSearch,
location: resObj.meta.currentSearch,
title: document.title
}]);
}
if (resObj.category && resObj.category.ID) {
$(document).trigger('viewed.category', [{
id: resObj.category.ID
}]);
if(window.dw) {
dw.ac.applyContext({ category: resObj.category.ID });
}
window.__metadata.category = window.__metadata.category || {};
window.__metadata.category = {
id: resObj.category.ID,
gender: determineCategoryGender(resObj.category.ID),
topProducts: []
};
}
if(resObj.hits.length && window.dw) {
let listOfProducts = resObj.hits.map(hit => {
return {
id: hit.ID,
type: dw.ac.EV_PRD_SEARCHHIT
}
});
dw.ac.capture(listOfProducts);
if (resObj.category && resObj.category.ID) {
window.__metadata.category.topProducts = listOfProducts.map(hit => hit.id).slice(0, 3);
}
}
}
function getBasePath(href = window.location.search) {
return new URI(href).pathname();
}
function setURL(newHref) {
let newPath = getBasePath(newHref);
if(state.basePath !== newPath) {
//Set a new point in the history.
history.pushState({}, "", newHref);
}
else {
history.replaceState({}, "", newHref);
}
setState({basePath: newPath});
}
function determineCategoryGender(categoryID) {
var gender = 'unisex';
if (categoryID.search(/^mens/i) > -1) {
gender = 'mens';
} else if (categoryID.search(/^womens/i) > -1) {
gender = 'womens';
} else if (categoryID.search(/^youth/i) > -1) {
gender = 'youth';
}
return gender;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment