UNPKG

locker-factory

Version:
775 lines (699 loc) 29 kB
var React = require( 'react' ), Constants = require( './constants/Constants' ), Actions = require( './Actions.js' ), Store = require( './Store.js' ), ListItem = require( './ListItem.react' ), GridItem = require( './GridItem.react' ), BillboardItem = require( './BillboardItem.react' ), ClassNames = require( 'classnames' ), AjaxOverlay = require( './AjaxOverlay.react' ); var mediaQueryStyles = { mobile: [], tablet: [], desktop: [], large: [] }; var Factory = React.createClass( { propTypes: { locker: React.PropTypes.object.isRequired, allowMock: React.PropTypes.bool.isRequired, clickItemCallback: React.PropTypes.func, closeLockerCallback: React.PropTypes.func.isRequired, offerActivated: React.PropTypes.bool, campaignParams: React.PropTypes.object }, getInitialState: function() { return { campaigns: [], index: 0, endOfList: false }; }, componentWillMount: function() { Store.addGetCampaignsListener( this._handleCampaigns ); Store.addBlockLockerListener( this._handleBlockLocker ); Store.addCloseLockerListener( this._handleCloseLocker ); // if we have campaign params, we are in the Admin UI if( this.props.campaignParams ) { Actions.getCampaigns( this.props.campaignParams ); } else { // else we are a live locker, use the getoffers targeting endpoint Actions.getCampaigns( { 'key': this.props.locker.key }, true ); } }, componentWillUnmount: function() { Store.removeGetCampaignsListener( this._handleCampaigns ); Store.removeBlockLockerListener( this._handleBlockLocker ); Store.removeCloseLockerListener( this._handleCloseLocker ); }, componentWillReceiveProps: function( nextProps ) { if( nextProps.campaignParams ) { // only make a request for new campaigns if the campaignParams have changed at all var requestCampaigns = false; var params = nextProps.campaignParams || {}; if( Object.keys( params ).length != Object.keys( this.props.campaignParams ).length ) { requestCampaigns = true; } if( !requestCampaigns ) { for( var key in params ) { if( this.props.campaignParams[ key ] != params[ key ] ) { requestCampaigns = true; break; } } } if( requestCampaigns ) { Actions.getCampaigns( params ); } } }, _handleCampaigns: function() { var campaigns = Store.getCampaigns(), state = {}; // check for end of list if( campaigns.length < this.props.locker.campaignCount && !this.props.allowMock ) { state.endOfList = true; } else { state.endOfList = false; } // don't set an empty list of campaigns if( campaigns.length > 0 ) { state.campaigns = campaigns; } this.setState( state ); }, _handleBlockLocker: function() { this.setState( { blockLocker: true, index: 0, endOfList: true } ); }, _handleCloseLocker: function() { var completionUrl = Store.getCompletionUrl(); this.props.closeLockerCallback( completionUrl ); }, _getNextCampaigns: function( e ) { e.preventDefault(); var newIndex = this.state.index + this.props.locker.campaignCount; if( this.props.campaignParams ) { var params = this.props.campaignParams, useTargeting = false; } else { var params = { 'key': this.props.locker.key }, useTargeting = true; } params.index = newIndex; Actions.getCampaigns( params, useTargeting ); this.setState( { index: newIndex } ); }, _getLastCampaigns: function( e ) { e.preventDefault(); var newIndex = this.state.index - this.props.locker.campaignCount; if( this.props.campaignParams ) { var params = this.props.campaignParams, useTargeting = false; } else { var params = { 'key': this.props.locker.key }, useTargeting = true; } params.index = newIndex; Actions.getCampaigns( params, useTargeting ); this.setState( { index: newIndex } ); }, /* HEADER MARKUP */ generateHeaderMarkup: function() { var locker = this.props.locker, style = locker.style, headerHtml = style.headerHtml, closeCallback = this.props.closeLockerCallback, header = []; header.push( <style key = "customStyle" type = "text/css" rel = "stylesheet" dangerouslySetInnerHTML = { { __html: style.customCss } } /> ); var configuredStyles = this.generateConfiguredStyles(); header.push( <style key = "configuredStyle" type = "text/css" rel = "stylesheet" dangerouslySetInnerHTML = { { __html: configuredStyles } } /> ); if( style.baseStyle.layout != Constants.layout.BILLBOARD ) { var tAlign = headerHtml.textAlignment || 'center', style = { 'textAlign': tAlign }; header.push( <div key = "header" className = "header" dangerouslySetInnerHTML = { { __html: headerHtml.html } } style = { style } /> ); } // this code is external url locker specific if( String( locker.popUp ) == 'false' && String( locker.bypassButton ) == 'true' ) { header.push( <span key = "bypass" onClick = { closeCallback } className = "bypassButton" >&times;</span> ); } return header; }, /* FOOTER MARKUP */ generateFooterMarkup: function() { var locker = this.props.locker, layout = locker.style.baseStyle.layout, footerHtml = locker.style.footerHtml, footer = []; if( layout != Constants.layout.BILLBOARD ) { var tAlign = footerHtml.textAlignment || 'center', style = { 'textAlign': tAlign }; footer.push( <div key = "footer" className = "footer" dangerouslySetInnerHTML = { { __html: footerHtml.html } } style = { style } /> ); } return footer; }, /* STYLE VALUE SUFFIX HANDLING * we find an appropriate one, or we grab the numeric characters and append a 'px' suffix */ handleValueSuffix: function( value ) { var validCheck = /^([0-9]|.)+(em|%|px)/, numberCheck = /([0-9]+)/; if( !value || validCheck.test( value ) ) { return value; } else { var numbers = numberCheck.exec( value )[ 1 ]; return numbers + 'px'; } }, hexToRGB: function( hex ) { var R = hexToR( hex ), G = hexToG( hex ), B = hexToB( hex ); function hexToR( h ) { return parseInt( ( cutHex( h ) ).substring( 0, 2 ), 16 ) } function hexToG( h ) { return parseInt( ( cutHex( h ) ).substring( 2, 4 ), 16 ) } function hexToB( h ) { return parseInt( ( cutHex( h ) ).substring( 4, 6 ), 16 ) } function cutHex( h ) { var result = ''; if( h.charAt( 0 ) == '#' ) { result = h.substring( 1, h.length ); } if( result.length == 3 ) { result = result[ 0 ] + result[ 0 ] + result[ 1 ] + result[ 1 ] + result[ 2 ] + result[ 2 ]; } return result; } return { r: R, g: G, b: B }; }, getRGB: function( hex, op ) { var rgb = this.hexToRGB( hex ); var op = ( op || 1 ); return 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + op + ')'; }, /* GENERATE STYLES */ generateConfiguredStyles: function() { var styles = ''; styles += this.handleBase(); styles += this.handleOverlay(); styles += this.handleBackground(); styles += this.handleItemBackground(); styles += this.handleButtons(); styles += this.handleMediaQueries(); return styles; }, handleBase: function() { var locker = this.props.locker, baseStyle = locker.style.baseStyle, bodyStr = '', bgStr = '', str = ''; if( baseStyle.fontColor ) { bodyStr += 'color:' + baseStyle.fontColor + ';'; } if( baseStyle.fontFamily ) { bodyStr += 'font-family:' + baseStyle.fontFamily + ';'; } if( baseStyle.fontSize ) { bodyStr += 'font-size:' + baseStyle.fontSize + 'px;'; } if( baseStyle.baseImage ) { bgStr += 'background: transparent url(' + baseStyle.baseImage + ') repeat-y; background-size: cover; height: 100%; width: 100%; position: fixed;'; } bodyStr = 'body{' + bodyStr + '}'; bgStr = '.background{' + bgStr + '}'; if( locker.bypassButton ) { bgStr += '.bypassButton{left:auto;right:0;}'; } if( locker.type == Constants.type.CONTENT_LOCKER && baseStyle.fullScreen ) { str += '.locker{width:100% !important;min-height:100%;margin:0 !important;}'; } return bodyStr + bgStr + str; }, handleOverlay: function() { var locker = this.props.locker.style.baseStyle, str = ''; if( locker.baseOverlay ) { str += '.overlay{display:block;position:fixed;top:0;left:0;bottom:0;right:0;background-color:' + locker.baseOverlayColor + ';opacity:' + locker.baseOverlayOpacity + ';z-index:0}'; } return str; }, handleBackground: function() { var locker = this.props.locker, style = locker.style.backgroundStyle, str = ''; if( style.background == Constants.bgtype.COLOR && style.backgroundValue ) { var rgb = this.getRGB( style.backgroundValue, style.backgroundOpacity ); str += '.locker{background-color:' + rgb + ';}'; } else if( style.background == Constants.bgtype.IMAGE ) { var position = ( locker.type == Constants.type.OFFER_WALL ? 'fixed' : 'absolute' ); str += '.locker:before{content:\' \';display:block;position:' + position + ';top:0;left:0;height:100%;width:100%;background:url(' + style.backgroundValue + ');background-position:50% 0;background-size:cover;background-repeat:no-repeat;opacity:' + style.backgroundOpacity + ';}'; } if( style.backgroundBorder ) { str += '.locker{border:' + this.handleValueSuffix( style.backgroundBorderThickness ) + ' solid ' + style.backgroundBorderColor + ';border-radius:' + this.handleValueSuffix( style.backgroundBorderRadius ) + ';}'; } return str; }, handleItemBackground: function() { var locker = this.props.locker.style.itemStyle, str = ''; if( locker.itemBackground == Constants.bgtype.COLOR && locker.itemBackgroundValue ) { var rgb = this.getRGB( locker.itemBackgroundValue, locker.itemBackgroundOpacity ); str += '.item{background-color:' + rgb + ';}'; if( locker.itemBackgroundTwoTone ) { var bg = this.getRGB( locker.itemBackgroundValue, locker.itemBackgroundOpacity ), fg = this.getRGB( locker.itemBackgroundTwoTone, locker.itemBackgroundOpacity ); str += '.item{background-image:-webkit-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:-moz-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:-ms-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:-o-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:linear-gradient(to bottom,' + bg + ',' + fg + ');}'; } } if( locker.itemBorder ) { str += '.item{border:' + this.handleValueSuffix( locker.itemBorderThickness ) + ' solid ' + locker.itemBorderColor + ';border-radius:' + this.handleValueSuffix( locker.itemBorderRadius ) + ';}'; } if( !locker.thumbnailHidden && locker.thumbnailRadius > 0 ) { str += '.item img{border-radius:' + locker.thumbnailRadius + 'px;}'; } if( locker.badgeEnabled ) { str += '.badge{background-color:' + locker.badgeBackgroundColor + ';color:' + locker.badgeFontColor + ';}'; } if( locker.starsEnabled && locker.starColor ) { str += '.stars{color:' + locker.starColor + ';}'; } return str; }, handleButtons: function() { var locker = this.props.locker.style.buttonStyle, str = ''; if( locker.buttonsEnabled ) { // button style str = '.lockerWrap .button{'; if( !locker.buttonBackgroundColor ) { str += 'opacity:' + locker.buttonOpacity + ';'; } else { if( locker.buttonBackgroundTwoTone ) { var bg = this.getRGB( locker.buttonBackgroundColor, locker.buttonOpacity ), fg = this.getRGB( locker.buttonBackgroundTwoTone, locker.buttonOpacity ); str += 'background-image:-webkit-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:-moz-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:-ms-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:-o-linear-gradient(top,' + bg + ',' + fg + ');' + 'background-image:linear-gradient(to bottom,' + bg + ',' + fg + ');'; } else { var rgb = this.getRGB( locker.buttonBackgroundColor, locker.buttonOpacity ); str += 'background-color:' + rgb + ';'; } } if( locker.buttonPadding ) { str += 'padding:' + locker.buttonPadding + ';'; } if( locker.buttonFontSize ) { str += 'font-size:' + locker.buttonFontSize + ';'; } if( locker.buttonTextColor ) { str += 'color:' + locker.buttonTextColor + ';'; } if( Number( locker.buttonBorder ) ) { str += 'border:' + ( locker.buttonBorderThickness != '' ? this.handleValueSuffix( locker.buttonBorderThickness ) : '2px' ) + ( locker.buttonBorderColor ? ' solid ' + locker.buttonBorderColor : ' outset buttonface' ) + ';border-radius:' + this.handleValueSuffix( locker.buttonBorderRadius ) + ';'; } str += '}.lockerWrap .buttonWrap{'; // button position switch( locker.buttonPosition ) { case Constants.position.LEFT: str += 'display:table-cell;vertical-align:middle;padding-right:5px;}'; break; case Constants.position.RIGHT: str += 'position:absolute;right:1em;top:50%;transform:translate(0,-50%);display:inline-block;}.item .contentWrap{max-width:60%;}'; break; case Constants.position.TOP_LEFT: str += 'padding-bottom:5px;'; case Constants.position.BOTTOM_LEFT: str += 'text-align:left;}'; break; case Constants.position.TOP: str += 'padding-bottom:5px;'; case Constants.position.BOTTOM: str += 'text-align:center;}'; break; case Constants.position.TOP_RIGHT: str += 'padding-bottom:5px;'; case Constants.position.BOTTOM_RIGHT: str += 'text-align:right;}'; default: str += '}'; } } return str; }, handleMediaQueries: function() { var str = ''; if( mediaQueryStyles.large.length ) { str += '@media screen and ( min-width: 1600px ) {'; mediaQueryStyles.large.forEach( function( style ) { str += style; } ); str += '}'; } if( mediaQueryStyles.desktop.length ) { str += '@media screen and ( min-width: 1024px and max-width: 1599px ) {'; mediaQueryStyles.desktop.forEach( function( style ) { str += style; } ); str += '}'; } if( mediaQueryStyles.tablet.length ) { str += '@media screen and ( min-width: 768px and max-width: 1023px ) {'; mediaQueryStyles.tablet.forEach( function( style ) { str += style; } ); str += '}'; } if( mediaQueryStyles.mobile.length ) { str += '@media screen and ( max-width: 767px ) {'; mediaQueryStyles.mobile.forEach( function( style ) { str += style; } ); str += '}'; } return str; }, /* OVERLAY MARKUP */ generateOverlayMarkup: function() { var locker = this.props.locker.style.baseStyle, result; if( locker.baseOverlay ) { result = <div className = "overlay" />; } return result; }, /* PAGINATION MARKUP */ generatePaginationMarkup: function() { // TODO: fix the pagination checking logic when the API begins returning totalCount var cCount = this.props.locker.campaignCount, sCampLen = Store.getCampaigns().length; var back = ( ( this.state.index > 0 && sCampLen != 0 ) || ( sCampLen != 0 && this.state.index == cCount ) ? <span onClick = { this._getLastCampaigns }><span className = "fa fa-caret-left"></span>Back</span> : undefined ); var next = ( !this.state.endOfList ? <span className = "fa-pull-right" onClick = { this._getNextCampaigns }>Next<span className = "fa fa-caret-right"></span></span> : undefined ); return ( <div className = "pagination" >{back}{next}<div className = "clearfix" ></div></div> ); }, generateBody: function() { var campaignMarkup = this.generateCampaignMarkup(), overlay = this.generateOverlayMarkup(), pagination = this.generatePaginationMarkup(), header = this.generateHeaderMarkup(), footer = this.generateFooterMarkup(), offerWall = ( this.props.locker.type == Constants.type.OFFER_WALL ), classNames = ClassNames( { 'locker': true, 'offerWall': offerWall, 'contentLocker': !offerWall } ); return( <div className = "pageWrap" > <AjaxOverlay /> <div className = "background" /> <div className = "lockerWrap" > { overlay } <div className = { classNames } > { header } { campaignMarkup } { pagination } { footer } </div> </div> </div> ); }, createMockCampaigns: function( numberOfCampaigns ) { var result = []; for( var i = 0; i < numberOfCampaigns; i++ ) { result.push( { name: 'Campaign-' + i, description: 'Lorem ipsum dolor sit amet, iuvaret inimicus nam ea, et eam modo equidem. Vix possit latine neglegentur ex, ius labores admodum scaevola in. Cu vim luptatum iracundia prodesset, ea nec nobis mucius. In graece omittam laboramus vim. Velit decore reprehendunt est eu, solum aeque scripserit te has. Cu vivendum electram efficiantur vix.', thumb: Constants.DEFAULT_THUMB } ); } return result; }, getThumbUrl: function( campaign ) { return window.location.protocol + '//' + ( campaign.appTileUrl ? campaign.appTileUrl : Constants.DEFAULT_THUMB ); }, generateCampaignMarkup: function() { var campaigns = this.state.campaigns, locker = this.props.locker, layout = locker.style.baseStyle.layout, clickCallback = this.props.clickItemCallback, active = this.props.offerActivated, blockLocker = this.state.blockLocker ? true : false, result; if( campaigns.length != locker.campaignCount && this.props.allowMock ) { if( campaigns.length < locker.campaignCount ) { campaigns = campaigns.concat( this.createMockCampaigns( locker.campaignCount - campaigns.length ) ); } else { var diff = campaigns.length - locker.campaignCount; campaigns.splice( campaigns.length - diff ); } } switch( layout ) { case Constants.layout.LIST: var list = []; campaigns.forEach( function( campaign, i ) { var key = "listItem" + i; if( !campaign.thumb ) { campaign.thumb = this.getThumbUrl( campaign ); } list.push( <ListItem key = { key } campaign = { campaign } locker = { locker } clickCallback = { clickCallback } index = { i } /> ); }.bind( this ) ); if( active ) { list.push( <li key = "notification" className = "lockerListItem notification" > <div className = "item" > <p> <span id="spinner"> <span className="block" id="rotate_01"></span> <span className="block" id="rotate_02"></span> <span className="block" id="rotate_03"></span> <span className="block" id="rotate_04"></span> <span className="block" id="rotate_05"></span> <span className="block" id="rotate_06"></span> <span className="block" id="rotate_07"></span> <span className="block" id="rotate_08"></span> </span> <span>We are checking for completion of your selected offer!</span> </p> </div> </li> ); } else if( blockLocker ) { list.push( <li key = "notification" className = "lockerListItem notification" > <div className = "item" > <p>This content is blocked for your geographic location and/or device OS.</p> </div> </li> ); } result = ( <ul className = "lockerList" > { list } </ul> ); break; case Constants.layout.GRID: var list = [], notification; campaigns.forEach( function( campaign, i ) { var key = 'gridItem' + i; if( !campaign.thumb ) { campaign.thumb = this.getThumbUrl( campaign ); } list.push( <GridItem key = { key } campaign = { campaign } locker = { locker } clickCallback = { clickCallback } index = { i } /> ); }.bind( this ) ); if( active ) { notification = ( <div className = "gridItem notification" key = "notification" > <div className = "item" > <p> <span id="spinner"> <span className="block" id="rotate_01"></span> <span className="block" id="rotate_02"></span> <span className="block" id="rotate_03"></span> <span className="block" id="rotate_04"></span> <span className="block" id="rotate_05"></span> <span className="block" id="rotate_06"></span> <span className="block" id="rotate_07"></span> <span className="block" id="rotate_08"></span> </span> <span>We are checking for completion of your selected offer!</span> </p> </div> </div> ); } else if( blockLocker ) { notification = ( <div className = "gridItem notification" key = "notification" > <div className = "item" > <p>This content is blocked for your geographic location and/or device OS.</p> </div> </div> ); } result = ( <div className = "lockerGrid" > { notification } <div> { list } </div> <div className = "clearfix" /> </div> ); break; case Constants.layout.BILLBOARD: var list = [], //TODO: figure out buttons buttons = []; // set our scroll variables this.campaignCount = locker.campaignCount; this.lastScrollLeft = 0; campaigns.forEach( function( campaign, i ) { var left = Math.min( 100, 100 * i ), key = 'billboard-' + i, ref = 'billboard-' + i, active = ( i == 0 ); if( !campaign.thumb ) { campaign.thumb = this.getThumbUrl( campaign ); } list.push( <BillboardItem key = { key } ref = { ref } campaign = { campaign } locker = { locker } left = { left } active = { active } clickCallback = { clickCallback } index = { i } /> ); }.bind( this ) ); result = ( <div className = "lockerBillboard" ref = "billboardWrapper" data-last-index = { 0 } > <div className = "billboardControlLayer" onScroll = { this._scrollBillboard } ref = "billboardControlLayer" > <div className = "billboardControl" ref = "billboardControl" /> </div> <div className = "billboardItemsLayer" ref = "billboardItemsLayer" > { list } </div> </div> ); break; default: result = campaigns; } return result; }, _scrollBillboard: function( e ) { // currently scrolling? shut it down. if( this.scrolling == true ) { return; } var slf = this, t = e.target, billboardWrapperNode = this.refs.billboardWrapper.getDOMNode(), lastIndex = Number( billboardWrapperNode.getAttribute( 'data-last-index' ) ), lastItem = this.refs[ 'billboard-' + lastIndex ].getDOMNode(), campaignCount = Number( this.campaignCount ), willScroll = false, dir = 0, MIN_SCROLL = 225, newIndex; // determine scroll direction // 1 is right, -1 is left if( t.scrollLeft > this.lastScrollLeft ) { dir = 1; } else if( t.scrollLeft < this.lastScrollLeft ) { dir = -1; } // no direction? shut it down. if( dir == 0 ) { return; } // make sure we aren't at the end of whatever direction we are scrolling if( dir == 1 && lastIndex != campaignCount - 1 || dir == -1 && lastIndex != 0 ) { willScroll = true; } // check if scroll distance satisfies the minimum distance to move to the next item if( Math.abs( t.scrollLeft - this.lastScrollLeft ) <= MIN_SCROLL ) { willScroll = false; } // can't scroll? shut it down. if( !willScroll ) { return; } // initialize scrolling this.scrolling = true; // if it is recursively update last item and new item positions until they are in place // add the dir to the last index to get the new index and new item newIndex = lastIndex + dir; window.setTimeout( function() { slf.handleScrolling( lastIndex, newIndex, dir ); }, 5 ); // then reset the billboardControlLayer scrollLeft position to 0, 100%, or 200% if index is 0, between 0 and n, or n respectively var itemWidth = lastItem.scrollWidth; var controlLeft = ( newIndex == 0 ? 0 : ( newIndex < campaignCount - 1 ? itemWidth : itemWidth * 2 ) ); this.refs.billboardControlLayer.getDOMNode().scrollLeft = controlLeft; // update the last index, lastScrollLeft, and set scrolling to false billboardWrapperNode.setAttribute( 'data-last-index', newIndex ); this.lastScrollLeft = controlLeft; //this.scrolling = false; }, handleScrolling: function( lastIndex, newIndex, dir ) { var slf = this, offset = 5, newItem = this.refs[ 'billboard-' + newIndex ].getDOMNode(), lastItem = this.refs[ 'billboard-' + lastIndex ].getDOMNode(); function updatePositions() { var lastLeftStyle = lastItem.style.left, newLeftStyle = newItem.style.left, lastLeft = ( lastLeftStyle ? Number( lastLeftStyle.replace( '%', '' ) ) : 0 ), newLeft = ( newLeftStyle ? Number( newLeftStyle.replace( '%', '' ) ) : 0 ); lastItem.style.left = lastLeft + ( offset * ( dir * -1 ) ) + '%'; newItem.style.left = newLeft + ( offset * ( dir * -1 ) ) + '%'; } var updateInterval = window.setInterval( function() { updatePositions(); if( Math.abs( Number( newItem.style.left.replace( '%', '' ) ) ) <= offset ) { window.clearInterval( updateInterval ); newItem.style.left = 0 + '%'; newItem.className = 'billboardItem active'; lastItem.style.left = ( 100 * ( dir * -1 ) ) + '%'; lastItem.className = 'billboardItem'; slf.scrolling = false; } }, 5 ); }, render: function() { return this.generateBody(); } } ); module.exports = Factory;