import LazyLoad from './LazyLoad';
import customEvent from '../utils/customEvent';
import ImageContainer from './ImageContainer';
import SophiTagWrapper from './SophiTagWrapper';
/**
* Created 'Load More' button
*
* @module LoadMore
* @prop {Integer} lastPostId - Id of the last post loaded on the page
* @prop {String} ad_class - Ad item identifying class
* @prop {String} ad_unit_class - Ad inner div identifying class
* @prop {string} loadmore_item_class - css class for post item
* @prop {string} loadmore_button_selector - load more button selector
* @prop {string} loadmore_ended_selector - selector of end of load more list
* @prop {string} data_region - data attribute for region
* @prop {String} data_post_id - data attribute for post id
*/
const LoadMore = {
lastPostId: null, // TO BE DELETED
ad_class: 'c-posts__ad',
ad_unit_class: 'c-ad__unit',
loadmore_item_class: 'c-posts__loadmore',
loadmore_button_selector: '[data-load-more-button]',
loadmore_ended_selector: '[data-load-more-ended]',
data_region: 'data-region',
data_post_id: 'data-post-id',
/**
* Initialize 'Load More' button and bind click event.
* @method init
*
*/
init() {
const $buttons = document.querySelectorAll( this.loadmore_button_selector );
[].forEach.call( $buttons, ( $button ) => {
const $resultList = document.querySelector( `#${$button.dataset.loadMoreTarget}` );
// Get last post id loaded on the page
const $renderedPosts = $resultList.querySelectorAll( `[${this.data_post_id}]` );
const $lastRenderedPost = $renderedPosts[$renderedPosts.length - 1];
this.lastPostId = $lastRenderedPost.getAttribute( this.data_post_id );
// New version of load more button
const adFrequency = JSON.parse( $button.dataset.adFrequency );
const adBpOffset = JSON.parse( $button.dataset.adOffset );
const breakpoints = Object.keys( adFrequency );
const numPosts = $renderedPosts.length;
[].forEach.call( breakpoints, ( bp ) => {
adBpOffset[bp] = adFrequency[bp] - ( ( numPosts - adBpOffset[bp]) % adFrequency[bp]);
});
// Set initial state for button
this.setButtonAttributes( $button, {
lastPostId: $lastRenderedPost.getAttribute( this.data_post_id ),
adOffset: JSON.stringify( adBpOffset ),
page: 1,
});
// Bind new click event listener to 'Load More' button
$button.addEventListener( 'click', ( event ) => {
LoadMore.handleButtonClick( event );
});
});
},
/**
* Updates button attributes for page result and ad position
* Calls ajax
* @method handleButtonClick
*
*/
handleButtonClick( evt ) {
if ( ! evt.currentTarget.dataset.action ) {
return;
}
const keys = Object.keys( evt.currentTarget.dataset );
const params = {};
[].forEach.call( keys, ( k ) => {
if ( k.indexOf( 'load-more' ) < 0 ) {
if ( /\{.+\}/.test( evt.currentTarget.dataset[k]) ) {
params[k] = JSON.parse( evt.currentTarget.dataset[k]);
} else {
params[k] = evt.currentTarget.dataset[k];
}
}
});
this.toggleButtonAndShimmer( evt.currentTarget );
// Then call ajax function.
this.loadMoreResults( evt.currentTarget, params.action, params );
// Track analytics
this.track( evt.currentTarget );
},
/**
* Updates button attributes for page result and ad position
* Calls ajax
* @method getMoreResults
* @param {HTMLElement} $button - html element of load more button
* @param {String} action - ajax action
* @param {Object} params - params to be sent via ajax call
*
*/
loadMoreResults( $button, action, params ) {
fetch( `/gnca-ajax-redesign/${params.action}/${encodeURI( JSON.stringify( params ) )}` )
.then( response => response.text() )
.then( ( content ) => {
const $htmlContent = new DOMParser().parseFromString( content, 'text/html' );
const $listItems = $htmlContent.querySelectorAll( `.${this.loadmore_item_class}` );
const stopLoadMore = $htmlContent.querySelector( `${this.loadmore_ended_selector}` );
const $resultList = document.querySelector( `#${$button.dataset.loadMoreTarget}` );
const { adPosition, adOffset, queryValue } = params;
if ( $resultList ) {
let $currentPostItem = null;
[].forEach.call( $listItems, ( $listItem ) => {
$resultList.appendChild( $listItem );
// Check if this unit is an ad
if ( $listItem.classList.contains( this.ad_class ) ) {
// If so get the inner div
const adInnerDiv = $listItem.querySelector( `.${this.ad_unit_class}` );
const { adBp: breakpoint, adPos } = adInnerDiv.dataset;
/* global gn_monetize */
/* eslint-disable camelcase */
if ( adInnerDiv && 'undefined' !== typeof gn_monetize && 'undefined' !== typeof gn_monetize.Ads ) {
// Initiate dynamic ad
gn_monetize.Ads.create({
sizes: '[300,250]',
biddable: true,
id: `${adInnerDiv.getAttribute( 'id' )}`,
lazy: true,
targeting: {
pos: `${adPos}`,
},
});
adPosition[breakpoint] = adPos;
}
/* eslint-enable camelcase */
} else {
// watch for lazy load images
LazyLoad.startWatching( $listItem.querySelector( `img${LazyLoad.selector}` ) );
ImageContainer.init( $listItem.querySelectorAll( ImageContainer.selector ) );
$currentPostItem = $listItem;
}
});
// Place focus on first new rendered story
// for improved screenreader and keyboard navigation experience
const lastRenderedPostId = $button.getAttribute( 'data-last-post-id' );
const lastRenderedItem = document.querySelector( `[${this.data_post_id}="${lastRenderedPostId}"]` );
if ( lastRenderedItem ) {
let firstNewItem = lastRenderedItem.nextElementSibling;
if ( firstNewItem ) {
// First new item can be an ad. In this case skip to the item after it
if ( firstNewItem.classList.contains( 'c-posts__ad' ) ) {
firstNewItem = firstNewItem.nextElementSibling;
}
// Focus on the link in first new rendered story
if ( firstNewItem.querySelector( 'a' ) ) {
firstNewItem.querySelector( 'a' ).focus();
}
}
}
// Calculate new ad placement offset
const numPosts = parseInt( params.number, 10 );
[].forEach.call( Object.keys( params.adPosition ), ( bp ) => {
const frequency = params.adFrequency[bp];
adOffset[bp] = frequency - ( ( numPosts - adOffset[bp]) % frequency );
});
// Update load more button attributes, if load more returns a valid response
if ( content ) {
const buttonParams = {
lastPostId: $currentPostItem ? $currentPostItem.dataset.postId : 0,
adPosition: JSON.stringify( adPosition ),
adOffset: JSON.stringify( adOffset ),
page: parseInt( params.page, 10 ) + 1,
};
// Update paged value for querying next page.
if ( 'object' === typeof ( queryValue ) && queryValue.paged ) {
queryValue.paged += 1;
buttonParams.queryValue = JSON.stringify( queryValue );
}
this.setButtonAttributes( $button, buttonParams );
}
this.toggleButtonAndShimmer( $button );
SophiTagWrapper.refreshTracking();
customEvent.fire( window, customEvent.MORE_LOADED );
// If there are no more posts in the query for next load,
// hide 'Load More' button
if ( stopLoadMore ) {
$button.style.display = 'none'; /* eslint-disable-line no-param-reassign */
}
}
});
},
/**
* Toggle hide/show class on 'Load More' button and placeholder list
* @method toggleButtonAndShimmer
*
*/
toggleButtonAndShimmer( $button ) {
document.querySelector( `#${$button.dataset.loadMoreTarget}-skeleton` ).classList.toggle( 'is-hidden' );
$button.classList.toggle( 'is-hidden' );
},
/**
* Set data attributes for load more button
*
* @method setButtonAttributes
* @param {HTMLElement} $button - button element
* @param {Object} data - parameters to be sent via ajax call
*/
setButtonAttributes( $button, data ) {
const keys = Object.keys( data );
[].forEach.call( keys, ( key ) => {
$button.dataset[key] = data[key]; /* eslint-disable-line no-param-reassign */
});
},
/**
* Track analytics when load more clicked
*
* @method track
* @param {HTMLElement} $target - button element
*/
track( $target ) {
const params = $target.dataset;
/* global gn_analytics */
/* eslint-disable camelcase */
if ( 'undefined' !== typeof ( gn_analytics ) ) {
gn_analytics.Analytics.track(['ga', 'adobe'], {
eventType: 'loadMore',
action: params.page,
target: $target,
data: {
loadMore: params.page,
},
});
}
/* eslint-enable camelcase */
},
};
export default LoadMore;