vue.jscheckboxv-model

Having an issue with checkbox v-model only returning the first keyword in array


I have several tracks in an array and have a search and checkboxes to filter the tracks. However, the checkboxes only return the first identified string in each filter. So if I click the checkbox for pop - in the example below - it will only show tracks containing only the pop genre. Tracks containing rock, pop genre won't appear. This is in composition API in Vue.

Here is the script:

const searchTerm = ref('')

const genreFilter = ref([])
const moodsFilter = ref([])
const tempoFilter = ref([])
const themeFilter = ref([])

const filteredTracks = computed(() => {
let filtered = useSong.tracks;

if (searchTerm.value) {
const term = searchTerm.value.toLowerCase();
filtered = filtered.filter(track =>
  track.description.toLowerCase().includes(term) || 
  track.genre.toLowerCase().includes(term) ||
  track.keywords.toLowerCase().includes(term) ||
  track.moods.toLowerCase().includes(term)
);
}

if (genreFilter.value.length > 0) {
filtered = filtered.filter(track => genreFilter.value.includes(track.genre))
}

if (moodsFilter.value.length > 0) {
filtered = filtered.filter(track => moodsFilter.value.includes(track.moods))
}

if (tempoFilter.value.length > 0) {
filtered = filtered.filter(track => tempoFilter.value.includes(track.tempo))
}

if (themeFilter.value.length > 0) {
filtered = filtered.filter(track => themeFilter.value.includes(track.theme))
}

return filtered;
});

Here is the html:

<div class="genres w-1/4"><h1 class="font-bold text-[#aeaeae]">Genres</h1><br>
        <input class="css-checkbox" type="checkbox" id="Rock" key="genre" value="Rock" v- 
 model="genreFilter">
        <label class="css-label pl-2 text-s font-light text-[#aeaeae]" for="Rock">Rock</label> 

        <input class="css-checkbox" type="checkbox" id="Pop" key="genre" value="Pop" v- 
 model="genreFilter">
        <label class="css-label pl-2 text-s font-light text-[#aeaeae]" for="Pop">Pop</label> 

How can I modify this so that I can check multiple boxes of genres and have them all show, removing only the unchecked boxes. Any help will be greatly appreciated. Also, you'll notice in the filters I have one for moods, tempo and theme. Whenever I click any of those checkboxes, nothing appears at all in the array despite the array having matching moods, tempo, and theme. Thanks.


Solution

  • Your genreFilter variable is an array:

    const genreFilter = ref([])
    

    But you put the whole thing into the checkboxes' v-model:

    <input type="checkbox" value="Rock" v-model="genreFilter">
    

    effectively replacing the array with a string.

    So when you do

    filtered = filtered.filter(track => genreFilter.value.includes(track.genre))
    

    You are not calling Array.prototype.includes(), but String.prototype.includes(). Which works, but not as you intended.

    Simple solution is to use indexes in the checkbox:

    <input type="checkbox" value="Rock" v-model="genreFilter[0]">
    

    A probably better solution is to add or remove elements when checking the checkbox:

    <input type="checkbox" value="Rock" @change="updateGeneres($event.target)">
    

    and

    const updateGenres = ({value, checked}) => checked 
    ? (genreFilter.value.includes(value) || genreFilter.value.push(value))
    : (genreFilter.value = genreFilter.value.filter(g => g !== value))
    

    const { createApp, ref, computed} = Vue;
    
    const App = {
      setup(){
        const genres = ['Rock', 'Pop']
        const genreFilter = ref([])
        const updateGenres = ({value, checked}) => checked
          ? (genreFilter.value.includes(value) || genreFilter.value.push(value))
          : (genreFilter.value = genreFilter.value.filter(g => g !== value))
        const items = [
          {genre: 'Pop', title: 'This is Pop'},
          {genre: 'Rock', title: 'This is Rock'},
          {genre: 'Something Else', title: 'This is something else'},
        ]
        const filteredItems = computed(() => genreFilter.value.length === 0 ? items : items.filter(i => genreFilter.value.includes(i.genre) ))
        return {genres, genreFilter, updateGenres, filteredItems}
      },
    }
    const app = createApp(App)
    app.mount('#app')
    <div id="app">
      
      <div v-for="genre in genres">
        <label>
          <input
            type="checkbox" 
            :value="genre" 
            @change="updateGenres($event.target)"
          > {{ genre }}
        </label>
      </div>
      
      Selected Genres: {{genreFilter}}
      
      <ul>
        <li v-for="item in filteredItems">{{ item.title }}</li>
      </ul>
      
    </div>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

    Here is the snippet in a SFC with setup script