From fe0de988210304cc32efed261934ebcd206bfe9f Mon Sep 17 00:00:00 2001 From: Matt-Yorkley <9029026+Matt-Yorkley@users.noreply.github.com> Date: Wed, 2 Oct 2019 01:52:36 +0100 Subject: [PATCH] Add pagination in Angular and views --- .../controllers/products_controller.js.coffee | 70 ++++++++++++------- .../darkswarm/services/products.js.coffee | 29 +++++--- app/views/shop/products/_filters.html.haml | 4 +- app/views/shop/products/_form.html.haml | 8 +-- .../products_controller_spec.js.coffee | 7 -- .../services/products_spec.js.coffee | 25 ++++--- 6 files changed, 84 insertions(+), 59 deletions(-) diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee index 3c32c83875..0d4567afda 100644 --- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -1,41 +1,61 @@ -Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Taxons, Properties) -> +Darkswarm.controller "ProductsCtrl", ($scope, $filter, $rootScope, Products, OrderCycle, FilterSelectorsService, Cart, Dereferencer, Taxons, Properties, currentHub, $timeout) -> $scope.Products = Products $scope.Cart = Cart $scope.query = "" $scope.taxonSelectors = FilterSelectorsService.createSelectors() $scope.propertySelectors = FilterSelectorsService.createSelectors() $scope.filtersActive = true - $scope.limit = 10 + $scope.page = 1 + $scope.per_page = 10 $scope.order_cycle = OrderCycle.order_cycle - # $scope.infiniteDisabled = true - # All of this logic basically just replicates the functionality filtering an ng-repeat - # except that it allows us to filter a separate list before rendering, meaning that - # we can get much better performance when applying filters by resetting the limit on the - # number of products being rendered each time a filter is changed. + $scope.supplied_taxons = -> + return $scope.memoized_taxons if $scope.memoized_taxons != undefined + $scope.memoized_taxons = {} + currentHub.supplied_taxons.map( (taxon) -> + $scope.memoized_taxons[taxon.id] = Taxons.taxons_by_id[taxon.id] + ) + $scope.memoized_taxons - $scope.$watch "Products.loading", (newValue, oldValue) -> - $scope.updateFilteredProducts() - $scope.$broadcast("loadFilterSelectors") if !newValue + $scope.supplied_properties = -> + return $scope.memoized_properties if $scope.memoized_properties != undefined + $scope.memoized_properties = {} + currentHub.supplied_properties.map( (property) -> + $scope.memoized_properties[property.id] = Properties.properties_by_id[property.id] + ) + $scope.memoized_properties - $scope.incrementLimit = -> - if $scope.limit < Products.products.length - $scope.limit += 10 - $scope.updateVisibleProducts() + $scope.loadMore = -> + if ($scope.page * $scope.per_page) <= Products.products.length + $scope.loadMoreProducts() - $scope.$watch 'query', -> $scope.updateFilteredProducts() - $scope.$watchCollection 'activeTaxons', -> $scope.updateFilteredProducts() - $scope.$watchCollection 'activeProperties', -> $scope.updateFilteredProducts() + $scope.$watch 'query', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue + $scope.$watchCollection 'activeTaxons', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue + $scope.$watchCollection 'activeProperties', (newValue, oldValue) -> $scope.loadProducts() if newValue != oldValue - $scope.updateFilteredProducts = -> - $scope.limit = 10 - f1 = $filter('products')(Products.products, $scope.query) - f2 = $filter('taxons')(f1, $scope.activeTaxons) - $scope.filteredProducts = $filter('properties')(f2, $scope.activeProperties) - $scope.updateVisibleProducts() + $scope.loadProducts = -> + $scope.page = 1 + params = { + id: $scope.order_cycle.order_cycle_id, + page: $scope.page, + per_page: $scope.per_page, + 'q[name_or_supplier_name_cont]': $scope.query, + 'q[properites_in_any][]': $scope.activeProperties, + 'q[primary_taxon_id_in_any][]': $scope.activeTaxons + } + Products.update(params) - $scope.updateVisibleProducts = -> - $scope.visibleProducts = $filter('limitTo')($scope.filteredProducts, $scope.limit) + $scope.loadMoreProducts = -> + params = { + id: $scope.order_cycle.order_cycle_id, + page: $scope.page + 1, + per_page: $scope.per_page, + 'q[name_or_supplier_name_cont]': $scope.query, + 'q[properites_in_any][]': $scope.activeProperties, + 'q[primary_taxon_id_in_any][]': $scope.activeTaxons + } + Products.update(params, true) + $scope.page += 1 $scope.searchKeypress = (e)-> code = e.keyCode || e.which diff --git a/app/assets/javascripts/darkswarm/services/products.js.coffee b/app/assets/javascripts/darkswarm/services/products.js.coffee index 0a24cbd374..9b7b5e5993 100644 --- a/app/assets/javascripts/darkswarm/services/products.js.coffee +++ b/app/assets/javascripts/darkswarm/services/products.js.coffee @@ -1,26 +1,33 @@ -Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Properties, Cart, Variants) -> +Darkswarm.factory 'Products', (OrderCycleResource, OrderCycle, Shopfront, Dereferencer, Taxons, Properties, Cart, Variants) -> new class Products constructor: -> @update() - # TODO: don't need to scope this into object - # Already on object as far as controller scope is concerned - products: null + products: [] + fetched_products: [] loading: true - update: => + update: (params = {}, load_more = false) => @loading = true - @products = [] - $resource("/shop/products").query (products)=> - @products = products + order_cycle_id = OrderCycle.order_cycle.order_cycle_id + if order_cycle_id == undefined + @loading = false + return + + params['id'] = OrderCycle.order_cycle.order_cycle_id if params['id'] == undefined + + OrderCycleResource.products params, (data)=> + @products = [] unless load_more + @fetched_products = data @extend() @dereference() @registerVariants() + @products = @products.concat(@fetched_products) @loading = false extend: -> - for product in @products + for product in @fetched_products if product.variants?.length > 0 prices = (v.price for v in product.variants) product.price = Math.min.apply(null, prices) @@ -30,7 +37,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope product.largeImage = product.images[0]?.large_url if product.images dereference: -> - for product in @products + for product in @fetched_products product.supplier = Shopfront.producers_by_id[product.supplier.id] Dereferencer.dereference product.taxons, Taxons.taxons_by_id @@ -40,7 +47,7 @@ Darkswarm.factory 'Products', ($resource, Shopfront, Dereferencer, Taxons, Prope # May return different objects! If the variant has already been registered # by another service, we fetch those registerVariants: -> - for product in @products + for product in @fetched_products if product.variants product.variant_names = "" product.variants = for variant in product.variants diff --git a/app/views/shop/products/_filters.html.haml b/app/views/shop/products/_filters.html.haml index 10c7c20deb..0f89c37166 100644 --- a/app/views/shop/products/_filters.html.haml +++ b/app/views/shop/products/_filters.html.haml @@ -1,5 +1,5 @@ .filter-shopfront.taxon-selectors.text-right - %single-line-selectors{ selectors: "taxonSelectors", objects: "Products.products | products:query | properties:activeProperties | taxonsOf", "active-selectors" => "activeTaxons"} + %single-line-selectors{ selectors: "taxonSelectors", objects: "supplied_taxons()", "active-selectors" => "activeTaxons"} .filter-shopfront.property-selectors.text-right - %single-line-selectors{ selectors: "propertySelectors", objects: "Products.products | products:query | taxons:activeTaxons | propertiesOf", "active-selectors" => "activeProperties"} + %single-line-selectors{ selectors: "propertySelectors", objects: "supplied_properties()", "active-selectors" => "activeProperties"} diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml index 3a9738a532..ed2b555f3e 100644 --- a/app/views/shop/products/_form.html.haml +++ b/app/views/shop/products/_form.html.haml @@ -22,14 +22,14 @@ .small-12.medium-6.large-5.columns %input#search.text{"ng-model" => "query", placeholder: t(:products_search), - "ng-debounce" => "100", + "ng-debounce" => "200", "ofn-disable-enter" => true} .small-12.medium-6.large-6.large-offset-1.columns = render partial: "shop/products/filters" - %div.pad-top{ "infinite-scroll" => "incrementLimit()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'filteredProducts.length <= limit' } - %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in visibleProducts track by product.id", "id" => "product-{{ product.id }}"} + %div.pad-top{ "infinite-scroll" => "loadMore()", "infinite-scroll-distance" => "1", "infinite-scroll-disabled" => 'Products.loading' } + %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in Products.products track by product.id", "id" => "product-{{ product.id }}"} = render "shop/products/summary" %shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants | orderBy: ['name_to_display','unit_value'] track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.on_hand == 0}"} @@ -41,7 +41,7 @@ .small-12.columns.text-center %img.spinner{ src: "/assets/spinning-circles.svg" } - %div{"ng-show" => "filteredProducts.length == 0 && !Products.loading"} + %div{"ng-show" => "Products.products.length == 0 && !Products.loading"} .row.summary .small-12.columns %p.no-results diff --git a/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee index 1e188f4f16..3a7dd8ba1b 100644 --- a/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers/products_controller_spec.js.coffee @@ -27,13 +27,6 @@ describe 'ProductsCtrl', -> it 'fetches products from Products', -> expect(scope.Products.products).toEqual ['testy mctest'] - it "increments the limit up to the number of products", -> - scope.limit = 0 - scope.incrementLimit() - expect(scope.limit).toEqual 10 - scope.incrementLimit() - expect(scope.limit).toEqual 10 - it "blocks keypresses on code 13", -> event = keyCode: 13 diff --git a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee index 0075750fba..e9d2111d83 100644 --- a/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/services/products_spec.js.coffee @@ -1,6 +1,7 @@ describe 'Products service', -> $httpBackend = null Products = null + OrderCycle = {} Shopfront = null Variants = null Cart = null @@ -38,6 +39,9 @@ describe 'Products service', -> producers: id: 9, name: "Test" + OrderCycle = + order_cycle: + order_cycle_id: 1 module 'Darkswarm' module ($provide)-> @@ -46,6 +50,7 @@ describe 'Products service', -> $provide.value "taxons", taxons $provide.value "properties", properties $provide.value "Geo", Geo + $provide.value "OrderCycle", OrderCycle null inject ($injector, _$httpBackend_)-> @@ -57,60 +62,60 @@ describe 'Products service', -> $httpBackend = _$httpBackend_ it "Fetches products from the backend on init", -> - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].test).toEqual "cats" it "dereferences suppliers", -> Shopfront.producers_by_id = {id: 9, name: "test"} - $httpBackend.expectGET("/shop/products").respond([{supplier : {id: 9}, master: {}}]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([{supplier : {id: 9}, master: {}}]) $httpBackend.flush() expect(Products.products[0].supplier).toBe Shopfront.producers_by_id["9"] it "dereferences taxons", -> product.taxons = [2] - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].taxons[1]).toBe taxons[0] it "dereferences properties", -> product.properties_with_values = [1] - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].properties[1]).toBe properties[0] it "registers variants with Variants service", -> product.variants = [{id: 1}] - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].variants[0]).toBe Variants.variants[1] it "stores variant names", -> product.variants = [{id: 1, name_to_display: "one"}, {id: 2, name_to_display: "two"}] - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].variant_names).toEqual "one two " it "sets primaryImageOrMissing when no images are provided", -> - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].primaryImage).toBeUndefined() expect(Products.products[0].primaryImageOrMissing).toEqual "/assets/noimage/small.png" it "sets largeImage", -> - $httpBackend.expectGET("/shop/products").respond([productWithImage]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([productWithImage]) $httpBackend.flush() expect(Products.products[0].largeImage).toEqual("foo.png") describe "determining the price to display for a product", -> it "displays the product price when the product does not have variants", -> - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].price).toEqual 11.00 it "displays the minimum variant price when the product has variants", -> product.variants = [{price: 22}, {price: 33}] - $httpBackend.expectGET("/shop/products").respond([product]) + $httpBackend.expectGET("/api/order_cycles/1/products").respond([product]) $httpBackend.flush() expect(Products.products[0].price).toEqual 22