diff --git a/front-api/controllers/search.rb b/front-api/controllers/search.rb
index 617501c..20dd909 100644
--- a/front-api/controllers/search.rb
+++ b/front-api/controllers/search.rb
@@ -1,7 +1,17 @@
+# TODO: make this private, not-public facing.
+# for now we keep this here for simplicity reasons
get '/search/index' do
es_client = Elasticsearch::Client.new log: true
- all_items = Item.all.to_a
+ # first delete the index
+ begin
+ es_client.indices.delete index: 'ribica'
+ rescue
+ logger.warn "Ribica index could not be deleted. Continuing with indexing operation..."
+ end
+
+ # now index items
+ all_items = Item.includes(sub_category: { category: :section }).all.to_a
all_items.each do |item|
es_client.index index: 'ribica', type: 'items', id: item.id, body: {
title: 'Test',
@@ -16,3 +26,39 @@ get '/search/index' do
"ok".to_json
end
+
+get '/search' do
+ es_client = Elasticsearch::Client.new log: true
+ q = params[:q]
+
+ # for now we do the basic query
+ results = es_client.search index: 'ribica', type: 'items', body: { query: { match: { _all: q } } }
+ ids = results["hits"]["hits"].map do |r|
+ r["_id"]
+ end
+
+ ids_with_score = {}
+ results["hits"]["hits"].each do |r|
+ ids_with_score[r["_id"].to_i] = {:score => r["_score"], :item => nil}
+ end
+
+ if ids.length > 0
+ res = Item.where(:id => ids).to_a
+ # make sure we have correct relevance order, since `where in` does not guarantee order
+ res.each do |ii|
+ ids_with_score[ii.id][:item] = ii
+ end
+ final = []
+ ids_with_score.each do |k,v|
+ final << v
+ end
+ final.sort_by! {|v| -v[:score]}
+
+ final = final.map do |f|
+ f[:item]
+ end
+ prepare_items_for_mass_display(final)
+ else
+ [].to_json
+ end
+end
diff --git a/front-api/db/schema.rb b/front-api/db/schema.rb
index fc2c17e..e0eac76 100644
--- a/front-api/db/schema.rb
+++ b/front-api/db/schema.rb
@@ -23,11 +23,11 @@ ActiveRecord::Schema.define(version: 20150321052740) do
t.datetime "updated_at", null: false
t.string "anonymous_id_string"
t.integer "delivery_destination_id"
- t.boolean "confirmed"
- t.boolean "packed"
- t.boolean "canceled_on_check"
- t.boolean "canceled_on_delivery"
- t.boolean "delivered"
+ t.boolean "confirmed", default: false
+ t.boolean "packed", default: false
+ t.boolean "canceled_on_check", default: false
+ t.boolean "canceled_on_delivery", default: false
+ t.boolean "delivered", default: false
t.text "internal_note"
end
@@ -109,8 +109,8 @@ ActiveRecord::Schema.define(version: 20150321052740) do
t.datetime "updated_at", null: false
t.string "tags"
t.json "traits"
- t.integer "supplier_id"
t.decimal "weight", precision: 5, scale: 3
+ t.integer "supplier_id"
t.integer "delivery_time_estimation_id"
end
diff --git a/front-ui/app/actions/navigationActions.js b/front-ui/app/actions/navigationActions.js
index 87f923e..17a7d4a 100644
--- a/front-ui/app/actions/navigationActions.js
+++ b/front-ui/app/actions/navigationActions.js
@@ -85,10 +85,13 @@ var NavigationActions = {
actionType: NavigationConstants.CHANGE_URL,
url: '/hvala'
});
+ },
+ goToSearchResults : function(q) {
+ AppDispatcher.handleAction({
+ actionType: NavigationConstants.CHANGE_URL,
+ url: '/pretraga?q=' + q
+ });
}
-
-
-
};
module.exports = NavigationActions;
diff --git a/front-ui/app/actions/searchActions.js b/front-ui/app/actions/searchActions.js
new file mode 100644
index 0000000..2694862
--- /dev/null
+++ b/front-ui/app/actions/searchActions.js
@@ -0,0 +1,20 @@
+var AppDispatcher = require('../dispatcher/appDispatcher');
+var SearchContants = require('../constants/searchConstants');
+
+// Define action methods
+var SearchActions = {
+ searchBoxChange: function(q) {
+ AppDispatcher.handleAction({
+ actionType: SearchContants.SEARCH_BOX_CHANGE,
+ q : q
+ });
+ },
+ getSearchResults: function(q) {
+ AppDispatcher.handleAction({
+ actionType: SearchContants.GET_SEARCH_RESULTS,
+ q : q
+ });
+ }
+};
+
+module.exports = SearchActions;
diff --git a/front-ui/app/components/rootApp.js b/front-ui/app/components/rootApp.js
index 36f41ce..ad2dde0 100644
--- a/front-ui/app/components/rootApp.js
+++ b/front-ui/app/components/rootApp.js
@@ -8,6 +8,7 @@ var React = require('react'),
InitializationActions = require('../actions/initializationActions');
var CartIcon = require('./cart/cartIcon');
+var SearchBox = require('./shared/searchBox');
var RootApp = React.createClass({
@@ -55,10 +56,15 @@ var RootApp = React.createClass({
-
+
diff --git a/front-ui/app/components/search/searchResultsPage.js b/front-ui/app/components/search/searchResultsPage.js
new file mode 100644
index 0000000..f86538a
--- /dev/null
+++ b/front-ui/app/components/search/searchResultsPage.js
@@ -0,0 +1,52 @@
+var React = require('react'),
+ NavigationActions = require('../../actions/navigationActions'),
+ Globals = require('../../globals')
+ Router = require("react-router"),
+ Link = Router.Link;
+
+var SearchStore = require('../../stores/searchStore');
+var SearchActions = require('../../actions/searchActions');
+
+var ItemList = require('../items/itemList');
+var SearchResultsPage = React.createClass({
+ mixins: [Router.State],
+ getInitialState: function() {
+ return SearchStore.getSearchResultsState();
+ },
+ render: function() {
+ return (
+
+
Resultati pretrage za {this.state.q}
+
+
+
+
+
+ );
+
+ },
+ componentWillReceiveProps: function() {
+ this.update();
+ },
+ update: function(){
+ var query = this.getQuery();
+ SearchActions.getSearchResults(query.q);
+ },
+ componentDidMount: function() {
+ SearchStore.addChangeListener(this._onChange);
+ this.update();
+
+ //CartActions.load();
+ },
+ componentWillUnmount: function () {
+ SearchStore.removeChangeListener(this._onChange);
+ },
+ _onChange: function() {
+ if(this.isMounted()) {
+ this.setState(SearchStore.getSearchResultsState());
+ }
+ }
+});
+
+
+module.exports = SearchResultsPage;
diff --git a/front-ui/app/components/shared/searchBox.js b/front-ui/app/components/shared/searchBox.js
new file mode 100644
index 0000000..bf2ef07
--- /dev/null
+++ b/front-ui/app/components/shared/searchBox.js
@@ -0,0 +1,46 @@
+var React = require('react'),
+ Router = require('react-router');
+
+var NavigationActions = require('../../actions/navigationActions');
+var SearchActions = require('../../actions/searchActions');
+var SearchStore = require('../../stores/searchStore');
+
+var SearchBox = React.createClass({
+ getInitialState: function() {
+ return SearchStore.getSearchBoxState();
+ },
+ onSearchClick: function(e) {
+ NavigationActions.goToSearchResults(this.state.q);
+ e.preventDefault();
+ },
+ componentDidMount: function() {
+ SearchStore.addChangeListener(this.onSearchStoreChange);
+ },
+ componentWillUnmount: function() {
+ SearchStore.removeChangeListener(this.onSearchStoreChange);
+ },
+ onSearchStoreChange: function() {
+ if(this.isMounted()) {
+ this.setState(SearchStore.getSearchBoxState());
+ }
+ },
+ onSearchBoxChange: function(e) {
+ SearchActions.searchBoxChange(e.currentTarget.value);
+ },
+ onKeyPress: function(e) {
+ var enterKeyCode = 13;
+ if(e.which == enterKeyCode) {
+ NavigationActions.goToSearchResults(this.state.q);
+ }
+ },
+ render: function() {
+ return (
+
+
+
+
+
)
+ }
+});
+
+module.exports = SearchBox;
diff --git a/front-ui/app/constants/searchConstants.js b/front-ui/app/constants/searchConstants.js
new file mode 100644
index 0000000..9166a75
--- /dev/null
+++ b/front-ui/app/constants/searchConstants.js
@@ -0,0 +1,7 @@
+var keyMirror = require('react/lib/keyMirror');
+
+// Define action constants
+module.exports = keyMirror({
+ SEARCH_BOX_CHANGE: null,
+ GET_SEARCH_RESULTS: null
+});
diff --git a/front-ui/app/models/itemSearchCollection.js b/front-ui/app/models/itemSearchCollection.js
new file mode 100644
index 0000000..816cdbd
--- /dev/null
+++ b/front-ui/app/models/itemSearchCollection.js
@@ -0,0 +1,24 @@
+var Backbone = require('backbone'),
+ Item = require('./item'),
+ Globals = require('../globals');
+
+var ItemSearchCollection = Backbone.Collection.extend({
+ initialize: function() {
+ $.ajaxPrefilter(
+ function(options, originalOptions, jqXHR) {
+ options.xhrFields = {
+ withCredentials: true
+ }
+ }
+ );
+ },
+ setQuery: function(q) {
+ this.q = q;
+ },
+ model: Item,
+ url: function() {
+ return Globals.ApiUrl + "/search?q=" + this.q;
+ }
+});
+
+module.exports = ItemSearchCollection;
diff --git a/front-ui/app/router.js b/front-ui/app/router.js
index e5755c9..452b9cc 100644
--- a/front-ui/app/router.js
+++ b/front-ui/app/router.js
@@ -18,6 +18,7 @@ var ThankYouPage = require('./components/thankyou/thankYouPage');
var Register = require('./components/account/register');
var Login = require('./components/account/login');
+var SearchResultsPage = require('./components/search/searchResultsPage');
var routes = (
@@ -30,6 +31,7 @@ var routes = (
+
);
diff --git a/front-ui/app/stores/searchStore.js b/front-ui/app/stores/searchStore.js
new file mode 100644
index 0000000..5a58ba1
--- /dev/null
+++ b/front-ui/app/stores/searchStore.js
@@ -0,0 +1,88 @@
+var AppDispatcher = require('../dispatcher/appDispatcher');
+var EventEmitter = require('events').EventEmitter;
+
+var SearchConstants = require('../constants/searchConstants');
+var SearchActions = require('../actions/searchActions');
+var NavigationActions = require('../actions/navigationActions');
+
+var ItemSearchCollection = require('../models/itemSearchCollection');
+var globals = require('../globals');
+var _ = require('underscore');
+
+var _searchBoxState = {
+ q: ''
+};
+
+var _searchResultsState = {
+ q: '',
+ items: (new ItemSearchCollection())
+};
+
+var handleSearchBoxChange = function(q) {
+ _searchBoxState.q = q;
+};
+
+
+var handleGetSearchResults = function(q) {
+ _searchResultsState.q = q;
+ _searchBoxState.q = '';
+
+ var searchResults = new ItemSearchCollection();
+ searchResults.setQuery(q);
+ searchResults.fetch({success: function() {
+ _searchResultsState.items = searchResults;
+ SearchStore.emit('change');
+ }});
+};
+
+// Extend ItemStore with EventEmitter to add eventing capabilities
+var SearchStore = _.extend({}, EventEmitter.prototype, {
+
+ getSearchBoxState: function() {
+ return _searchBoxState;
+ },
+ getSearchResultsState: function() {
+ return _searchResultsState;
+ },
+ // Emit Change event
+ emitChange: function() {
+ console.log("SearchStore 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
+SearchStore.dispatchToken = AppDispatcher.register(function(payload) {
+ var action = payload.action;
+
+ switch(action.actionType) {
+ case SearchConstants.SEARCH_BOX_CHANGE:
+ handleSearchBoxChange(action.q);
+ break;
+
+ case SearchConstants.GET_SEARCH_RESULTS:
+ handleGetSearchResults(action.q);
+ break;
+ default:
+ return true;
+ }
+
+ // If action was responded to, emit change event
+ SearchStore.emitChange();
+ return true;
+
+});
+
+module.exports = SearchStore;