<!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
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.8/vue.min.js