OH3 - Hue Light Card

Hello,
i have made my first custom widget and want to share it with you. I hope you like it.
Maybe you can help improving it, there are some points under construction :blush:
Also tips e.g. regarding best practice are welcome, because the openhab, yaml, javascript world is new to me.

Design goal:

  • Widget optimized to be used on mobile phone using opanhab app
  • Most important stuff should be available in default state
  • Access to all options shall be available.
  • Self configuring from model (I loose overview of my items w/o model)

screnshot default state / after pressing lightbulb / after pressing button “LAMPEN”:

Functionality default state:

  • lightbulb indicates dimmer value and opens further settings.
  • S-Button: Select Scene
  • Colorpicker
  • On/Off Switch

Configurable:

  • card header background image
  • card header title
  • card header “All lights switch item”. If no item is configured switch is not shown.

Everything else is generated from the model information.

The model must follow the scheme:

  • The Hue Room must be an equipment under a location (category: light or colorlight; semantic class: lightbulb)
  • The single bulbs in the room must be child equipments of the room equipment (category: light or colorlight; semantic class: lightbulb)
  • The items must have default hue names in german (can be adjusted in the widget code).
  • Switch and dimmer items have to be setup manually with “_Betrieb” and _“Helligkeit” for each single rgb lightbulb (not for rooms).
  • Switch items have to be setup manually with “_Betrieb” for each single rgb lightbulb (not for rooms).
uid: cardLightProps
tags:
  - light
  - productive
props:
  parameters:
    - description: Title of the card
      label: Card title
      name: cardTitle
      required: false
      type: TEXT
    - description: Background image for card header (e.g. url("http://vps-openhab3:8080/static/devices/colorlight.jpg"))
      label: Background Image
      name: bgImage
      required: false
      type: TEXT
    - description: NOT USED
      label: Tags
      name: filterTags
      required: false
      type: TEXT
      advanced: true
    - context: item
      description: Switch item for all ligths. Keep empty to not display all lights switch.
      label: All lights switch item
      name: allLights
      required: false
      type: TEXT
      advanced: true
    - context: item
      description: Parent equipment. Keep empty in base card used internally for "lampen" pop-up.
      label: Parent equipment
      name: parEquip
      required: false
      type: TEXT
      advanced: true
  parameterGroups: []
