3-way toggle

3-way toggle

3 way toggle with intermediate UNDEF position

I needed this toggle switch to show the status of various rooms as all-OFF, all-ON or a mixture. I wanted to be able to switch all ON or all OFF from the same widget (obviously you can’t select the intermediate position). A standard toggle linked to a grouped item with an aggregation function will show either ON or OFF in the mixed state depending on whether the aggregation function is AND or OR. If the aggregation is OR and the status is mixed the toggle will show ON; clicking the toggle in that state will turn everything OFF which might not be what you want to do.

The widget below shows OFF if all are OFF, ON if all are ON and an intermediate state if the group item is UNDEF i.e. some, but not all, are ON. Clicking the right side of the toggle will switch all ON and the left will switch all OFF irrespective of the current state (NB it is not possible to “slide” the toggle. It’s just a click.

Props are the group item. Members of the group are picked up with a repeater. The group item should be set up as a switch without an aggregation function specified (actually that defaults to EQUALITY, which gives the required UNDEF state if not all the members are in the same state).

EDIT: It now looks right on mobile devices. f7 adds in some secret CSS for mobile devices incuding a min-width property of 27px for f7-buttons. I needed to override that in the style properties so that the button wasn’t stretched horizontally.

This is my first published widget so I’d be grateful for any sugestions or improvements

uid: GroupToggle
tags: []
props:
  parameters:
    - context: item
      description: Group item without aggregation function
      label: Item
      name: item
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Feb 7, 2024, 10:29:35 PM
component: f7-row
config: {}
slots:
  default:
    - component: oh-button
      config:
        actionItem: =props.item
        action: command
        actionCommand: OFF
        round: true
        outline: false
        style:
          height: 30px
          width: 32px
          border-radius: 15px 0px 0px 15px
          background: =(items[props.item].state === 'ON')?'#f60':'#505050'
          position: absolute
          left: 0px
    - component: oh-button
      config:
        actionItem: =props.item
        action: command
        actionCommand: ON
        round: true
        outline: false
        style:
          height: 30px
          width: 32px
          border-radius: 0px 15px 15px 0px
          background: =(items[props.item].state === 'ON')?'#f60':'#505050'
          position: absolute
          left: 32px
    - component: f7-button
      config:
        round: true
        outline: false
        style:
          height: 24px
          width: 24px
          min-width: 24px
          border-radius: 12px
          background: =(items[props.item].state === 'OFF')?("white"):(items[props.item].state === 'ON')?("white"):("#707070")
          top: 3px
          left: =(items[props.item].state === 'OFF')?("3px"):(items[props.item].state === 'ON')?("37px"):("20px")

If you want to test it out, here is a little widget that creates buttons for the group and for its members and shows the 3-way toggle underneath. Clicking the top button - the group item - obviously turns the whole group on or off. Clicking individual buttons can put the group into the mixed state which will be shown on the toggle. Clicking the right or left of the toggle can be used to specify ON or OFF from any state.

Toggle_test

uid: GroupToggle_Test
tags: []
props:
  parameters:
    - context: item
      description: Group item without aggregation function
      label: Item
      name: item
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Feb 7, 2024, 10:44:15 PM
component: f7-card
config: {}
slots:
  default:
    - component: f7-row
      config:
        style:
          height: 30px
      slots:
        default:
          - component: oh-button
            config:
              action: toggle
              actionCommand: ON
              actionCommandAlt: OFF
              actionItem: =props.item
              outline: true
              raised: true
              round: true
              active: "=(items[props.item].state === 'ON') ? true : false"
              style:
                height: 28px
                width: 100px
              text: =items[props.item].state
    - component: oh-repeater
      config:
        for: loopItem
        groupItem: =[props.item]
        sourceType: itemsInGroup
      slots:
        default:
          - component: f7-row
            config:
              style:
                height: 30px
            slots:
              default:
                - component: oh-button
                  config:
                    action: toggle
                    actionCommand: ON
                    actionCommandAlt: OFF
                    actionItem: =loop.loopItem.name
                    active: "=(items[loop.loopItem.name].state === 'ON') ? true : false"
                    outline: true
                    round: true
                    style:
                      height: 28px
                      width: 100px
                    text: =items[loop.loopItem.name].state
    - component: f7-row
      config:
        style:
          height: 30px
      slots:
        default:
          - component: widget:GroupToggle
            config:
              item: =props.item
3 Likes

Hmmm the scaling issue is not Android related. Same problem on Chrome on an iPad Pro.

Thanks for posting, I like the idea.

This is a small point, but you could clarify your description some (and the confusion probably does come from the UI a little bit).

The groups you are using DO have an aggregation function. As soon as you apply a member type such as Switch to a group there is a default aggregation function of Equality applied when you don’t give some more specific function. In the UI this does say None and maybe should say Default instead, because it’s not actually none.

Hi Justin

Yes you’re right. EQUALITY is the default aggregation function if you don’t specify AND, OR etc. I guess that is what I meant. As it says in the docs, unlike OR which is either a) ON or b) OFF depending on whether a) some or all are ON or b) none is ON, with EQUALITY you have UNDEF as a third state if the members are neither all ON nor all OFF. I’ve edited my original post.

My item definitions for testing are simply:

Group:Switch          Test_group_switch          "Test group"     
Switch                Test_switch_1              "Test switch 1"               (Test_group_switch)
Switch                Test_switch_2              "Test switch 2"               (Test_group_switch)            

No aggregation function specified.

Test_group_switch is the item defined in the props. The members are picked up by the repeater (I confess I haven’t tested it with more than two yet but it should work).

Thanks for posting, I like the idea.

Yeah - it’s important for the UI I am designing. I want to be able to go quickly to a room’s lighting or the heating for a whole floor. If say some of the lights are on, or some of the rooms are set to Auto I don’t want the widget to be either ON or OFF. That’s not useful. Also if some lights are on I might want to turn all of them on. On the other hand I might want to turn all of them off. With a conventional toggle I might have to switch them all off before then switching them back on. That’s not going to go down well with my wife and it would be goofy if you have guests. This way I get the choice all in one simple widget. I hope it helps others.

Now resolved - see edited first post.