Custom date/time range picker for Grafana widget — oh-input "variable" binding broken (related to PR #3978?), looking for a workaround

Hi all,

I’m extending the well-known “Grafana chart with time ranges” widget (originally by Marcus, see Grafana chart with time ranges) with an additional “…” button that opens an f7-popup containing date/time fields, so users can pick an arbitrary custom range instead of only the predefined presets.

Goal: Two oh-input fields (date + time, or a single datepicker) inside an f7-popup, whose values get combined into a string like from=2026-06-19T14:30:00&to=2026-06-19T18:00:00 and inserted into the Grafana iframe URL via props.URL.replace('{period}', ...).

Problem: Regardless of whether I use type: datepicker, type: date + type: text, or the variable: shorthand vs. the value / valueChange: actionVariable / actionVariable combo, the bound variable stays undefined, or in one case returned the literal string "Invalid Date". Meanwhile, oh-button with action: variable / actionVariable / actionVariableValue works completely reliably elsewhere in the same widget (the preset buttons and the “Apply” button both set variables fine).

I found this related thread, which looks like the same root cause:

That thread describes TypeError: this.getLastVariableKeyValue is not a function when using oh-input with variable:, traced to a recent refactor of the widget variable code, fixed in oh-input: Fix value computation for variables by florian-h05 · Pull Request #3978 · openhab/openhab-webui · GitHub (merged into main, not yet in 5.1.3).

My questions:

  1. Does PR #3978 cover plain oh-input (not inside oh-context) with type: date / type: text / type: datepicker as well, or only the oh-context + variableKey case described in that thread?
  2. Is there a known-working workaround for getting a date/time value out of an oh-input into a widget variable on current stable (5.1.x), before the fix lands in a release?
  3. Alternatively, is there a recommended pattern for building a date/time picker purely from oh-button components (which I’ve confirmed works reliably for setting variables), e.g. separate buttons for year/month/day/hour/minute increments?

My current minimal repro:

- component: oh-input
  config:
    clearButton: true
    label: Von (Datum)
    outline: true
    type: date
    variable: customFromDateDraft
- component: Label
  config:
    text: =JSON.stringify(vars.customFromDateDraft)

Selecting a date in the picker leaves the Label showing undefined, or in some attempts an “Invalid Date” string.

Running openHAB 5.2M5, MainUI. Happy to share the full widget YAML if useful.

Thanks for any pointers!

I am on the latest snapshot and a date type input correctly sets a variable value every time…but only if the clearbutton option is not set. Remove that option from your test and try again (also there is no reason to JSON.stringify the variable, but that’s not causing the issue).

I don’t know why the clear button should cause an issue, but I think that comes more from the upstream f7 library than OH.


Also: please help others on the forum and start clearly marking AI posts as I suspect this one is. I can’t address much of the rest of this or the questions because so much of the post just doesn’t make sense.

Thanks for the earlier pointer about clearButton — removing it did fix type: date + variable: for a single field on the snapshot I’m running.

However, I’m now hitting a related issue with two oh-input (type: date, variable:) fields in the same f7-list, used for a “from/to” range picker inside an f7-popup:

- component: oh-input
  config:
    label: Von
    outline: true
    type: date
    variable: customFromDraft
- component: oh-input
  config:
    label: Bis
    outline: true
    type: date
    variable: customToDraft

Observed behavior:

  • The first field (“Von” / customFromDraft) reliably sets its variable — confirmed via a debug Label showing JSON.stringify(vars.customFromDraft), which correctly shows e.g. "2026-06-18T10:31:30+02:00".
  • The second field (“Bis” / customToDraft) visually shows the selected date in the input (e.g. 20.06.2026), and is highlighted with what looks like an orange focus/validation outline, but vars.customToDraft stays undefined no matter how many times I reselect it.
  • This happens consistently regardless of the order in which I interact with the two fields (tried Von-then-Bis, Bis-only, and alternating multiple times).
  • I also tried adding value: =vars.customFromDraft / value: =vars.customToDraft bound back to the same variables (thinking it might help restore state when the popup reopens), but that made things worse — possibly because the reactive value re-evaluates on every variable change in the widget and may be resetting the native input’s internal state for whichever field didn’t just change. Removing value again, the first field still works but the second still doesn’t.

This is on a recent snapshot build, not 5.1.3.

Questions:

  1. Is there a known limitation with multiple oh-input type:date + variable: instances inside the same f7-list/oh-repeater-free static list? Could the variable-binding fix (PR #3978) only have been verified for a single instance?
  2. Could this be a Framework7 calendar/native input event collision when two sibling date inputs without distinct otherwise-set props (no clearButton, no value) live in the same list?
  3. Is there a recommended way to give each input a more explicit/unique binding (e.g. some kind of id or key) to rule out the two fields’ internal Vue/F7 component instances being conflated?

Happy to share a minimal full-widget YAML reproduction if useful — this is for a “pick an arbitrary from/to range” popup extending the well-known Grafana time-range-picker widget.

Thanks again for the quick help so far!

@JustinG ALSO: This text is not AI it’s human generated :slight_smile: How do I mark it when a human produced the text?

No, and in fact I cannot replicate the issue you are reporting on a very recent snapshot (openHAB 5.2.0 Build #5443; Main UI Commit 650377ce).

If I understand what you are reporting correctly then the following widget should exhibit the faulty behavior:

version: 1
widgets:
  input_demo:
    props:
      parameterGroups: []
      parameters: []
    tags: []
    component: f7-card
    config:
      title: =vars.testDate || 'No date selected'
      footer: =vars.testDateFooter || 'No date selected'
    slots:
      default:
        - component: f7-list
          config:
            simpleList: true
          slots:
            default:
              - component: oh-input
                config:
                  type: date
                  variable: testDate
              - component: oh-input
                config:
                  type: date
                  variable: testDateFooter

but it does not. Both dates reliably change every time I use them regardless of sequence.

For completeness, because you are putting them in the list you will get more predictable position and style behavior if you use oh-input-item instead of the simple oh-input.

        - component: f7-list
          config:
            simpleList: false
          slots:
            default:
              - component: oh-input-item
                config:
                  title: Header date
                  type: date
                  variable: testDate
              - component: oh-input-item
                config:
                  title: Footer date
                  type: date
                  variable: testDateFooter

:+1:

It’s not necessary for human text. There were a few inconsistencies in the original post that could have been AI or could have just been human error over the course of a lengthy post (we’ve all been there), so I wasn’t sure.

Thanks for testing — really appreciate it. I switched to oh-input-item / simpleList: false as you suggested, but the issue persists exactly as before: the first date field (“Von”) reliably sets its variable, the second (“Bis”) visually shows the picked date but vars.customToDraft stays undefined.

version: 1
widgets:
  widget_Grafanachartwithtimeranges:
    tags: []
    props:
      parameters:
        - description: Titel der Karte
          label: Titel
          name: title
          required: false
          type: TEXT
        - description: URL für den Grafana-Frame
          label: Quell-URL
          name: URL
          required: true
          type: TEXT
        - default: from=now-6h&to=now;-6h,from=now-12h&to=now;-12h,from=now-1d&to=now;-24h,from=now/d&to=now/d;Heute,from=now-3d&to=now;-3 Tage,from=now-7d&to=now;-7 Tage,from=now-14d&to=now;-14 Tage
          description: Komma-getrennte Liste. Beispiel "from=now-6h&to=now;6h,from=now-1d/d&to=now-1d/d;Gestern". Erster Eintrag ist Standard.
          label: Zeitraum-Optionen
          name: timerange
          required: true
          type: TEXT
        - default: "420"
          description: Höhe des Frames in px (leer = Standard)
          label: Höhe (px)
          name: height
          required: false
          type: TEXT
        - default: "#FFFFFF"
          label: Hintergrundfarbe
          name: bgColor
          required: false
          type: TEXT
      parameterGroups:
        - name: general
          label: Allgemein
        - name: colors
          label: Farben
    component: f7-card
    config:
      style:
        background: =props.bgColor
        border-radius: 12px
        margin: 0
        padding: 12px
      title: =props.title
    slots:
      default:
        - component: Label
          config:
            style:
              color: var(--f7-color-gray)
              display: block
              font-size: 10px
              margin-bottom: 4px
              word-break: break-all
            text: >-
              =('customRange: ' + vars.customRange + ' | src: ' + (vars.customRange ? props.URL.replace('{period}', vars.customRange) : props.URL.replace('{period}', vars.selectedPeriod || props.timerange.split(',')[0].split(';')[0])))
        - component: oh-webframe-card
          config:
            borders: false
            class:
              - display-block
            height: =(props.height || '420') + 'px'
            noBorder: true
            noShadow: true
            style:
              border-radius: 8px
              overflow: hidden
            src: >-
              =vars.customRange
                ? props.URL.replace('{period}', vars.customRange)
                : props.URL.replace('{period}', vars.selectedPeriod || props.timerange.split(',')[0].split(';')[0])
        - component: f7-segmented
          config:
            class:
              - padding-top-half
            outline: false
            round: true
            style:
              --f7-button-border-radius: 8px
              --f7-button-font-size: 13px
              --f7-button-font-weight: 500
              --f7-button-padding-horizontal: 0px
              --f7-button-padding-vertical: 0px
              --f7-button-text-transform: none
              margin: 0
          slots:
            default:
              - component: oh-repeater
                config:
                  for: size
                  fragment: true
                  sourceType: range
                slots:
                  default:
                    - component: oh-repeater
                      config:
                        for: period
                        fragment: true
                        in: =[props.timerange.split(",")[loop.size].split(";")[1]]
                      slots:
                        default:
                          - component: oh-button
                            config:
                              action: variable
                              actionVariable: selectedPeriod
                              actionVariableValue: =props.timerange.split(",")[loop.size].split(";")[0]
                              fill: =(!vars.customRange && ((props.timerange.split(',')[loop.size].split(';')[0] == vars.selectedPeriod) || (props.timerange.split(',')[0].split(';')[1] === loop.period && !vars.selectedPeriod)))
                              round: true
                              style:
                                --f7-button-border-color: var(--f7-card-outline-border-color)
                              text: =loop.period
              - component: oh-button
                config:
                  popupOpen: .grafana-custom-range-popup
                  fill: =(!!vars.customRange)
                  round: true
                  style:
                    --f7-button-border-color: var(--f7-card-outline-border-color)
                  text: "..."
        - component: f7-popup
          config:
            class:
              - grafana-custom-range-popup
          slots:
            default:
              - component: f7-navbar
                config:
                  title: Eigener Zeitraum
                slots:
                  right:
                    - component: oh-button
                      config:
                        popupClose: true
                        text: Schließen
              - component: f7-block
                slots:
                  default:
                    - component: f7-list
                      config:
                        simpleList: false
                      slots:
                        default:
                          - component: oh-input-item
                            config:
                              title: Von
                              type: date
                              variable: customFromDraft
                          - component: oh-input-item
                            config:
                              title: Bis
                              type: date
                              variable: customToDraft
                    - component: f7-block
                      config:
                        style:
                          color: var(--f7-color-gray)
                          font-size: 10px
                          margin: 4px 0
                          word-break: break-all
                      slots:
                        default:
                          - component: Label
                            config:
                              text: >-
                                =('Von: ' + JSON.stringify(vars.customFromDraft) + ' | Bis: ' + JSON.stringify(vars.customToDraft))
                    - component: oh-button
                      config:
                        class:
                          - padding-top-half
                        action: variable
                        actionVariable: customRange
                        actionVariableValue: >-
                          =(!vars.customFromDraft || !vars.customToDraft) ? '' : ('from=' + new Date(vars.customFromDraft).toISOString().slice(0,19) + '&to=' + new Date(vars.customToDraft).toISOString().slice(0,19))
                        fill: true
                        round: true
                        text: Übernehmen
                        popupClose: true
                    - component: oh-button
                      config:
                        class:
                          - padding-top-half
                        outline: true
                        round: true
                        text: Zurücksetzen auf Schnellauswahl
                        action: variable
                        actionVariable: customRange
                        actionVariableValue: ""

Since your minimal repro didn’t show the bug, there must be something specific to my actual widget structure that triggers it — possibly the nesting depth, the f7-popup + f7-navbar wrapper, or the sibling debug Label component that re-renders on every variable change. Posting the full widget below so you can see exactly what’s different from your repro.
Structural differences from your minimal repro that I suspect might matter:

  • The two date fields are inside an f7-popupf7-blockf7-list, not directly in the card’s default slot.
  • There’s an f7-navbar as a sibling slot inside the popup (for the title/close button).
  • There’s a second f7-block immediately after the f7-list, containing a debug Label whose text expression reads both customFromDraft and customToDraft — this re-renders on every keystroke/selection in either field.
  • The “Apply” button references both variables together in a single actionVariableValue ternary expression.

If you have a moment to drop this exact YAML into a test instance, I’d be curious whether it reproduces for you too. If it does, that narrows it down to something in this structure; if it doesn’t, then it’s likely something environment-specific on my end (browser, theme, locale — running Chrome desktop, German locale, if that’s relevant) and I’ll dig further on that side.

Thanks again, sorry for the back-and-forth!

Your popup works just fine for me on both Chrome and Firefox:

inputtest

Mhhm strange… anything I can do cleaning the cache?

@JustinG on my mobile it runs fine but not on my desktop browser

Also not working on my mobile

it only happens with the date selection - interesting all others are working

Do you see any errors in the browser console when you try to set the second date?

No error nothing

Then I don’t really have any more specific suggestions for you, unfortunately. Here are a few things I would try to see if I could understand what’s going on:

Maybe make sure you’ve cleared the cache on your browser page.

You could test to see if datepicker works better instead of date for the input type.

You could switch the order of the two inputs and see if it is specifically the second input that doesn’t work or the input with the customToDraft variable that doesn’t work.

See if the input works with an item and sendButton instead of a variable.

Okay so I found it out how it works if I press not the actual date but the day before and after that I can select the current date