OH3: Image card in MainUI - How to refresh image?

This is a perfect approach !
Will get into it tomorrow !
Thanks a lot !

Magical! Thank yo so much, this looks awesome!
I’ll use this on my dashboard, too!

Actually you can simulate a default period…!
In the oh-button config:
fill: =(vars.selectedPeriod || '6h') === loop.period.value
And in the oh-webframe-card config:
src: ='...' + (vars.selectedPeriod || '6h') + '...'
and you can remove the visible parameter as you don’t need it anymore.

Did all of that - when I hardcode the URL in YAML code, it works.

Then I built a widget for it - as long as the url remains hardcoded - no problem.

As soon as I use props for Title, Footer and URL, the URL and footer part doesn’t get updated anymore when the time period is changed:
(I use the footer just to check the logic for the URL string)

grafana widget 1

grafana widget props 1

URL = footer:

='http://192.168.0.13:3000/d-solo/4OdYJNbMk/energie-pv-haus?orgId=1&refresh=1m&from=now-' + (vars.selectedPeriod || '6h') + '&to=now&panelId=2'

YAML of the widget:

uid: Time series Grafana chart
tags: []
props:
  parameters:
    - description: Title of the chart
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Footer of the chart
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: URL to show in the frame
      label: Source URL
      name: URL
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Jan 6, 2021, 11:40:52 AM
component: f7-card-content
slots:
  default:
    - component: oh-webframe-card
      config:
        title: =props.title
        borders: false
        noBorder: false
        outline: true
        src: =props.URL
        footer: =props.footer
    - component: f7-segmented
      config:
        round: true
        outline: true
      slots:
        default:
          - component: oh-repeater
            config:
              fragment: true
              for: period
              in:
                - value: 6h
                  label: 6 Std
                - value: 12h
                  label: 12 Std
                - value: 1d
                  label: 1 Tag
                - value: 7d
                  label: 7 Tage
                - value: 30d
                  label: 30 Tage
            slots:
              default:
                - component: oh-button
                  config:
                    text: =loop.period.label
                    fill: =(vars.selectedPeriod || '6h') === loop.period.value
                    round: false
                    outline: true
                    action: variable
                    actionVariable: selectedPeriod
                    actionVariableValue: =loop.period.value

Any idea why this happens?

That selectedPeriod is a variable set within the context of the widget so it’s constrained to that conterxt - that’s why it can’t be used in props because it’s outside the context of the parent.

That’s a bit equivalent to doing:

class Widget {
  String selectedPeriod;

  void renderWidget(String url) {
    String src = url;
    ...
  }

  void buttonClicked(String value) {
    selectedPeriod = value;
}

class Page {
  Widget widget = new Widget();

  void renderPage() {
    widget.renderWidget("http://" + selectedPeriod + "...");
}

This analogy falls short on one thing, is that variables already defined on the parent, if they’re updated in the widget, will be updated for the parent too. I explain with some illustrations how that works here: Widgets: context variables, add oh-input by ghys · Pull Request #356 · openhab/openhab-webui · GitHub

What would likely work is add defineVars in the page config, that’s the equivalent of declaring a global variable.
Better yet, you can for example decide on a placeholder for your period, like {period} and in the image use:

src: =props.url.replace('{period}', vars.selectedPeriod || '6h')

then configure your widget with http://192.168.0.13:3000/d-solo/4OdYJNbMk/energie-pv-haus?orgId=1&refresh=1m&from=now-{period}&to=now&panelId=2

Perfect - this works !

Final polishing… I tried to align the style of the buttons to the Web frame card (Main UI default style). Was on F7 website, tried lots of different things… partial success.

Unfortunately, I’m not familiar with styles, etc. at all, but eager to learn on the go…
I’d be grateful, if you could guide me here also… Thank you.

grafana widget 2

