1
votes

I have a long list with contacts. If you scroll the list the toolbar title should show the name of the person that is below the toolbar title.

CodePen example: https://codepen.io/tomtomsx/pen/pozKaBv?editors=1010

I'm using VueJS with the Vuetify Framework. Vuetify offers the components below but it's based on the position - how to get the name of the list entry?

  1. Scroll: https://vuetifyjs.com/en/styles/scroll
  2. Scrolling: https://vuetifyjs.com/en/directives/scrolling

HTML:

<div id="app">
  <v-app id="inspire">
    <v-card
      max-width="500"
      class="mx-auto"
    >
      <v-toolbar
        color="pink"
        dark
        fixed
      >
        <v-app-bar-nav-icon></v-app-bar-nav-icon>

        <v-toolbar-title>NAME: </v-toolbar-title>

        <div class="flex-grow-1"></div>

        <v-btn icon>
          <v-icon>mdi-magnify</v-icon>
        </v-btn>

        <v-btn icon>
          <v-icon>mdi-checkbox-marked-circle</v-icon>
        </v-btn>
      </v-toolbar>

      <v-list two-line>
        <v-list-item-group
          v-model="selected"
          multiple
          active-class="pink--text"
        >
          <template v-for="(item, index) in items">
            <v-list-item :key="item.title">
              <template v-slot:default="{ active, toggle }">
                <v-list-item-content>
                  <v-list-item-title v-text="item.title"></v-list-item-title>
                  <v-list-item-subtitle class="text--primary" v-text="item.headline"></v-list-item-subtitle>
                  <v-list-item-subtitle v-text="item.subtitle"></v-list-item-subtitle>
                </v-list-item-content>

                <v-list-item-action>
                  <v-list-item-action-text v-text="item.action"></v-list-item-action-text>
                  <v-icon
                    v-if="!active"
                    color="grey lighten-1"
                  >
                    star_border
                  </v-icon>

                  <v-icon
                    v-else
                    color="yellow"
                  >
                    star
                  </v-icon>
                </v-list-item-action>
              </template>
            </v-list-item>

            <v-divider
              v-if="index + 1 < items.length"
              :key="index"
            ></v-divider>
          </template>
        </v-list-item-group>
      </v-list>
    </v-card>
  </v-app>
</div>

JS:

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    selected: [2],
    items: [
      {
        action: '15 min',
        headline: 'Brunch this weekend?',
        title: 'Ali Connors',
        subtitle: "I'll be in your neighborhood doing errands this weekend. Do you want to hang out?",
      },
      {
        action: '2 hr',
        headline: 'Summer BBQ',
        title: 'me, Scrott, Jennifer',
        subtitle: "Wish I could come, but I'm out of town this weekend.",
      },
      [...]
2
Your toolbar is not sticky, do you have a working example where this is correct? - Sven Hakvoort
It's fixed - the toolbar is not the issue - it's getting the current name of the list item that is below the titlebar while scrolling. - Tom
I know that is the issue but for that in order to work your codepen example must have a fixed header. Currently it scrolls out of view, making it impossible to verify if it works correctly ;) - Sven Hakvoort
@SvenHakvoort is right. The problem is, that the whole page is scrolling. To start working on your problem, the navbar needs to be sticky, so only the list of users scrolls down. - Tom Marienfeld

2 Answers

2
votes

I believe you're looking for the IntersectionObserver API. You need to bind a bit of data to the toolbar title, and then update that data with the text that has breached the observer.

Here is a new pen that should do what you want: https://codepen.io/hallucinarium/pen/661bff04a6b9e696d8cde480e08172e5

This binds the data:

<v-toolbar-title>NAME: {{ activeTitle }} </v-toolbar-title>

This marks the element that will be set as the IntersectionObserver root:

<v-list id="observer" two-line>

This CSS gets your title to remain fixed above the entries, and restricts the list to the height of the viewport (important for the IntersectionObserver):

#observer {
  height: 100vh;
  overflow-y: scroll;
}

And this summarises the changes I've made to your JavaScript (this is very bad JavaScript I'm afraid, but it suffices for this example):

new Vue({
  el: '#app',
  vuetify: new Vuetify(),
  data: () => ({
    selected: [2],
    items: […],
    intersectionObserver: Object.create(null),
    activeTitle: "",
  }),
  methods: {
    onIntersection: function (entries, observer) { // Create a callback.
      entries.forEach(entry => { // Cycles through every intersected item.
        if(entry.isIntersecting) { // Ensure the item is in fact intersecting.
          this.activeTitle = entry.target.textContent; // Update the bound data.
        }
      });
    },
  },
  mounted: () => {    
    const observerEl = document.getElementById("observer"); // Find the element to use as an observer.

    const options = { // Set observer options.
      root: observerEl,
      rootMargin: "0px 0px -90% 0px", // Shrink observer so it watches only the top.
      // threshold: 0, // Changes the amount of intersection required to fire.
    };


    this.intersectionObserver = new window.IntersectionObserver(this.onIntersection, options); // Create the observer.

    const titles = document.getElementsByClassName("v-list-item__title");
    for(let i = 0; i < titles.length; i++) { // Observe the list-item titles.
      this.intersectionObserver.observe(titles[i]);
    }
  },
});

I hope that helps.

0
votes

Well, your problem is: you're assigning the same value for various items, not just one. Just use the index value, then use it to work with Vuetify. To solve You can bind the value of your input to a variable using v-bind:value="variablename" (or simply :value="variablename"), then do a search query for the positions which contains these titles, create a new array to store this new list and render it like you did. You can do this inside a function:

let newArray = [];
    items.map((item, index) => {
if(variablename === item.title){
newArray = [...item]
}
}
});

Also you can store the position of your items using the same procedure as above, just adding the 'index' to an array.