basic version of search implemented
This commit is contained in:
@@ -1,7 +1,17 @@
|
|||||||
|
# TODO: make this private, not-public facing.
|
||||||
|
# for now we keep this here for simplicity reasons
|
||||||
get '/search/index' do
|
get '/search/index' do
|
||||||
es_client = Elasticsearch::Client.new log: true
|
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|
|
all_items.each do |item|
|
||||||
es_client.index index: 'ribica', type: 'items', id: item.id, body: {
|
es_client.index index: 'ribica', type: 'items', id: item.id, body: {
|
||||||
title: 'Test',
|
title: 'Test',
|
||||||
@@ -16,3 +26,39 @@ get '/search/index' do
|
|||||||
|
|
||||||
"ok".to_json
|
"ok".to_json
|
||||||
end
|
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
|
||||||
|
|||||||
@@ -23,11 +23,11 @@ ActiveRecord::Schema.define(version: 20150321052740) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "anonymous_id_string"
|
t.string "anonymous_id_string"
|
||||||
t.integer "delivery_destination_id"
|
t.integer "delivery_destination_id"
|
||||||
t.boolean "confirmed"
|
t.boolean "confirmed", default: false
|
||||||
t.boolean "packed"
|
t.boolean "packed", default: false
|
||||||
t.boolean "canceled_on_check"
|
t.boolean "canceled_on_check", default: false
|
||||||
t.boolean "canceled_on_delivery"
|
t.boolean "canceled_on_delivery", default: false
|
||||||
t.boolean "delivered"
|
t.boolean "delivered", default: false
|
||||||
t.text "internal_note"
|
t.text "internal_note"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -109,8 +109,8 @@ ActiveRecord::Schema.define(version: 20150321052740) do
|
|||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.string "tags"
|
t.string "tags"
|
||||||
t.json "traits"
|
t.json "traits"
|
||||||
t.integer "supplier_id"
|
|
||||||
t.decimal "weight", precision: 5, scale: 3
|
t.decimal "weight", precision: 5, scale: 3
|
||||||
|
t.integer "supplier_id"
|
||||||
t.integer "delivery_time_estimation_id"
|
t.integer "delivery_time_estimation_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -85,10 +85,13 @@ var NavigationActions = {
|
|||||||
actionType: NavigationConstants.CHANGE_URL,
|
actionType: NavigationConstants.CHANGE_URL,
|
||||||
url: '/hvala'
|
url: '/hvala'
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
goToSearchResults : function(q) {
|
||||||
|
AppDispatcher.handleAction({
|
||||||
|
actionType: NavigationConstants.CHANGE_URL,
|
||||||
|
url: '/pretraga?q=' + q
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = NavigationActions;
|
module.exports = NavigationActions;
|
||||||
|
|||||||
20
front-ui/app/actions/searchActions.js
Normal file
20
front-ui/app/actions/searchActions.js
Normal file
@@ -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;
|
||||||
@@ -8,6 +8,7 @@ var React = require('react'),
|
|||||||
InitializationActions = require('../actions/initializationActions');
|
InitializationActions = require('../actions/initializationActions');
|
||||||
|
|
||||||
var CartIcon = require('./cart/cartIcon');
|
var CartIcon = require('./cart/cartIcon');
|
||||||
|
var SearchBox = require('./shared/searchBox');
|
||||||
|
|
||||||
var RootApp = React.createClass({
|
var RootApp = React.createClass({
|
||||||
|
|
||||||
@@ -55,10 +56,15 @@ var RootApp = React.createClass({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<div className='col-md-12' id='header'>
|
<div className='col-md-8' id='header'>
|
||||||
<SectionsListComponent />
|
<SectionsListComponent />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="col-md-4">
|
||||||
|
<SearchBox />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='row'>
|
<div className='row'>
|
||||||
<RouteHandler />
|
<RouteHandler />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
52
front-ui/app/components/search/searchResultsPage.js
Normal file
52
front-ui/app/components/search/searchResultsPage.js
Normal file
@@ -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 (
|
||||||
|
<div>
|
||||||
|
<h1>Resultati pretrage za {this.state.q}</h1>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemList items={this.state.items} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
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;
|
||||||
46
front-ui/app/components/shared/searchBox.js
Normal file
46
front-ui/app/components/shared/searchBox.js
Normal file
@@ -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 (<div className="input-group">
|
||||||
|
<input type="text" onKeyPress={this.onKeyPress} className="form-control" value={this.state.q} onChange={this.onSearchBoxChange} placeholder="Pretraga"> </input>
|
||||||
|
<span className="input-group-btn">
|
||||||
|
<button className="btn btn-default" type="button" onClick={this.onSearchClick}>Traži</button>
|
||||||
|
</span>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = SearchBox;
|
||||||
7
front-ui/app/constants/searchConstants.js
Normal file
7
front-ui/app/constants/searchConstants.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
var keyMirror = require('react/lib/keyMirror');
|
||||||
|
|
||||||
|
// Define action constants
|
||||||
|
module.exports = keyMirror({
|
||||||
|
SEARCH_BOX_CHANGE: null,
|
||||||
|
GET_SEARCH_RESULTS: null
|
||||||
|
});
|
||||||
24
front-ui/app/models/itemSearchCollection.js
Normal file
24
front-ui/app/models/itemSearchCollection.js
Normal file
@@ -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;
|
||||||
@@ -18,6 +18,7 @@ var ThankYouPage = require('./components/thankyou/thankYouPage');
|
|||||||
|
|
||||||
var Register = require('./components/account/register');
|
var Register = require('./components/account/register');
|
||||||
var Login = require('./components/account/login');
|
var Login = require('./components/account/login');
|
||||||
|
var SearchResultsPage = require('./components/search/searchResultsPage');
|
||||||
|
|
||||||
|
|
||||||
var routes = (
|
var routes = (
|
||||||
@@ -30,6 +31,7 @@ var routes = (
|
|||||||
<Route name='login' path="/login" handler={Login} />
|
<Route name='login' path="/login" handler={Login} />
|
||||||
<Route name='byCat' path="sekcija/:sekcijaName/kategorija/:id/*" handler={ByCategory} />
|
<Route name='byCat' path="sekcija/:sekcijaName/kategorija/:id/*" handler={ByCategory} />
|
||||||
<Route name='hvala' path="/hvala" handler={ThankYouPage} />
|
<Route name='hvala' path="/hvala" handler={ThankYouPage} />
|
||||||
|
<Route name='pretraga' path="/pretraga" handler={SearchResultsPage} />
|
||||||
<DefaultRoute handler={StartPage}/>
|
<DefaultRoute handler={StartPage}/>
|
||||||
</Route>
|
</Route>
|
||||||
);
|
);
|
||||||
|
|||||||
88
front-ui/app/stores/searchStore.js
Normal file
88
front-ui/app/stores/searchStore.js
Normal file
@@ -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;
|
||||||
Reference in New Issue
Block a user