paging done, needs some additional refactoring

This commit is contained in:
Edin Dazdarevic
2015-02-15 14:21:50 +01:00
parent ba22ea8bd7
commit f675f8b024
11 changed files with 136 additions and 32 deletions

View File

@@ -16,6 +16,7 @@ before do
headers 'Access-Control-Allow-Origin' => 'http://localhost:3001', headers 'Access-Control-Allow-Origin' => 'http://localhost:3001',
'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST','PUT'], 'Access-Control-Allow-Methods' => ['OPTIONS', 'GET', 'POST','PUT'],
'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept', 'Access-Control-Allow-Headers' => 'Origin, X-Requested-With, Content-Type, Accept',
'Access-Control-Expose-Headers' => 'X-Total-Count',
'Access-Control-Allow-Credentials' => 'true' 'Access-Control-Allow-Credentials' => 'true'
request.body.rewind request.body.rewind

View File

@@ -3,6 +3,10 @@ def get_querystring_hash()
Rack::Utils.parse_nested_query(request.query_string) Rack::Utils.parse_nested_query(request.query_string)
end end
def add_total_count_header(total)
response.headers['X-Total-Count'] = total
end
# converts list of parameters to array of integers # converts list of parameters to array of integers
def mass_to_i(*id_strings) def mass_to_i(*id_strings)
id_strings.map(&:to_i) id_strings.map(&:to_i)

View File

@@ -21,7 +21,7 @@ end
def filter_by_traits(items) def filter_by_traits(items)
get_querystring_hash.each do |k,v| get_querystring_hash.each do |k,v|
items = items.where(["traits ->> '#{k}' = '#{v}'"]) items = items.where(["traits ->> ? = ?", k, v])
end end
items items
end 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 input_invalid = offset_and_limit_invalid?(offset,limit) or category_id <= 0
return [].to_json if input_invalid 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 = Item.best_selling_in_category(category_id, offset,limit)
items = filter_by_traits(items) items = filter_by_traits(items)
add_total_count_header(all_in_cat.count)
prepare_items_for_mass_display(items) prepare_items_for_mass_display(items)
end end

View File

@@ -10,7 +10,13 @@ class Item < ActiveRecord::Base
# TODO: change "best selling" algorithm when get some data - currently it's only sorted by earnings # 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, -> (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_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_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 :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 end

View File

@@ -1,6 +1,7 @@
class SubCategory < ActiveRecord::Base class SubCategory < ActiveRecord::Base
belongs_to :category belongs_to :category
has_many :filter_criterias, as: :owner has_many :filter_criterias, as: :owner
has_many :items
end end

View File

@@ -4,11 +4,13 @@ var ItemConstants = require('../constants/itemConstants');
// Define action methods // Define action methods
var ItemActions = { var ItemActions = {
loadByCategory: function(categoryId, query) { loadByCategory: function(categoryId, offset, limit, query) {
AppDispatcher.handleAction({ AppDispatcher.handleAction({
actionType: ItemConstants.LOAD_BY_CATEGORY, actionType: ItemConstants.LOAD_BY_CATEGORY,
categoryId : categoryId, categoryId : categoryId,
query : query query : query,
offset : offset,
limit: limit
}); });
}, },

View File

@@ -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 url ='/sekcija/' + section.get('name') +'/kategorija/'+ category.get('id') + '/' + category.get('name');
var q = ''; var q = '';
var qp = [];
if(query) { if(query) {
var qp = [];
for(var key in query) { for(var key in query) {
if (query.hasOwnProperty(key)) {
if (key !== 'offset' && key !== 'limit' && query.hasOwnProperty(key)) {
qp.push(key + '=' + query[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({ AppDispatcher.handleAction({
actionType: NavigationConstants.CHANGE_URL, actionType: NavigationConstants.CHANGE_URL,

View File

@@ -19,7 +19,8 @@ var ByCategory = React.createClass({
return { return {
category: category, category: category,
items: items, items: items,
filter :{} filter :{},
pagination: {}
}; };
}, },
filter: {}, filter: {},
@@ -30,16 +31,24 @@ var ByCategory = React.createClass({
var category = this.state.category; var category = this.state.category;
this.filter[fc.field_name] = fcv.filter_value; 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) { removeAppliedFilter: function(name) {
delete this.filter[name]; delete this.filter[name];
var section = new Section(this.state.category.get('section')); var section = new Section(this.state.category.get('section'));
var category = this.state.category; 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() { render: function() {
var self = this; var self = this;
return ( return (
<div> <div>
<div className='col-md-2'> <div className='col-md-2'>
@@ -54,7 +63,7 @@ var ByCategory = React.createClass({
{this.state.category.get('filter_criterias').map(function(fc) { {this.state.category.get('filter_criterias').map(function(fc) {
return (<div> return (<div>
<div className='h4'>{fc.title}</div> <div className='h4' style={{color: '#cd3071'}}>{fc.title}</div>
<ul> <ul>
{fc.filter_criteria_values.map(function(fcv) { {fc.filter_criteria_values.map(function(fcv) {
return (<li> return (<li>
@@ -68,7 +77,7 @@ var ByCategory = React.createClass({
<div className='col-md-10'> <div className='col-md-10'>
<h3> Browse products by category : {this.state.category.get('name')}</h3> <h3> Kategorija - {this.state.category.get('name')}</h3>
Number of items in this category: {this.state.items.length} Number of items in this category: {this.state.items.length}
<div> <div>
{this.appliedCategoryFiltersArray().map(function(acf) { {this.appliedCategoryFiltersArray().map(function(acf) {
@@ -80,7 +89,8 @@ var ByCategory = React.createClass({
</div> </div>
<ItemList items={this.state.items} /> <div> total count is : {this.state.items.totalCount}</div>
<ItemList items={this.state.items} paginationEnabled={true} total={this.state.items.totalCount} limit={this.state.pagination.limit} onPageChange={this.changePage} currentOffset={this.state.pagination.offset} />
</div> </div>
</div> </div>
); );
@@ -88,7 +98,7 @@ var ByCategory = React.createClass({
appliedCategoryFiltersArray: function() { appliedCategoryFiltersArray: function() {
var filters = []; var filters = [];
for(var key in this.state.filter) { 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]}); filters.push({name: key, value: this.state.filter[key]});
} }
} }
@@ -98,21 +108,36 @@ var ByCategory = React.createClass({
var categoryId = this.getParams().id; var categoryId = this.getParams().id;
this.filter = this.getQuery(); this.filter = this.getQuery();
var offset = this.filter.offset || 0;
var limit = this.filter.limit || 30;
this.setState({ 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); CategoryActions.loadCategoryDetails(categoryId);
}, },
componentDidMount: function() { componentDidMount: function() {
var categoryId = this.getParams().id; var categoryId = this.getParams().id;
this.filter = this.getQuery(); this.filter = this.getQuery();
var offset = this.filter.offset || 0;
var limit = this.filter.limit || 30;
this.setState({ 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); CategoryActions.loadCategoryDetails(categoryId);
ItemStore.addChangeListener(this._onChange); ItemStore.addChangeListener(this._onChange);

View File

@@ -3,7 +3,12 @@ var SingleItem = require('./singleItem');
var ItemCollection = require('../../models/itemCollection.js'); var ItemCollection = require('../../models/itemCollection.js');
var ItemList = React.createClass({ var ItemList = React.createClass({
changePage: function(page, e) {
e.preventDefault();
if(this.props.onPageChange) {
this.props.onPageChange(page);
}
},
render: function() { render: function() {
var items = this.props.items.models.map( function(item) { var items = this.props.items.models.map( function(item) {
@@ -18,10 +23,50 @@ var ItemList = React.createClass({
<ul className="item_list"> <ul className="item_list">
{items} {items}
</ul> </ul>
{this.getPages()}
</div> </div>
</div> </div>
); );
} },
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(<li className={cn}><a onClick={this.changePage.bind(this, i)}href="#">{i + 1}</a></li>)
}
return (
<nav>
<ul className="pagination">
{pages}
</ul>
</nav>)
}
}); });

View File

@@ -13,7 +13,9 @@ var ItemCollection = Backbone.Collection.extend({
} }
); );
}, },
setTotalCount: function(total) {
this.totalCount = total;
},
addFilter: function(name, value) { addFilter: function(name, value) {
this.filters = this.filters || {}; this.filters = this.filters || {};
this.filters[name] = value; this.filters[name] = value;

View File

@@ -51,24 +51,30 @@ var fetchItemWithDetails = function() {
} }
} }
var fetchItemsByCategory = function(categoryId, query) { var fetchItemsByCategory = function(categoryId, offset, limit, query) {
var items = _itemsByCategory; //var items = _itemsByCategory;
var items = new ItemCollection();
items.clearFilter(); items.clearFilter();
items.setClassificationType(2); items.setClassificationType(2);
items.setClassificationId(categoryId); items.setClassificationId(categoryId);
items.setLimit(30); items.setLimit(limit);
items.setOffset(0); items.setOffset(offset);
for(var key in query) { for(var key in query) {
if (query.hasOwnProperty(key)) { if (query.hasOwnProperty(key) && key != 'limit' && key !='offset') {
items.addFilter(key, query[key]); items.addFilter(key, query[key]);
} }
} }
items.fetch({ items.fetch({
success: function() { success: function(collection, response, options) {
var total = options.xhr.getResponseHeader('x-total-count');
items.setTotalCount(total);
ItemStore.emitChange(); ItemStore.emitChange();
}}); }});
_itemsByCategory = items;
}; };
var fetchBestSellingItemsForSection = function(sectionId) { var fetchBestSellingItemsForSection = function(sectionId) {
@@ -79,7 +85,6 @@ var fetchBestSellingItemsForSection = function(sectionId) {
items.setLimit(30); items.setLimit(30);
items.setOffset(0); items.setOffset(0);
items.fetch({ items.fetch({
success: function() { success: function() {
ItemStore.emitChange(); ItemStore.emitChange();
@@ -147,7 +152,7 @@ AppDispatcher.register(function(payload) {
loadItemsForFrontpage(); loadItemsForFrontpage();
break; break;
case ItemConstants.LOAD_BY_CATEGORY: case ItemConstants.LOAD_BY_CATEGORY:
fetchItemsByCategory(action.categoryId, action.query); fetchItemsByCategory(action.categoryId, action.offset, action.limit, action.query);
break; break;
default: default: