Hi there,
I’m just starting out with looking at Vue (training in Vue 2 at the moment due to the site infrastructure we have) and have built out a simple quiz/filtering app. All of the inputs are being generated based on the object items and their parameters such as gender, bike brand, colour etc. However, I’m now at the stage where I want to connect these inputs to the list of items which are initially shown from the unfiltered object items.
I’ve found various guides which detail how to filter based on single parameter value. However what I’m struggling with is that my app has a range of input types which are dependent on the user selection - granted most of them are checkboxes but as a result they require me to track multiple inputs from the user and filter accordingly. For example, I imagine that filtering by the radio button inputs is quite simple as I’d do a like-for-like match, whereas multiple selections of brands and colours seems much more complex?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Bike Finder</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div id="app">
<product></product>
</div>
<script src="vue.js"></script>
<script src="script_filtereditems.js" async defer></script>
</body>
</html>
Vue.component('product', {
props: {
},
template: `
<div class="container">
<h2>About You</h2>
<div class="question-section question-section--aboutyou">
<div class="container__question">
<strong>Preferred bike gender?</strong>
<div>
<template v-for="gender in filteredGenders">
<input type="radio" :id="gender" name="gender" :key="gender"><label :for="gender">{{gender}}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>What terrain do you normally ride on?</strong>
<div>
<template v-for="terrain in filteredTerrains">
<input type="checkbox" :id="terrain" :name="terrain" :key="terrain"><label :for="terrain">{{terrain}}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>What is your price range?</strong>
<div>
<input type="checkbox" id="priceRangeNoPref"><label for="priceRangeNoPref">No preference</label>
<input type="checkbox" id="priceRange200400"><label for="priceRange200400">£200-400</label>
<input type="checkbox" id="priceRange400600"><label for="priceRange400600">£400-600</label>
<input type="checkbox" id="priceRange6001000"><label for="priceRange6001000">£600-1,000</label>
<input type="checkbox" id="priceRange1000plus"><label for="priceRange1000plus">£1,000+</label>
</div>
</div>
<div class="container__question">
<strong>What is your average mileage per week?</strong>
<div>
<input type="radio" id="avgMilesUndisclosed" name="average-miles"><label for="avgMilesUndisclosed">Prefer not to say</label>
<input type="radio" id="avgMilesLess25" name="average-miles"><label for="avgMilesLess25">Less than 25</label>
<input type="radio" id="avgMiles2550" name="average-miles"><label for="avgMiles2550">25-50 miles</label>
<input type="radio" id="avgMiles50100" name="average-miles"><label for="avgMiles50100">50-100 miles</label>
<input type="radio" id="avgMiles100plus" name="average-miles"><label for="avgMiles100plus">100+ miles</label>
</div>
</div>
<button id="butAboutYouComplete">Complete, next section!</button>
</div>
<h2>Bike Preferences</h2>
<div class="question-section question-section--bike-preferences">
<div class="container__question">
<strong>Do you have any brand favourites?</strong>
<div>
<template v-for="brand in filteredBrands">
<input type="checkbox" class="favourite-brand" :key="brand" :name="brand" :id="brand"><label :for="brand" class="favourite-brand">{{ brand }}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>Any particular colour you're looking for?</strong>
<div class="container__answer--colours">
<template v-for="colour in filteredColours">
<input type="checkbox" class="colour" :key="colour" :name="colour" :id="colour"><label :for="colour" class="colour" :style="{'background-color': colour}"></label>
</template>
</div>
</div>
<div class="container__question">
<strong>What is preference on gears?</strong>
<div>
<template v-for="gear in filteredGears">
<input type="checkbox" class="favourite-gears" :key="gear" :name="gear" :id="gear"><label :for="gear" class="favourite-gear">{{gear}}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>What is your preferred suspension type?</strong>
<div>
<template v-for="suspension in filteredSuspension">
<input type="checkbox" class="favourite-suspension" :key="suspension" :name="suspension" :id="suspension"><label :for="suspension" class="favourite-suspension">{{suspension}}</label>
</template>
</div>
</div>
</div>
<h2>Products:</h2>
<div v-html="productFeed" class="product-feed"></div>
</div>
`,
data() {
return {
"bikes": [
{
id: 1,
image: "womens_hybrid_apollo_cosmo_blue.jpg",
terrain: "gravel",
gender: "womens",
type: "hybrid",
brand: "apollo",
name: "cosmo",
colour: "blue",
gears: 18,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 205,
priceEuros: null
},
{
id: 2,
image: "mens_hybrid_boardman_hyb89_blue.jpg",
terrain: "road",
gender: "mens",
type: "hybrid",
brand: "boardman",
name: "hyb89",
colour: "blue",
gears: 20,
suspension: "hard tail",
brakeType: "hydraulic disc brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: 2,
pricePounds: 750,
priceEuros: null
},
{
id: 3,
image: "mens_hybrid_carrera_code_orange.jpg",
terrain: "gravel",
gender: "mens",
type: "hybrid",
brand: "carrera",
name: "code",
colour: "orange",
gears: 16,
suspension: "hard tail",
brakeType: "mechanical disc brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 350,
priceEuros: null
},
{
id: 4,
image: "mens_mountain_apollo_valier_green.jpg",
terrain: "mountain",
gender: "mens",
type: "mountain",
brand: "apollo",
name: "valier",
colour: "green",
gears: 21,
suspension: "hard tail",
brakeType: "mechanical disc brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: 4,
pricePounds: 228,
priceEuros: null
},
{
id: 5,
image: "neutral_adventure_voodoo_limba_green.jpg",
terrain: "mountain",
gender: "neutral",
type: "adventure",
brand: "voodoo",
name: "limba",
colour: "green",
gears: 16,
suspension: "none",
brakeType: "mechanical disc brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: 2,
pricePounds: 428,
priceEuros: null
},
{
id: 6,
image: "neutral_bmx_voodoo_malice_grey.jpg",
terrain: "stunt",
gender: "neutral",
type: "bmx",
brand: "voodoo",
name: "malice",
colour: "black",
gears: 0,
suspension: "none",
brakeType: "u brakes",
electricMilesMaxRange: null,
inStock: false,
stockLeadtimeWeeks: 1,
pricePounds: 200,
priceEuros: null
},
{
id: 7,
image: "neutral_folding_apollo_tuck_blue.jpg",
terrain: "road",
gender: "neutral",
type: "folding",
brand: "apollo",
name: "tuck",
colour: "blue",
gears: 1,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 235,
priceEuros: null
},
{
id: 8,
image: "neutral_hybrid_electric_assist_2021_white.jpg",
terrain: "gravel",
gender: "neutral",
type: "electric",
brand: "assist",
name: "2021",
colour: "white",
gears: 1,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: 20,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 649,
priceEuros: null
},
{
id: 9,
image: "neutral_hybrid_electric_assist_stepthru_silver.jpg",
terrain: "gravel",
gender: "neutral",
type: "electric",
brand: "assist",
name: "stepthru",
colour: "silver",
gears: 1,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: 20,
inStock: false,
stockLeadtimeWeeks: 3,
pricePounds: 649,
priceEuros: null
},
{
id: 10,
image: "kids_hybrid_apollo_haze_goldwhite.jpg",
terrain: "road",
gender: "kids",
type: "hybrid",
brand: "apollo",
name: "haze",
colour: "gold white",
gears: 6,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: 2,
pricePounds: 150,
priceEuros: null
},
{
id: 11,
image: "kids_hybrid_carrera_saruna_red.jpg",
terrain: "road",
gender: "kids",
type: "hybrid",
brand: "carrera",
name: "saruna",
colour: "red",
gears: 7,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: 2,
pricePounds: 260,
priceEuros: null
},
{
id: 12,
image: "kids_training_apollo_pawpatrol_blueyellow.jpg",
terrain: "road",
gender: "kids",
type: "training",
brand: "apollo",
name: "paw patrol",
colour: "blue yellow",
gears: 0,
suspension: "none",
brakeType: "caliper",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 130,
priceEuros: null
},
{
id: 13,
image: "kids_training_apollo_peppapig_pink.jpg",
terrain: "road",
gender: "kids",
type: "training",
brand: "apollo",
name: "peppa pig",
colour: "pink",
gears: 0,
suspension: "none",
brakeType: "caliper",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 130,
priceEuros: null
},
{
id: 14,
image: "kids_training_apollo_batman_black.jpg",
terrain: "road",
gender: "kids",
type: "training",
brand: "apollo",
name: "batman",
colour: "black",
gears: 0,
suspension: "none",
brakeType: "caliper",
electricMilesMaxRange: null,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 175,
priceEuros: null
}
]
}
},
methods: {
cleanData(dataType, dataToClean) {
const cleanData = [];
let multiColouredDuplicate = false;
// console.log(dataToClean);
for (let i = 0; i < dataToClean.length; i++) {
let filteredData = dataToClean[i];
let spaceFound;
if (typeof filteredData === "string") {
spaceFound = filteredData.indexOf(' ') > 0;
}
let formattedData = this.formatData(filteredData);
if (dataType === "colour") {
// If space found but duplicate multicoloured result, skip
if (spaceFound && multiColouredDuplicate) {
multiColouredDuplicate = false;
continue;
// If space found and first multicoloured
} else if (spaceFound && !multiColouredDuplicate) {
formattedData = "multicoloured";
multiColouredDuplicate = true;
}
}
cleanData.push(formattedData);
}
return cleanData;
},
formatData(dataToFormat) {
let formattedData = dataToFormat;
return formattedData;
},
},
computed: {
filteredGenders() {
const typeGender = "gender";
const filteredGenders = Array.from(new Set(this.bikes.map(bike => bike.gender)));
let formattedGenders = this.cleanData(typeGender, filteredGenders);
return formattedGenders;
},
filteredTerrains() {
const typeTerrain = "terrain";
const filteredTerrains = Array.from(new Set(this.bikes.map(bike => bike.terrain)));
let formattedTerrains = this.cleanData(typeTerrain, filteredTerrains);
return formattedTerrains;
},
filteredBrands() {
const typeBrand = "brand";
const filteredBrands = Array.from(new Set(this.bikes.map(bike => bike.brand)));
let formattedBrands = this.cleanData(typeBrand, filteredBrands);
return formattedBrands;
},
filteredColours() {
const typeColour = "colour";
const filteredColours = Array.from(new Set(this.bikes.map(bike => bike.colour)));
let formattedColours = this.cleanData(typeColour, filteredColours);
return formattedColours;
},
filteredGears() {
const typeGears = "gears";
const filteredGearNumbers = Array.from(new Set(this.bikes.map(bike => bike.gears))).sort(function (a, b) {
return a - b
});
let formattedGears = this.cleanData(typeGears, filteredGearNumbers);
return formattedGears;
},
filteredSuspension() {
const typeSuspension = "suspension";
const filteredSuspension = Array.from(new Set(this.bikes.map(bike => bike.suspension)));
let formattedSuspension = this.cleanData(typeSuspension, filteredSuspension);
return formattedSuspension;
},
productFeed() {
let productFeed = "";
for (let i = 0; i < this.bikes.length; i++) {
let productFeedItem = this.bikes[i];
productFeed += `
<div class="product-feed__item product-feed__item--${productFeedItem.gender}">
<span class="product-feed__item__brand">${productFeedItem.brand}</span> - <span class="product-feed__item__gender">${productFeedItem.gender}</span>
<img src="images/${productFeedItem.gender}/${productFeedItem.image}" class="product-feed__item__image" />
</div>`;
//console.log(`${this.bikes[i].brand}`)
}
return productFeed;
}
}
})
// Creates new Vue instance with options
var app = new Vue({
// Property to connect to div with 'app' ID
el: '#app',
data: {
premium: true
}
})
.question-section {
margin-bottom: 2rem;
}
.container__question {
margin-bottom: 2rem;
}
input[type="checkbox"].favourite-brand {
display: none;
}
input[type="checkbox"].favourite-brand#apollo+label {
background-image: url('images/brands/brand_apollo.png')
}
input[type="checkbox"].favourite-brand#assist+label {
background-image: url('images/brands/brand_assist.png')
}
input[type="checkbox"].favourite-brand#boardman+label {
background-image: url('images/brands/brand_boardman.png')
}
input[type="checkbox"].favourite-brand#carrera+label {
background-image: url('images/brands/brand_carrera.png')
}
input[type="checkbox"].favourite-brand#voodoo+label {
background-image: url('images/brands/brand_voodoo.png')
}
label.favourite-brand {
display: inline-block;
margin-right: 1rem;
width: 200px;
height: 200px;
cursor: pointer;
background-color: #66a1bf;
background-position: center center;
background-repeat: no-repeat;
background-size: 100%;
transition: background-size 0.1s linear;
}
input.favourite-brand:checked+label {
background-size: 90%;
background-color: #244f66;
}
.container__answer--colours{
display: flex;
}
input[type="checkbox"].colour {
display: none;
}
label.colour {
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-right: 1rem;
width: 50px;
height: 50px;
cursor: pointer;
border: 1px solid black;
border-radius: 50%;
opacity: 1;
}
input#multicoloured+label{
background: rgb(255,0,0);
background: -moz-linear-gradient(180deg, rgba(255,0,0,1) 0%, rgba(255,158,0,1) 10%, rgba(255,248,0,1) 25%, rgba(91,255,0,1) 40%, rgba(0,224,255,1) 55%, rgba(0,22,255,1) 70%, rgba(192,0,255,1) 85%, rgba(255,0,219,1) 100%);
background: -webkit-linear-gradient(180deg, rgba(255,0,0,1) 0%, rgba(255,158,0,1) 10%, rgba(255,248,0,1) 25%, rgba(91,255,0,1) 40%, rgba(0,224,255,1) 55%, rgba(0,22,255,1) 70%, rgba(192,0,255,1) 85%, rgba(255,0,219,1) 100%);
background: linear-gradient(180deg, rgba(255,0,0,1) 0%, rgba(255,158,0,1) 10%, rgba(255,248,0,1) 25%, rgba(91,255,0,1) 40%, rgba(0,224,255,1) 55%, rgba(0,22,255,1) 70%, rgba(192,0,255,1) 85%, rgba(255,0,219,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#ff0000",endColorstr="#ff00db",GradientType=1);
}
label.colour::before {
content: " ";
position: absolute;
height: 25px;
transition-duration: 0.4s;
}
input.colour:checked+label.colour::before {
content: '✓';
color: #66a1bf;
}
.product-feed{
display: grid;
flex-direction: row;
grid-template-columns: repeat(4, 0.25fr);
grid-gap: 1rem;
}
.product-feed__item{
padding: 1rem;
outline: 1px solid grey;
border-radius: 10px;
}
.product-feed__item--womens{
background-color: lightpink;
}
.product-feed__item--mens{
background-color: lightblue;
}
.product-feed__item--neutral{
background-color: lightgray;
}
.product-feed__item--kids{
background-color: lightgreen;
}
.product-feed__item__brand{
font-weight: 600;
text-transform: capitalize;
}
.product-feed__item__image{
width: 100%;
}
.product-feed__item__gender{
text-transform: capitalize;
}
Any thoughts would be much appreciated.
Thanks in advance!