Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

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

CSS

              
                .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;
}
              
            
!

JS

              
                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
              
            
!
999px

Console