From f675f8b0248cc49d4dc983e24f7d8050369e19c3 Mon Sep 17 00:00:00 2001 From: Edin Dazdarevic Date: Sun, 15 Feb 2015 14:21:50 +0100 Subject: [PATCH] paging done, needs some additional refactoring --- front-api/app.rb | 1 + .../controllers/common_for_controllers.rb | 4 ++ front-api/controllers/item.rb | 5 +- front-api/models/item.rb | 6 +++ front-api/models/sub_category.rb | 1 + front-ui/app/actions/itemActions.js | 6 ++- front-ui/app/actions/navigationActions.js | 22 ++++++--- .../app/components/browsing/byCategory.js | 47 +++++++++++++----- front-ui/app/components/items/itemList.js | 49 ++++++++++++++++++- front-ui/app/models/itemCollection.js | 6 ++- front-ui/app/stores/itemStore.js | 21 +++++--- 11 files changed, 136 insertions(+), 32 deletions(-) diff --git a/front-api/app.rb b/front-api/app.rb index 696a71b..3e0dc31 100644 --- a/front-api/app.rb +++ b/front-api/app.rb @@ -16,6 +16,7 @@ before do headers 'Access-Control-Allow-Origin' => 'http://localhost:3001', 'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST','PUT'], 'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept', + 'Access-Control-Expose-Headers' => 'X-Total-Count', 'Access-Control-Allow-Credentials' => 'true' request.body.rewind diff --git a/front-api/controllers/common_for_controllers.rb b/front-api/controllers/common_for_controllers.rb index f1374f3..547af54 100644 --- a/front-api/controllers/common_for_controllers.rb +++ b/front-api/controllers/common_for_controllers.rb @@ -3,6 +3,10 @@ def get_querystring_hash() Rack::Utils.parse_nested_query(request.query_string) end +def add_total_count_header(total) + response.headers['X-Total-Count'] = total +end + # converts list of parameters to array of integers def mass_to_i(*id_strings) id_strings.map(&:to_i) diff --git a/front-api/controllers/item.rb b/front-api/controllers/item.rb index ec3a6d1..fb88f2a 100644 --- a/front-api/controllers/item.rb +++ b/front-api/controllers/item.rb @@ -21,7 +21,7 @@ end def filter_by_traits(items) get_querystring_hash.each do |k,v| - items = items.where(["traits ->> '#{k}' = '#{v}'"]) + items = items.where(["traits ->> ? = ?", k, v]) end items end @@ -54,9 +54,12 @@ get '/item/category/:category_id/offset/:offset/limit/:limit' do |category_id_s, input_invalid = offset_and_limit_invalid?(offset,limit) or category_id <= 0 return [].to_json if input_invalid + all_in_cat = filter_by_traits(Item.all_in_category(category_id)) items = Item.best_selling_in_category(category_id, offset,limit) items = filter_by_traits(items) + add_total_count_header(all_in_cat.count) + prepare_items_for_mass_display(items) end diff --git a/front-api/models/item.rb b/front-api/models/item.rb index dab0a8c..25a6904 100644 --- a/front-api/models/item.rb +++ b/front-api/models/item.rb @@ -10,7 +10,13 @@ class Item < ActiveRecord::Base # TODO: change "best selling" algorithm when get some data - currently it's only sorted by earnings scope :best_selling, -> (offset, limit) { where(:on_display => true).order("(list_price - current_input_price) DESC").limit(limit).offset(offset) } + scope :best_selling_in_sub_category, -> (sub_category_id, offset, limit) { best_selling(offset, limit).where(sub_category_id: sub_category_id) } + scope :best_selling_in_category, -> (category_id, offset, limit) { best_selling(offset, limit).joins( sub_category: [:category] ).where(["category_id = ?", category_id]) } + scope :best_selling_in_section, -> (section_id, offset, limit) { best_selling(offset, limit).joins( sub_category: [category: [ :section ]] ).where( ["section_id = ?", section_id] ) } + + scope :all_in_category, -> (category_id) { where(on_display: true).joins( sub_category: [:category]).where(["category_id = ?", category_id]) } + end diff --git a/front-api/models/sub_category.rb b/front-api/models/sub_category.rb index 9d779fe..9a4a9af 100644 --- a/front-api/models/sub_category.rb +++ b/front-api/models/sub_category.rb @@ -1,6 +1,7 @@ class SubCategory < ActiveRecord::Base belongs_to :category has_many :filter_criterias, as: :owner + has_many :items end diff --git a/front-ui/app/actions/itemActions.js b/front-ui/app/actions/itemActions.js index 3a892a6..d26e254 100644 --- a/front-ui/app/actions/itemActions.js +++ b/front-ui/app/actions/itemActions.js @@ -4,11 +4,13 @@ var ItemConstants = require('../constants/itemConstants'); // Define action methods var ItemActions = { - loadByCategory: function(categoryId, query) { + loadByCategory: function(categoryId, offset, limit, query) { AppDispatcher.handleAction({ actionType: ItemConstants.LOAD_BY_CATEGORY, categoryId : categoryId, - query : query + query : query, + offset : offset, + limit: limit }); }, diff --git a/front-ui/app/actions/navigationActions.js b/front-ui/app/actions/navigationActions.js index fc7325d..6a92e3c 100644 --- a/front-ui/app/actions/navigationActions.js +++ b/front-ui/app/actions/navigationActions.js @@ -21,20 +21,30 @@ var NavigationActions = { }); }, - goToCategory: function(category,section, query) { + goToCategory: function(category,section, query, offset, limit) { var url ='/sekcija/' + section.get('name') +'/kategorija/'+ category.get('id') + '/' + category.get('name'); var q = ''; + var qp = []; + if(query) { - var qp = []; for(var key in query) { - if (query.hasOwnProperty(key)) { + + if (key !== 'offset' && key !== 'limit' && query.hasOwnProperty(key)) { qp.push(key + '=' + query[key]); } } - if (qp.length > 0) { - q = '?' + qp.join('&'); - } + } + if (offset !== undefined) { + qp.push('offset='+offset); + } + + if (limit !== undefined) { + qp.push('limit='+limit); + } + + if (qp.length > 0) { + q = '?' + qp.join('&'); } AppDispatcher.handleAction({ actionType: NavigationConstants.CHANGE_URL, diff --git a/front-ui/app/components/browsing/byCategory.js b/front-ui/app/components/browsing/byCategory.js index e99081d..2125f05 100644 --- a/front-ui/app/components/browsing/byCategory.js +++ b/front-ui/app/components/browsing/byCategory.js @@ -19,7 +19,8 @@ var ByCategory = React.createClass({ return { category: category, items: items, - filter :{} + filter :{}, + pagination: {} }; }, filter: {}, @@ -30,16 +31,24 @@ var ByCategory = React.createClass({ var category = this.state.category; this.filter[fc.field_name] = fcv.filter_value; - NavigationActions.goToCategory(category, section, this.filter); + NavigationActions.goToCategory(category, section, this.filter, 0, this.state.pagination.limit); }, removeAppliedFilter: function(name) { delete this.filter[name]; var section = new Section(this.state.category.get('section')); var category = this.state.category; - NavigationActions.goToCategory(category, section, this.filter); + NavigationActions.goToCategory(category, section, this.filter, 0, this.state.pagination.limit); + + }, + changePage: function(page) { + var section = new Section(this.state.category.get('section')); + var category = this.state.category; + + NavigationActions.goToCategory(category, section, this.filter, parseInt(page) * this.state.pagination.limit, this.state.pagination.limit); }, render: function() { var self = this; + return (
@@ -54,7 +63,7 @@ var ByCategory = React.createClass({ {this.state.category.get('filter_criterias').map(function(fc) { return (
-
{fc.title}
+
{fc.title}
    {fc.filter_criteria_values.map(function(fcv) { return (
  • @@ -68,7 +77,7 @@ var ByCategory = React.createClass({
    -

    Browse products by category : {this.state.category.get('name')}

    +

    Kategorija - {this.state.category.get('name')}

    Number of items in this category: {this.state.items.length}
    {this.appliedCategoryFiltersArray().map(function(acf) { @@ -80,7 +89,8 @@ var ByCategory = React.createClass({
    - +
    total count is : {this.state.items.totalCount}
    +
); @@ -88,7 +98,7 @@ var ByCategory = React.createClass({ appliedCategoryFiltersArray: function() { var filters = []; for(var key in this.state.filter) { - if(this.state.filter.hasOwnProperty(key)) { + if(this.state.filter.hasOwnProperty(key) && key !== 'limit' && key !== 'offset') { filters.push({name: key, value: this.state.filter[key]}); } } @@ -98,21 +108,36 @@ var ByCategory = React.createClass({ var categoryId = this.getParams().id; this.filter = this.getQuery(); + var offset = this.filter.offset || 0; + var limit = this.filter.limit || 30; + + this.setState({ - filter: this.filter + filter: this.filter, + pagination: { + offset: offset, + limit: limit + } }); - ItemActions.loadByCategory(categoryId, this.filter); + ItemActions.loadByCategory(categoryId, offset, limit, this.filter); CategoryActions.loadCategoryDetails(categoryId); }, componentDidMount: function() { var categoryId = this.getParams().id; this.filter = this.getQuery(); + + var offset = this.filter.offset || 0; + var limit = this.filter.limit || 30; this.setState({ - filter: this.filter + filter: this.filter, + pagination: { + offset: offset, + limit: limit + } }); - ItemActions.loadByCategory(categoryId, this.getQuery()); + ItemActions.loadByCategory(categoryId, offset, limit, this.getQuery()); CategoryActions.loadCategoryDetails(categoryId); ItemStore.addChangeListener(this._onChange); diff --git a/front-ui/app/components/items/itemList.js b/front-ui/app/components/items/itemList.js index 5f01bcb..1f75fa0 100644 --- a/front-ui/app/components/items/itemList.js +++ b/front-ui/app/components/items/itemList.js @@ -3,7 +3,12 @@ var SingleItem = require('./singleItem'); var ItemCollection = require('../../models/itemCollection.js'); var ItemList = React.createClass({ - + changePage: function(page, e) { + e.preventDefault(); + if(this.props.onPageChange) { + this.props.onPageChange(page); + } + }, render: function() { var items = this.props.items.models.map( function(item) { @@ -18,10 +23,50 @@ var ItemList = React.createClass({
    {items}
+ {this.getPages()}
); - } + }, + getPages: function() { + if (!this.props.paginationEnabled) { + return ""; + } + + var nrOfPages = Math.ceil(this.props.total/ this.props.limit); + if (nrOfPages === 1) { + return ""; + } + + var maxSlots = 3; + var selectedIndex = Math.floor(this.props.currentOffset / this.props.limit); + + var start, end; + start = selectedIndex - Math.floor(maxSlots / 2); + end = selectedIndex + Math.floor(maxSlots / 2 ) + 1; + + if (start < 0) start = 0; + if (end > nrOfPages) end = nrOfPages; + + if ((end - start) < maxSlots) { + start = Math.max(0, end - maxSlots); + end = Math.min(nrOfPages, start + maxSlots); + } + + var pages = []; + for(var i = start; i < end; i++) { + var cn = i === selectedIndex ? "active": ""; + pages.push(
  • {i + 1}
  • ) + } + + return ( + ) + } }); diff --git a/front-ui/app/models/itemCollection.js b/front-ui/app/models/itemCollection.js index 4d94b70..a103e93 100644 --- a/front-ui/app/models/itemCollection.js +++ b/front-ui/app/models/itemCollection.js @@ -13,7 +13,9 @@ var ItemCollection = Backbone.Collection.extend({ } ); }, - + setTotalCount: function(total) { + this.totalCount = total; + }, addFilter: function(name, value) { this.filters = this.filters || {}; this.filters[name] = value; @@ -75,4 +77,4 @@ var ItemCollection = Backbone.Collection.extend({ } }); -module.exports = ItemCollection; \ No newline at end of file +module.exports = ItemCollection; diff --git a/front-ui/app/stores/itemStore.js b/front-ui/app/stores/itemStore.js index 688938e..3b63020 100644 --- a/front-ui/app/stores/itemStore.js +++ b/front-ui/app/stores/itemStore.js @@ -51,24 +51,30 @@ var fetchItemWithDetails = function() { } } -var fetchItemsByCategory = function(categoryId, query) { - var items = _itemsByCategory; +var fetchItemsByCategory = function(categoryId, offset, limit, query) { + //var items = _itemsByCategory; + var items = new ItemCollection(); items.clearFilter(); items.setClassificationType(2); items.setClassificationId(categoryId); - items.setLimit(30); - items.setOffset(0); + items.setLimit(limit); + items.setOffset(offset); for(var key in query) { - if (query.hasOwnProperty(key)) { + if (query.hasOwnProperty(key) && key != 'limit' && key !='offset') { items.addFilter(key, query[key]); } } items.fetch({ - success: function() { + success: function(collection, response, options) { + var total = options.xhr.getResponseHeader('x-total-count'); + items.setTotalCount(total); + ItemStore.emitChange(); }}); + + _itemsByCategory = items; }; var fetchBestSellingItemsForSection = function(sectionId) { @@ -78,7 +84,6 @@ var fetchBestSellingItemsForSection = function(sectionId) { items.setClassificationId(sectionId); items.setLimit(30); items.setOffset(0); - items.fetch({ success: function() { @@ -147,7 +152,7 @@ AppDispatcher.register(function(payload) { loadItemsForFrontpage(); break; case ItemConstants.LOAD_BY_CATEGORY: - fetchItemsByCategory(action.categoryId, action.query); + fetchItemsByCategory(action.categoryId, action.offset, action.limit, action.query); break; default: