<template>
- <div class="flex relative items-start sm:block">
+ <div class="relative" :class="{ 'overflow-hidden': noResults }">
<!-- Filters column -->
- <div class="products-filters-wrapper sticky sm:static top-60 mr-1v sm:mr-0 pt-4 whitespace-no-wrap">
+ <div class="products-filters-wrapper sticky sm:static top-60 mr-1v sm:mr-0 pt-4 whitespace-no-wrap float-left sm:float-none">
<!-- Filters panel -->
<div class="bg-white p-4">
<div v-if="filter.type === 'range'">
<vue-slider class="mt-3"
v-model="filters[filter.id]"
- :min="filter.min"
- :max="filter.max"
+ :min="parseFloat(filter.min)"
+ :max="parseFloat(filter.max)"
:enable-cross="false"
:contained="true"
:lazy="true"
<div class="products-grid flex-grow">
<!-- Grid summary header -->
- <div class="products-grid-summary sticky sm:static top-60 z-10 bg-grey-100 pt-4 pb-4 flex justify-between sm:block">
+ <div :class="{ 'top-60': !noResults }" class="products-grid-summary sticky sm:static z-10 bg-grey-100 pt-4 pb-4 flex justify-between sm:block">
<!-- Active filters -->
<div class="products-grid-active-filters flex-grow text-sm">
- <ul class="flex flex-wrap -mb-3 md:mb-0">
+ <ul class="-mb-3 md:mb-0">
<template v-for="(filter, filterID) in filters">
- <li class="bg-white whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200"
+ <li class="bg-white inline-block whitespace-no-wrap py-2 px-4 rounded-full mr-3 mb-3 hover:bg-grey-200"
v-for="filter_option in filter" v-if="getFilter(filterID).options">
{{ getFilter(filterID).options[filter_option].label }}
</div>
</div>
+ <!--
+ Slot is hidden because we only use it to gather products into Vue's data object.
+ However, we can't get any data unless we display it somewhere. Normally we'd pass
+ data to view in a less awkward way but this allows us to have the products render
+ even before the Vue fully loads. It also lets the page work when there's no Vue
+ component (ie. when there are no filters available). Lastly, the template for the
+ product view is centralised in the blade partial instead of being duplicated here.
+ -->
+ <div class="hidden">
+ <slot></slot>
+ </div>
+
<!-- Product Grid -->
- <slot></slot>
+ <div class="grid grid-cols-auto grid-gap-lg products-grid mt-6 sm:mt-2">
+ <template v-for="product in products" v-if="productVisible(product.id)">
+ <div v-html="product.html"></div>
+ </template>
+ </div>
<!-- No results -->
- <div class="text-navy font-display text-center my-8" v-if="Object.keys(this.matches.hits).length === 0">
+ <div class="text-navy font-display text-center my-8" v-if="noResults">
{{ translations.no_results }}
</div>
data: () => ({
filters: {},
matches: {},
+ products: [],
}),
computed: {
return `${count} ${results}`;
},
+ noResults() {
+ return Object.keys(this.matches.hits).length === 0
+ },
+
filterQuerystring() {
let filter_list = [];
this.filters = filters;
},
+ mounted() {
+
+ // Collect product elements from the default slot so we can handle them better as Vue objects
+ this.$slots.default[0].children.forEach(child => {
+
+ if (child.tag) { // Ensure it's a tag and not an empty text element
+ let product = child.elm;
+ let ID = parseInt(product.dataset.productId);
+
+ this.products.push({id: ID, html: product.outerHTML});
+ }
+ });
+
+ },
+
methods: {
getFilter(id) {
axios.get(endpoint)
.then(function (response) {
$this.matches = response.data.results;
- $this.filterProducts();
+ //$this.filterProducts();
})
.catch(function (error) {
console.error('Error filtering products', error);
});
},
- filterProducts() {
- let matches = Object.values(this.matches.hits);
-
- this.$slots.default[0].children.forEach(child => {
- if (child.tag) { // Ensure it's a tag and not an empty text element
- let product = child.elm;
- let ID = parseInt(product.dataset.productId);
- if (matches.includes(ID)) {
- product.classList.remove('hidden');
- } else {
- product.classList.add('hidden');
- }
- }
- });
+ // filterProducts() {
+ // let matches = Object.values(this.matches.hits);
+ //
+ // this.$slots.default[0].children.forEach(child => {
+ // if (child.tag) { // Ensure it's a tag and not an empty text element
+ // let product = child.elm;
+ // let ID = parseInt(product.dataset.productId);
+ // if (matches.includes(ID)) {
+ // product.classList.remove('hidden');
+ // } else {
+ // product.classList.add('hidden');
+ // }
+ // }
+ // });
+ // },
+
+ productVisible(id) {
+ return Object.values(this.matches.hits).includes(id)
},
removeFilter(filterID, optionID) {
$fallback_grid_gap_px = unit($fallback_grid_gap / 100, '') * $base-width
grid-fallback(cols) {
- $column_width = 100% / cols
- $nth_child = s('%sn', cols)
.no-cssgrid & {
> * {
- width: s('calc(99.9% * 1/%s - %s - (%s * 1/%s))', cols, $fallback_grid_gap, $fallback_grid_gap, cols)
- max-width: @width
- flex-basis: @width
- flex-shrink: 0
- flex-grow: 0
- constrain(margin-right, $fallback_grid_gap)
- constrain(margin-bottom, $fallback_grid_gap)
-
- // Need to avoid hidden elements or product grids display incorrectly
- // This wasn't possible with lost grid so it had to be recreated here
- &:not(.hidden):nth-child($nth_child) {
- margin-right: 0
- margin-left: auto
- }
-
- // Cap grid gaps
- +above($base-width) {
- width: s('calc(99.9% * 1/%s - %s - (%s * 1/%s))', cols, $fallback_grid_gap_px, $fallback_grid_gap_px, cols)
- max-width: @width
- flex-basis: @width
- }
+ lost-column: s('1/%s %s %s', cols, cols, $fallback_grid_gap)
+ margin-bottom: $fallback_grid_gap
}
}
}