<!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">
</head>
<body>
<div id="app">
<product></product>
</div>
</body>
</html>
.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;
}
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 category?</strong>
<div>
<template v-for="category in filterCategories">
<label :for="category">
<input type="radio" :id="category" :name="category" :value="category" v-model="selectedCategory">{{category}}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>What terrain do you normally ride on?</strong>
<div>
<template v-for="terrain in filterTerrains">
<input type="checkbox" :id="terrain" :name="terrain" :key="terrain" :value="terrain" v-model="selectedTerrains"><label :for="terrain">{{terrain}}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>What is your preferred bike colour?</strong>
<div>
<template v-for="colour in filterColours">
<input type="checkbox" :id="colour" :name="colour" :key="colour" :value="colour" v-model="selectedColours"><label :for="colour">{{colour}}</label>
</template>
</div>
</div>
<div class="container__question">
<strong>What is your budget?</strong>
<div>
<template v-for="price in filterPrice">
<input type="checkbox" :id="price" :name="price" :key="price" :value="price" v-model="selectedPrice"><label :for="price">{{price}}</label>
</template>
</div>
</div>
<div class="container__question">
<div class="multi-range-group" v-for="range in ranges" :key="range.lbl">
<label :for="range.lbl">
<input :id="range.lbl" type="radio" :value="{min: range.min, max: range.max}"
name="price" v-model="selectPrice"
@change="filterRangePrice" />
{{range.lbl}}</label>
</div>
</div>
<div class="container__question">
<strong>What is your average mileage?</strong>
<div>
<template v-for="electric in filterElectric">
<input type="checkbox" :id="electric" :name="electric" :key="electric" :value="electric" v-model="selectedElectric"><label :for="electric">{{electric}}</label>
</template>
</div>
</div>
<h2>Bike Preferences</h2>
<em>(Questions about bike preferences here)</em>
<h2>Products:</h2>
<div class="product-feed">
<div v-for="bike in filterBikes" :key="bike.id" class="product-feed__item" :class="'product-feed__item--' + bike.category">
<span class="product-feed__item__brand">{{ bike.brand }} {{ bike.name }}</span> - <span class="product-feed__item__category">{{ bike.category }}</span> - <span>{{ bike.terrain }}</span>
<img :src="'images/' + bike.category + '/' + bike.image" class="product-feed__item__image" />
</div>
<!-- Add @filter-price="filterByPrice" ? -->
</div>
</div>
</div>
`,
data() {
return {
selectPrice: { min: 0, max: 1000000 },
ranges: [{ lbl: 'All', min: 0, max: 1000000 }, { lbl: '<=£10', min: 0, max: 10 }, { lbl: '£10 - £100', min: 10, max: 100 }, { lbl: '£100 - £500', min: 100, max: 500 }, { lbl: '>=£500', min: 500, max: 1000000 }],
selectedCategory: '',
selectedTerrains: [],
selectedColours: [],
selectedElectric: [],
selectedPrice: [],
"bikes": [
{
id: 1,
image: "womens_hybrid_apollo_cosmo_blue.jpg",
terrain: "gravel",
category: "womens",
type: "hybrid",
brand: "apollo",
name: "cosmo",
colour: "blue",
gears: 18,
suspension: "none",
brakeType: "v-brakes",
electricMilesMaxRange: 40,
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 205,
priceEuros: null
},
{
id: 2,
image: "mens_hybrid_boardman_hyb89_blue.jpg",
terrain: "road",
category: "mens",
type: "hybrid",
brand: "boardman",
name: "hyb89",
colour: "blue",
gears: 20,
suspension: "hard tail",
brakeType: "hydraulic disc brakes",
electricMilesMaxRange: "N/A",
inStock: true,
stockLeadtimeWeeks: 2,
pricePounds: 750,
priceEuros: null
},
{
id: 3,
image: "mens_hybrid_carrera_code_orange.jpg",
terrain: "gravel",
category: "mens",
type: "hybrid",
brand: "carrera",
name: "code",
colour: "orange",
gears: 16,
suspension: "hard tail",
brakeType: "mechanical disc brakes",
electricMilesMaxRange: "N/A",
inStock: true,
stockLeadtimeWeeks: null,
pricePounds: 350,
priceEuros: null
},
{
id: 4,
image: "mens_mountain_apollo_valier_green.jpg",
terrain: "mountain",
category: "mens",
type: "mountain",
brand: "apollo",
name: "valier",
colour: "green",
gears: 21,
suspension: "hard tail",
brakeType: "mechanical disc brakes",
electricMilesMaxRange: "N/A",
inStock: true,
stockLeadtimeWeeks: 4,
pricePounds: 228,
priceEuros: null
},
{
id: 5,
image: "neutral_adventure_voodoo_limba_green.jpg",
terrain: "mountain",
category: "neutral",
type: "adventure",
brand: "voodoo",
name: "limba",
colour: "multicoloured",
gears: 16,
suspension: "none",
brakeType: "mechanical disc brakes",
electricMilesMaxRange: "N/A",
inStock: true,
stockLeadtimeWeeks: 2,
pricePounds: 428,
priceEuros: null
},
{
id: 6,
image: "neutral_hybrid_electric_assist_2021_white.jpg",
terrain: "gravel",
category: "neutral",
type: "electric",
brand: "assist",
name: "2021",
colour: "white",
gears: 1,
suspension: "none",
brakeType: "V-Brakes",
electricMilesMaxRange: "20",
inStock: true,
stockLeadtimeWeeks: 4,
pricePounds: 649,
priceEuros: null
},
]
}
},
methods: {
cleanData(dataType, dataToClean) {
const cleanData = [];
let multiColouredDuplicate = false;
// Loop through data to clean based on amount of data
for (let i = 0; i < dataToClean.length; i++) {
// Grab data at index
let filteredData = dataToClean[i];
let spaceFound;
// If data is a string, find spaces
if (typeof filteredData === "string") {
spaceFound = filteredData.indexOf(' ') > 0;
}
// If dataType is colour, check for duplicate colour names
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) {
filteredData = "multicoloured";
multiColouredDuplicate = true;
}
}
// if (dataType === "electric" && dataToClean[i] === 0) {
// //console.log("data is 0");
// }
if (dataType === "price") {
let price = dataToClean[i];
}
// Return filteredData value
cleanData.push(filteredData);
}
return cleanData;
},
filterRangePrice() {
this.$emit("filter-price", this.pricePounds);
}
},
computed: {
filterCategories() {
const typeCategory = "category";
const filterCategories = Array.from(new Set(this.bikes.map(bike => bike.category)));
let formatCategories = this.cleanData(typeCategory, filterCategories);
return formatCategories;
},
filterTerrains() {
const typeTerrain = "terrain";
const filterTerrains = Array.from(new Set(this.bikes.map(bike => bike.terrain)));
let formatTerrains = this.cleanData(typeTerrain, filterTerrains);
return formatTerrains;
},
filterColours() {
const typeColour = "colour";
const filterColours = Array.from(new Set(this.bikes.map(bike => bike.colour)));
let formatColours = this.cleanData(typeColour, filterColours);
return formatColours;
},
filterElectric() {
const typeElectric = "electric";
const filterElectric = Array.from(new Set(this.bikes.map(bike => bike.electricMilesMaxRange))).sort();
let formatElectric = this.cleanData(typeElectric, filterElectric);
return formatElectric;
},
filterPrice() {
const typePrice = "price";
const filterPrice = Array.from(new Set(this.bikes.map(bike => bike.pricePounds))).sort();
let formatPrice = this.cleanData(typePrice, filterPrice);
return formatPrice;
},
filterBikes() {
let filteredBikes = this.bikes;
if (this.selectedCategory) {
filteredBikes = filteredBikes.filter(bike => bike.category === this.selectedCategory);
}
if (this.selectedTerrains.length > 0) {
filteredBikes = filteredBikes.filter(bike => this.selectedTerrains.includes(bike.terrain));
}
if (this.selectedColours.length > 0) {
filteredBikes = filteredBikes.filter(bike => this.selectedColours.includes(bike.colour));
}
if (this.selectedElectric.length > 0) {
filteredBikes = filteredBikes.filter(bike => this.selectedElectric.includes(bike.electricMilesMaxRange));
}
if (this.selectedPrice.length > 0) {
console.log(this.selectedPrice);
filteredBikes = filteredBikes.filter(bike => this.selectedPrice.includes((bike.pricePounds)));
}
return filteredBikes;
},
}
})
// Creates new Vue instance with options
var app = new Vue({
// Property to connect to div with 'app' ID
el: '#app',
data: {
premium: true
}
})
// Add "terrain" to generated items on front-end
// Add colour selection
// Add average mileage selection
This Pen doesn't use any external CSS resources.