React Bootstrap OverlayTrigger with trigger="focus" bug work around
React Bootstrap OverlayTrigger with trigger="focus" bug work around
在 iOS Safari 中,带有 trigger="focus" 的 OverlayTrigger 在点击外部时无法关闭。这是我的代码:
<OverlayTrigger
trigger="focus"
placement="right"
overlay={ <Popover id="popoverID" title="Popover Title">
What a popover...
</Popover> } >
<a bsStyle="default" className="btn btn-default btn-circle" role="Button" tabIndex={18}>
<div className="btn-circle-text">?</div>
</a>
</OverlayTrigger>
我知道这是 Bootstrap 的一个已知错误,因为它甚至不适用于 iOS 中的 their own website,但是有人知道有什么方法可以绕过它吗?如果是不需要 jQuery 的东西最好,但是欢迎 jQuery 解决方案。谢谢
好的,因为没有其他人给我一个解决方法,我和我的同事一起解决了这个问题 3 天,我们想出了这个沉重的解决方案:
问题:
使用 trigger="focus",Bootstrap Popover/Tooltip 可以在点击 Popover/Tooltip 之外时被关闭,但不能被 TOUCHING 关闭。 Android 浏览器显然会自动将触摸更改为点击,因此在 Android 上一切正常。但是 iOS safari 和基于 iOS safari 的浏览器(iOS chrome、iOS firefox 等)不会那样做。
修复:
我们发现在React Bootstrap中,Overlay组件实际上可以让你自定义何时显示Popover/Tooltip,所以我们在Overlay的基础上构建了这个组件InfoOverlay。为了处理组件外的点击,我们需要为 Popover/Tooltip 和 window 添加事件监听器来处理 'mousedown' 和 'touchstart'。此外,由于组件的 padding-right 最初为 0px,因此此方法会使 Popover 始终具有其最小宽度,并且我们根据某些父组件的宽度进行制作,以便它基于父组件进行响应。代码如下所示:
import React, { Component, PropTypes as PT } from 'react';
import {Popover, Overlay} from 'react-bootstrap';
export default class InfoOverlay extends Component {
static propTypes = {
PopoverId: PT.string,
PopoverTitle: PT.string,
PopoverContent: PT.node,
// You need to add this prop and pass it some numbers
// if you need to customize the arrowOffsetTop, it's sketchy...
arrowOffsetTop: PT.number,
// This is to be able to select the parent component
componentId: PT.string
}
constructor(props) {
super(props);
this.state = {
showPopover: false,
popoverClicked: false
};
}
componentDidMount() {
// Here are the event listeners and an algorithm
// so that clicking popover would not dismiss itself
const popover = document.getElementById('popoverTrigger');
if (popover) {
popover.addEventListener('mousedown', () => {
this.setState({
popoverClicked: true
});
});
popover.addEventListener('touchstart', () => {
this.setState({
popoverClicked: true
});
});
}
window.addEventListener('mousedown', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
window.addEventListener('touchstart', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
// this is to resize padding-right when window resizes
window.onresize = ()=>{
this.setState({});
};
}
// This function sets the style and more importantly, padding-right
getStyle() {
if (document.getElementById(this.props.componentId) && document.getElementById('popoverTrigger')) {
const offsetRight = document.getElementById(this.props.componentId).offsetWidth - document.getElementById('popoverTrigger').offsetLeft - 15;
return (
{display: 'inline-block', position: 'absolute', 'paddingRight': offsetRight + 'px'}
);
}
return (
{display: 'inline-block', position: 'absolute'}
);
}
overlayOnClick() {
this.setState({
showPopover: !(this.state.showPopover)
});
}
render() {
const customPopover = (props) => {
return (
{/* The reason why Popover is wrapped by another
invisible Popover is so that we can customize
the arrowOffsetTop, it's sketchy... */}
<div id="customPopover">
<Popover style={{'visibility': 'hidden', 'width': '100%'}}>
<Popover {...props} arrowOffsetTop={props.arrowOffsetTop + 30} id={this.props.PopoverId} title={this.props.PopoverTitle} style={{'marginLeft': '25px', 'marginTop': '-25px', 'visibility': 'visible'}}>
{this.props.PopoverContent}
</Popover>
</Popover>
</div>
);
};
return (
<div id="popoverTrigger" style={this.getStyle()}>
<a bsStyle="default" className="btn btn-default btn-circle" onClick={this.overlayOnClick.bind(this)} role="Button" tabIndex={13}>
<div id="info-button" className="btn-circle-text">?</div>
</a>
<Overlay
show={this.state.showPopover}
placement="right"
onHide={()=>{this.setState({showPopover: false});}}
container={this}>
{customPopover(this.props)}
</Overlay>
</div>
);
}
}
最后,这是一项繁重的工作,因为它需要大量代码来修复,而且您可能会感觉到您的站点由于 4 个事件侦听器而稍微变慢了。最好的解决办法就是告诉 Bootstrap 解决这个问题...
在 iOS Safari 中,带有 trigger="focus" 的 OverlayTrigger 在点击外部时无法关闭。这是我的代码:
<OverlayTrigger
trigger="focus"
placement="right"
overlay={ <Popover id="popoverID" title="Popover Title">
What a popover...
</Popover> } >
<a bsStyle="default" className="btn btn-default btn-circle" role="Button" tabIndex={18}>
<div className="btn-circle-text">?</div>
</a>
</OverlayTrigger>
我知道这是 Bootstrap 的一个已知错误,因为它甚至不适用于 iOS 中的 their own website,但是有人知道有什么方法可以绕过它吗?如果是不需要 jQuery 的东西最好,但是欢迎 jQuery 解决方案。谢谢
好的,因为没有其他人给我一个解决方法,我和我的同事一起解决了这个问题 3 天,我们想出了这个沉重的解决方案:
问题:
使用 trigger="focus",Bootstrap Popover/Tooltip 可以在点击 Popover/Tooltip 之外时被关闭,但不能被 TOUCHING 关闭。 Android 浏览器显然会自动将触摸更改为点击,因此在 Android 上一切正常。但是 iOS safari 和基于 iOS safari 的浏览器(iOS chrome、iOS firefox 等)不会那样做。
修复:
我们发现在React Bootstrap中,Overlay组件实际上可以让你自定义何时显示Popover/Tooltip,所以我们在Overlay的基础上构建了这个组件InfoOverlay。为了处理组件外的点击,我们需要为 Popover/Tooltip 和 window 添加事件监听器来处理 'mousedown' 和 'touchstart'。此外,由于组件的 padding-right 最初为 0px,因此此方法会使 Popover 始终具有其最小宽度,并且我们根据某些父组件的宽度进行制作,以便它基于父组件进行响应。代码如下所示:
import React, { Component, PropTypes as PT } from 'react';
import {Popover, Overlay} from 'react-bootstrap';
export default class InfoOverlay extends Component {
static propTypes = {
PopoverId: PT.string,
PopoverTitle: PT.string,
PopoverContent: PT.node,
// You need to add this prop and pass it some numbers
// if you need to customize the arrowOffsetTop, it's sketchy...
arrowOffsetTop: PT.number,
// This is to be able to select the parent component
componentId: PT.string
}
constructor(props) {
super(props);
this.state = {
showPopover: false,
popoverClicked: false
};
}
componentDidMount() {
// Here are the event listeners and an algorithm
// so that clicking popover would not dismiss itself
const popover = document.getElementById('popoverTrigger');
if (popover) {
popover.addEventListener('mousedown', () => {
this.setState({
popoverClicked: true
});
});
popover.addEventListener('touchstart', () => {
this.setState({
popoverClicked: true
});
});
}
window.addEventListener('mousedown', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
window.addEventListener('touchstart', () => {
if (!this.state.popoverClicked) {
this.setState({
showPopover: false
});
} else {
this.setState({
popoverClicked: false
});
}
});
// this is to resize padding-right when window resizes
window.onresize = ()=>{
this.setState({});
};
}
// This function sets the style and more importantly, padding-right
getStyle() {
if (document.getElementById(this.props.componentId) && document.getElementById('popoverTrigger')) {
const offsetRight = document.getElementById(this.props.componentId).offsetWidth - document.getElementById('popoverTrigger').offsetLeft - 15;
return (
{display: 'inline-block', position: 'absolute', 'paddingRight': offsetRight + 'px'}
);
}
return (
{display: 'inline-block', position: 'absolute'}
);
}
overlayOnClick() {
this.setState({
showPopover: !(this.state.showPopover)
});
}
render() {
const customPopover = (props) => {
return (
{/* The reason why Popover is wrapped by another
invisible Popover is so that we can customize
the arrowOffsetTop, it's sketchy... */}
<div id="customPopover">
<Popover style={{'visibility': 'hidden', 'width': '100%'}}>
<Popover {...props} arrowOffsetTop={props.arrowOffsetTop + 30} id={this.props.PopoverId} title={this.props.PopoverTitle} style={{'marginLeft': '25px', 'marginTop': '-25px', 'visibility': 'visible'}}>
{this.props.PopoverContent}
</Popover>
</Popover>
</div>
);
};
return (
<div id="popoverTrigger" style={this.getStyle()}>
<a bsStyle="default" className="btn btn-default btn-circle" onClick={this.overlayOnClick.bind(this)} role="Button" tabIndex={13}>
<div id="info-button" className="btn-circle-text">?</div>
</a>
<Overlay
show={this.state.showPopover}
placement="right"
onHide={()=>{this.setState({showPopover: false});}}
container={this}>
{customPopover(this.props)}
</Overlay>
</div>
);
}
}
最后,这是一项繁重的工作,因为它需要大量代码来修复,而且您可能会感觉到您的站点由于 4 个事件侦听器而稍微变慢了。最好的解决办法就是告诉 Bootstrap 解决这个问题...