]> _ Git - pmi.git/commitdiff
WIP #3034 @10
authorStephen Cameron <stephen@cubedesigners.com>
Mon, 16 Sep 2019 18:18:13 +0000 (20:18 +0200)
committerStephen Cameron <stephen@cubedesigners.com>
Mon, 16 Sep 2019 18:18:13 +0000 (20:18 +0200)
package.json
resources/js/components/ProductsFilters.vue
resources/js/components/Tab.vue
resources/styles/common/global.styl
resources/styles/common/setup.styl
resources/styles/components/grids.styl
resources/styles/components/navigation.styl
resources/views/layouts/app.blade.php
resources/views/pages/products.blade.php
webpack.mix.js
yarn.lock

index 70dbd349067424dd8947c3d4afe3dbc28d3041cb..ce2d4a96cf27ca2d9a1baf1bee0fa0ac2daeabb3 100644 (file)
@@ -39,6 +39,7 @@
         "stylus-loader": "^3.0.2",
         "tailwindcss": "^1.0.4",
         "tippy.js": "^4.3.5",
+        "unorm": "^1.6.0",
         "vue": "^2.6.10",
         "vue-slide-up-down": "^1.7.2",
         "vue-slider-component": "^3.0.40",
index 63f0eb1ec631167299044eaccbf7f5e4ccd0059e..e6f146ad0c5b903a5ac87ad6eac55d564418dad4 100644 (file)
@@ -1,9 +1,9 @@
 <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">
@@ -29,8 +29,8 @@
                     <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) {
index 5e73e960a6fe44677dc66d84735cb509f52b80f1..1ee852fb49a454ef5e564ed3726dca6df9428168 100644 (file)
@@ -9,6 +9,8 @@
 </template>
 
 <script>
+    import 'unorm'  // .normalize polyfill for IE11
+
     export default {
         name: 'Tab',
 
index 246ba106b3185c24ea9cb66e7eab910503264c11..d64220b2110ab71dbefddd32df37cd1437a18643 100644 (file)
@@ -31,3 +31,7 @@ $overlap-amount = 10vw
     +above($breakpoint-columns)
       constrain(margin-bottom, -5vw)
       z-index: 10
+
+// VueJS styling to hide elements until they're ready
+[v-cloak]
+  visibility: hidden
index d2e9ce0ad60699e30d1c8b1e1160aa708c97fb6e..e68a121b5b182fb8b3e70111e03f559718d6db3d 100644 (file)
@@ -19,5 +19,6 @@ rupture.scale-names  = 'small' 'medium' 'large'
 rupture.anti-overlap = -1px // Results in +below(1000px) being max-width: 999px and +above(1000px) being min-width: 1000px
 
 // Lost Grid setup
-@lost gutter 5%
-@lost flexbox flex // Use flexbox (set to no-flex to disable)
+// This is currently only used as a fallback for browsers that don't support CSS Grid
+// We use the no-flex option (floats) because this works better in old browsers...
+@lost flexbox no-flex // [flex | no-flex]
index 6ec437604424a889f5943edba7c80fc5789378d2..21a4c77728d22c3331a47dd93c09c77ed50afd7a 100644 (file)
@@ -45,31 +45,10 @@ $fallback_grid_gap = $vw-spacing.lg // Basis for all grid gaps and
 $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
     }
   }
 }
index 1733287889577133da5be362d1805f7082e4d634..3cee1019897a370c44069bce956f53683f438af5 100644 (file)
 
   // Top level items
   > li
-    @apply relative py-5
+    @apply py-4
+    //position: relative
+    // NOTE: Submenus can't be positioned relative to the top level menu item because IE11 limits
+    // absolutely positioned children to the width of the parent container. So the flex element
+    // gets clipped. If we remove the relative positioning here and don't specify the top/left
+    // for the submenus, it will still work.
 
     &.active
       @apply text-blue
@@ -36,8 +41,9 @@
     @apply bg-white text-base shadow-2xl
     display: none
     position: absolute
-    top: 100%
-    left: 0
+    //top: 100%
+    //left: 0
+    margin-top: 1rem // Extra margin to replace top: 100% (see IE11 notes above)
     padding: 1em 0
 
 // Submenu items
@@ -64,6 +70,7 @@
     // Animated dash before link
     &:before
       content: ''
+      display: block
       width: 1.5em
       height: 2px
       margin-right: 0.5em
index 3b90c46b590b588ea4e5b315ff44058a25200d42..05b5f452c6b5fe2e5aa1b5955df3009ae6016a9d 100644 (file)
@@ -6,7 +6,7 @@
 @prepend('scripts')
 
     {{-- Automatic ES6 polyfills --}}
-    <script src="https://polyfill.io/v3/polyfill.js?features=es6&flags=gated"></script>
+    <script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.js?features=es6&flags=gated"></script>
 
     {{-- MMenu polyfills --}}
     <script src="https://cdnjs.cloudflare.com/ajax/libs/dom4/2.0.0/dom4.js"></script>
index db30dd49945dc9c09fbe68c4bc21ab590be1b956..f00197b3d3b54cf486a5830f777aed7ee4b8aa57 100644 (file)
@@ -8,7 +8,7 @@
         <content>
 
             @if ($filters && $filter_results)
-                <products-filters class=""
+                <products-filters v-cloak
                                   :product-type="{{ $product_type->id }}"
                                   :filter-data='@json($filters)'
                                   :result-data='@json($filter_results)'
index de12411ffc3b695b487c087307f8bb7fd278f791..432a6e6f6347841e838267d14f53aa86e128dbb1 100644 (file)
@@ -38,9 +38,6 @@ mix.js('resources/js/app.js', 'public/js')
 //     targets: {"firefox": "50", "ie": 11}
 // });
 
-// Transpile code for older browsers like IE11
-mix.babel(['public/js/app.js'], 'public/js/app.js');
-
 //=== Stylus / PostCSS
 mix.stylus('resources/styles/app.styl', 'public/css', {
         use: [
@@ -72,6 +69,9 @@ mix.purgeCss({
 //=== Production build
 if (mix.inProduction()) {
     mix.version(); // Enable unique hashed filenames when in production
+
+    // Transpile code for older browsers like IE11
+    mix.babel(['public/js/app.js'], 'public/js/app.js');
 }
 
 //=== BrowserSync
index 8cacfb420c82df33fac4fe6016955877cc159fd0..df849317858cc58f598f7b4b286b133802b4f1b7 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
@@ -7938,6 +7938,11 @@ universalify@^0.1.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
+unorm@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.yarnpkg.com/unorm/-/unorm-1.6.0.tgz#029b289661fba714f1a9af439eb51d9b16c205af"
+  integrity sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"