Tooltips for App Input Switches

I have been developing a new App and wanted to add some tooltips explaining some of the settings with a link to more information, and after a long period of.... learning... I finally got it working the way I want it to :grin:

There's a bit going on here in the code that is not immediately obvious, so it may take me a little while before I explain it all, and I may end up rationalising it even more than I have and produce a bit of library code and/or a CSS file that people can download.

String getZindexToggle(String setting, int low = 10, int high = 50) {
    return "<style> div:has(label[for^='settings[${setting}]']) { z-index: ${low}; } div:has(label):has(div):has(span):hover { z-index: ${high}; } </style>";
}

String getTooltipHTML(String heading, String tooltipText, String hrefURL, String hrefLabel='View Documentation'){
    return "<span class='help-tip'> <p> <span class='help-tip-header'>${heading}</span> <br/>${tooltipText}<br/> <a href='${hrefURL}' target='_blank'>${hrefLabel}</a> </p> </span>";
}

def pageDeviceConfiguration(params) {
    String tooltipStyle = "<style> /* The icon */ .help-tip{     /* HE styling overrides */ 	box-sizing: content-box; 	white-space: collapse; 	 	display: inline-block; 	margin: auto; 	vertical-align: text-top; 	text-align: center; 	border: 2px solid white; 	border-radius: 50%; 	width: 16px; 	height: 16px; 	font-size: 12px; 	 	cursor: default; 	color: white; 	background-color: #2f4a9c; } /* Add the icon text, e.g. question mark */ .help-tip:before{     white-space: collapse; 	content:'?';     font-family: sans-serif;     font-weight: normal;     color: white; 	z-index: 10; } /* When hovering over the icon, display the tooltip */ .help-tip:hover p{     display:block;     transform-origin: 100% 0%;     -webkit-animation: fadeIn 0.5s ease;     animation: fadeIn 0.5s ease; } /* The tooltip */ .help-tip p {     /* HE styling overrides */ 	box-sizing: content-box; 	 	/* initially hidden */ 	display: none; 	 	position: relative; 	float: right; 	width: 178px; 	height: auto; 	left: 50%; 	transform: translate(204px, -90px); 	border-radius: 3px; 	box-shadow: 0 0px 20px 0 rgba(0,0,0,0.1);	 	background-color: #FFFFFF; 	padding: 12px 16px; 	z-index: 999; 	 	color: #37393D; 	 	text-align: center; 	line-height: 18px; 	font-family: sans-serif; 	font-size: 12px; 	text-rendering: optimizeLegibility; 	-webkit-font-smoothing: antialiased; 	 } .help-tip p a { 	color: #067df7; 	text-decoration: none; 	z-index: 100; } .help-tip p a:hover { 	text-decoration: underline; } .help-tip-header {     font-weight: bold; 	color: #6482de; } /* CSS animation */ @-webkit-keyframes fadeIn {     0% { opacity:0; }     100% { opacity:100%; } } @keyframes fadeIn {     0% { opacity:0; }     100% { opacity:100%; } } </style>";
    dynamicPage (name: "pageDeviceConfiguration", title: "Mobile Device Configuration", nextPage: "pageApplyConfiguration", install: false, uninstall: false) {
        section("") {
            paragraph "Select the permissions you want to grant for Mobile Controller on your mobile device: ${tooltipStyle}"
            input ("btMonitor", "bool", title: "Monitor Bluetooth Connections? ${getZindexToggle('btMonitor')} ${getTooltipHTML('Bluetooth Monitoring', 'Allow Mobile Controller to detect devices paired to the mobile device.  Child devices will be created in HE to capture the connection status for each bluetooth device.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#bluetooth-monitoring')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("wifiMonitor", "bool", title: "Monitor Wi-Fi Connections? ${getZindexToggle('wifiMonitor')} ${getTooltipHTML('Wi-Fi Monitoring','Allow Mobile Controller to detect connections to a specified list of Wi-Fi networks, allowing easy switching between local and cloud communications and contributing to presence detection.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#wi-fi-monitoring')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("callMonitor", "bool", title: "Monitor Calls? ${getZindexToggle('callMonitor')} ${getTooltipHTML('Call Monitoring', 'Allow Mobile Controller to detect incoming, ongoing and missed calls, reporting the call status to HE.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#call-monitoring')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("msgMonitor", "bool", title: "Monitor Messages? ${getZindexToggle('msgMonitor')} ${getTooltipHTML('Message Monitoring', 'Allow Mobile Controller to detect new or unread SMS/MMS messages on the mobile device, reporting the status back to HE.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#message-monitoring')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("syncModes", "bool", title: "Synchronize HE Modes? ${getZindexToggle('syncModes')} ${getTooltipHTML('Synchronizing HE Modes', 'Changes to the HE mode will be communicated to and stored on the mobile device, allowing use of the mode in custom automations on the mobile device.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#synchronizing-he-modes')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("controlModes", "bool", title: "Allow Control of HE Mode From The Device? ${getZindexToggle('controlModes')} ${getTooltipHTML('Control HE Modes', 'Allows changes to the HE mode to be initiated on the mobile device through elements such as a home-screen widget.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#control-he-modes')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("cloudComms", "bool", title: "Allow Cloud Communication? ${getZindexToggle('cloudComms')} ${getTooltipHTML('Cloud Communication', 'Allows the mobile controller to send status updates and other commands from the mobile device when not connected to the HE hub over a local Wi-Fi or VPN connection.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#cloud-communication')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("useVPN", "bool", title: "Communicate using VPN Connection When Available? ${getZindexToggle('useVPN')} ${getTooltipHTML('VPN Connection', 'If a VPN connection is available (connected) on the mobile device, this will be used when communicating from HE to the mobile device.', 'https://github.com/sburke781/MobileController/blob/master/Settings.md#vpn-connection')}",     required: true, submitOnChange: true, defaultValue: false)
            input ("vpnIP", "string", title: "Mobile Device VPN IP Address",     required: false, submitOnChange: true, defaultValue: '')
            paragraph "Click Next to apply the configuration settings"
        }
    }
}

If you want the main tooltip CSS in a more readable format:

/* The icon */
.help-tip{
    /* HE styling overrides */
	box-sizing: content-box;
	white-space: collapse;
	
	display: inline-block;
	margin: auto;
	vertical-align: text-top;
	text-align: center;
	border: 2px solid white;
	border-radius: 50%;
	width: 16px;
	height: 16px;
	font-size: 12px;
	
	cursor: default;
	color: white;
	background-color: #2f4a9c;
}

/* Add the icon text, e.g. question mark */
.help-tip:before{
    white-space: collapse;
	content:'?';
    font-family: sans-serif;
    font-weight: normal;
    color: white;
	z-index: 10;
}

/* When hovering over the icon, display the tooltip */
.help-tip:hover p{
    display:block;
    transform-origin: 100% 0%;
    -webkit-animation: fadeIn 0.5s ease;
    animation: fadeIn 0.5s ease;
}

/* The tooltip */
.help-tip p {
    /* HE styling overrides */
	box-sizing: content-box;
	
	/* initially hidden */
	display: none;
	
	position: relative;
	float: right;
	width: 178px;
	height: auto;
	left: 50%;
	transform: translate(204px, -90px);
	border-radius: 3px;
	box-shadow: 0 0px 20px 0 rgba(0,0,0,0.1);	
	background-color: #FFFFFF;
	padding: 12px 16px;
	z-index: 999;
	
	color: #37393D;
	
	text-align: center;
	line-height: 18px;
	font-family: sans-serif;
	font-size: 12px;
	text-rendering: optimizeLegibility;
	-webkit-font-smoothing: antialiased;
	
}

.help-tip p a {
	color: #067df7;
	text-decoration: none;
	z-index: 100;
}

.help-tip p a:hover {
	text-decoration: underline;
}

.help-tip-header {
    font-weight: bold;
	color: #6482de;
}

/* CSS animation */
@-webkit-keyframes fadeIn {
    0% { opacity:0; }
    100% { opacity:100%; }
}

@keyframes fadeIn {
    0% { opacity:0; }
    100% { opacity:100%; } }
7 Likes

Great, thank you for sharing!

Do you think that the same principle will work also on the driver pages?

1 Like

I hope so.... But.... To cut a very long story short.... It took a long time to work out how I could ensure the tooltip sat above the DIV's, i.e. settings, above and below the one hovered over, so it may take some time on the device page as well, but I still expect it will be possible. Obviously once the secret recipe is identified, it will be much easier to repeat.

1 Like

The Journey.....

I'll try and explain a bit about how I arrived at this solution... Warning, there's a reasonable amount of HTML and CSS content discussed :slight_smile: . Feel free to ask questions.... I would also add that with each of the solutions that I have "copied", there can sometimes be multiple layers to what makes them work, more than I am likely to explain because my code has evolved.

After deciding I wanted to get some fancy tooltips into my new app, I started looking for some options and stumbled across this code sample:

The sample code works well on it's own in a HTML page and I played around with it some more to get it displaying how I wanted it, in particular formatting the icon more like an information or help icon that people are used to (blue background with a white circle and text of either a question mark or an i, but also:

  • adding a title to the pop-up tooltip
  • adding my own description relevant to the App I was building
  • adding my own URL link to my GitHub Readme page

The next challenge was to display the tooltip to the right of the icon, instead of below, like the sample code did. From memory, at first, I don't think this was too much of a problem, aside from two sticking points:

  • The containing element would expand, e.g. a DIV containing the icon. This was understandable as the contents of the DIV, in my case, had grown bigger than the height of the icon
  • The tooltip needs to be displayed close to the icon if you want to move the mouse over the tooltip to access the URL displayed at the bottom

The second of these issues has not really been solved, just acknowledged that a 2px margin is the upper limit for how close the tooltip needs to be to the icon.

To avoid the expansion of the containing element and allow the tooltip to display over the top of adjacent elements, the float:right option has been used. There was likely some more to the use of this than just adding float:right into the mix, but hopefully that will be enough to point people in the right direction if they need to dig a little deeper.

The last challenge has been ensuring the tooltip displays over the top of the contents of the adjacent elements and remains on screen while the mouse is on the tooltip. This will need some more explanation at another time, but involves:

  • Setting the z-index for each adjacent App setting
  • Increasing the z-index of the DIV for the setting when the tooltip is being displayed
  • Likely needing the box-sizing adjustment in the tooltip CSS

I will need to come back and explain more about this, including these screenshots...

1 Like