Scheduler widget

I want to create a widget with a schedule. The schedule itself is stored in a StringItem in JSON format like {“monday”: {“on”: “08:00”, “off”: “22:00”}, …}.
The widget should allow editing any value for any day of the week.
Something is clearly going wrong in the widget’s code, but I can’t figure out what. I’d appreciate any advice.

uid: WAN_schedule_editor
tags: []
props:
  parameters:
    - description: Terminal Name (e.g. kimiphone2)
      label: Terminal Name
      name: terminal
      required: true
      type: text
    - description: Item Postfix (default _autoWAN_schedule)
      label: Item Postfix
      name: postfix
      required: false
      type: text
      default: _autoWAN_schedule
timestamp: Nov 26, 2025, 12:30:00 PM
component: f7-card
config:
  title: ='Schedule ' + props.terminal
  outline: true
slots:
  content:
    - component: oh-context
      config:
        constants:
          targetItemName: =props.terminal + props.postfix
        variables:
          mySchedule: >
            =(items[props.terminal + props.postfix] && items[props.terminal + props.postfix].state !== 'NULL' && items[props.terminal + props.postfix].state !== 'UNDEF')
            ? JSON.parse(items[props.terminal + props.postfix].state.toString()) : 
            JSON.parse("{'monday':{'on':'08:00','off':'22:00'}, 'tuesday':{'on':'08:00','off':'22:00'}, 'wednesday':{'on':'08:00','off':'22:00'}, 'thursday':{'on':'08:00','off':'22:00'}, 'friday':{'on':'08:00','off':'23:00'}, 'saturday':{'on':'10:00','off':'22:00'}, 'sunday':{'on':'10:00','off':'22:00'}}")
      slots:
        default:
        # DEBUG BLOCK
          - component: f7-block
            config:
              visible: true
              style:
                color: red
            slots:
              default:
                - component: Label
                  config:
                    text: =vars.mySchedule.monday.on
        # DEBUG BLOCK END
          - component: f7-block
            config:
              visible: =items[const.targetItemName] === undefined
              style:
                color: red
                text-align: center
            slots:
              default:
                - component: Label
                  config:
                    text: ="Item " + constants.targetItemName + " not found!"
          - component: f7-list
            config:
              accordionList: true
              visible: =items[const.targetItemName] !== undefined
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: dayCfg
                    sourceType: array
                    in:
                      - day: monday
                        label: Monday
                      - day: tuesday
                        label: Tuesday
                      - day: wednesday
                        label: Wednesday
                      - day: thursday
                        label: Thursday
                      - day: friday
                        label: Friday
                      - day: saturday
                        label: Saturday
                      - day: sunday
                        label: Sunday
                  slots:
                    default:
                      - component: f7-list-item
                        config:
                          title: =loop.dayCfg.label
                          accordionItem: true
                        slots:
                          default:
                            - component: f7-accordion-content
                              slots:
                                default:
                                  - component: f7-block
                                    config:
                                      class:
                                        - display-flex
                                        - justify-content-space-between
                                        - align-items-center
                                    slots:
                                      default:
                                        - component: oh-input
                                          config:
                                            type: time
                                            #type: text
                                            label: On
                                            style:
                                              width: 45%
                                            variable: vars.mySchedule
                                            variableKey: =[loop.dayCfg.day].on
                                        - component: oh-input
                                          config:
                                            type: time
                                            label: Off
                                            style:
                                              width: 45%
                                            variable: vars.mySchedule #vars[loop.dayCfg.day].on
                                            variableKey: monday.off
                                            #variableKey: =[loop.dayCfg.day].off
- component: f7-block
  config:
    visible: =items[constants.targetItemName] !== undefined
  slots:
    default:
      - component: f7-button
        config:
          text: Save
          fill: true
          action: command
          actionItem: =constants.targetItemName
          actionCommand: vars.mySchedule

Sorry for providing the code in two parts – I couldn’t get around the site’s parser. The second part is the button that saves the schedule back to the item.

Can you be more specific about what is going wrong. I see a couple of issues, but I don’t know if they address the problem you are seeing.

This is most likely what you are referring to. This will not work because the actions are not an f7 feature, they are specific to OH. You need to use the oh-button here not the f7-button.

Yes, thank you. I have already noticed and fixed this, as well as a few other points. But the main problem is something else, of course: the input fields do not pick up the initial time values loaded from the JSON (see screenshot), and they also do not overwrite them with what I select. When the button is pressed, the same JSON that was loaded is written back to the item.

Fixed code:

uid: WAN_schedule_editor_time
tags: []
props:
  parameters:
    - description: Terminal Name (e.g. kimiphone)
      label: Terminal Name
      name: terminal
      required: true
      type: text
    - description: Item Postfix (default _autoWAN_schedule)
      label: Item Postfix
      name: postfix
      required: false
      type: text
      default: _autoWAN_schedule
timestamp: Nov 28, 2025, 18:30:00 PM
component: f7-card
config:
  title: =props.terminal + " schedule"
  outline: true
