import React, { PropTypes } from 'react' | |
export const insertScriptHelper = (projectId) => { | |
const el = document.querySelector('[data-optimizely-snippet]') | |
if (!isReadyHelper() && !el) { | |
const protocol = `${document.location.protocol}//` | |
const scriptTag = document.createElement('script') | |
scriptTag.type = 'text/javascript' | |
scriptTag.async = true | |
scriptTag.src = `${protocol}cdn.optimizely.com/js/${projectId}.js` | |
scriptTag.setAttribute('data-optimizely-snippet', true) | |
document.getElementsByTagName('head')[0].appendChild(scriptTag) | |
} | |
} | |
export const triggerEventHelper = (eventName) => { | |
try { | |
typeof window.optimizely === 'object' && | |
window.optimizely.push({ | |
type: 'event', | |
eventName: eventName, | |
}) | |
} catch (e) { | |
throw Error('OptimizelyExperiment triggerEventHelper unexpected error!') | |
} | |
} | |
export const isReadyHelper = () => { | |
return typeof window.optimizely === 'object' && | |
typeof window.optimizely.data === 'object' && | |
typeof window.optimizely.allExperiments === 'object' | |
} | |
class OptimizelyExperiment extends React.Component { | |
constructor (props) { | |
super(props) | |
window.optimizely = window.optimizely || [] | |
window.optimizely.push({ | |
type: 'log', | |
level: 'info', | |
}) | |
this.onReadyRecallMs = 10 | |
this.defaultState = { | |
variantName: null, | |
result: null, | |
goalCompleted: false, | |
} | |
this.eventListeners = [] | |
this.state = Object.assign({}, this.defaultState) | |
this.mapVariantToState = this.mapVariantToState.bind(this) | |
this.eventHandler = this.eventHandler.bind(this) | |
} | |
componentWillMount () { | |
if (!isReadyHelper()) { | |
this.insertScript() | |
} | |
} | |
componentDidMount () { | |
this.observeReady(this.mapVariantToState) | |
} | |
componentDidUpdate (prevProps, prevState) { | |
if (isReadyHelper() && prevState.variantName !== this.state.variantName) { | |
this.setResult() | |
} | |
if (prevState.goalCompleted !== this.state.goalCompleted) { | |
this.removeAllEventListeners() | |
} | |
} | |
componentWillUnmount () { | |
this.removeAllEventListeners() | |
} | |
observeReady (cb) { | |
const { allExperiments } = window.optimizely | |
if (allExperiments && typeof allExperiments[this.props.experimentId] !== 'undefined') { | |
this.triggerPageActivation() | |
this.props.customEvents.forEach(({selector, eventType, eventName}) => | |
this.attachEventHandler(selector, eventType, eventName)) | |
if (typeof cb === 'function') cb() | |
} else { | |
const t = setTimeout(() => { | |
clearTimeout(t) | |
this.observeReady(cb) | |
}, this.onReadyRecallMs) | |
} | |
} | |
mapVariantToState () { | |
const { experimentId } = this.props | |
const { variationIdsMap, allVariations } = window.optimizely | |
let variantName = 'Original' | |
if (this.isExperimentActive(experimentId)) { | |
const variationId = variationIdsMap[experimentId][0] // simple AB test, not multiple | |
variantName = allVariations[variationId].name | |
} | |
this.setState(() => { | |
// keep a reference for debugging | |
this.setMiddlemanRefHelper({ | |
variantName, | |
}) | |
return { | |
variantName, | |
} | |
}) | |
} | |
isExperimentActive (experimentId) { | |
const { activeExperiments } = window.optimizely | |
return activeExperiments.find(v => v === experimentId) | |
} | |
insertScript () { | |
const { projectId } = this.props | |
insertScriptHelper(projectId) | |
} | |
runExperiment () { | |
const { variantName } = this.state | |
const { experimentsMap } = this.props | |
let result = experimentsMap[variantName] || false | |
if (typeof result === 'function') { | |
result = result() | |
} | |
return result | |
} | |
setResult () { | |
this.setState({ | |
result: this.runExperiment(), | |
}) | |
} | |
triggerPageActivation () { | |
window.optimizely.push({ | |
type: 'page', | |
pageName: this.props.pageName, | |
}) | |
} | |
attachEventHandler (selector, eventType, eventName) { | |
if (Array.isArray(selector)) { | |
return selector.forEach(v => this.attachEventHandler(v, eventType, eventName)) | |
} | |
const target = document.querySelector(selector) | |
const eventHandler = this.eventHandler.bind(undefined, target, eventType, eventName) | |
if (target) { | |
target.addEventListener(eventType, eventHandler, true) | |
this.eventListeners.push({ | |
target, | |
eventHandler, | |
eventType, | |
}) | |
} | |
} | |
eventHandler (target, eventType, eventName) { | |
this.setState(() => { | |
triggerEventHelper(eventName) | |
return { | |
goalCompleted: true, | |
} | |
}) | |
} | |
removeAllEventListeners () { | |
this.eventListeners.forEach(({target, eventType, eventHandler}) => | |
this.removeEventListener(target, eventType, eventHandler)) | |
} | |
removeEventListener (target, eventType, eventHandler) { | |
target.removeEventListener(eventType, eventHandler, true) | |
} | |
setMiddlemanRefHelper (data) { | |
const propName = '__AB_TEST_STATE_REF__' | |
window[propName] = Object.assign({}, window[propName], data) | |
} | |
render () { | |
return this.state.result | |
} | |
} | |
OptimizelyExperiment.propTypes = { | |
projectId: PropTypes.string.isRequired, | |
experimentId: PropTypes.string.isRequired, | |
name: PropTypes.string.isRequired, | |
experimentsMap: PropTypes.object.isRequired, | |
pageName: PropTypes.string, | |
customEvents: PropTypes.array, | |
} | |
export default OptimizelyExperiment |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment