F7 swiper params, Getting updates on swiper events?

Does anyone know if it’s possible to read and trigger actions based on the currently active or visible slide of the f7-swiper component in MainUI?

I have tried this, but I am not getting any response.

uid: swiper-test-widget
tags: []
props:
  parameters: []
  parameterGroups: []
timestamp: Dec 8, 2024, 1:27:21 PM
component: f7-card
config:
  style:
    margin: 0 auto
    width: 80%
slots:
  default:
    - component: f7-block
      config:
        style:
          text-align: center
          margin-bottom: 20px
      slots:
        default:
          - component: Label
            config:
              text: "Active slide: =vars.activeIndex"
              style:
                font-size: 18px
                font-weight: bold
    - component: f7-swiper
      config:
        observer: true
        observeSlideChildren: true
        params:
          initialSlide: 3
          activeIndex: 
          onSlideChange:
            - action: variable
              variable: activeSlideIndex
              value: =vars.activeIndex
        navigation: true
      slots:
        default:
          - component: f7-swiper-slide
            config:
              style:
                background-color: lightblue
                border-radius: 10px
                display: flex
                align-items: center
                justify-content: center
            slots:
              default:
                - component: Label
                  config:
                    text: Slide 1
                    style:
                      font-size: 20px
                      color: white
          - component: f7-swiper-slide
            config:
              style:
                background-color: lightcoral
                border-radius: 10px
                display: flex
                align-items: center
                justify-content: center
            slots:
              default:
                - component: Label
                  config:
                    text: Slide 2
                    style:
                      font-size: 20px
                      color: white
          - component: f7-swiper-slide
            config:
              style:
                background-color: lightgreen
                border-radius: 10px
                display: flex
                align-items: center
                justify-content: center
            slots:
              default:
                - component: Label
                  config:
                    text: Slide 3
                    style:
                      font-size: 20px
                      color: white

Anders

  1. Access to javascript events is not supported in the ui widgets. You can read through this post for an explanation and examples of things that might be possible, especially the advanced javascript section.

  2. This:

is not how OH actions work. Actions are a specific feature of some of the oh components and can only be configured for those components. They are not a general js function.

Overall, this smells a little like an XY problem. If you explain what you are actually trying to accomplish there may be a more native way to solve the problem.

Hi Justin,

I’m just tinkering with a widget where I want to synchronize two f7-swipers. I’ve been inspired by the SemanticHome Menu and have borrowed the approach they use to fetch data from the semantic model. In this case, it’s a room overview page displaying rooms from the model, similar to the Location tab but with a different design.

The idea is to generate a swiper slide for each floor, outbuilding, or outdoor area based on how things are organized in the semantic model. The top swiper generates data points with names of floors, buildings, etc. The bottom swiper generates a swiper slide with room cards for the rooms on the selected floor, building, or outdoor area.

My goal is that when selecting, for example, the ground floor in the top swiper, the bottom swiper will show the room cards for that floor. If you instead swipe through the bottom swiper, the selection in the top swiper should follow the current floor.

I’ve created two versions: one where I use two swipers, which I haven’t been able to get working, and another where I use only one swiper at the top. In the second version, selecting a floor or area displays room cards for the chosen selection.

Two-swiper version:

uid: RoomsWidget_with_Floor_and_Room_Swipers_v4
tags:
  - MainUI
props:
  parameters:
    - description: Navn for rum i dit sprog
      label: Title
      name: title
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 9, 2024, 7:39:39 PM
component: f7-card
config:
  style:
    --f7-card-bg-color: transparent
    --f7-card-header-border-color: transparent
    box-shadow: none
    font-family: sans-serif
    font-weight: bold
    height: auto
    margin-left: =(screen.viewAreaWidth >= 800) ? ("35px") :("5px")
    margin-right: =(screen.viewAreaWidth >= 800) ? ("35px") :("5px")
    margin-top: 0px
  stylesheet: |
    .button-style {
      box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
      margin: 0;
      padding: 0; 
      background-color: =(themeOptions.dark=="light") ? ("#FFFFFF"):("#252525");
      border-radius: var(--f7-card-expandable-border-radius);
      height: 155px;
      box-sizing: border-box; 
    }
    .button-style:active {
      box-shadow: inset 0px 10px 20px 2px rgba(0, 0, 0, 0.75);
    }

    .selected_menu_item {
      border: 1px solid red;
      border-radius: var(--f7-card-expandable-border-radius);
    }

    .responsive-swiper {
      width: 70%;
      margin: 0 auto;
    }

    .responsive-font {
      font-size: 18px;
    }

    .f7-col {
      flex: 1 1 100%;
      max-width: calc(100% - 8px);
      margin: 4px;
      box-sizing: border-box; 
    }

    /* Media queries */
    @media (max-width: 1200px) {
      .responsive-swiper {
        width: 90%;
      }
      .responsive-font {
        font-size: 16px;
      }
    }

    @media (max-width: 800px) {
      .responsive-swiper {
        width: 90%;
      }
    }

    @media (max-width: 400px) {
      .responsive-swiper {
        width: calc(100% - 20px);
        margin: 0 10px;
      }
      .responsive-font {
        font-size: 14px;
      }
    }

    @media (min-width: 600px) {
      .f7-col {
        flex: 1 1 30%; /* Tre kort per række */
        max-width: calc(100% - 10px);
        box-sizing: border-box;
        margin-left: 5px; 
        margin-right: 5px;
        padding: 0px;
      }
    }
      
    .responsive-row {
      margin-left: 0px;
      margin-right: 0px;
    }

    @media (min-width: 800px) {
      .responsive-row {
        margin-left: 50px;
        margin-right: 50px;
      }
    }
slots:
  default:
    - component: f7-row
      config:
        style:
          align-items: flex-start
          display: flex
          justify-content: space-between
          margin-bottom: 25px
          margin-top: 0
          padding-top: 14px
          width: 100%
      slots:
        default:
          - component: oh-clock
            config:
              format: HH:mm
              style:
                align-items: center
                background: =(themeOptions.dark == "light") ? "white" :"black"
                border-radius: var(--f7-card-expandable-border-radius)
                display: flex
                height: 25px
                justify-content: flex-start
                margin-left: 14px
                width: 52px
          - component: Label
            config:
              style:
                color: =(themeOptions.dark == "light") ? "black" :"white"
                flex-grow: 1
                font-size: "=screen.viewAreaWidth <= 400 ? '25px' : (screen.viewAreaWidth <= 600
                  ? '30px' : (screen.viewAreaWidth <= 800 ? '35px' :
                  (screen.viewAreaWidth <= 1000 ? '40px' : '45px')))"
                margin: 0
                text-align: center
              text: "=props.title ? props.title : 'Rooms'"
          - component: f7-block
            config:
              style:
                width: 52px
    - component: f7-col
      config:
        style:
          flex-grow: 1
      slots:
        default:
          - component: f7-swiper
            config:
              class:
                - responsive-swiper
              params:
                onSlideChange:
                  - action: update
                    variable: activeFloor
                    value: =swiper.activeIndex
                breakpoints:
                  "0":
                    slidesPerView: 2.2
                    spaceBetween: 0
                  "240":
                    slidesPerView: 3.2
                    spaceBetween: 0
                  "320":
                    slidesPerView: 4.2
                    spaceBetween: 0
                  "480":
                    slidesPerView: 5.2
                    spaceBetween: 0
                  "640":
                    slidesPerView: 6.2
                    spaceBetween: 0
                  "940":
                    slidesPerView: 7.2
                    spaceBetween: 0
                  "1400":
                    slidesPerView: 8.2
                    spaceBetween: 0
                  "1800":
                    slidesPerView: 9.2
                    spaceBetween: 0
                centerInsufficientSlides: true
                grabCursor: true
                keyboard: true
                mousewheel: true
                navigation: false
                observeSlideChildren: true
                observer: true
                runCallbacksOnInit: true
                updateOnWindowResize: true
                watchOverflow: true
            slots:
              default:
                - component: f7-swiper-slide
                  config:
                    expandable: true
                    style:
                      border-radius: 5px
                  slots:
                    default:
                      - component: oh-button
                        config:
                          action: variable
                          actionVariable: objVar
                          actionVariableValue:
                            floor: ALL
                            selectSection: SECTION1
                          class:
                            - '=vars.objVar ? "" : "unselected_menu_item"'
                            - '=(vars.objVar.floor == loop.floorArraySrc.name) ?
                              "selected_menu_item" : "unselected_menu_item"'
                            - responsive-font
                          style:
                            color: =(themeOptions.dark=="light") ? "black" :"white"
                          text: ALL
                - component: f7-swiper-slide
                  config:
                    expandable: true
                    style:
                      border-radius: 5px
                  slots:
                    default:
                      - component: oh-button
                        config:
                          actionVariable: objVar
                          actionVariableValue:
                            floor: FAVORITES
                            selectSection: SECTION1
                          class:
                            - '=vars.objVar ? "" : "unselected_menu_item"'
                            - '=(vars.objVar.floor == loop.floorArraySrc.name) ?
                              "selected_menu_item" : "unselected_menu_item"'
                            - responsive-font
                          style:
                            color: =(themeOptions.dark=="light") ? "black" :"white"
                          text: FAVORITES
                - component: oh-repeater
                  config:
                    cacheSource: true
                    fetchMetadata: metadata,semantics,widgetOrder
                    filter: (loop.floorArray.metadata) &&
                      ((loop.floorArray.metadata.semantics.value).includes("Floor")
                      ||
                      (loop.floorArray.metadata.semantics.value).includes("Outdoor")
                      ||
                      (loop.floorArray.metadata.semantics.value).includes("Building"))
                    for: floorArray
                    fragment: true
                    itemTags: ","
                    sortBy: loop.floorArray.metadata.sortKey
                    sortOrder: ascending
                    sourceType: itemsWithTags
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          cacheSource: true
                          fetchMetadata: metadata
                          for: floorArraySrc
                          fragment: true
                          in: =loop.floorArray_source
                          sourceType: array
                          visible: =loop.floorArray_idx == "0"
                        slots:
                          default:
                            - component: f7-swiper-slide
                              config:
                                expandable: true
                                style:
                                  border-radius: 5px
                              slots:
                                default:
                                  - component: oh-button
                                    config:
                                      action: variable
                                      actionVariable: objVar
                                      actionVariableValue:
                                        floor: =loop.floorArraySrc.name
                                        selectSection: SECTION2
                                      class:
                                        - '=vars.objVar ? "" :
                                          "unselected_menu_item"'
                                        - '=(vars.objVar.floor ==
                                          loop.floorArraySrc.name) ?
                                          "selected_menu_item" :
                                          "unselected_menu_item"'
                                        - responsive-font
                                      style:
                                        color: =(themeOptions.dark=="light") ? "black" :"white"
                                      text: =loop.floorArraySrc.label
    - component: f7-swiper
      config:
        class:
          - custom-swiper
        params:
          onSlideChange:
            - action: update
              variable: activeFloor
              value: =swiper.activeIndex
          spaceBetween: 10
          slidesPerView: 1
          centerInsufficientSlides: true
          grabCursor: true
          keyboard: true
          mousewheel: true
          navigation: false
          observeSlideChildren: true
          observer: true
          runCallbacksOnInit: true
          updateOnWindowResize: true
          watchOverflow: true
      slots:
        default:
          - component: oh-repeater
            config:
              cacheSource: true
              fetchMetadata: metadata, semantics, widgetOrder
              filter: (loop.floorArray.metadata) &&
                ((loop.floorArray.metadata.semantics.value).includes("Floor") ||
                (loop.floorArray.metadata.semantics.value).includes("Outdoor")
                ||
                (loop.floorArray.metadata.semantics.value).includes("Building"))
              for: floorArray
              fragment: true
              itemTags: ","
              sortBy: loop.floorArray.metadata.sortKey
              sortOrder: ascending
              sourceType: itemsWithTags
            slots:
              default:
                - component: f7-swiper-slide
                  config:
                    expandable: true
                    style:
                      border-radius: 5px
                  slots:
                    default:
                      - component: f7-row
                        config:
                          class:
                            - responsive-row
                          style:
                            display: flex
                            flex-wrap: wrap
                            gap: 3px
                            margin-top: 30px
                        slots:
                          default:
                            - component: oh-repeater
                              config:
                                cacheSource: true
                                fetchMetadata: metadata, semantics, widgetOrder
                                filter: (loop.roomArray.metadata) &&
                                  ((loop.roomArray.metadata.semantics.value).includes("Room")
                                  ||
                                  (loop.roomArray.metadata.semantics.value).includes("Corridor"))
                                  &&
                                  ((loop.roomArray.metadata.semantics.config.isPartOf).includes(loop.floorArray.name))
                                for: roomArray
                                fragment: true
                                itemTags: ","
                                sourceType: itemsWithTags
                              slots:
                                default:
                                  - component: f7-col
                                    config:
                                      class:
                                        - f7-col
                                    slots:
                                      default:
                                        - component: oh-button
                                          config:
                                            action: navigate
                                            actionPage: ='page:' + props.link_1
                                            actionPageTransition: f7-dive
                                            class:
                                              - button-style
                                          slots:
                                            default:
                                              - component: oh-icon
                                                config:
                                                  icon: iconify:system-uicons:lightbulb-on
                                                  style:
                                                    color: yellow
                                                    font-size: "=screen.viewAreaWidth <= 400 ? '26px' : (screen.viewAreaWidth <= 600
                                                      ? '28px' :
                                                      (screen.viewAreaWidth <=
                                                      800 ? '30px'
                                                      :(screen.viewAreaWidth <=
                                                      1100 ? '32px' : '34px')))"
                                                    margin-bottom: 5px
                                              - component: Label
                                                config:
                                                  style:
                                                    color: =(themeOptions.dark=="light") ? "black" :"white"
                                                    font-size: "=screen.viewAreaWidth <= 400 ? '12px' : (screen.viewAreaWidth <= 600
                                                      ? '16px' :
                                                      (screen.viewAreaWidth <=
                                                      800 ? '14px'
                                                      :(screen.viewAreaWidth <=
                                                      1100 ? '16px' : '18px')))"
                                                    font-weight: bold
                                                    margin-top: auto
                                                  text: =loop.roomArray.label

One-swiper version:

uid: RoomsWidget_with_Floor_and_Room_Swipers_v3
tags:
  - MainUI
props:
  parameters:
    - description: Navn for rum i dit sprog
      label: Title
      name: title
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 1, 2024, 7:34:59 PM
component: f7-card
config:
  style:
    --f7-card-bg-color: transparent
    --f7-card-header-border-color: transparent
    box-shadow: none
    font-family: sans-serif
    font-weight: bold
    height: auto
    margin-left: =(screen.viewAreaWidth >= 800) ? ("35px") :("5px")
    margin-right: =(screen.viewAreaWidth >= 800) ? ("35px") :("5px")
    margin-top: 0px
  stylesheet: |
    .button-style {
      box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
      margin: 0;
      padding: 0; 
      background-color: =(themeOptions.dark=="light") ? ("#FFFFFF"):("#252525");
      border-radius: var(--f7-card-expandable-border-radius);
      height: 155px;
      box-sizing: border-box; 
    }
    .button-style:active {
      box-shadow: inset 0px 10px 20px 2px rgba(0, 0, 0, 0.75);
    }

    .selected_menu_item {
      border: 1px solid red;
      border-radius: var(--f7-card-expandable-border-radius);
    }

    .responsive-swiper {
      width: 70%;
      margin: 0 auto;
    }

    .responsive-font {
      font-size: 18px;
    }

    .f7-col {
      flex: 1 1 100%;
      max-width: calc(100% - 8px);
      margin: 4px;
      box-sizing: border-box; 
    }

    /* Media queries */
    @media (max-width: 1200px) {
      .responsive-swiper {
        width: 90%;
      }
      .responsive-font {
        font-size: 16px;
      }
    }

    @media (max-width: 800px) {
      .responsive-swiper {
        width: 90%;
      }
    }

    @media (max-width: 400px) {
      .responsive-swiper {
        width: calc(100% - 20px);
        margin: 0 10px;
      }
      .responsive-font {
        font-size: 14px;
      }
    }

    @media (min-width: 600px) {
      .f7-col {
        flex: 1 1 30%; /* Tre kort per række */
        max-width: calc(100% - 10px);
        box-sizing: border-box;
        margin-left: 5px; 
        margin-right: 5px;
        padding: 0px;
      }
    }
      
    .responsive-row {
      margin-left: 0px;
      margin-right: 0px;
    }

    @media (min-width: 800px) {
      .responsive-row {
        margin-left: 50px;
        margin-right: 50px;
      }
    }
slots:
  default:
    - component: f7-row
      config:
        style:
          align-items: flex-start
          display: flex
          justify-content: space-between
          margin-bottom: 25px
          margin-top: 0
          padding-top: 14px
          width: 100%
      slots:
        default:
          - component: oh-clock
            config:
              format: HH:mm
              style:
                align-items: center
                background: =(themeOptions.dark == "light") ? "white" :"black"
                border-radius: var(--f7-card-expandable-border-radius)
                display: flex
                height: 25px
                justify-content: flex-start
                margin-left: 14px
                width: 52px
          - component: Label
            config:
              style:
                color: =(themeOptions.dark == "light") ? "black" :"white"
                flex-grow: 1
                font-size: "=screen.viewAreaWidth <= 400 ? '25px' : (screen.viewAreaWidth <= 600
                  ? '30px' : (screen.viewAreaWidth <= 800 ? '35px' :
                  (screen.viewAreaWidth <= 1000 ? '40px' : '45px')))"
                margin: 0
                text-align: center
              text: "=props.title ? props.title : 'Rooms'"
          - component: f7-block
            config:
              style:
                width: 52px
    - component: f7-col
      config:
        style:
          flex-grow: 1
      slots:
        default:
          - component: f7-swiper
            config:
              class:
                - responsive-swiper
              params:
                breakpoints:
                  "0":
                    slidesPerView: 2.2
                    spaceBetween: 0
                  "240":
                    slidesPerView: 3.2
                    spaceBetween: 0
                  "320":
                    slidesPerView: 4.2
                    spaceBetween: 0
                  "480":
                    slidesPerView: 5.2
                    spaceBetween: 0
                  "640":
                    slidesPerView: 6.2
                    spaceBetween: 0
                  "940":
                    slidesPerView: 7.2
                    spaceBetween: 0
                  "1400":
                    slidesPerView: 8.2
                    spaceBetween: 0
                  "1800":
                    slidesPerView: 9.2
                    spaceBetween: 0
                centerInsufficientSlides: true
                grabCursor: true
                keyboard: true
                mousewheel: true
                navigation: false
                observeSlideChildren: true
                observer: true
                runCallbacksOnInit: true
                updateOnWindowResize: true
                watchOverflow: true
            slots:
              default:
                - component: f7-swiper-slide
                  config:
                    expandable: true
                    style:
                      border-radius: 5px
                  slots:
                    default:
                      - component: oh-button
                        config:
                          action: variable
                          actionVariable: objVar
                          actionVariableValue:
                            floor: ALL
                            selectSection: SECTION1
                          class:
                            - '=vars.objVar ? "" : "unselected_menu_item"'
                            - '=(vars.objVar.floor == loop.floorArraySrc.name) ?
                              "selected_menu_item" : "unselected_menu_item"'
                            - responsive-font
                          style:
                            color: =(themeOptions.dark=="light") ? "black" :"white"
                          text: ALL
                - component: f7-swiper-slide
                  config:
                    expandable: true
                    style:
                      border-radius: 5px
                  slots:
                    default:
                      - component: oh-button
                        config:
                          actionVariable: objVar
                          actionVariableValue:
                            floor: FAVORITES
                            selectSection: SECTION1
                          class:
                            - '=vars.objVar ? "" : "unselected_menu_item"'
                            - '=(vars.objVar.floor == loop.floorArraySrc.name) ?
                              "selected_menu_item" : "unselected_menu_item"'
                            - responsive-font
                          style:
                            color: =(themeOptions.dark=="light") ? "black" :"white"
                          text: FAVORITES
                - component: oh-repeater
                  config:
                    cacheSource: true
                    fetchMetadata: metadata,semantics,widgetOrder
                    filter: (loop.floorArray.metadata) &&
                      ((loop.floorArray.metadata.semantics.value).includes("Floor")
                      ||
                      (loop.floorArray.metadata.semantics.value).includes("Outdoor")
                      ||
                      (loop.floorArray.metadata.semantics.value).includes("Building"))
                    for: floorArray
                    fragment: true
                    itemTags: ","
                    sortBy: loop.floorArray.metadata.sortKey
                    sortOrder: ascending
                    sourceType: itemsWithTags
                  slots:
                    default:
                      - component: oh-repeater
                        config:
                          cacheSource: true
                          fetchMetadata: metadata
                          for: floorArraySrc
                          fragment: true
                          in: =loop.floorArray_source
                          sourceType: array
                          visible: =loop.floorArray_idx == "0"
                        slots:
                          default:
                            - component: f7-swiper-slide
                              config:
                                expandable: true
                                style:
                                  border-radius: 5px
                              slots:
                                default:
                                  - component: oh-button
                                    config:
                                      action: variable
                                      actionVariable: objVar
                                      actionVariableValue:
                                        floor: =loop.floorArraySrc.name
                                        selectSection: SECTION2
                                      class:
                                        - '=vars.objVar ? "" :
                                          "unselected_menu_item"'
                                        - '=(vars.objVar.floor ==
                                          loop.floorArraySrc.name) ?
                                          "selected_menu_item" :
                                          "unselected_menu_item"'
                                        - responsive-font
                                      style:
                                        color: =(themeOptions.dark=="light") ? "black" :"white"
                                      text: =loop.floorArraySrc.label
    - component: f7-row
      config:
        class:
          - responsive-row
        style:
          display: flex
          flex-wrap: wrap
          gap: 3px
          margin-top: 30px
      slots:
        default:
          - component: oh-repeater
            config:
              cacheSource: true
              fetchMetadata: metadata, semantics, widgetOrder
              filter: (loop.roomArray.metadata) &&
                ((loop.roomArray.metadata.semantics.value).includes("Room") ||
                (loop.roomArray.metadata.semantics.value).includes("Corridor"))
                &&
                ((loop.roomArray.metadata.semantics.config.isPartOf).includes(vars.objVar.floor))
              for: roomArray
              fragment: true
              itemTags: ","
              sourceType: itemsWithTags
            slots:
              default:
                - component: f7-col
                  config:
                    class:
                      - f7-col
                  slots:
                    default:
                      - component: oh-button
                        config:
                          action: navigate
                          actionPage: ='page:' + props.link_1
                          actionPageTransition: f7-dive
                          class:
                            - button-style
                        slots:
                          default:
                            - component: oh-icon
                              config:
                                icon: iconify:system-uicons:lightbulb-on
                                style:
                                  color: yellow
                                  font-size: "=screen.viewAreaWidth <= 400 ? '26px' : (screen.viewAreaWidth <= 600
                                    ? '28px' : (screen.viewAreaWidth <= 800 ?
                                    '30px' :(screen.viewAreaWidth <= 1100 ?
                                    '32px' : '34px')))"
                                  margin-bottom: 5px
                            - component: Label
                              config:
                                style:
                                  color: =(themeOptions.dark=="light") ? "black" :"white"
                                  font-size: "=screen.viewAreaWidth <= 400 ? '12px' : (screen.viewAreaWidth <= 600
                                    ? '16px' : (screen.viewAreaWidth <= 800 ?
                                    '14px' :(screen.viewAreaWidth <= 1100 ?
                                    '16px' : '18px')))"
                                  font-weight: bold
                                  margin-top: auto
                                text: =loop.roomArray.label

I’m working on some UI design ideas, aiming to create something easy to navigate and visually clear.
Of course, based on my preferences, I think the approach they’ve taken with the SemanticHomeMenu is the way forward and a really good initiative.

screenshot:

Best regards,
Anders

I can’t think of any way to achieve this legitimately at the moment. Swiper-js does have a built-in library for this, but it is not loaded in the MainUI in order to keep overhead low. Some of the javascript integration methods in the link above may, in fact, be your best choice if you insist on using swipers (and even then I can’t say with 100% certainty that you’ll be successful).

It almost certainly possible (again, similar to what SemanticHome Menu does) to build your own swiper-like setup from other components. Off the top of my head, I’d say you could get very close with a set of f7-tabs with the swipeable property enabled. That already covers most of what you want in the secondary “swipers”, the trick would just be making the tabbar behave closer to the a swiper itself, which can probably be done with some clever css.

Hi Justin

I’m trying to create the widget based on f7 tabs, but I can’t get it to work when generating the content automatically. I can’t create a separate tab for each floor; instead, it creates one tab with all the content in it. I’m stuck. Am I on the right track, or should I take a completely different approach?

uid: RoomsWidget_with_ResponsiveTabbar_and_DynamicLinks
tags:
  - MainUI
props:
  parameters:
    - description: Navn for rum i dit sprog
      label: Title
      name: title
      required: false
      type: TEXT
  parameterGroups: []