  1. Align width of both cards
    The segmented button card is a bit wider than the Web frame card.
    It is not only the appearance, it is mor that the Web frame card gets a bit queezed in width.
    I tried with margin, etc, but failed.

  2. Outline
    I’d wish to have the same outline on the segmented buttons as the Web frame card (which is the Main UI default) = a thin light gray line on dark gray background
    buttons

  3. Background color
    The background colors of both cards should ideally be the same - not sure they are.

  4. Minimize gap between Web frame card and segmented buttons card
    Can that be made smaller.

  5. Font on buttons:
    How can I change font size and style (reduce font weight)?

The current YAML:

uid: Time series Grafana chart
tags: []
props:
  parameters:
    - description: Title of the chart
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Footer of the chart
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: URL to show in the frame
      label: Source URL
      name: URL
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Jan 6, 2021, 4:48:24 PM
component: f7-card-content
slots:
  default:
    - component: oh-webframe-card
      config:
        title: =props.title
        borders: false
        noBorder: false
        outline: true
        src: =props.URL.replace('{period}', vars.selectedPeriod || '6h')
        footer: =props.footer
    - component: f7-segmented
      config:
        round: false
        outline: true
        class:
          - color-theme-xxxx
          - segmented-strong
      slots:
        default:
          - component: oh-repeater
            config:
              fragment: true
              for: period
              in:
                - value: 6h
                  label: 6 Std
                - value: 12h
                  label: 12 Std
                - value: 1d
                  label: 1 Tag
                - value: 7d
                  label: 7 Tage
                - value: 30d
                  label: 30 Tage
            slots:
              default:
                - component: oh-button
                  config:
                    text: =loop.period.label
                    fill: =(vars.selectedPeriod || '6h') === loop.period.value
                    round: false
                    outline: false
                    action: variable
                    actionVariable: selectedPeriod
                    actionVariableValue: =loop.period.value

How can that be done?

1 Like

I almost got there…

It is just that the alignment of the widget in the grid is not as it should be - it gets queezed in width when Web frame and buttons reside inside the same “component: f7-card-content”.

How can this be avoided?

uid: Time series Grafana chart
tags: []
props:
  parameters:
    - description: Title of the chart
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Footer of the chart
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: URL to show in the frame
      label: Source URL
      name: URL
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Jan 7, 2021, 6:25:42 PM
component: f7-card-content
slots:
  default:
    - component: oh-webframe-card
      config:
        title: =props.title
        borders: false
        noBorder: false
        outline: true
        src: =props.URL.replace('{period}', vars.selectedPeriod || '6h')
        footer: =props.footer
        class:
          - display-block
    - component: f7-segmented
      config:
        round: false
        outline: false
        class:
          - segmented-strong
          - margin-left-half
          - margin-right-half
        style:
          --f7-segmented-strong-padding: 0px
          --f7-segmented-strong-between-buttons: 5px
          --f7-segmented-strong-button-font-weight: 100
          --f7-segmented-strong-bg-color: rgba(255, 255, 255, 0.05)
          --f7-segmented-strong-button-hover-bg-color: rgba(255, 255, 255, 0.07)
      slots:
        default:
          - component: oh-repeater
            config:
              fragment: true
              for: period
              in:
                - value: 6h
                  label: 6 Std
                - value: 12h
                  label: 12 Std
                - value: 1d
                  label: 1 Tag
                - value: 7d
                  label: 7 Tage
                - value: 30d
                  label: 30 Tage
            slots:
              default:
                - component: oh-button
                  config:
                    text: =loop.period.label
                    fill: =(vars.selectedPeriod || '6h') === loop.period.value
                    round: false
                    outline: false
                    style:
                      --f7-button-font-size: 14px
                      --f7-button-border-width: 1px
                      --f7-button-text-color: white
                      --f7-button-border-color: rgba(255,255,255,.15)
                      --f7-button-border-radius: 5px
                    action: variable
                    actionVariable: selectedPeriod
                    actionVariableValue: =loop.period.value

Maybe by redefining the first variable here: CSS Variables Reference | Framework7 Documentation

style:
  --f7-card-content-padding-horizontal: 0

This was the right hint !
I totally forgot to adjust the styles on “component: f7-card-content”.

By applying:

  style:
    --f7-card-margin-horizontal: 0px
    --f7-card-margin-vertical: 3px
    --f7-card-content-padding-horizontal: 10px
    --f7-card-content-padding-vertical: 10px

… all the elements are nicely aligned to the oh-grid. This also helped to adjust the space between the chart and the buttons.

I tried to further improve to usability/flexibility of the widget -> define time ranges via props.
In a way the same that is done in oh-list-card with command options.

command options

The props part in the widget would look like this:

    - description: Comma-separated list of options. Use value=label format to provide a label different than the option.
      label: Time range options
      name: timerange
      required: false
      type: TEXT

… but how to feed this back with “=props.timerange” to the following section?

          - component: oh-repeater
            config:
              fragment: true
              for: period
              in: 
                - value: 6h
                  label: 6 Std
                - value: 12h
                  label: 12 Std
                - value: 1d
                  label: 1 Tag
                - value: 7d
                  label: 7 Tage
                - value: 30d
                  label: 30 Tage

The below code doesn’t work.

          - component: oh-repeater
            config:
              fragment: true
              for: period
              in: =props.timerange

What have I done wrong?

@ysc

I’m struggeling to get the timeranges via props working - see my first attempt above.

I would like to feed the input list:

6h=6 Std,12h=12 Std,1d=1 Tag,7d=7 Tage,30d=30 Tage

with props definition:

    - description: Comma-separated list
      label: Time range options
      name: timerange
      required: false
      type: TEXT

to the oh-repeater “in” section (I tried now with split function):

          - component: oh-repeater
            config:
              fragment: true
              for: period
              in: =props.timerange.split(",")

Result:

Status:
The number of buttons shown below the chart is correct - for the above input list 5 buttons.
The split function seems to work.

Problems:

  • No labels are shown on the buttons
  • Nothing happens when the buttons are pressed

What did I miss to get this working?

Hey @maxmaximax

You’ve not defined an array position to output in your loop. To itterate over all available array elements, you could add another for-loop

...
          - component: oh-repeater
            config:
              sourceType: range
              for: size
              fragment: true
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: period
                    in: =[props.timerange.split(",")[loop.size].split("=")[1]]
                  slots:
                    default:
                      ...

Nice widget btw.

1 Like

@RGroll
Glad you like the widget…

Inserted your code, but unfortunately no change at all.
Maybe I misunderstood how to properly insert it?

Here the full YAML as reference:

uid: test grafana props
tags: []
props:
  parameters:
    - description: Title of the chart
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Footer of the chart
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: URL to show in the frame
      label: Source URL
      name: URL
      required: true
      type: TEXT
    - description: Comma-separated list of options. Use value=label format to provide a label different than the option.
      label: Time range options
      name: timerange
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 10, 2021, 6:32:21 PM
component: f7-card-content
config:
  style:
    --f7-card-margin-horizontal: 0px
    --f7-card-margin-vertical: 3px
    --f7-card-content-padding-horizontal: 10px
    --f7-card-content-padding-vertical: 10px
slots:
  default:
    - component: oh-webframe-card
      config:
        title: =props.title
        borders: false
        noBorder: false
        outline: true
        src: =props.URL.replace('{period}', vars.selectedPeriod || '6h')
        footer: =props.footer
        class:
          - display-block
    - component: f7-segmented
      config:
        round: false
        outline: false
        class:
          - segmented-strong
        style:
          --f7-segmented-strong-padding: 0px
          --f7-segmented-strong-between-buttons: 5px
          --f7-segmented-strong-button-font-weight: 300
          --f7-segmented-strong-bg-color: rgba(255, 255, 255, 0.05)
          --f7-segmented-strong-button-hover-bg-color: rgba(255, 255, 255, 0.07)
      slots:
        default:
          - component: oh-repeater
            config:
              sourceType: range
              for: size
              fragment: true
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: period
                    in: =[props.timerange.split(",")[loop.size].split("=")[1]]
                  slots:
                    default:
                      - component: oh-button
                        config:
                          text: =loop.period.label
                          fill: =(vars.selectedPeriod || '6h') === loop.period.value
                          round: false
                          outline: false
                          style:
                            --f7-button-font-size: 14px
                            --f7-button-border-width: 1px
                            --f7-button-text-color: white
                            --f7-button-border-color: rgba(255,255,255,.15)
                            --f7-button-border-radius: 5px
                          action: variable
                          actionVariable: selectedPeriod
                          actionVariableValue: =loop.period.value

At this stage all of my abilities are exhausted… unfortunately…

Hope you can help…

Try this - hope this it what you’d like to achieve :slight_smile:

uid: test grafana props
tags: []
props:
  parameters:
    - description: Title of the chart
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Footer of the chart
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: URL to show in the frame
      label: Source URL
      name: URL
      required: true
      type: TEXT
    - description: Comma-separated list of options. Use value=label format to provide a label different than the option.
      label: Time range options
      name: timerange
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Jan 11, 2021, 12:46:27 AM
component: f7-card-content
config:
  style:
    --f7-card-margin-horizontal: 0px
    --f7-card-margin-vertical: 3px
    --f7-card-content-padding-horizontal: 10px
    --f7-card-content-padding-vertical: 10px
slots:
  default:
    - component: oh-webframe-card
      config:
        title: =props.title
        borders: false
        noBorder: false
        outline: true
        src: =props.URL.replace('{period}', vars.selectedPeriod || [props.timerange.split(',')[0].split('=')[0]])
        footer: =props.footer
        class:
          - display-block
    - component: f7-segmented
      config:
        round: false
        outline: false
        class:
          - segmented-strong
        style:
          --f7-segmented-strong-padding: 0px
          --f7-segmented-strong-between-buttons: 5px
          --f7-segmented-strong-button-font-weight: 300
          --f7-segmented-strong-bg-color: rgba(255, 255, 255, 0.05)
          --f7-segmented-strong-button-hover-bg-color: rgba(255, 255, 255, 0.07)
      slots:
        default:
          - component: oh-repeater
            config:
              sourceType: range
              for: size
              fragment: true
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: period
                    in: =[props.timerange.split(",")[loop.size].split("=")[1]]
                  slots:
                    default:
                      - component: oh-button
                        config:
                          text: =loop.period
                          fill: "=(([props.timerange.split(',')[loop.size].split('=')[0]] == vars.selectedPeriod) || (props.timerange.split(',')[0].split('=')[1] === loop.period) && !vars.selectedPeriod) ? true : false"
                          round: false
                          outline: false
                          action: variable
                          actionVariable: selectedPeriod
                          actionVariableValue: =props.timerange.split(",")[loop.size].split("=")[0]

variable_time_select

1 Like

Yes, thanks a lot!

The selected time range used to be highlighted in deeporange as per default color theme. This got lost somehow, even though the style elements have not changed (I hope). Very strange…

Do you think that some part of the new code had changed that?

Is there a way to make the default period = the first timeframe entry?
(instead of hardcoding 6h)

Can this be done?

Updated the above post with the working code (including the highlighting) - I forgot to remove some quotes which made the comparison resulting to false in every case.

Updated this also in the code so it should work without a fixed value now

Oh and before I forget - would be very nice if you could post your widget as an example for others here when you’re ready. I think many users could be interested in a solution like this!

agreed
That looks awesome Marcus!

Just… Yannik, you are a super hero for giving us such a awesome interface and the users are really just getting started mucking around with it, excited to see all the cool stuff to come :+1: :+1: :+1:

2 Likes

When you define a repeater with e.g. for: period, in the repeated components you’ll have 3 variables defined in the loop object actually.