slots:
  content:
    - component: oh-context
      config:
        constants:
          targetItemName: =props.terminal + props.postfix
        variables:
          mySchedule: >
            =(items[props.terminal + props.postfix] && items[props.terminal + props.postfix].state !== 'NULL' && items[props.terminal + props.postfix].state !== 'UNDEF')
            ? JSON.parse(items[props.terminal + props.postfix].state.toString()) : 
            JSON.parse("{'monday':{'on':'08:00','off':'22:00'}, 'tuesday':{'on':'08:00','off':'22:00'}, 'wednesday':{'on':'08:00','off':'22:00'}, 'thursday':{'on':'08:00','off':'22:00'}, 'friday':{'on':'08:00','off':'23:00'}, 'saturday':{'on':'10:00','off':'22:00'}, 'sunday':{'on':'10:00','off':'22:00'}}")
      slots:
        default:
          # DEBUG BLOCK
          - component: f7-block
            config:
              visible: true
              style:
                color: red
            slots:
              default:
                - component: Label
                  config:
                    text: =vars.mySchedule.monday.on
          # DEBUG BLOCK END
          - component: f7-block
            config:
              visible: =items[const.targetItemName] === undefined
              style:
                color: red
                text-align: center
            slots:
              default:
                - component: Label
                  config:
                    text: ="Item " + const.targetItemName + " not found!"
          - component: f7-list
            config:
              accordionList: true
              visible: =items[const.targetItemName] !== undefined
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: dayCfg
                    sourceType: array
                    in:
                      - day: monday
                        label: Monday
                      - day: tuesday
                        label: Tuesday
                      - day: wednesday
                        label: Wednesday
                      - day: thursday
                        label: Thursday
                      - day: friday
                        label: Friday
                      - day: saturday
                        label: Saturday
                      - day: sunday
                        label: Sunday
                  slots:
                    default:
                      - component: f7-list-item
                        config:
                          title: =loop.dayCfg.label
                          accordionItem: true
                        slots:
                          default:
                            - component: f7-accordion-content
                              slots:
                                default:
                                  - component: f7-block
                                    config:
                                      class:
                                        - display-flex
                                        - justify-content-space-between
                                        - align-items-center
                                    slots:
                                      default:
                                        - component: oh-input
                                          config:
                                            type: time
                                            label: On
                                            style:
                                              width: 45%
                                            variable: vars.mySchedule
                                            variableKey: =loop.dayCfg.day + '.on'
                                        - component: oh-input
                                          config:
                                            type: time
                                            label: Off
                                            style:
                                              width: 45%
                                            variable: vars.mySchedule
                                            variableKey: =loop.dayCfg.day + '.off'
          - component: f7-block
            config:
              visible: =items[constants.targetItemName] !== undefined
            slots:
              default:
                - component: oh-button
                  config:
                    text: Save
                    fill: true
                    action: command
                    actionItem: =const.targetItemName
                    actionCommand: =JSON.stringify(vars.mySchedule)

I suspect that oh-input with type: time does not actually understand the HH:mm format at all, and I do not know how to parse a string into the DateTime or whatever it expects. And even if that works, does type: time even work with variables at all, or does it require a DateTime Item only?

You could be correct about that, there is a bit of specialized code in the oh-input for processing date data, and I have never tried to work with just time values. The only widget I have ever created that work with time values I’ve split up into separate hour, minute, and second strings.

I do know that the input works with full date-time values so you could also try to work around the issue by manipulating dull date-times. It would add a deal of complexity, but, since you already have an oh-context, you could easily add a couple of functions that take care of much of that complexity.

Yes, in the end the full date format worked. The main issue, as it turned out, was completely non-obvious from a syntax standpoint — the variable name in the oh-input:﬋ variable: field shouldn’t include vars.. Because of that, I probably skipped several working versions. In the end, I added a couple of functions and had to introduce two layers of oh-context, since variables and funcs defined higher up in the same context aren’t recognized, but it seems to be working now.
It would be great if someone with more experience could take a look and point out places where the code could be simplified.

uid: WAN_schedule_editor_time
tags: []
props:
  parameters:
    - description: Terminal Name (e.g. kimiphone)
      label: Terminal Name
      name: terminal
      required: true
    - default: _autoWAN_schedule
      description: Item Postfix (default _autoWAN_schedule)
      label: Item Postfix
      name: postfix
      required: false
timestamp: Dec 2, 2025, 7:10:10 PM
component: f7-card
config:
  title: =props.terminal + " schedule"
  outline: true
