From 7c1eb2f4fd5abe9ebca92251a9578fb8323adbbe Mon Sep 17 00:00:00 2001 From: Edin Dazdarevic Date: Sun, 29 Mar 2015 23:23:21 +0200 Subject: [PATCH] - browsing by subcategory --- front-api/controllers/item.rb | 7 + front-api/controllers/sub_category.rb | 18 ++ front-api/models/item.rb | 1 + front-ui/app/actions/bySubCategoryActions.js | 30 ++++ front-ui/app/actions/navigationActions.js | 20 ++- .../app/components/browsing/byCategory.js | 10 +- .../app/components/browsing/bySubCategory.js | 80 +++++++++ .../app/components/browsing/bySubcategory.js | 0 .../app/constants/bySubCategoryConstants.js | 8 + front-ui/app/models/subCategory.js | 13 ++ front-ui/app/router.js | 2 + front-ui/app/stores/bySubCategoryStore.js | 170 ++++++++++++++++++ 12 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 front-api/controllers/sub_category.rb create mode 100644 front-ui/app/actions/bySubCategoryActions.js create mode 100644 front-ui/app/components/browsing/bySubCategory.js delete mode 100644 front-ui/app/components/browsing/bySubcategory.js create mode 100644 front-ui/app/constants/bySubCategoryConstants.js create mode 100644 front-ui/app/models/subCategory.js create mode 100644 front-ui/app/stores/bySubCategoryStore.js 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
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;