  • loop.period is the current element of the iteration
  • loop.period_idx is the index of the current element in the collection (for instance 0 for the first)
  • loop.period_source is the complete collection you’re iterating over - so the first element is loop.period_source[0] and the last is loop.period_source[loop.period_source.length - 1]
2 Likes

Thanks for the added explanation!

@RGroll,
Thank you so much for helping out doing the final tweaks. Really appreciated!
Sure I will make a post under the link you posted above when I have a little more time. It should be there in a few days.

As reference, here is the final YAML of the widget in which I re-included the style of oh-button. That part disappeared in your YAML above with the result that the borders of the buttons got lost.

uid: Grafana chart with timeranges
tags: []
props:
  parameters:
    - description: Title of the chart
      label: Title
      name: title
      required: false
      type: TEXT
    - description: Footer of the chart
      label: Footer
      name: footer
      required: false
      type: TEXT
    - description: URL to show in the frame
      label: Source URL
      name: URL
      required: true
      type: TEXT
    - description: Comma-separated list of options. Use value=label format (e.g. 1d=1 day) to provide a label different than the option. Minimum 1 entry required. The first entry is the default timerange.
      label: Time range options
      name: timerange
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Jan 11, 2021, 9:28:32 AM
component: f7-card-content
config:
  style:
    --f7-card-margin-horizontal: 0px
    --f7-card-margin-vertical: 3px
    --f7-card-content-padding-horizontal: 10px
    --f7-card-content-padding-vertical: 10px
slots:
  default:
    - component: oh-webframe-card
      config:
        title: =props.title
        borders: false
        noBorder: false
        outline: true
        src: =props.URL.replace('{period}', vars.selectedPeriod || [props.timerange.split(',')[0].split('=')[0]])
        footer: =props.footer
        class:
          - display-block
    - component: f7-segmented
      config:
        round: false
        outline: false
        class:
          - segmented-strong
        style:
          --f7-segmented-strong-padding: 0px
          --f7-segmented-strong-between-buttons: 5px
          --f7-segmented-strong-button-font-weight: 300
          --f7-segmented-strong-bg-color: rgba(255, 255, 255, 0.05)
          --f7-segmented-strong-button-hover-bg-color: rgba(255, 255, 255, 0.07)
      slots:
        default:
          - component: oh-repeater
            config:
              sourceType: range
              for: size
              fragment: true
            slots:
              default:
                - component: oh-repeater
                  config:
                    fragment: true
                    for: period
                    in: =[props.timerange.split(",")[loop.size].split("=")[1]]
                  slots:
                    default:
                      - component: oh-button
                        config:
                          text: =loop.period
                          fill: "=(([props.timerange.split(',')[loop.size].split('=')[0]] == vars.selectedPeriod) || (props.timerange.split(',')[0].split('=')[1] === loop.period) && !vars.selectedPeriod) ? true : false"
                          round: false
                          outline: false
                          style:
                            --f7-button-font-size: 14px
                            --f7-button-border-width: 1px
                            --f7-button-text-color: white
                            --f7-button-border-color: rgba(255,255,255,.15)
                            --f7-button-border-radius: 5px
                          action: variable
                          actionVariable: selectedPeriod
                          actionVariableValue: =props.timerange.split(",")[loop.size].split("=")[0]

Thanks again to everyone who helped on the way creating this widget !

4 Likes

Hi,
I’m using the code in the post above, but clicking on the buttons does nothing.
Running on OH 3.0 and after upgrading to OH 3.1.0

The URL for Grafana includes the period:
https://grafanaurl/grafana/d/UV-pd2VMz/lounge?orgId=1&refresh=10s&viewPanel=2&fullscreen&kiosk&from=now-{period}&to=now

Not sure what the issue is.

Thanks,
Richie