/*
* HE Regions Shell - UI
*
* A lightweight UI shell for the HE environment featuring draggable, resizeable regions running the
* hub's apps/devices/utilities.
*
* Licensed Virtual the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under License is distributed
* on an "AS IS" BASIS, WIyTHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Date Who Description
* ------------- ------------------- ---------------------------------------------------------
* 2026-04-23 thebearmay code cleanup, add app filter
* 2026-04-24 About box enhancements using definitionData property
*/
import groovy.transform.Field
//#include thebearmay.uiRegions
String version() {return this.definitionData.version}
String appDesc() {return this.definitionData.description}
definition (
name: "HE Regions Shell - UI",
namespace: "thebearmay",
author: "Jean P. May, Jr.",
description: "A lightweight UI shell for the HE environment featuring draggable, resizeable regions running the hub's apps/devices/utilities.",
category: "Utility",
importUrl: "https://raw.githubusercontent.com/thebearmay/hubitat/main/apps/heRegionShell.groovy",
installOnOpen: true,
oauth: false,
iconUrl: "",
iconX2Url: "",
menu: "Apps",
version: '0.0.3'
)
preferences {
page name: "_mainPage"
//page name: "_appsList"
}
def installed() {
state?.isInstalled = true
initialize()
}
def updated(){
if(!state?.isInstalled) { state?.isInstalled = true }
if(debugEnabled) runIn(1800,logsOff)
}
def initialize(){
}
void logsOff(){
app.updateSetting("debugEnabled",[value:"false",type:"bool"])
}
def _mainPage(){
//
"
return retVal
}
@Field static String fullScrn = ""
/*
*
* Set of methods for UI elements
*
*
* Licensed Virtual the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
* on an "AS IS" BASIS, WIyTHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License.
*
* Date Who Description
* ---------- -------------- -------------------------------------------------------------------------
* 11Mar2025 thebearmay Add checkbox uiType, add trackColor and switchColor for type = bool
* 13Mar2025 Added hoverText, code cleanup
* 15Mar2025 Expand btnIcon to handle he- and fa- icons
* 18Mar2025 Add btnDivHide to hide/display div's (uiType='divHide')
* 03Apr2025 Enable a default value for enums
* 04Apr2025 Size option for icons
* 23May2025 Add device. to capability
* 07Oct2025 Added textarea uiType
* 20Feb2026 Minor enhancements and added 'hidden' as a valid input field type
* 06Mar2026 Fixed mis-matched in capability element
*/
import groovy.transform.Field
import java.text.SimpleDateFormat
library (
base: "app",
author: "Jean P. May Jr.",
category: "UI",
description: "Set of methods that allow the customization of the INPUT UI Elements",
name: "uiInputElements",
namespace: "thebearmay",
importUrl: "https://raw.githubusercontent.com/thebearmay/hubitat/main/libraries/uiInputElements.groovy",
version: "0.0.9",
documentationLink: ""
)
/************************************************************************
* Note: If using hoverText, you must add $ttStyleStr to at least one *
* element display *
************************************************************************/
String getInputElemStr(HashMap opt){
switch (opt.type){
case "text":
return inputItem(opt)
break
case "hidden":
return inputItem(opt)
break
case "number":
return inputItem(opt)
break
case "decimal":
return inputItem(opt)
break
case "date":
return inputItem(opt)
break
case "time":
return inputItem(opt)
break
case "password":
return inputItem(opt)
break
case "color":
return inputItem(opt)
break
case "textarea":
return inputTarea(opt)
break
case "enum":
return inputEnum(opt)
break
case "mode":
return inputEnum(opt)
break
case "bool":
return inputBool(opt)
break
case "checkbox":
return inputCheckbox(opt)
break
case "button":
return buttonLink(opt)
break
case "icon":
return btnIcon(opt)
break
case "href":
return buttonHref(opt)
break
case "divHide":
return btnDivHide(opt)
break
default:
if(opt.type && (opt.type.contains('capability') || opt.type.contains('device')))
return inputCap(opt)
else
return "Type ${opt.type} is not supported"
break
}
}
String appLocation() { return "http://${location.hub.localIP}/installedapp/configure/${app.id}/" }
/*****************************************************************************
* Returns a string that will create an input element for an app - limited to *
* text, password, number, date and time inputs currently *
* *
* HashMap fields: *
* name - (required) name to store the input as a setting, no spaces or *
* special characters *
* type - (required) input type *
* title - displayed description for the input element *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* multiple - true/ *
* defaultValue - default for the field *
* radius - CSS border radius value (rounded corners) *
* hoverText - Text to display as a tool tip *
*****************************************************************************/
String inputItem(HashMap opt) {
if(!opt.name || !opt.type) return "Error missing name or type"
if(settings[opt.name] != null){
if(opt.type != 'time') {
opt.defaultValue = settings[opt.name]
} else {
SimpleDateFormat sdf = new SimpleDateFormat('HH:mm')
SimpleDateFormat sdfIn = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
opt.defaultValue = sdf.format(sdfIn.parse(settings[opt.name]))
}
}
typeAlt = opt.type
if(opt.type == 'number') {
step = ' step=\"1\" '
} else if (opt.type == 'decimal') {
step = ' step=\"any\" '
typeAlt = 'number'
} else {
step = ' '
}
String computedStyle = ''
if(opt.type == 'hidden'){
opt.type='text'
typeAlt = 'hidden'
computedStyle += 'visibility:hidden;'
}
if (opt.float) computedStyle +="float:${opt.float};"
if(opt.width) computedStyle += "width:${opt.width};min-width:${opt.width};"
if(opt.background) computedStyle += "background-color:${opt.background};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.fontSize) computedStyle += "font-size:${opt.fontSize};"
if(opt.radius) computedStyle += "border-radius:${opt.radius};"
if(!opt.multiple) opt.multiple = false
if(opt.hoverText && opt.hoverText != 'null'){
opt.title ="${opt.title}
"
return retVal
}
/*****************************************************************************
* Returns a string that will create an textArea element for an app - *
* *
* HashMap fields: *
* name - (required) name to store the input as a setting, no spaces or *
* special characters *
* type - (required) input type *
* title - displayed description for the input element *
* width - CSS descriptor for field width *
* height - CSS descriptor for field height *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* defaultValue - default for the field *
* radius - CSS border radius value (rounded corners) *
* hoverText - Text to display as a tool tip *
*****************************************************************************/
String inputTarea(HashMap opt) {
if(!opt.name || !opt.type) return "Error missing name or type"
typeAlt = opt.type
String computedStyle = 'resize:both;'
if(opt.width) computedStyle += "width:${opt.width};min-width:${opt.width};"
if(opt.height) computedStyle += "height:${opt.height};"
if(opt.background) computedStyle += "background-color:${opt.background};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.fontSize) computedStyle += "font-size:${opt.fontSize};"
if(opt.radius) computedStyle += "border-radius:${opt.radius};"
if (opt.float) computedStyle +="float:${opt.float};"
if(opt.hoverText && opt.hoverText != 'null'){
opt.title ="${opt.title}
"
return retVal
}
/*****************************************************************************
* Returns a string that will create an input enum or mode element for an app *
* *
* HashMap fields: *
* name - (required) name to store the input as a setting, no spaces or *
* special characters *
* type - (required) capability type, *
* title - displayed description for the input element *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* multiple - true/ *
* options - list of values for the enum (modes will autofill) *
* defaultValue - default for the field *
* radius - CSS border radius value (rounded corners) *
* hoverText - Text to display as a tool tip *
*****************************************************************************/
String inputEnum(HashMap opt){
String computedStyle = opt.style ? opt.style:''
if (opt.float) computedStyle +="float:${opt.float};"
if(opt.type == 'mode') opt.options = location.getModes()
if(opt.width) computedStyle += "width:${opt.width};min-width:${opt.width};"
if(opt.background) computedStyle += "background-color:${opt.background};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.fontSize) computedStyle += "font-size:${opt.fontSize};"
if(opt.radius) computedStyle += "border-radius:${opt.radius};"
if(!opt.multiple) {
opt.multiple = false
mult = ' '
} else {
mult = 'multiple'
}
if(opt.hoverText && opt.hoverText != 'null')
opt.title ="${opt.title}
"
return retVal
}
int ordinalIndexOf(String str, String substr, int n) {
int pos = -1
(0..n).each {
if (pos != -1 || it == 0) pos = str.indexOf(substr, pos + 1)
}
pos
}
/*****************************************************************************
* Returns a string that will create an input boolean element for an app *
* *
* HashMap fields: *
* name - (required) name to store the input as a setting, no spaces or *
* special characters *
* type - (required) capability type, *
* title - displayed description for the input element *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* defaultValue - default for the field *
* radius - CSS border radius value (rounded corners) *
* trackColor - CSS color descriptor for the switch track *
* switchColor - CSS color descriptor for the switch knob *
* hoverText - Text to display as a tool tip *
*****************************************************************************/
String inputBool(HashMap opt) {
if(!opt.name || !opt.type) return "Error missing name or type"
if(opt.hoverText && opt.hoverText != 'null')
opt.title ="${opt.title}
"
return retVal
}
/*****************************************************************************
* Returns a string that will create an input checkbox element for an app *
* *
* HashMap fields: *
* name - (required) name to store the input as a setting, no spaces or *
* special characters *
* type - (required) capability type, *
* title - displayed description for the input element *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* defaultValue - default for the field *
* radius - CSS border radius value (rounded corners) *
* cBoxColor - CSS color descriptor for the checkbox color *
* hoverText - Text to display as a tool tip *
*****************************************************************************/
String inputCheckbox(HashMap opt) {
if(!opt.name || !opt.type) return "Error missing name or type"
if(opt.hoverText && opt.hoverText != 'null')
opt.title ="${opt.title}
"
return retVal
}
/*****************************************************************************
* Returns a string that will create an button element for an app *
* *
* HashMap fields: *
* name - (required) name to identify the button, no spaces or *
* special characters *
* title - (required) button label *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* radius - CSS border radius descriptor (corner rounding) *
* hoverText - Text to display as a tool tip *
*****************************************************************************/
String buttonLink(HashMap opt) { //modified slightly from jtp10181's code
if(!opt.name || !opt.title )
return "Error missing name or title"
String computedStyle = 'cursor:pointer;text-align:center;box-shadow: 2px 2px 4px #71797E;'
if(opt.style) computedStyle += "$opt.style"
if(opt.noBorder == true) computedStyle += "outline: none;box-shadow: none;text-align:left;"
if (opt.float) computedStyle +="float:${opt.float};"
if(opt.width) computedStyle += "width:${opt.width};min-width:${opt.width};"
if(opt.background) computedStyle += "background-color:${opt.background};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.fontSize) computedStyle += "font-size:${opt.fontSize};"
if(opt.radius) computedStyle += "border-radius:${opt.radius};"
if(!opt.icon) opt.icon = [name:'fa-circle-info']
if(opt.hoverText && opt.hoverText != 'null')
opt.title ="${opt.title}
${btnIcon(opt.icon)}${opt.hoverText}
"
return "
${opt.title}
"
}
/*****************************************************************************
* Returns a string that will create an button HREF element for an app *
* *
* HashMap fields: *
* name - (required) name to identify the button, no spaces or *
* special characters *
* title - (required) button label *
* destPage - (required unless using destUrl) name of the app page to go to *
* destUrl - (required unless using destPage) URL for the external page *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* radius - CSS border radius descriptor (corner rounding) *
*****************************************************************************/
String buttonHref(HashMap opt) { //modified jtp10181's code
if(!opt.name || !opt.title )
return "Error missing name or title"
if(!opt.destPage && !opt.destUrl)
return "Error missing Destination info"
String computedStyle = 'cursor:pointer;text-align:center;box-shadow: 2px 2px 4px #71797E;'
if (opt.float) computedStyle +="float:${opt.float};"
if(opt.width) computedStyle += "width:${opt.width};min-width:${opt.width};"
if(opt.background) computedStyle += "background-color:${opt.background};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.fontSize) computedStyle += "font-size:${opt.fontSize};"
if(opt.radius) computedStyle += "border-radius:${opt.radius};"
if(!opt.icon) opt.icon = [name:'fa-circle-info']
if(opt.destPage) {
inx = appLocation().lastIndexOf("/")
dest = appLocation().substring(0,inx)+"/${opt.destPage}"
} else if(opt.destUrl) {
dest=opt.destUrl
}
if(opt.hoverText && opt.hoverText != 'null')
opt.title ="${opt.title}
${btnIcon(opt.icon)}${opt.hoverText}
"
return "
${opt.title}
"
}
/*****************************************************************************
* Returns a string that will create an button element to hide/display a div *
* for an app *
* HashMap fields: *
* name - (required) name to identify the button, no spaces or *
* special characters *
* title - (required) button label *
* divName - (require) name of the division to hide or display *
* hidden - if true will hide the div immediately *
* width - CSS descriptor for field width *
* background - CSS color descriptor for the input background color *
* color - CSS color descriptor for text color *
* fontSize - CSS text size descriptor *
* radius - CSS border radius descriptor (corner rounding) *
*****************************************************************************/
String btnDivHide(HashMap opt) {
if(!opt.name || !opt.title || !opt.divName)
return "Error missing name, title or division"
String computedStyle = 'cursor:pointer;box-shadow: 2px 2px 4px #71797E;'
if (opt.float) computedStyle +="float:${opt.float};"
if(!opt.width) opt.width = '100%'
computedStyle += "width:${opt.width};"
if(opt.background) computedStyle += "background-color:${opt.background};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.fontSize) computedStyle += "font-size:${opt.fontSize};"
if(opt.radius) computedStyle += "border-radius:${opt.radius};"
if(!opt.icon) opt.icon = [name:'fa-circle-info']
if(opt.destPage) {
inx = appLocation().lastIndexOf("/")
dest = appLocation().substring(0,inx)+"/${opt.destPage}"
} else if(opt.destUrl) {
dest=opt.destUrl
}
String btnElem = ""
String script= ""
if(opt.hidden){
btnElem = ""
script=""
}
opt.title = "${btnElem} ${opt.title}"
if(opt.hoverText && opt.hoverText != 'null')
opt.title ="${opt.title}
${btnIcon(opt.icon)}${opt.hoverText}
"
return "$script
${opt.title}
"
}
/*****************************************************************************
* Returns a string that will create an button icon element for an app from *
* the materials-icon font *
* *
* name - (required) name of the icon to create *
*****************************************************************************/
String btnIcon(HashMap opt) { //modified from jtp10181's code
String computedStyle = ' '
if(opt.size) computedStyle += "font-size:${opt.size};"
if(opt.color) computedStyle += "color:${opt.color};"
if(opt.name.startsWith('he'))
return ""
else if(opt.name.startsWith('fa'))
return ""//fa-circle-info
else
return "${opt.name}"
}
/*****************************************************************************
* Code sample that returns a string that will create a standard HE table *
*****************************************************************************/
/*
String listTable() {
ArrayList tHead = ["","Disable","Name","Device","Attributes","Interval","Output File",""]
String X = ""
String O = ""
String settingsIcon = "settings_applications"
String removeIcon = ""
String str = "$tableStyle