slots:
  content:
    - component: oh-context
      config:
        constants:
          targetItemName: =props.terminal + props.postfix
        variables:
          schedule: >
            =(items[props.terminal + props.postfix] && items[props.terminal +
            props.postfix].state !== 'NULL' && items[props.terminal +
            props.postfix].state !== 'UNDEF') ? JSON.parse(items[props.terminal
            + props.postfix].state.toString()) :  JSON.parse('{"monday": {"on":
            "07:00", "off": "23:00"}, "tuesday": {"on": "07:00", "off":
            "23:00"}, "wednesday": {"on": "07:00", "off": "23:00"}, "thursday":
            {"on": "07:00", "off": "23:00"}, "friday": {"on": "07:00", "off":
            "23:00"}, "saturday": {"on": "07:00", "off": "23:00"}, "sunday":
            {"on": "07:00","off": "23:00"}}')
        functions:
          toDT: =(x) => ("2020-01-01T" + x.toString() + ":00.000")
          fromDT: =(x) => x.split("T")[1].substring(0,5)
      slots:
        default:
          - component: oh-context
            config:
              variables:
                sched:
                  monday:
                    on: =fn.toDT(vars.schedule.monday.on)
                    off: =fn.toDT(vars.schedule.monday.off)
                  tuesday:
                    on: =fn.toDT(vars.schedule.tuesday.on)
                    off: =fn.toDT(vars.schedule.tuesday.off)
                  wednesday:
                    on: =fn.toDT(vars.schedule.wednesday.on)
                    off: =fn.toDT(vars.schedule.wednesday.off)
                  thursday:
                    on: =fn.toDT(vars.schedule.thursday.on)
                    off: =fn.toDT(vars.schedule.thursday.off)
                  friday:
                    on: =fn.toDT(vars.schedule.friday.on)
                    off: =fn.toDT(vars.schedule.friday.off)
                  saturday:
                    on: =fn.toDT(vars.schedule.saturday.on)
                    off: =fn.toDT(vars.schedule.saturday.off)
                  sunday:
                    on: =fn.toDT(vars.schedule.sunday.on)
                    off: =fn.toDT(vars.schedule.sunday.off)
            slots:
              default:
                - component: f7-block
                  config:
                    visible: =items[const.targetItemName] === undefined
                    style:
                      color: red
                      text-align: center
                  slots:
                    default:
                      - component: Label
                        config:
                          text: ="Item " + const.targetItemName + " not found!"
                - component: f7-list
                  config:
                    style:
                      padding: 10px 10px
                    accordionList: true
                    visible: =items[const.targetItemName] !== undefined
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          fragment: true
                          for: dayCfg
                          sourceType: array
                          in:
                            - day: monday
                              label: Monday
                            - day: tuesday
                              label: Tuesday
                            - day: wednesday
                              label: Wednesday
                            - day: thursday
                              label: Thursday
                            - day: friday
                              label: Friday
                            - day: saturday
                              label: Saturday
                            - day: sunday
                              label: Sunday
                        slots:
                          default:
                            - component: f7-list-item
                              config:
                                title: =loop.dayCfg.label
                                accordionItem: true
                              slots:
                                default:
                                  - component: f7-accordion-content
                                    slots:
                                      default:
                                        - component: f7-block
                                          config:
                                            class:
                                              - display-flex
                                              - justify-content-space-between
                                              - align-items-center
                                          slots:
                                            default:
                                              - component: oh-input
                                                config:
                                                  type: time
                                                  label: On
                                                  style:
                                                    width: 45%
                                                  variable: sched
                                                  variableKey: =loop.dayCfg.day + '.on'
                                              - component: oh-input
                                                config:
                                                  type: time
                                                  label: Off
                                                  style:
                                                    width: 45%
                                                  variable: sched
                                                  variableKey: =loop.dayCfg.day + '.off'
                - component: f7-block
                  config:
                    style:
                      padding: 10px 10px
                    visible: =items[const.targetItemName] !== undefined
                  slots:
                    default:
                      - component: oh-button
                        config:
                          text: "Save"
                          fill: true
                          action: command
                          actionItem: =const.targetItemName
                          actionCommand: >
                            ='{"monday":{"on":"' + 
                            fn.fromDT(vars.sched.monday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.monday.off) +
                            '"}, "tuesday": {"on":"' +
                            fn.fromDT(vars.sched.tuesday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.tuesday.off) +
                            '"}, "wednesday": {"on":"' +
                            fn.fromDT(vars.sched.wednesday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.wednesday.off) +
                            '"}, "thursday": {"on":"' +
                            fn.fromDT(vars.sched.thursday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.thursday.off) +
                            '"}, "friday": {"on":"' +
                            fn.fromDT(vars.sched.friday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.friday.off) +
                            '"}, "saturday": {"on":"' +
                            fn.fromDT(vars.sched.saturday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.saturday.off) +
                            '"}, "sunday": {"on":"' +
                            fn.fromDT(vars.sched.sunday.on) +
                            '","off":"' +
                            fn.fromDT(vars.sched.sunday.off) +
                            '"}}'
                            

I tested your widget in openHAB 5.1 Milestone 3. I can successfully save values to a string item, but the time does not display within the widget itself.

I suspect this is an issue with the underlying oh-input component. I attempted to use f7-input instead, which successfully displays the time, but this prevents the saving function from working.f7-input does not appear to support the necessary variable and variableKey parameters for data binding/saving.

What is the openHAB version you are currently using?