diff --git a/front-api/controllers/item.rb b/front-api/controllers/item.rb
index 4cdfc54..b4ec821 100644
--- a/front-api/controllers/item.rb
+++ b/front-api/controllers/item.rb
@@ -66,11 +66,18 @@ end
# gets items in sub category ( useful for page showing single sub_category )
get '/item/sub_category/:sub_category_id/offset/:offset/limit/:limit' do |sub_category_id_s, offset_s, limit_s|
+
sub_category_id, offset, limit = mass_to_i(sub_category_id_s, offset_s, limit_s)
input_invalid = offset_and_limit_invalid?(offset,limit) or sub_category_id <= 0
return [].to_json if input_invalid
+ all_in_sub_cat = filter_by_traits(Item.all_in_sub_category(sub_category_id))
+
items = Item.best_selling_in_sub_category(sub_category_id, offset, limit)
+ items = filter_by_traits(items)
+
+ add_total_count_header(all_in_sub_cat.count)
+
prepare_items_for_mass_display(items)
end
diff --git a/front-api/controllers/sub_category.rb b/front-api/controllers/sub_category.rb
new file mode 100644
index 0000000..c96775a
--- /dev/null
+++ b/front-api/controllers/sub_category.rb
@@ -0,0 +1,18 @@
+
+get '/subcategory' do
+ SubCategory
+ .eager_load(category: :section)
+ .order(:order)
+ .all
+ .to_json(:include => [filter_criterias: {include: :filter_criteria_values}, category: {include: :section} ])
+end
+
+get '/subcategory/:id' do
+ id = params[:id].to_i
+
+ SubCategory
+ .eager_load(category: :section)
+ .order(:order)
+ .find(id)
+ .to_json(:include => [filter_criterias: {include: :filter_criteria_values}, category: {include: :section} ])
+end
diff --git a/front-api/models/item.rb b/front-api/models/item.rb
index dbba0c2..8368e63 100644
--- a/front-api/models/item.rb
+++ b/front-api/models/item.rb
@@ -20,4 +20,5 @@ class Item < ActiveRecord::Base
scope :all_in_category, -> (category_id) { where(on_display: true).joins( sub_category: [:category]).where(["category_id = ?", category_id]) }
+ scope :all_in_sub_category, -> (sub_category_id) { where(on_display: true).where(["sub_category_id = ?", sub_category_id]) }
end
diff --git a/front-ui/app/actions/bySubCategoryActions.js b/front-ui/app/actions/bySubCategoryActions.js
new file mode 100644
index 0000000..49ad8ba
--- /dev/null
+++ b/front-ui/app/actions/bySubCategoryActions.js
@@ -0,0 +1,30 @@
+var AppDispatcher = require('../dispatcher/appDispatcher');
+var BySubCategoryConstants = require('../constants/bySubCategoryConstants');
+
+// Define action methods
+var BySubCategoryActions = {
+ load: function(subCategoryId, offset, limit, filter) {
+ AppDispatcher.handleAction({
+ actionType: BySubCategoryConstants.LOAD,
+ subCategoryId : subCategoryId,
+ offset: offset,
+ limit: limit,
+ filter: filter
+ });
+ },
+ filterCriteriaClick: function(fc, fcv) {
+ AppDispatcher.handleAction({
+ actionType: BySubCategoryConstants.FILTER_CRITERIA_CLICK,
+ fc: fc,
+ fcv: fcv
+ });
+ },
+ removeAppliedFilter: function(name) {
+ AppDispatcher.handleAction({
+ actionType: BySubCategoryConstants.REMOVE_APPLIED_FILTER,
+ name: name
+ });
+ },
+};
+
+module.exports = BySubCategoryActions;
diff --git a/front-ui/app/actions/navigationActions.js b/front-ui/app/actions/navigationActions.js
index be3c001..1e7c978 100644
--- a/front-ui/app/actions/navigationActions.js
+++ b/front-ui/app/actions/navigationActions.js
@@ -3,7 +3,6 @@ var NavigationConstants = require('../constants/navigationConstants');
// Define action methods
var NavigationActions = {
-
// select item
goToItemDetails: function(item) {
console.log("Going to item details");
@@ -20,10 +19,7 @@ var NavigationActions = {
url: '/sekcija/'+ section.get('id') + '/' + section.get('name')
});
},
-
- goToCategory: function(category,section, query, offset, limit) {
- var url ='/sekcija/' + section.get('name') +'/kategorija/'+ category.get('id') + '/' + category.get('name');
-
+ _getQueryStringPart: function(query, offset, limit) {
var q = '';
var qp = [];
@@ -46,17 +42,25 @@ var NavigationActions = {
if (qp.length > 0) {
q = '?' + qp.join('&');
}
+
+ return q;
+ },
+
+ goToCategory: function(category,section, query, offset, limit) {
+ var url ='/sekcija/' + section.get('name') +'/kategorija/'+ category.get('id') + '/' + category.get('name');
+ var q = this._getQueryStringPart(query, offset, limit);
AppDispatcher.handleAction({
actionType: NavigationConstants.CHANGE_URL,
url: (url + q)
});
},
- goToSubCategory: function(subCategory) {
- // TODO: implement when ready
+ goToSubCategory: function(subCategory, offset, limit, query) {
+ var q = this._getQueryStringPart(query, offset, limit);
+ var url = '/podkategorija/' + subCategory.get('id') + '/' + subCategory.get('name');
AppDispatcher.handleAction({
actionType: NavigationConstants.CHANGE_URL,
- url: '/'
+ url: (url + q)
});
},
diff --git a/front-ui/app/components/browsing/byCategory.js b/front-ui/app/components/browsing/byCategory.js
index 92b7148..fcc5100 100644
--- a/front-ui/app/components/browsing/byCategory.js
+++ b/front-ui/app/components/browsing/byCategory.js
@@ -1,6 +1,7 @@
var React = require('react'),
Router = require('react-router'),
Category = require('../../models/category'),
+ SubCategory = require('../../models/subCategory'),
Section = require('../../models/section'),
ItemCollection = require('../../models/itemCollection'),
ItemActions = require('../../actions/itemActions.js'),
@@ -10,7 +11,7 @@ var React = require('react'),
NavigationStore = require('../../stores/navigationStore'),
ItemList = require('../items/itemList'),
NavigationActions = require('../../actions/navigationActions'),
- LinkBanner = require('../linkBanner/linkBanner'),
+ LinkBanner = require('../linkBanner/linkBanner'),
Globals = require('../../globals');
var FilterCriteriaSelector = require('./filterCriteriaSelector');
@@ -45,6 +46,11 @@ var ByCategory = React.createClass({
NavigationActions.goToCategory(category, section, this.filter, 0, this.state.pagination.limit);
},
+ onSCClick: function(sc) {
+ var subCategory = new SubCategory(sc);
+ NavigationActions.goToSubCategory(subCategory, 0, Globals.defaultLimit);
+ event.preventDefault();
+ },
changePage: function(page) {
var section = new Section(this.state.category.get('section'));
var category = this.state.category;
@@ -60,7 +66,7 @@ var ByCategory = React.createClass({
Podkategorije
{(this.state.category.get('sub_categories') || []).map(function(sc) {
- return (- {sc.name}
)
+ return (- {sc.name}
)
})}
diff --git a/front-ui/app/components/browsing/bySubCategory.js b/front-ui/app/components/browsing/bySubCategory.js
new file mode 100644
index 0000000..0415aa5
--- /dev/null
+++ b/front-ui/app/components/browsing/bySubCategory.js
@@ -0,0 +1,80 @@
+var React = require('react'),
+ Router = require('react-router');
+
+var BySubCategoryActions = require('../../actions/bySubCategoryActions');
+var BySubCategoryStore = require('../../stores/bySubCategoryStore');
+var ItemStore = require('../../stores/itemStore');
+var CategoryStore = require('../../stores/categoryStore');
+
+var Globals = require('../../globals');
+var FilterCriteriaSelector = require('./filterCriteriaSelector');
+var AppliedFiltersList = require('./appliedFiltersList');
+
+var NavigationActions = require('../../actions/navigationActions');
+var ItemList = require('../items/itemList');
+var BySubCategory = React.createClass({
+ mixins: [Router.State],
+ getInitialState : function() {
+ return BySubCategoryStore.getState();
+ },
+ onFCClick: function(fc, fcv) {
+ BySubCategoryActions.filterCriteriaClick(fc, fcv);
+ },
+ removeAppliedFilter: function(name) {
+ BySubCategoryActions.removeAppliedFilter(name);
+ },
+ render : function() {
+
+ return (
+
+
+
+
+
+
+ Podkategorija {this.state.subCategory.get('name')}
+
+ Number of items in this subcategory: {this.state.items.length}
+
+
+
+
)
+ },
+ appliedSubCategoryFiltersArray: function() {
+ var filters = [];
+ for(var key in this.state.filter) {
+ if(this.state.filter.hasOwnProperty(key) && key !== 'limit' && key !== 'offset') {
+ filters.push({name: key, value: this.state.filter[key]});
+ }
+ }
+ return filters;
+ },
+ update: function() {
+ var subCategoryId = this.getParams().id;
+ var filter = this.getQuery();
+ var offset = filter.offset || 0;
+ var limit = filter.limit || Globals.DefaultPageSize;
+
+ BySubCategoryActions.load(subCategoryId, offset, limit, filter);
+ },
+ componentWillReceiveProps: function() {
+ this.update();
+ },
+ componentDidMount: function() {
+ BySubCategoryStore.addChangeListener(this._onChange);
+ this.update();
+ },
+ componentWillUnmount: function() {
+ BySubCategoryStore.removeChangeListener(this._onChange);
+ },
+ _onChange: function() {
+ if(this.isMounted()) {
+ this.setState(BySubCategoryStore.getState());
+ }
+ }
+});
+
+module.exports = BySubCategory;
diff --git a/front-ui/app/components/browsing/bySubcategory.js b/front-ui/app/components/browsing/bySubcategory.js
deleted file mode 100644
index e69de29..0000000
diff --git a/front-ui/app/constants/bySubCategoryConstants.js b/front-ui/app/constants/bySubCategoryConstants.js
new file mode 100644
index 0000000..68a2b72
--- /dev/null
+++ b/front-ui/app/constants/bySubCategoryConstants.js
@@ -0,0 +1,8 @@
+var keyMirror = require('react/lib/keyMirror');
+
+// Define action constants
+module.exports = keyMirror({
+ LOAD: null,
+ FILTER_CRITERIA_CLICK: null,
+ REMOVE_APPLIED_FILTER: null
+});
diff --git a/front-ui/app/models/subCategory.js b/front-ui/app/models/subCategory.js
new file mode 100644
index 0000000..c3b459f
--- /dev/null
+++ b/front-ui/app/models/subCategory.js
@@ -0,0 +1,13 @@
+var Backbone = require('backbone');
+var Globals = require('../globals');
+
+var SubCategory = Backbone.Model.extend({
+ urlRoot : Globals.ApiUrl + '/subcategory',
+ defaults : {
+ name: '',
+ filter_criterias: []
+ }
+});
+
+
+module.exports = SubCategory;
diff --git a/front-ui/app/router.js b/front-ui/app/router.js
index 2ea4f27..a7713e0 100644
--- a/front-ui/app/router.js
+++ b/front-ui/app/router.js
@@ -14,6 +14,7 @@ var CheckoutPage = require('./components/cart/checkoutPage');
var RootApp = require('./components/rootApp');
var StartPage = require('./components/startPage/startPage');
var ByCategory = require('./components/browsing/byCategory');
+var BySubCategory = require('./components/browsing/bySubCategory');
var BySection = require('./components/browsing/bySection');
var ThankYouPage = require('./components/thankyou/thankYouPage');
@@ -31,6 +32,7 @@ var routes = (
+
diff --git a/front-ui/app/stores/bySubCategoryStore.js b/front-ui/app/stores/bySubCategoryStore.js
new file mode 100644
index 0000000..2bd0807
--- /dev/null
+++ b/front-ui/app/stores/bySubCategoryStore.js
@@ -0,0 +1,170 @@
+var AppDispatcher = require('../dispatcher/appDispatcher');
+var EventEmitter = require('events').EventEmitter;
+//var SubCategoryCollection = require('../models/subCategoryCollection');
+var SubCategory = require('../models/subCategory');
+
+var NavigationActions = require('../actions/navigationActions');
+var BySubCategoryConstants = require('../constants/bySubCategoryConstants');
+var ItemCollection = require('../models/itemCollection');
+var _ = require('underscore');
+
+var _state = {
+ subCategory : (new SubCategory()),
+ items: (new ItemCollection()),
+ filter: {},
+ pagination: {}
+};
+//var _categoryDetails = new Category();
+
+//var loadSections = function() {
+ //var sections = new SectionCollection();
+ //sections.fetch({success: function() {
+ //sectionState.sections = sections.models;
+ //// change will be called automatically when
+ //// action is run but we need to emit it again
+ //// when the data arive
+ //// it's a bit "unfluxy" but convenient.
+ //// "true philosophy" would be to run another "data arrived" action
+ //SectionStore.emitChange();
+ //}});
+//};
+var fetchItemsBySubCategory = function(subCategoryId, offset, limit, query) {
+ //var items = _itemsByCategory;
+ query = query || {};
+ var items = new ItemCollection();
+ items.clearFilter();
+ items.setClassificationType(3);
+ items.setClassificationId(subCategoryId);
+ items.setLimit(limit);
+ items.setOffset(offset);
+
+ for(var key in query) {
+ if (query.hasOwnProperty(key) && key != 'limit' && key !='offset') {
+ items.addFilter(key, query[key]);
+ }
+ }
+
+ items.fetch({
+ success: function(collection, response, options) {
+ var total = options.xhr.getResponseHeader('x-total-count');
+ items.setTotalCount(total);
+
+ _state.items = items;
+ BySubCategoryStore.emitChange();
+ }});
+
+};
+var load = function(subCategoryId, offset, limit, filter) {
+
+ var subCategory = new SubCategory({id : subCategoryId});
+ subCategory.fetch({
+ success: function() {
+ _state.subCategory = subCategory;
+ fetchItemsBySubCategory(subCategoryId, offset, limit, filter);
+
+ }
+ });
+ _state.filter = filter;
+ //BySubCategoryStore.emitChange();
+};
+
+var handleFilterCriteriaClick = function(fc, fcv) {
+ _state.filter[fc.field_name] = fcv.filter_value;
+
+ setTimeout(function() {
+ NavigationActions.goToSubCategory(_state.subCategory, 0, _state.pagination.limit, _state.filter);
+ }, 0);
+};
+
+var handleRemoveAppliedFilter= function(name) {
+ delete _state.filter[name];
+
+ setTimeout(function() {
+ NavigationActions.goToSubCategory(_state.subCategory, 0, _state.pagination.limit, _state.filter);
+ }, 0);
+};
+//var loadCategoryDetails = function(categoryId) {
+ //var category = new Category({id : categoryId});
+ //category.fetch({
+ //success: function() {
+ //_categoryDetails = category;
+ //BySubCategoryStore.emitChange();
+ //}
+ //});
+//};
+
+//var setHovered = function(id) {
+ //sectionState.hoveredSection = id;
+//}
+
+
+// Extend SectionStore with EventEmitter to add eventing capabilities
+var BySubCategoryStore = _.extend({}, EventEmitter.prototype, {
+
+ getState: function() {
+ return _state;
+ },
+ // Emit Change event
+ emitChange: function() {
+ console.log("Emmiting BySubCategory change!");
+ this.emit('change');
+ },
+
+ // Add change listener
+ addChangeListener: function(callback) {
+ this.on('change', callback);
+ },
+
+ // Remove change listener
+ removeChangeListener: function(callback) {
+ this.removeListener('change', callback);
+ }
+});
+
+
+// Register callback with AppDispatcher
+AppDispatcher.register(function(payload) {
+ var action = payload.action;
+ var text;
+
+ switch(action.actionType) {
+
+ case BySubCategoryConstants.LOAD:
+ load(action.subCategoryId, action.offset, action.limit, action.filter);
+ break;
+
+ case BySubCategoryConstants.FILTER_CRITERIA_CLICK:
+ handleFilterCriteriaClick(action.fc, action.fcv);
+ break;
+ case BySubCategoryConstants.REMOVE_APPLIED_FILTER:
+ handleRemoveAppliedFilter(action.name);
+ break;
+
+ // Respond to SELECT_ITEM action
+ //case SectionConstants.LOAD_SECTIONS:
+ //loadSections();
+ //break;
+
+ //case SectionConstants.SET_SECTION_HOVER:
+ //setHovered(action.section.get('id'));
+ //break;
+
+ //case SectionConstants.UNSET_SECTION_HOVER:
+ //setHovered('');
+ //break;
+
+ //case CategoryConstants.LOAD_CATEGORY_DETAILS:
+ //loadCategoryDetails(action.categoryId);
+ //break;
+
+ default:
+ return true;
+ }
+
+ // If action was responded to, emit change event
+ BySubCategoryStore.emitChange();
+ return true;
+
+});
+
+module.exports = BySubCategoryStore;