timestamp: Dec 15, 2024, 5:47:13 PM
component: f7-card
config:
  style:
    --f7-card-bg-color: transparent
    --f7-card-header-border-color: transparent
    box-shadow: none
    font-family: sans-serif
    font-weight: bold
    height: auto
    margin-left: =(screen.viewAreaWidth >= 800) ? ("35px") :("5px")
    margin-right: =(screen.viewAreaWidth >= 800) ? ("35px") :("5px")
    margin-top: 0px
  stylesheet: |
    .button-style {
      box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.5);
      margin: 0;
      padding: 0; 
      background-color: =(themeOptions.dark=="light") ? ("#FFFFFF"):("#252525");
      border-radius: var(--f7-card-expandable-border-radius);
      height: 155px;
      box-sizing: border-box; 
    }
    .button-style:active {
      box-shadow: inset 0px 10px 20px 2px rgba(0, 0, 0, 0.75);
    }

    .selected_menu_item {
      border: 1px solid red;
      border-radius: var(--f7-card-expandable-border-radius);
    }

    .responsive-swiper {
      width: 70%;
      margin: 0 auto;
    }

    .responsive-font {
      font-size: 18px;
    }

    .f7-col {
      flex: 1 1 100%;
      max-width: calc(100% - 8px);
      margin: 4px;
      box-sizing: border-box; 
    }

    /* Media queries */
    @media (max-width: 1200px) {
      .responsive-swiper {
        width: 90%;
      }
      .responsive-font {
        font-size: 16px;
      }
    }

    @media (max-width: 800px) {
      .responsive-swiper {
        width: 90%;
      }
    }

    @media (max-width: 400px) {
      .responsive-swiper {
        width: calc(100% - 20px);
        margin: 0 10px;
      }
      .responsive-font {
        font-size: 14px;
      }
    }

    @media (min-width: 600px) {
      .f7-col {
        flex: 1 1 30%; /* Tre kort per række */
        max-width: calc(100% - 10px);
        box-sizing: border-box;
        margin-left: 5px; 
        margin-right: 5px;
        padding: 0px;
      }
    }
      
    .responsive-row {
      margin-left: 0px;
      margin-right: 0px;
    }

    @media (min-width: 800px) {
      .responsive-row {
        margin-left: 50px;
        margin-right: 50px;
      }
    }
    .tabbar-scrollable {
      display: flex;
      overflow-x: auto;
      white-space: nowrap;
      -webkit-overflow-scrolling: touch;
      margin: 0 auto; /* Centrer tabbaren */
      width: 70%; /* Standardbredde for større skærme */
    }
    .tabbar-scrollable::-webkit-scrollbar {
      display: none; /* Skjul scrollbar */
    }
    .tabbar-scrollable .f7-link {
      flex-shrink: 0; /* Undgå at tabs krymper */
      margin-right: 10px;
    }

    /* Responsiv styling */
    @media (max-width: 800px) {
      .tabbar-scrollable {
        width: 90%; /* Fylder 90% af bredden på mellemstore skærme */
      }
    }

    @media (max-width: 400px) {
      .tabbar-scrollable {
        width: calc(100% - 20px); /* Næsten hele bredden på små skærme */
      }
    }
