如何在Vuetify自动完成组件中创建无限滚动?

时间:2019-06-07 10:47:11

标签: javascript vue.js vuetify.js

我有一个带有Vuetify自动完成组件的页面,以及带有'/ vendors'方法的REST API后端。此方法采用 limit page name 参数,并返回具有 id name 的JSON。 >字段。

我在用户输入事件上编写了一些带有延迟列表加载的代码。但是,现在我想添加一项功能,可以在用户 scroll事件上加载此列表。

例如,默认情况下有100个供应商的列表。用户滚动此列表直至结束,然后调用“某个事件”并加载下100个供应商。然后用户继续滚动并重复操作。

是否可以通过Vuetify自动完成组件来实现此功能,还是应该使用其他库?

当前组件的示例代码如下所示:

<template>
  <v-autocomplete
          :items="vendors"
          v-model="selectedVendorId"
          item-text="name"
          item-value="id"
          label="Select a vendor"
          @input.native="getVendorsFromApi"
  ></v-autocomplete>
</template>

<script>
  export default {
    data () {
      return {
        page: 0,
        limit: 100,
        selectedVendorId: null,
        vendors: [],
        loading: true
      }
    },
    created: function (){
      this.getVendorsFromApi();
    },
    methods: {
      getVendorsFromApi (event) {
        return new Promise(() => {
          this.$axios.get(this.$backendLink 
                  + '/vendors?limit=' + this.limit 
                  + '&page=' + this.page 
                  + '&name=' + (event ? event.target.value : ''))
            .then(response => {
              this.vendors = response.data;
            })
        })
      }
    }
  }
</script>

2 个答案:

答案 0 :(得分:2)

我能够使用 Vuetify AutoComplete 组件进行自动加载。这有点黑客,但基本上解决方案是使用 v-slot 附加项目,v-intersect 指令来检测该附加项目是否可见,如果是,则调用您的 API 以获取更多项并将其附加到您当前的列表中。

  <v-autocomplete
          :items="vendors"
          v-model="selectedVendorId"
          item-text="name"
          item-value="id"
          label="Select a vendor"
          @input.native="getVendorsFromApi"
  >
  <template v-slot:append-item>
    <div v-intersect="endIntersect" />
  </template>
</v-autocomplete>


...

export default {
  methods: {
    endIntersect(entries, observer, isIntersecting) {
      if (isIntersecting) {
        let moreVendors = loadMoreFromApi()
        this.vendors = [ ...this.vendors, ...moreVendors]
      }
    }
  }
}

在我的用例中,我使用 API 平台作为后端,使用 GraphQL 分页和游标。

   <component
      v-bind:is="canAdd ? 'v-combobox' : 'v-autocomplete'"
      v-model="user"
      :items="computedUsers"
      :search-input.sync="search"
      item-text="item.node.userProfile.username"
      hide-details
      rounded
      solo
      :filter="
      (item, queryText, itemText) => { 
        return item.node.userProfile.username.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
        } "
      :loading="loading"
      item-value="username"
      class="text-left pl-1"
      color="blue-grey lighten-2"
      :label="label"
    >
      <template v-slot:selection="{ item }">
        <v-chip v-if="typeof item == 'object'">
          <v-avatar left>
            <v-img v-if="item.node.userProfile.image" :src="item.node.userProfile.image" />
            <v-icon v-else>mdi-account-circle</v-icon>
          </v-avatar>
          {{ item.node.userProfile.firstName }} {{ item.node.userProfile.lastName }}
        </v-chip>
        <v-chip v-else-if="typeof item == 'string'">
          {{ item }}
        </v-chip>
      </template>
      <template v-slot:item="{ item: { node } }">
        <v-list-item-avatar >
          <img v-if="node.userProfile.avatar" :src="node.userProfile.avatar" />
          <v-icon v-else>mdi-account-circle</v-icon>
        </v-list-item-avatar>
        <v-list-item-content class="text-left">
          <v-list-item-title>
            {{ $t('fullName', { firstName: node.userProfile.firstName, lastName: node.userProfile.lastName } )}}
          </v-list-item-title>
          <v-list-item-subtitle v-html="node.userProfile.username"></v-list-item-subtitle>
        </v-list-item-content>
      </template>
      <template v-slot:append-item="">
        <div v-intersect="endIntersect" >
        </div>
      </template>
    </component>
import { VCombobox, VAutocomplete } from "vuetify/lib";
import debounce from "@/helpers/debounce"
import { SEARCH_USER_BY_USERNAME } from "@/graphql/UserQueries";
const RESULTS_TO_SHOW = 5
export default {
  props: {
    canAdd: {
      type: Boolean,
      default: false,
    },
    value: [Object, String],
    label: String,
  },
  components: { VCombobox, VAutocomplete },
  apollo: {
    users: {
      query: SEARCH_USER_BY_USERNAME,
      variables() { 
        return  {
          username: this.search,
          numberToShow: RESULTS_TO_SHOW,
          cursor: null,
        }
      },
      watchLoading(isLoading) {
        this.loading = isLoading
      },
      skip() {
        if (this.search) {
          return !(this.search.length > 1)
        }
        return true
      },
    },
  },
  data() {
    return {
      user: this.value,
      search: "",
      cursor: null,
      loading: false,
    };
  },
  watch: {
    user(newValue) {
      let emit = newValue
      if (newValue) {
        emit = newValue.node
      }
      this.$emit("input", emit);
    },
    value(newValue) {
      if (this.user && this.user.node != newValue) {
        if (newValue == null) {
          this.user = null
        }
        else {
          this.user =  { node: newValue };
        }
      }
    },
    search(newValue) {
      this.debouncedSearch(newValue)
    },
  },
  methods: {
    endIntersect(entries, observer, isIntersecting) {
      if (isIntersecting && this.users && this.users.pageInfo.hasNextPage) {
        let cursor = this.users.pageInfo.endCursor
        
        this.$apollo.queries.users.fetchMore({
          variables: { cursor: cursor},
          updateQuery: (previousResult, { fetchMoreResult }) => {
            let edges = [
              ...previousResult.users.edges,
              ...fetchMoreResult.users.edges,
            ]

            let pageInfo = fetchMoreResult.users.pageInfo;
            return { 
              users: {
                edges: edges,
                pageInfo: pageInfo,
                __typename: previousResult.users.__typename,
              }
            }
          }
        })
      }
    },
    debouncedSearch: debounce(function (search) {
      if (this.users) {
        this.$apollo.queries.users.refetch({
          username: search,
          numberToShow: RESULTS_TO_SHOW, 
          cursor: null,
        });
      }
    }, 500),
    filter(item, queryText) {
      return item.node.userProfile.username.toLocaleLowerCase().indexOf(queryText.toLocaleLowerCase()) > -1
    }
  },
  computed: {
    computedUsers() {
      if (this.users){
        return this.users.edges
      }
      return []
    },
    skip() {
      if (this.search) {
        return this.search.length > 1
      }
      return false
    }
  }
};
</script>

Before scrolling down

After scrolling down

答案 1 :(得分:0)

似乎无法使用默认的v-autocomplete组件(至少在vuetify 1.5.16或更低版本中)。 提供最相似功能的组件是VueInfiniteAutocomplete

但是请记住,在这种情况下,样式,验证等可能会出现问题。

此库有一个示例。

<template>
    <div>
    <vue-infinite-autocomplete
      :data-source="getAsyncOptions"
      :fetch-size="limit"
      v-on:select="handleOnSelect"
      :value="autocompleteViewValue"
    >
    </vue-infinite-autocomplete>
  </div>
</template>
<script>
  export default {
    data () {
      return {
          selectedVendorId : null,
          limit: 100,
          autocompleteViewValue: null
      }
    },
    methods: {
        getAsyncOptions(text, page, fetchSize) {
            return new Promise((resolve, reject) => {
                resolve(
                    this.$axios.get(this.$backendLink
                        + '/vendors?limit=' + fetchSize
                        + '&page=' + page
                        + '&name=' + text)
                        .then(response => {
                            //Response MUST contain 'id' and 'text' fields, and nothing else.
                            //If there are other fields, you should remove it here
                            //and create 'id' and 'text' fields in response JSON by yourself
                            return response.data;
                        })
                )
            });
        },

        handleOnSelect(selectedItem) {
            this.autocompleteViewValue = selectedItem.text;
            this.selectedVendorId = selectedItem.id;
        }
    }
  }
</script>

PS:如果您只想在服务器端分页中使用v-autocomplete组件,则可以使用 append-item 插槽创建“加载更多...”按钮,如{ {3}}。