timestamp: Dec 13, 2021, 11:30:27 AM
component: f7-card
slots:
  default:
    - component: f7-card-header
      config:
        valign: center
        noBorder: true
        style:
          background-image: =props.bgImage
          background-size: cover
          background-position: center
          height: 42px
          --f7-card-header-text-color: rgba(255,255,255)
      slots:
        default:
          - component: Label
            config:
              text: =props.cardTitle
              style:
                padding-right: 0px
          - component: oh-toggle
            config:
              item: =props.allLights
              visible: "=props.allLights ? true : false"
              style:
                margin-left: 20px
                margin-right: 0px
                padding-rigth: 0px
    - component: f7-card-content
      config:
        style:
          padding: 0px
          margin: 0px
      slots:
        default:
          - component: f7-list
            config:
              style:
                padding-bottom: 1px
                margin: 0px
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: repItem
                    sourceType: itemsWithTags
                    itemTags: Lightbulb
                    fetchMetadata: semantics
                    filter: 'props.parEquip ? ((loop.repItem.category == "light" || loop.repItem.category == "colorlight") && loop.repItem.metadata.semantics.config.isPartOf == props.parEquip) : ((loop.repItem.category == "light" || loop.repItem.category == "colorlight") && loop.repItem.metadata.semantics.config.hasLocation)'
                  slots:
                    default:
                      - component: f7-card
                        slots:
                          default:
                            - component: f7-card-header
                              config:
                                valign: bottom
                                noBorder: true
                                style:
                                  height: 48px
                                  padding-left: 8px
                                  padding-right: 8px
                                  padding-top: 0px
                                  padding-bottom: 4px
                              slots:
                                default:
                                  - component: f7-block
                                    config:
                                      class:
                                        - display-flex
                                        - justify-content-left
                                        - align-items-center
                                      style:
                                        padding: 0px
                                        height: 40px
                                        margin: 0px
                                    slots:
                                      default:
                                        - component: oh-icon
                                          config:
                                            icon: "=(items[loop.repItem.name + '_Helligkeit'].state == 0) ? 'slider-0' : ((items[loop.repItem.name + '_Helligkeit'].state <= 10) ? 'slider-10' : ((items[loop.repItem.name + '_Helligkeit'].state <= 20) ? 'slider-20' : ((items[loop.repItem.name + '_Helligkeit'].state <= 30) ? 'slider-30' : ((items[loop.repItem.name + '_Helligkeit'].state <= 40) ? 'slider-40' : ((items[loop.repItem.name + '_Helligkeit'].state <= 50) ? 'slider-50' : ((items[loop.repItem.name + '_Helligkeit'].state <= 60) ? 'slider-60' : ((items[loop.repItem.name + '_Helligkeit'].state <= 70) ? 'slider-70' : ((items[loop.repItem.name + '_Helligkeit'].state <= 80) ? 'slider-80' : ((items[loop.repItem.name + '_Helligkeit'].state <= 90) ? 'slider-90' : ('slider'))))))))))"
                                            width: 32px
                                            action: variable
                                            actionVariable: =visBright + loop.repItem.name
                                            actionVariableValue: "=(vars[visBright + loop.repItem.name] == undefined) ? true : !vars[visBright + loop.repItem.name]"
                                            style:
                                              margin-right: 8px
                                              padding: 0px
                                        - component: Label
                                          config:
                                            text: "=(loop.repItem.metadata.semantics.config.hasLocation ? loop.repItem.metadata.semantics.config.hasLocation : loop.repItem.name.split(loop.repItem.metadata.semantics.config.isPartOf)[1])"
                                  - component: f7-block
                                    config:
                                      class:
                                        - display-flex
                                        - justify-content-right
                                        - align-items-center
                                      style:
                                        padding: 0px
                                        height: 40px
                                    slots:
                                      default:
                                        - component: oh-button
                                          config:
                                            visible: "=((vars[visBright + loop.repItem.name] == undefined) ? true : !vars[visBright + loop.repItem.name]) && (loop.repItem.metadata.semantics.config.hasLocation?true:false)"
                                            text: S
                                            small: false
                                            raised: true
                                            outline: true
                                            item: =loop.repItem.name + "_Szene"
                                            actionItem: =loop.repItem.name + "_Szene"
                                            action: options
                                            style:
                                              height: 32px
                                              width: 32px
                                              margin-right: '=(loop.repItem.category == "colorlight") ? "20px" : "52px"'
                                        - component: oh-colorpicker
                                          config:
                                            openIn: auto
                                            item: =(loop.repItem.category == "colorlight") ? loop.repItem.name + "_Farbe":""
                                            modules:
                                              - hs-spectrum
                                            visible: =(loop.repItem.category == "colorlight") ? true:false
                                        - component: oh-toggle
                                          config:
                                            item: =loop.repItem.name + "_Betrieb"
                                            style:
                                              margin-left: 20px
                                              margin-right: 0px
                                              padding-rigth: 0px
                            - component: f7-card-content
                              config:
                                visible: "=(((vars[visBright + loop.repItem.name] == undefined) ? false : vars[visBright + loop.repItem.name]) || (loop.repItem.metadata.semantics.config.hasLocation?false:true))"
                                style:
                                  height: auto
                              slots:
                                default:
                                  - component: f7-row
                                    slots:
                                      default:
                                        - component: f7-col
                                          config:
                                            style:
                                              padding-left: 0px
                                              padding-right: 0px
                                            class:
                                              - display-flex
                                              - justify-content-space-between
                                          slots:
                                            default:
                                              - component: Label
                                                config:
                                                  text: ="Helligkeit:"
                                                  min: 0
                                                  style:
                                                    padding-right: 0px
                                                    font-size: 12px
                                              - component: Label
                                                config:
                                                  text: =items[loop.repItem.name + "_Helligkeit"].displayState
                                                  style:
                                                    padding-right: 0px
                                                    font-size: 12px
                                  - component: f7-row
                                    slots:
                                      default:
                                        - component: f7-col
                                          config:
                                            class:
                                              - display-flex
                                              - justify-content-center
                                          slots:
                                            default:
                                              - component: oh-slider
                                                config:
                                                  item: =loop.repItem.name + "_Helligkeit"
                                                  max: 100
                                                  step: 1
                                                  scale: true
                                                  scaleSteps: 5
                                                  scaleSubSteps: 5
                                                  releaseOnly: false
                                                  class:
                                                    - margin-bottom
                                                  style:
                                                    margin-left: 0px
                                                    margin-right: 0px
                                                    margin-top: 0px
                                  - component: f7-row
                                    config:
                                      visible: =(loop.repItem.metadata.semantics.config.hasLocation?true:false)
                                      style:
                                        height: auto
                                        margin-top: 10px
                                    slots:
                                      default:
                                        - component: f7-col
                                          config:
                                            class:
                                              - display-flex
                                              - justify-content-right
                                            style:
                                              height: auto
                                              margin: 0px
                                              padding: 0px
                                          slots:
                                            default:
                                              - component: oh-button
                                                config:
                                                  text: "='Szene: ' + items[loop.repItem.name + '_Szene'].displayState"
                                                  item: =loop.repItem.name + "_Szene"
                                                  actionItem: =loop.repItem.name + "_Szene"
                                                  action: options
                                                  small: true
                                                  outline: true
                                                  raised: true
                                                  style:
                                                    height: auto
                                                    width: inherit
                                                    margin-right: 0px
                                              - component: oh-button
                                                config:
                                                  text: Lampen
                                                  small: true
                                                  raised: true
                                                  outline: true
                                                  action: popup
                                                  actionModal: widget:cardLightProps
                                                  actionModalConfig:
                                                    bgImage: =props.bgImage
                                                    cardTitle: ="Licht - " + loop.repItem.metadata.semantics.config.hasLocation.toString()
                                                    parEquip: =loop.repItem.name
                                                  style:
                                                    height: auto
                                                    width: 150px
                                                    margin-left: 20px

Open Points (Tips/Hints are appreciated.):

  • Colorpicker for RGB is very laggy or not responding.
  • I need a colorpicker supporting color temperature for the ambient bulbs. It should match the rgb colorpicker design.
  • Probably gaps outside of my use case (other hue bulbs, room configurations, other vendor lights, etc)

best regards
Peter