slots:
  default:
    - component: f7-row
      config:
        style:
          align-items: flex-start
          display: flex
          justify-content: space-between
          margin-bottom: 25px
          margin-top: 0
          padding-top: 14px
          width: 100%
      slots:
        default:
          - component: oh-clock
            config:
              format: HH:mm
              style:
                align-items: center
                background: =(themeOptions.dark == "light") ? "white" :"black"
                border-radius: var(--f7-card-expandable-border-radius)
                display: flex
                height: 25px
                justify-content: flex-start
                margin-left: 14px
                width: 52px
          - component: Label
            config:
              style:
                color: =(themeOptions.dark == "light") ? "black" :"white"
                flex-grow: 1
                font-size: "=screen.viewAreaWidth <= 400 ? '25px' : (screen.viewAreaWidth <= 600
                  ? '30px' : (screen.viewAreaWidth <= 800 ? '35px' :
                  (screen.viewAreaWidth <= 1000 ? '40px' : '45px'))) "
                margin: 0
                text-align: center
              text: "=props.title ? props.title : 'Rooms'"
          - component: f7-block
            config:
              style:
                width: 52px
    - component: f7-toolbar
      config:
        class: tabbar-scrollable
        tabbar: true
        bottom: true
      slots:
        default:
          - component: oh-repeater
            config:
              cacheSource: true
              fetchMetadata: metadata, semantics, widgetOrder
              filter: (loop.floorArray.metadata) &&
                ((loop.floorArray.metadata.semantics.value).includes("Floor") ||
                (loop.floorArray.metadata.semantics.value).includes("Outdoor")||
                (loop.floorArray.metadata.semantics.value).includes("Building"))
              for: floorArray
              fragment: true
              itemTags: ","
              sortBy: loop.floorArray.metadata.sortKey
              sortOrder: ascending
              sourceType: itemsWithTags
            slots:
              default:
                - component: f7-link
                  config:
                    tabLink: = 'tab_' + loop.floorArray_idx
                    text: "= 'Etage: ' + loop.floorArray.label + ' (ID: tab_' + loop.floorArray_idx+ ')'"
                    tabLinkActive: =loop.floorArray_idx == 0
    - component: f7-tabs
      config:
        swipeable: true
      slots:
        default:
          - component: oh-repeater
            config:
              cacheSource: true
              fetchMetadata: metadata, semantics, widgetOrder
              filter: (loop.floorArray.metadata) &&
                ((loop.floorArray.metadata.semantics.value).includes("Floor") ||
                (loop.floorArray.metadata.semantics.value).includes("Outdoor") ||
                (loop.floorArray.metadata.semantics.value).includes("Building"))
              for: floorArray
              fragment: true
              itemTags: ","
              sortBy: loop.floorArray.metadata.sortKey
              sortOrder: ascending
              sourceType: itemsWithTags
            slots:
              default:
                - component: f7-tab
                  config:
                    id: = 'tab_' + loop.floorArray_idx
                    tabActive: =loop.floorArray_idx == 0
                  slots:
                    default:
                      - component: Label
                        config:
                          text: "= 'This is tab ID: tab_' + loop.floorArray_idx + ', floor: ' +
                            loop.floorArray.label"
                      

Anders

Interesting. It seems that the oh-repeater is interfering with the f7 library’s method of registering the tabs during page creation. If you look at the created html, all the tabs are, in fact, created properly, it’s just that for some reason they are not responding to the clicks on the tab links. I’m not sure why the repeater should cause this problem, but it’s possible that even using fragment: true, the tabs are not found as children by the f7-tabs element.

Off the top of my head, I can’t think of any way around this issue, but I’ll give it a little thought and let you know if I come up with anything.

I will check as well. If I remember right, we tried this in our projekt, but not sure if it was for rooms/floors or the bottom menu…

Edit: It was for the buttom menu :unamused: