Virtual List + Ajax + search


#1

I cannot for the life of me seem to get the search to work with a Virtual List that is populated via AJAX - I can only ever get partial functionality - either the search works but the list cannot be repopulated, or the list can be repopulated but the search doesn’t work. Here is the version where the search does not work, but everything else does. Does anybody have any ideas?

	<template id="page-vehicle-list">
	<f7-page>
		<f7-navbar>
			<f7-nav-left>
				<f7-link icon-if-ios="f7:menu" icon-if-md="material:menu" panel-open="left"></f7-link>
			</f7-nav-left>
			<f7-nav-title>Inspections</f7-nav-title>
			<f7-subnavbar :inner="false">
			<f7-searchbar
				search-container=".virtual-list"
				search-item="li"
				search-in=".item-title"
			></f7-searchbar>
			</f7-subnavbar>
		</f7-navbar>
		<f7-list class="searchbar-not-found">
			<f7-list-item title="Nothing found"></f7-list-item>
		</f7-list>

		<div class="error" v-if="error">
  			Error: {{ error }}
		</div>
		
		<f7-list
			class="searchbar-found"
			media-list
			virtual-list
			v-if="items.length" 
			:virtual-list-params="{ items, searchAll, renderExternal, height: $theme.ios ? 63 : 73}" 
		>
			<ul>
				<f7-list-item 
					v-for="(item, index) in vlData.items" 
					:key="index" 
					media-item 
					:link="`/vehicle/` + item.id" 
					:title="item.registration" 
					:subtitle="[item.manufacturer, item.model, item.variant].join(' ')" 
					:style="`top: ${vlData.topPosition}px`">
				</f7-list-item>
			</ul>
		</f7-list>
	</f7-page>
</template>

Vue.component('page-vehicle-list', {
template: '#page-vehicle-list',
data() {
	return {
		items : [],
		vlData: {
			items: [],
		},
		error: null
	};
},
created: function() {
	// fetch the data when the view is created and the data is
	// already being observed		
	this.fetchData();	
},
watch: {
	// call again the method if the route changes
	'$route': 'fetchData'	
},
methods: {
	searchAll(query, items) {
		const found = [];
		for (let i = 0; i < items.length; i += 1) {
			
			var vehiclestr = [items[i].manufacturer, items[i].model, items[i].variant].join(' ');
			
			if ((items[i].registration &&items[i].registration.toLowerCase().indexOf(query.toLowerCase()) >= 0) 
				|| (vehiclestr && vehiclestr.toLowerCase().indexOf(query.toLowerCase()) >= 0)   
				|| query.trim() === '') found.push(i);
		}
		return found; // return array with matched indexes
	},
	renderExternal(vl, vlData) {
		this.vlData = vlData;
	},
	fetchData() {
		
		const self = this;
		
		self.$f7.preloader.show();
		self.error = null;
		self.vehicles = [];

		app.ajax({
			url: config.motorfinancecollections_api_base_url + '/vehicles',
			data: {
				username: localStorage.getItem('username'),
				usertoken: localStorage.getItem('usertoken')
			},
			success: function(data, status, xhr) {
				app.vehicles = data;
				self.items = app.vehicles;
				self.$f7.preloader.hide();
			},
			error: function(xhr, status) {
				// error
				alert('Sorry, the vehicle list is unavailable.');
			},
			store: true
		});
	}
}
});

#2

If I remove v-if=“items.length” from the f7-list and add:

self.$f7.virtualList.get().replaceAllItems(self.items)

to the AJAX success callback - search works - but the list fails to redraw after a second AJAX request.


#3

replaceAllItems is the correct method. I just tried and it works fine for me. Here is the working modified example from Kitchen Sink:

<template>
  <f7-page>
    <f7-navbar title="Virtual List" back-link="Back">
      <f7-subnavbar :inner="false">
        <f7-searchbar
          search-container=".virtual-list"
          search-item="li"
          search-in=".item-title"
        ></f7-searchbar>
      </f7-subnavbar>
    </f7-navbar>
    <f7-block>
      <p>Virtual List allows to render lists with huge amount of elements without loss of performance. And it is fully compatible with all Framework7 list components such as Search Bar, Infinite Scroll, Pull To Refresh, Swipeouts (swipe-to-delete) and Sortable.</p>
      <p>Here is the example of virtual list with 10 000 items:</p>
    </f7-block>
    <f7-list class="searchbar-not-found">
      <f7-list-item title="Nothing found"></f7-list-item>
    </f7-list>
    <f7-list
      class="searchbar-found"
      ref="f7List"
      medial-list
      virtual-list
      :virtual-list-params="{ items, searchAll, renderExternal, height: $theme.ios ? 63 : 73}"
    >
      <ul>
        <f7-list-item
          v-for="(item, index) in vlData.items"
          :key="index"
          media-item
          link="#"
          :title="item.title"
          :subtitle="item.subtitle"
          :style="`top: ${vlData.topPosition}px`"
        ></f7-list-item>
      </ul>
    </f7-list>
    <f7-block>
      <a @click="addItems">Add items</a>
    </f7-block>
  </f7-page>
</template>
<script>
  import { f7Navbar, f7Page, f7List, f7ListItem, f7Subnavbar, f7Searchbar, f7Block } from 'framework7-vue';

  export default {
    components: {
      f7Navbar, f7Page, f7List, f7ListItem, f7Subnavbar, f7Searchbar, f7Block,
    },
    data() {
      const items = [];
      for (let i = 1; i <= 100; i += 1) {
        items.push({
          title: `Item ${i}`,
          subtitle: `Subtitle ${i}`,
        });
      }
      return {
        items,
        vlData: {
          items: [],
        },
      };
    },
    methods: {
      addItems() {
        const last = this.items.length;
        const items = [];
        for (let i = last + 1; i <= last + 100; i += 1) {
          items.push({
            title: `Item ${i}`,
            subtitle: `Subtitle ${i}`,
          });
        }
        this.items = [...this.items, ...items];
        this.$refs.f7List.f7VirtualList.replaceAllItems(this.items)
      },
      searchAll(query, items) {
        const found = [];
        for (let i = 0; i < items.length; i += 1) {
          if (items[i].title.toLowerCase().indexOf(query.toLowerCase()) >= 0 || query.trim() === '') found.push(i);
        }
        return found; // return array with mathced indexes
      },
      renderExternal(vl, vlData) {
        this.vlData = vlData;
      },
    },
  };
</script>

#4

There’s something funny going on here… If we take the version where the search was working, and change the links on the next page (the item detail pages) to be be back links via class=“back” rather than explicitly going (back) to the vehicle-list page, everything seems to work. In isolation, your code may work, but there is something causing problems elsewhere - sorry to be so vague - I am considering abandoning the virtual list component and using a normal list instead, so i can have more understanding of what is actually happening… at what point does vlData get populated, for example? In the broken code, when the virtual list was populated for a second time, this is ending up empty.

Thank you for your response and apologies I can’t be more specific about what the issue may be - I have tried to narrow it down but with no luck!

One thing I did notice, in the chrome vue debugger, is that in the broken version, a second virtual list element was ending up in the “vue DOM”.