Add pagination in Angular and views

This commit is contained in:
Matt-Yorkley
2019-10-02 01:52:36 +01:00
parent 01d1e8243c
commit fe0de98821
6 changed files with 84 additions and 59 deletions

View File

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

View File

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

View File

@@ -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"}

View File

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

View File

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

View File

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