Home > Net >  Trouble writing proper code for multiple option filters for JSON data in Vue3
Trouble writing proper code for multiple option filters for JSON data in Vue3

Time:01-28

I am making a vue project. My goal is to have an onchange sort option which sorts my json file objects according to the selected value of dropdown. Second goal is to have a dynamic searchbar.

I've implemented both and these two features must be independent of my third goal, which is to have a list of checkboxes with filter options. After I select/unselect one or more checkboxes and then click Apply button, my filter changes should apply.

Currently, as I select/unselect checkboxes, the changes are applied... But I do not want this. I want it to happen only after I click Apply. I've tried to change code many times but either if affects my first two goals or some weird scenario happens such as:

  1. When I unselect China and select USA and hit apply, no update takes place.
  2. when I unselect both and hit apply I wish all items to be returned, but this doesn't happen
  3. initially when page loads no item is rendered.

I'm not posting my multiple variations of code as there are too many. I'm posting my current variation where the Apply button is useless as changes take place as soon as I select/deselect checkboxes.

Can someone propose a solution where the filters take place only when Apply button is clicked, and no weird scenario happens?

The filter must be independent of the search and sort functions. Search and sort must Not rely on Apply button. I'm struggling to break down the logic indepedently here.

My Code

<template>
  <div>
    <input v-model="searchValue" placeholder="Search by title...">
    <label v-for="(country, index) in countries" :key="index">
        <input type="checkbox" v-model="selectedCountries" :value="country"/>{{country}}
    </label>
    <select v-model="sortMethod">
      <option value="alphabetical">Alphabetical</option>
      <option value="numerical">Numerical</option>
    </select>
    <button @click="applyFilter">Apply</button>
    <div v-for="item in filteredItems" :key="item.title">
      <h3>{{ item.title }}</h3>
      <p>{{ item.cost }}</p>
      <p>{{ item.country }}</p>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import jsonData from "../data/test.json"
export default {
  setup() {
    const items = ref(jsonData)
    const searchValue = ref('')
    const selectedCountries = ref([])
    const sortMethod = ref('alphabetical')
  const countries = ref(['USA', 'China'])
    const filteredItems = computed(() => {
        return sortAndSearch(selectedCountries.value, searchValue.value);
    });

    function sortAndSearch(selectedCountries, searchValue) {
        let filtered = items.value;
        if (selectedCountries.length) {
            filtered = filtered.filter(item => selectedCountries.includes(item.country))
        }
        if (searchValue) {
            filtered = filtered.filter(item => item.title.toLowerCase().includes(searchValue.toLowerCase()))
        }
        if (sortMethod.value === 'alphabetical') {
            filtered = filtered.sort((a, b) => a.title.localeCompare(b.title))
        } else {
            filtered = filtered.sort((a, b) => a.cost - b.cost)
        }
        return filtered
    }

    function applyFilter() {
        filteredItems.value = sortAndSearch(selectedCountries.value, searchValue.value)
    }

    return {
        items,
        searchValue,
        selectedCountries,
        sortMethod,
        filteredItems,
        applyFilter
    }
  }
}
</script>

CodePudding user response:

You can create a separate computed property for the filtered items, and use the applyFilter method to update it.

Here is an example of how you can implement it:

<template>
  <div>
    <input v-model="searchValue" placeholder="Search by title...">
    <label v-for="(country, index) in countries" :key="index">
        <input type="checkbox" v-model="selectedCountries" :value="country"/>{{country}}
    </label>
    <select v-model="sortMethod">
      <option value="alphabetical">Alphabetical</option>
      <option value="numerical">Numerical</option>
    </select>
    <button @click="applyFilter">Apply</button>
    <div v-for="item in filteredItems" :key="item.title">
      <h3>{{ item.title }}</h3>
      <p>{{ item.cost }}</p>
      <p>{{ item.country }}</p>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import jsonData from "../data/test.json"
export default {
  setup() {
    const items = ref(jsonData)
    const searchValue = ref('')
    const selectedCountries = ref([])
    const sortMethod = ref('alphabetical')
    const countries = ref(['USA', 'China'])
    const filteredItems = computed(() => {
        return sortAndSearch(selectedCountries.value, searchValue.value);
    });
    const filteredItemsOnApply = ref(filteredItems.value);

    function sortAndSearch(selectedCountries, searchValue) {
        let filtered = items.value;
        if (selectedCountries.length) {
            filtered = filtered.filter(item => selectedCountries.includes(item.country))
        }
        if (searchValue) {
            filtered = filtered.filter(item => item.title.toLowerCase().includes(searchValue.toLowerCase()))
        }
        if (sortMethod.value === 'alphabetical') {
            filtered = filtered.sort((a, b) => a.title.localeCompare(b.title))
        } else {
            filtered = filtered.sort((a, b) => a.cost - b.cost)
        }
        return filtered
    }

    function applyFilter() {
        filteredItemsOnApply.value = sortAndSearch(selectedCountries.value, searchValue.value)
    }

    return {
        items,
        searchValue,
        selectedCountries,
        sortMethod,
        filteredItems: filteredItemsOnApply,
        applyFilter
    }
  }
}
</script>

With this code, when you select/unselect checkboxes, the filtered items will not change until you click the Apply button. The applyFilter method will update the value of the filteredItemsOnApply property, which is being used in the template to render the items.

CodePudding user response:

Take a look at following snippet: (split sort and filter)

const { ref, computed, onMounted } = Vue
const app = Vue.createApp({
  setup() {
    const items = ref([{title: 'aaa', country: 'USA', cost: 40}, {title: 'bbb', country: 'China', cost: 20}, {title: 'ccc', country: 'USA', cost: 30}])
    const searchValue = ref('')
    const selectedCountries = ref([])
    const sortMethod = ref('alphabetical')
    const countries = ref(['USA', 'China'])
    const sorted = ref([])
    onMounted(() => {
      sorted.value = [...items.value]
    })
    const filteredItems = computed(() => {
      return sortMethod.value === 'alphabetical' 
      ? sorted.value.sort((a, b) => a.title.localeCompare(b.title))
      : sorted.value.sort((a, b) => a.cost - b.cost)
    })
    function sortAndSearch() {
      let filtered = [...items.value];
      if (selectedCountries.value.length) {
        filtered = filtered.filter(item => selectedCountries.value.includes(item.country))
      }
      if (searchValue.value) {
        filtered = filtered.filter(item => item.title.toLowerCase().includes(searchValue.value.toLowerCase()))
      }
      sorted.value = filtered
    }

    return {
      countries,
      searchValue,
      selectedCountries,
      sortMethod,
      filteredItems,
      sortAndSearch
    }
  }
})
app.mount('#demo')
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="demo">
  <div>
    <input v-model="searchValue" placeholder="Search by title...">
    <label v-for="(country, index) in countries" :key="index">
        <input type="checkbox" v-model="selectedCountries" :value="country" />{{country}}
    </label>
    <select v-model="sortMethod">
      <option value="alphabetical">Alphabetical</option>
      <option value="numerical">Numerical</option>
    </select>
    <button @click="sortAndSearch">Apply</button>
    <div v-for="item in filteredItems" :key="item.title">
      <h3>{{ item.title }}</h3>
      <p>{{ item.cost }}</p>
      <p>{{ item.country }}</p>
    </div>
  </div>
</div>

  • Related