javascript 第11期:20180620 - no73。トップページのFV改善フェーズ1(ポイントモーダル,店铺名下の整理)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了javascript 第11期:20180620 - no73。トップページのFV改善フェーズ1(ポイントモーダル,店铺名下の整理)相关的知识,希望对你有一定的参考价值。
/**
* BaseModalで使用しているProps
* @param {function} closeModal モーダルを閉じる関数(必須)
* @param {string} componentName モーダルを閉じるボタンのComponent名(default:CloseButton)
* @param {string} style モーダルを閉じるボタンのクラス名(default:button__close)
* @param {string} modalCloseButtonText モーダルを閉じるボタンで表示するテキスト(default: x 閉じる)
* @param {boolean} isModalCloseButton モーダルを閉じるボタンを表示するかどうかのFlag(default: true)
* @param {boolean} isFade モーダルの表示・非表示する際にアニメーションするかどうかのFlag(default true)
* @param {number} fadeTime モーダルの表示・非表示する際のアニメーションする時間(default: 300ms)
*/
import React from 'react'
import { compose, setDisplayName, defaultProps, branch, onlyUpdateForKeys, renderNothing } from 'recompose'
import BaseButton from './BaseButton'
const ButtonText = ({ text }) => <span>{text}</span>
const ModalCloseButton = props => (
<BaseButton {...props} handler={props.closeModal}>
<ButtonText text={props.modalCloseButtonText} />
</BaseButton>
)
const identity = Component => Component
const modalCloseButtonStatus = ({ isModalCloseButton }) => isModalCloseButton
const withModalCloseButtonStatusCheck = branch(modalCloseButtonStatus, identity, renderNothing)
const EnhanceModalCloseButton = compose(
defaultProps({
componentName: 'CloseButton',
style: 'button__close',
modalCloseButtonText: 'x 閉じる',
}),
withModalCloseButtonStatusCheck,
onlyUpdateForKeys(['componentName', 'style', 'modalCloseButtonText']),
)(ModalCloseButton)
const Component = props => {
const cls = ['modal']
if (props.isModalOpen) cls.push('modal__active')
const style = cls.join(' ')
const styleObj = props.isFade ? { opacity: 0, transition: `opacity ${props.fadeTime}ms ease-in-out` } : {}
return (
<div className={style}>
<div // eslint-disable-line jsx-a11y/no-static-element-interactions
className="modal__bg"
onClick={props.closeModal}
/>
<div className="modal__container" style={styleObj}>
{props.children}
<EnhanceModalCloseButton {...props} />
</div>
</div>
)
}
const Enhance = compose(
setDisplayName('BaseModal'),
defaultProps({ isModalCloseButton: true, isFade: true, fadeTime: 300 }),
onlyUpdateForKeys(['isModalCloseButton', 'isFade', 'fadeTime']),
)
export default Enhance(Component)
import React from 'react'
import { compose, setDisplayName, defaultProps, onlyUpdateForKeys } from 'recompose'
const Component = props => (
<div className={props.style}>
<button type={props.type} onClick={props.handler}>
{props.children}
</button>
</div>
)
const dispName = ({ componentName }) => componentName
const Enhance = compose(
setDisplayName(dispName),
defaultProps({ type: 'button' }),
onlyUpdateForKeys(['componentName', 'type', 'style', 'handler']),
)
export default Enhance(Component)
import forEach from 'lodash/forEach'
// 新しいtagをtarget要素の最後に追加
export const newChildInsertEnd = (tag, attr, target) => {
const addTag = document.createElement(tag)
forEach(attr, (val, key) => {
addTag.setAttribute(key, val)
})
target.appendChild(addTag)
return addTag
}
// 要素をラッピング
export const wrapElement = (tag, attr, target) => {
const wrapper = document.createElement(tag)
forEach(attr, (val, key) => {
wrapper.setAttribute(key, val)
})
target.parentNode.insertBefore(wrapper, target)
wrapper.appendChild(target)
}
// yyyymmdd - noxx.hoge // GitLab Issueのタイトル
// https://gnavi-work.gnavi.co.jp/jira/browse/MIKAMEI_PDCA-xxx // JIRAのチケット
import abtestRun from './abTestRun'
const IssueXXX = () => {
const removeElement = () => {
console.log('in')
}
const caseMap = {
'1': function() {
removeElement()
},
'2': function() {},
'3': function() {},
}
const init = caseNumber => {
console.log('hoge')
// 共通処理
// $('#page').addClass('issueXXX')
// patternごとの実装
caseMap[caseNumber.toString()]()
}
window.issueXXX = { start: init }
abtestRun()
}
export default IssueXXX
// 20180620 - no73.トップページのFV改善フェーズ1(ポイントモーダル, 店舗名下の整理)
// https://gnavi-work.gnavi.co.jp/jira/browse/MIKAMEI_PDCA-467 // JIRAのチケット
// shop_1-1b, shop_1-1bMovid
/**
*** abテストと実装時での仕様変更内容 ***
*
*
abテスト時は#page内にModalを作成し、bodyにfixedを付与して、Modalの背景スクロールを阻止
```
<div id="page">
<Modal>
</div>
```
*
*
*
実装になった場合は、#pageと同じ改装にModalを作成し、#pageをdisplay:noneで非表示(「空席確認する」のカレンダーモーダルと同じ仕様へ変更)
```
<div id="page">...</div>
<Modal />
```
*
*
*** abテストと実装時での仕様変更内容 ***
*/
import React from 'react'
import ReactDOM from 'react-dom'
import abtestRun from '../abTestRun'
import wrapVacantArea from './wrapVacantArea'
import { newChildInsertEnd, wrapElement } from '../utils/controlElement'
import App from './App'
const Issue11 = (path, extras) => {
const init = () => {
document.getElementById('page').classList.add('issue11')
// 動画用
const movieDialog = document.querySelector('#js-movie-dialog-wrap')
const vacantArea = document.querySelector('.topHeader .topHeader__layout .topHeader__right .akiari')
if (movieDialog !== null && vacantArea !== null) {
const topHeaderFooter = document.querySelector('.topHeader__footer')
if (topHeaderFooter !== null) {
topHeaderFooter.classList.add('issue11__flexTopHeader__footer')
const attr = {
id: 'movieBody',
class: 'topHeader__body',
}
wrapElement('div', attr, topHeaderFooter.children[0])
const mBody = document.querySelector('#movieBody')
while (mBody.nextElementSibling) {
mBody.appendChild(mBody.nextElementSibling)
}
topHeaderFooter.appendChild(vacantArea.parentNode)
}
}
// 個室空きありのwrapping
wrapVacantArea()
// ポイントとモーダルの描画
const topHeader = document.querySelector('.topHeader')
const pointUse = topHeader.querySelector('.point-use')
if (pointUse === null) return
const pointArea = newChildInsertEnd('section', { id: 'pointArea' }, document.querySelector('.topHeader'))
const props = {
imagePath: path.get('image'),
revision: `?revision=${extras.revision}`,
}
ReactDOM.render(<App {...props} />, pointArea)
pointUse.style.display = 'none'
}
window.issue11 = { start: init }
abtestRun()
}
export default Issue11
/**
* ポイント貯まる[OK]、店頭でポイント使える[NG]などをcloneし、
* パラメーターを付与
*/
import forEach from 'lodash/forEach'
const createPointLayout = () => {
const addParameterToList = clonePointLayout => {
const targetLink = '.gnaviService__layout .gnaviService__item'
const listLink = clonePointLayout.querySelectorAll(targetLink)
forEach(listLink, el => {
const linkText = el.querySelector('.gnaviService__text').innerHTML
const list = el.children[0]
switch (linkText) {
case 'ポイント貯まる':
list.setAttribute('href', list.href + '?sc_lid=r-smp_relinfo_point1')
break
case 'ネット予約でポイント使える':
list.setAttribute('href', list.href + '?sc_lid=r-smp_point_use1')
break
case '店頭でポイント使える':
list.setAttribute('href', list.href + '?sc_lid=r-smp_relinfo_point2')
break
case 'ぐるなびギフトカード使える':
el.parentNode.removeChild(el)
break
// no default
}
})
}
const gnaviService = document.querySelector('.gnaviService')
if (!gnaviService) return
const clonePointLayout = document.querySelector('.gnaviService').cloneNode(true)
clonePointLayout.setAttribute('class', 'modalPointLayout')
addParameterToList(clonePointLayout)
if (clonePointLayout.children[1]) {
clonePointLayout.removeChild(clonePointLayout.children[1])
}
const wrapper = document.createElement('div')
wrapper.setAttribute('class', '-mt4')
wrapper.innerHTML = `<p class="text--small">※「NG」と表示されているポイントは、この店舗ではご利用いただけません。</p>`
clonePointLayout.insertBefore(wrapper, clonePointLayout.children[1])
const modalList = document.querySelector('.modal__inner .point-container')
if (modalList === null) return
modalList.parentNode.insertBefore(clonePointLayout, modalList)
}
export default createPointLayout
import React from 'react'
import { branch, renderNothing } from 'recompose'
const getTargetEls = document.querySelectorAll('.section div.button div.button__body a p.text--big span')
const filteredReservEls = els =>
Array.prototype.filter.call(els, el => el.innerHTML.indexOf('ネット予約できるコース一覧') !== -1)
const getUrl = els => {
const el = filteredReservEls(els)
return el.length > 0 ? el[0].parentNode.parentNode.parentNode.href : ''
}
const ReserveButton = () => {
const handleClick = () => {
if (typeof window.sc_count === 'function') window.sc_count('r-smp_point_modal_coursebtn')
}
return (
<div className="reserve__button">
<div className="button">
<div className="button__body">
<a href={getUrl(getTargetEls)} className="button__item button__item--big" onClick={handleClick}>
<div className="ic-net-yoyaku">
<div>
<p className="text--huge -clr-white">
<span>ネット予約できるコース一覧</span>
</p>
</div>
</div>
</a>
</div>
</div>
</div>
)
}
const isReserveButton = () => getUrl(getTargetEls) !== ''
const isDrawButton = branch(isReserveButton, component => component, renderNothing)
export default isDrawButton(ReserveButton)
import React from 'react'
const PointList = ({ imagePath, revision }) => {
const poinText = [
{
id: 0,
img: imagePath + 'img/issue11/point_get.png' + revision,
theme: 'ポイント貯まる',
text: 'ネット予約して来店すると、ぐるなびのポイントや、提携先のマイルや数種類のポイントが貯まります。',
},
{
id: 1,
img: imagePath + 'img/issue11/point_reserve.png' + revision,
theme: 'ネット予約でポイント使える',
text: '貯まったポイントは、お店のネット予約時にご利用いただけます。',
},
{
id: 2,
img: imagePath + 'img/issue11/point_use.png' + revision,
theme: '店頭でポイント使える',
text: '貯まったポイントは、当日店頭でのお支払時にご利用いただけます。',
},
]
const pointList = poinText.map(val => (
<li className="list__content" key={val.id}>
<div className="img-wrapper">
<img src={val.img} alt={val.theme} />
</div>
<div className="text">
<h3 className="text__theme">{val.theme}</h3>
<p className="text__content">{val.text}</p>
</div>
</li>
))
return <ul className="list">{pointList}</ul>
}
export default PointList
import React from 'react'
import { compose, renderComponent, setDisplayName, branch, renderNothing, pure, nest } from 'recompose'
import BaseModal from '../../../module/BaseModal'
import BaseButton from '../../../module/BaseButton'
import PointList from './PointList'
import ReserveButton from './ReserveBUtton'
const Modal = props => {
const buttonProps = {
componentName: 'CloseButton',
style: 'button__close',
}
return (
<div className="modal__inner">
<div className="modal__header">
<h1 className="theme">このお店で使えるポイント</h1>
<BaseButton {...buttonProps} handler={props.closeModal}>
{props.modalCloseButtonText}
</BaseButton>
</div>
<section className="point point-container">
<h2 className="point__head">ぐるなびのポイントとは</h2>
<p className="text">
ぐるなびのポイント(ぐるなびスーパー「ぐ」ポイント)は、獲得対象サービスを利用することで貯まり、1ポイント=1円相当としてご利用いただけます。また、貯まったポイントは、ギフトカードなどに交換できます。
</p>
<PointList {...props} />
<div className="-center -mt12">
<p className="-bold">
<a href="http://my.gnavi.co.jp/point/?sc_lid=r-smp_pointclub_top" className="-underline -clr-link">
ポイントについて詳しく見る
</a>
</p>
</div>
</section>
<ReserveButton />
</div>
)
}
const Component = renderComponent(nest(BaseModal, Modal))
const checkModalStatus = ({ isModalOpen }) => isModalOpen
const withModalOpenCheck = branch(checkModalStatus, Component, renderNothing)
const Enhance = compose(setDisplayName('Modal'), withModalOpenCheck, pure)
export default Enhance(Component)
import React from 'react'
const AboutPoint = () => (
<div className="-center -mt12">
<p className="-bold">
<a href="http://my.gnavi.co.jp/point/?sc_lid=r-smp_pointclub_top" className="-underline -clr-link">
ポイントについて詳しく見る
</a>
</p>
</div>
)
export default AboutPoint
import React from 'react'
import { compose, withStateHandlers, lifecycle } from 'recompose'
import PointButton from './Component/PointButton'
import Modal from './Component/Modal'
import createPointLayout from './createPointLayout'
const App = props => (
<React.Fragment>
<PointButton {...props} />
<Modal {...props} />
</React.Fragment>
)
const createCloseBtn = () => <div className="planCal__close ic-close-batu js-popover-close">閉じる</div>
const ms300 = 300
const ms400 = 400
const Enhance = compose(
withStateHandlers(
{
isFadeOutModal: false,
isModalOpen: false,
isPointLayoutComponent: false,
positionY: 0,
modalCloseButtonText: createCloseBtn(),
},
{
renderPointLayout: () => () => ({ isPointLayoutComponent: true }),
closeModal: ({ isFadeOutModal }) => () => ({ isFadeOutModal: !isFadeOutModal }),
fadeOutModal: ({ positionY }) => () => {
document.querySelector('body').style.position = 'relative'
window.scrollTo(0, positionY)
return {
isFadeOutModal: false,
isModalOpen: false,
isPointLayoutComponent: false,
positionY: 0,
}
},
openModal: () => e => {
e.preventDefault()
if (typeof window.sc_count === 'function') window.sc_count('r-smp_point_pop')
return {
isModalOpen: true,
positionY: window.pageYOffset || document.documentElement.scrollTop,
}
},
},
),
lifecycle({
componentDidUpdate() {
if (this.props.isModalOpen && !this.props.isPointLayoutComponent) {
this.props.renderPointLayout()
createPointLayout()
setTimeout(() => {
document.querySelector('body').style.position = 'fixed'
}, 0)
setTimeout(() => {
document.querySelector('.modal__container').style.opacity = 1
}, ms300)
}
if (this.props.isFadeOutModal) {
document.querySelector('.modal__container').style.opacity = 0
setTimeout(() => {
this.props.fadeOutModal()
}, ms400)
}
},
}),
)
export default Enhance(App)
const abtestRun = () => {
if (
location.href.match(/ab_issue=(.+)/) &&
location.href.match(/ab_pattern=(.)/) &&
location.hostname.match(/localhost|frontend/)
) {
const issueNum = parseInt(location.href.match(/ab_issue=(\d+)/)[1], 10)
const pattern = parseInt(location.href.match(/ab_pattern=(\d+)/)[1], 10)
window.addEventListener('load', () => window['issue' + issueNum].start(pattern))
}
}
export default abtestRun
// import React from 'react'
import { wrapElement } from '../utils/controlElement'
const werapVacantArea = () => {
const vacantArea = document.querySelector('.topHeader .topHeader__right .akiari')
if (vacantArea === null) return
wrapElement('div', { class: 'wrapper' }, vacantArea.children[0])
const title = vacantArea.querySelector('.akiari__title')
vacantArea.querySelector('.wrapper').appendChild(title)
// 「本日の18時~20時」を変更
vacantArea.querySelector('.akiari__small').innerHTML = '本日 18 - 20時'
}
export default werapVacantArea
// 既存(登録済み)のイベント削除
// issue11用のポイントモーダル
import forEach from 'lodash/forEach'
const removeEvent = triggers => {
forEach(triggers, el => {
el.classList.toggle('js-point-use-modal-trigger')
el.classList.add('issue11-pointModal')
const clone = el.cloneNode(true)
const parent = el.parentNode
el.parentNode.removeChild(el)
parent.appendChild(clone)
})
}
export default removeEvent
import forEach from 'lodash/forEach'
import React from 'react'
// -enableクラスの判別とクラスの生成
const hasEnable = (hasEnableClass, baseClass) => (hasEnableClass ? baseClass + ' -enable' : baseClass)
const isEnable = target => target.indexOf('-enable') !== -1
const PointButton = ({ imagePath, revision, openModal }) => {
const pointEl = document.querySelector('.topHeader .point-use')
// -save: ポイント貯まる
const pointSaveEl = pointEl.querySelector('.-save .point-use__state.-enable')
const saveStateClass = hasEnable(pointSaveEl, 'point-use__state')
const saveTextStateClass = hasEnable(pointSaveEl, 'point-use__box__save-text')
// -use-list: ポイント使える
const useList = pointEl.querySelectorAll('.-use-list .point-use__list__item')
const useClassArr = []
const useListLabelArr = []
forEach(useList, el => {
const stateText = el.querySelector('.point-use__state-text.-enable')
const state = hasEnable(stateText, 'point-use__state')
// ネット予約、店頭文言
const text = el.querySelector('.point-use__state-text').innerHTML
// [OK]のみ取得
if (state.indexOf('-enable') !== -1) useListLabelArr.push(text)
useClassArr.push(state)
})
// 長いクラス名を取得(.-enableがある方が長い)
const useListClass = useClassArr[0].length > useClassArr[1].length ? useClassArr[0] : useClassArr[1]
const useListTextClass = hasEnable(isEnable(useListClass), 'point-use__box__use-list-text')
const useListLabel = isEnable(useListTextClass) ? useListLabelArr.join('・') : 'ネット予約・店頭'
return (
<div className="point-use-inner">
<a href="" className="point-use__list__item__link js-issue11-pointModal" onClick={openModal}>
<p className="theme">ポイント</p>
<div className="point-use__box -save">
<span className={saveStateClass} />
<span className={saveTextStateClass}>貯まる</span>
</div>
<div className="point-use__box -use-list">
<span className={useListClass} />
<span className={useListTextClass}>
使える ( <span className="label">{useListLabel}</span> )
</span>
</div>
<div className="questionIcon">
<img src={imagePath + 'img/issue11/question.png' + revision} alt="?" />
</div>
</a>
</div>
)
}
export default PointButton
以上是关于javascript 第11期:20180620 - no73。トップページのFV改善フェーズ1(ポイントモーダル,店铺名下の整理)的主要内容,如果未能解决你的问题,请参考以下文章
javascript Issue12:20180620 - no74。ナビゲーションメニュー改修
第910期No JQuery! 原生JavaScript操作DOM