In iOS safari, OverlayTrigger with trigger="focus" isn't able to dismiss when tapping outside. Here is my code:
<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>
I know that this is a known bug for Bootstrap cuz this doesn't even work on their own website in iOS, but does anyone know any method to go around it? It would be the best if it is something that doesn't require jQuery, but jQuery solution is welcome. Thanks.
OK, since no one else gives me a work around, I worked on this problem with my co-worker together for 3 days, and we came up with this heavy solution:
THE PROBLEM:
With trigger="focus", Bootstrap Popover/Tooltip can be dismissed when CLICKING outside the Popover/Tooltip, but not TOUCHING. Android browsers apparently changes touches to clicks automatically, so things are fine on Android. But iOS safari and browsers that is based on iOS safari (iOS chrome, iOS firefox, etc...) don't do that.
THE FIX:
We found out that in React Bootstrap, the Overlay component actually lets you customize when to show the Popover/Tooltip, so we built this component InfoOverlay based on Overlay. And to handle clicking outside the component, we need to add event listeners for both the Popover/Tooltip and window to handle both 'mousedown' and 'touchstart'. Also, this method would make the Popover have its smallest width all the time because of the padding-right of the component is initially 0px, and we make based on the width of some parent component so that it is responsive based on the parent component. And the code looks like this:
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>
);
}
}
In the end, this is a heavy work around because it is a big amount of code for a fix, and you can probably feel your site is slowed down by a tiny bit because of the 4 event listeners. And the best solution is just tell Bootstrap to fix this problem...
Related
I have a link in a modal that goes to a new page, and I can't seem to reset the state of the component with the modal when directing to that page.
The component with the modal seems to be keeping its state after directing to the new page, because when I hit the back button, it automatically opens the modal.
The modal is either opened or closed based on the state of modalIsOpen.
So I have my simplified Listings component:
import React from 'react'
import ListingModalContent from '../ListingModalContent'
import Modal from '../Modal'
export default class Listings extends React.Component {
constructor(props) {
super(props)
this.state = {
modalIsOpen: false,
modalContent: null
}
}
modalClick = (e, listing) => {
e.preventDefault()
this.setState({
modalContent: <ListingModalContent listing={listing}/>
}, () => {
this.setState({modalIsOpen: true})
})
}
modalClose = () => {
this.setState({modalIsOpen: false})
}
componentWillMount() {
this.setState({modalIsOpen: false})
console.log('mounting...')
console.log(this.state.modalIsOpen)
}
componentWillUnmount() {
console.log('unmounting...')
this.setState({
modalIsOpen: false
}, () => {
console.log('got here...')
console.log(this.state.modalIsOpen)
})
console.log(this.state.modalIsOpen)
}
render() {
const listings = this.props.listings.map(listing => (<div className="listing">
<a href="#" onClick={e => this.modalClick(e, listing)}>More Details</a>
</div>))
return (<div id="listings">
<section className="listings">
{listings}
<Modal visible={this.state.modalIsOpen} onClose={this.modalClose}>
{this.state.modalContent}
</Modal>
</section>
</div>)
}
}
And my ListingsModalContent component:
import React from 'react'
export default class ListingModalContent extends React.Component {
constructor(props) {
super(props)
}
render() {
const {listing} = this.props
return (<div className="listing-modal">
<div className="details">
<h2 className="address">{listing.address}</h2>
<p className="description">{listing.description}</p>
</div>
<div className="btn-container">
<a href={`/listings/${listing.slug}`} onClick={this.props.modalClose}>View Full Listing</a>
</div>
</div>)
}
}
The console output is...
// after initially mounting:
mounting...
false
// after clicking the listing link:
unmounting...
true
// after hitting the back button:
mounting...
false
I'm pretty sure I need to fix this by using componentWillUnmount to set the state of modalIsOpen to false before the component unmounts, but it never seems to finish setting the state before unmounting.
I'm using react on rails, which seems to use some hybrid routing rails/react routing system, but I'm not too familiar with it, and don't want to go down that rabbit hole at the moment if I don't have to.
So my question is, if this is expected behavior of the react component lifecycle, is there a way I can ensure the state of modalIsOpen is reset before unmounting? Or is there a way I can make sure my state is reset to its initial state when going back? Or is this more likely a consequence of the routing system I'm using?
This is strange, unexpected bahaviour in react and for sure is not caused by react (as #azium stated) but some 'things around', probably react_on_rails issue (or 'feature'). Report a bug/create an issue on github.
As you see in log state has proper value on mounting and there is no reason to render modal. 'Normal' react would work as expected.
There is no sense to set state on unmount - instance of component will be destroyed, its state, too.
HINTS
You shouldn't store modal content in state. It's possible, it works for simple cases, it can be used a kind of cache for parts of content, but you can have issues when conditional rerendering needed (using prop/state changes).
After setting state this.setState({modalIsOpen: true, modalContent:listing}) in click handler you can use conditional rendering (in render):
{this.state.modalIsOpen && <ListingModalContent listing={this.state.modalContent}/>}
To be true even this.setState({modalIsOpen: true}) can be removed (by save only listing idx in state, '-1' for closing) but then code can be less readable (storing additional pointer is cheap).
I have an Ionic Popover on my app. The popover appears when I run ionic serve, ionic emulate ios, and the XCode simulator. However, as soon as I simulate the app to my iPhone 4S on XCode or use the Ionic View app to view my own application, the popover does not appear. I've debugged EVERYTHING and the code does not work. No errors appear on my console when running the app.
Once in a blue moon the popover will appear on my 4S, but there is no logic to how the popover appears. I would change a piece of code, the popover appears, then when I change it again, the popover disappears. I repeat this process and go back to my old code version that worked and it doesn't work. This is frustrating. What's worse I fear no one will respond to this message. Any help as to why there is a discrepancy between the iPhone simulator and my actual iPhone would be great. Thanks.
Button HTML
<div ng-controller="FilterPopoverController as filterPopover" class="text-right">
<div on-tap="filterPopover.open()" ng-class="{filterButtonOpened: filterPopover.opened}" id="filter-button">
<span class="assertive" >
<i class="icon ion-arrow-down-b"></i>
<span class="bold">FILTER</span>
</span>
</div>
</div>
Popover HTML
<ion-popover-view id="filterPopover">
<ion-header-bar class="bar-dark">
<h1 id="popoverTitle" class="bold">FILTER BY</h1>
</ion-header-bar>
<ion-content>
<p>Content here</p>
</ion-content>
</ion-popover-view>
The Popover Controller
.controller('FilterPopoverController', filterPopoverController)
filterPopoverController.$inject = ['$ionicPopover', '$filter', '$scope', '$timeout'];
function filterPopoverController($ionicPopover, $filter, $scope, $timeout) {
var vm = this;
vm.open = open;
vm.popover = null;
vm.opened = false;
activate();
//Cleanup the popover when we're done with it!
$scope.$on('$destroy', function() {
vm.popover.remove();
vm.opened = false;
});
$scope.$on('popover.hidden', function() {
vm.opened = false;
});
function activate( ) {
$ionicPopover.fromTemplateUrl('/templates/search/filter-popover.html', {
scope: $scope
}).then(function(popover) {
vm.popover = popover;
});
}
function open( ) {
vm.opened = true;
vm.popover.show();
}
}
I've had to remove sensitive info from some of this code but this is the gist of it.
I have made two modifications to your posted code:
first one is to change the path of popover template to be:
'templates/search/filter-popover.html'
instead of
'/templates/search/filter-popover.html'
You need to reference this file starting from current directory instead of root directory
Second changed is to pass $event input while opening the popover, this is from official documentation of ionic Popover
After applying both of these changes to the posted code, I manage to see popover on desktop browser, ios simulator, real iPhone 4 consistenly
Here is the final code:
angular.module('starter', ['ionic'])
.run(function($ionicPlatform) {
$ionicPlatform.ready(function() {
// Hide the accessory bar by default (remove this to show the accessory bar above the keyboard
// for form inputs)
if(window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if(window.StatusBar) {
StatusBar.styleDefault();
}
});
})
.controller('FilterPopoverController', filterPopoverController)
filterPopoverController.$inject = ['$ionicPopover', '$filter', '$scope', '$timeout'];
function filterPopoverController($ionicPopover, $filter, $scope, $timeout) {
var vm = this;
vm.open = open;
vm.popover = null;
vm.opened = false;
activate();
//Cleanup the popover when we're done with it!
$scope.$on('$destroy', function() {
vm.popover.remove();
vm.opened = false;
});
$scope.$on('popover.hidden', function() {
vm.opened = false;
});
function activate( ) {
$ionicPopover.fromTemplateUrl('templates/search/filter-popover.html', {
scope: $scope
}).then(function(popover) {
vm.popover = popover;
});
}
function open( $event ) {
vm.opened = true;
vm.popover.show($event);
}
}
<div ng-controller="FilterPopoverController as filterPopover" class="text-right">
<div on-tap="filterPopover.open($event)" ng-class="{filterButtonOpened: filterPopover.opened}" id="filter-button">
<span class="assertive" >
<i class="icon ion-arrow-down-b"></i>
<span class="bold">FILTER</span>
</span>
</div>
</div>
I hope that this solves your problem.
I want to show a context menu on right click of the series plotted in High charts. I am not able to find any option in High charts to do this. Can any one suggest a way to achieve this requirement.
Well it is 2019 and there still isn't a solution for this that comes with the base HighCharts download. I have found a way to manipulate the LEFT click, in order to show a menu of sorts. Now I understand this may not be the best case scenario, but you still have full access to all of the data from the click, and will still be able to do normal drill down functionality etc. You just might have to rework it. This is a TypeScript example, but can easily be replicated to JavaScript with a few edits.
Please excuse the lack of CSS for the menu.
Your functions initialized before the chart. The variable is used to keep the menu from disappearing and is NOT mandatory here.
let callDrillDown = () => {
alert('drill1');
}
let callDrillDown2 = () => {
alert('drill2');
}
let mouseIn: boolean;
This is the bread and butter, during the click, you're pulling the <div> from the HTML and adding an onclick action to it.
plotOptions: {
column: {
events: {
click: (event: any) => {
let contextMenu = document.getElementById('contextMenu');
let contextMenuItem1 = document.getElementById('contextMenuItem1');
let contextMenuItem2 = document.getElementById('contextMenuItem2');
contextMenuItem1.onclick = callDrillDown;
contextMenuItem2.onclick = callDrillDown2;
contextMenu.onmouseenter = () => {
mouseIn = true;
};
contextMenu.onmouseleave = () => {
mouseIn = false;
setTimeout(() => {
if (!mouseIn) {
contextMenu.setAttribute('style', 'display: none');
}
}, 1000);
};
contextMenu.setAttribute('style', 'top: '
+ event.pageY + 'px; left:'
+ event.pageX + 'px;');
}
}
}
}
Inside of the body add the HTML
<div id="contextMenu" style="display: none" class="contextMenu">
<div id="contextMenuItem1">Data</div>
<div id="contextMenuItem2">Data2</div>
</div>
Here is the jsFiddle. Hope this helped.
I did the solution below. Hope it helps.
plotOptions: {
series: {
point: {
events: {
contextmenu: function (e) {
$('#constext-menu-div').css({top: e.chartY, left: e.chartX});
$('#constext-menu-div').show();
console.log(e);
},
click: function(){
$('#constext-menu-div').hide();
}
}
}
}
},
"http://jsfiddle.net/c42Ms/45/"
It is not built-in functionality, but you can use custom-events extention and then catch right click. Last step will be show/hide any div with menu.
How the new jQueryUI's tooltip widget can be modified to open the tooltip on click event on certain element's on document, while the others are still showing their tootip on mouseover event. In click-open case the tooltip should be closed by clicking somewhere else on the document.
Is this possible at all?
Using jqueryui:
HTML:
<div id="tt" >Test</div>
JS:
$('#tt').on({
"click": function() {
$(this).tooltip({ items: "#tt", content: "Displaying on click"});
$(this).tooltip("open");
},
"mouseout": function() {
$(this).tooltip("disable");
}
});
You can check it using
http://jsfiddle.net/adamovic/A44EB/
Thanks Piradian for helping improve the code.
This code creates a tooltip that stays open until you click outside the tooltip. It works even after you dismiss the tooltip. It's an elaboration of Mladen Adamovic's answer.
Fiddle: http://jsfiddle.net/c6wa4un8/57/
Code:
var id = "#tt";
var $elem = $(id);
$elem.on("mouseenter", function (e) {
e.stopImmediatePropagation();
});
$elem.tooltip({ items: id, content: "Displaying on click"});
$elem.on("click", function (e) {
$elem.tooltip("open");
});
$elem.on("mouseleave", function (e) {
e.stopImmediatePropagation();
});
$(document).mouseup(function (e) {
var container = $(".ui-tooltip");
if (! container.is(e.target) &&
container.has(e.target).length === 0)
{
$elem.tooltip("close");
}
});
This answer is based on working with different classes. When the click event takes place on an element with class 'trigger' the class is changed to 'trigger on' and the mouseenter event is triggered in order to pass it on to jquery ui.
The Mouseout is cancelled in this example to make everything based on click events.
HTML
<p>
<input id="input_box1" />
<button id="trigger1" class="trigger" data-tooltip-id="1" title="bla bla 1">
?</button>
</p>
<p>
<input id="input_box2" />
<button id="trigger2" class="trigger" data-tooltip-id="2" title="bla bla 2">
?</button>
</p>
jQuery
$(document).ready(function(){
$(function () {
//show
$(document).on('click', '.trigger', function () {
$(this).addClass("on");
$(this).tooltip({
items: '.trigger.on',
position: {
my: "left+15 center",
at: "right center",
collision: "flip"
}
});
$(this).trigger('mouseenter');
});
//hide
$(document).on('click', '.trigger.on', function () {
$(this).tooltip('close');
$(this).removeClass("on")
});
//prevent mouseout and other related events from firing their handlers
$(".trigger").on('mouseout', function (e) {
e.stopImmediatePropagation();
});
})
})
http://jsfiddle.net/AK7pv/111/
I have been playing with this issue today, I figured I would share my results...
Using the example from jQueryUI tooltip, custom styling and custom content
I wanted to have a hybrid of these two. I wanted to be able to have a popover and not a tooltip, and the content needed to be custom HTML. So no hover state, but instead a click state.
My JS is like this:
$(function() {
$( document ).tooltip({
items: "input",
content: function() {
return $('.myPopover').html();
},
position: {
my: "center bottom-20",
at: "center top",
using: function( position, feedback ) {
$( this ).css( position );
$( "<div>" )
.addClass( "arrow" )
.addClass( feedback.vertical )
.addClass( feedback.horizontal )
.appendTo( this );
}
}
});
$('.fireTip').click(function () {
if(!$(this).hasClass('open')) {
$('#age').trigger('mouseover');
$(this).addClass('open');
} else {
$('#age').trigger('mouseout');
$(this).removeClass('open');
}
})
});
The first part is more or less a direct copy of the code example from UI site with the addition of items and content in the tooltip block.
My HTML:
<p>
<input class='hidden' id="age" />
Click me ya bastard
</p>
<div class="myPopover hidden">
<h3>Hi Sten this is the div</h3>
</div>
Bacially we trick the hover state when we click the anchor tag (fireTip class), the input tag that holds the tooltip has a mouseover state invoked, thus firing the tooltip and keeping it up as long as we wish... The CSS is on the fiddle...
Anyways, here is a fiddle to see the interaction a bit better:
http://jsfiddle.net/AK7pv/
This version ensures the tooltip stays visible long enough for user to move mouse over tooltip and stays visible until mouseout. Handy for allowing the user to select some text from tooltip.
$(document).on("click", ".tooltip", function() {
$(this).tooltip(
{
items: ".tooltip",
content: function(){
return $(this).data('description');
},
close: function( event, ui ) {
var me = this;
ui.tooltip.hover(
function () {
$(this).stop(true).fadeTo(400, 1);
},
function () {
$(this).fadeOut("400", function(){
$(this).remove();
});
}
);
ui.tooltip.on("remove", function(){
$(me).tooltip("destroy");
});
},
}
);
$(this).tooltip("open");
});
HTML
Test
Sample: http://jsfiddle.net/A44EB/123/
Update Mladen Adamovic answer has one drawback. It work only once. Then tooltip is disabled. To make it work each time the code should be supplement with enabling tool tip on click.
$('#tt').on({
"click": function() {
$(this).tooltip({ items: "#tt", content: "Displaying on click"});
$(this).tooltip("enable"); // this line added
$(this).tooltip("open");
},
"mouseout": function() {
$(this).tooltip("disable");
}
});
jsfiddle
http://jsfiddle.net/bh4ctmuj/225/
This may help.
<!-- HTML -->
Click me to see Tooltip
<!-- Jquery code-->
$('a').tooltip({
disabled: true,
close: function( event, ui ) { $(this).tooltip('disable'); }
});
$('a').on('click', function () {
$(this).tooltip('enable').tooltip('open');
});
I have a jquery mobile page that contains a button that when tapped should show/hide a div. I seem to be missing something. I've worked through similar questions on SO with no success, can anyone suggest where I'm going wrong?
HTML:
View more filters
<div id="filters"> Blah </div>
CSS:
div#filters {
display: none;
}
JQ:
$('#myPage').live('pageinit', function(event) {
$("#moreFilters").bind('tap',function(event, ui){
$('#filters').toggle('fast', function() {});
})
});
I've also tried:
$('#moreFilters').live('tap',function(event) {
$("#filters").toggle(); // toggles the visibility/display of the element.
});
Could anyone point me in the right direction?
Many thanks in advance
can you do something like this,
<script>
var isMenuVisible = true
function showHideMenu() {
isMenuVisible = !isMenuVisible;
var menuPrin= document.getElementById("divMenu");
if(isMenuVisible) {
menuPrin.style.visibility = "visible";
menuPrin.style.width = "30%";
} else {
menuPrin.style.visibility = "hidden";
menuPrin.style.width = "0%";
}
}
</script>
This script could you put it at end of your page.
Now, point you id="divMenu" to your div that want hide, ok, and onclick="showHideMenu();" function on element that fire event.
All this simulate it tap and toggle events.
I hope this helps. :)