diff --git a/lib/facet-helpers.js b/lib/facet-helpers.js
index abc01c0..b5ba21a 100644
--- a/lib/facet-helpers.js
+++ b/lib/facet-helpers.js
@@ -12,7 +12,9 @@ export function createFacetNameMap (arr, field) {
return out
}
+
export function getBreadcrumbList (pool, selected) {
+ const hasOwnProperty = Object.prototype.hasOwnProperty
let out = []
if (!selected)
@@ -23,31 +25,41 @@ export function getBreadcrumbList (pool, selected) {
if (!selKeys.length)
return out
+ // loop through the pool of potential facets
+ // (these are the responses returned w/ a search query)
for (let p = 0; p < pool.length; p++) {
- if (!selKeys.length)
- break
-
- let group = pool[p]
+ const group = pool[p]
+ // loop through the keys of the selected facets
+ // and find our matching group (which should exist
+ // because it's part of our search)
for (let s = 0; s < selKeys.length; s++) {
- let name = selKeys[s]
+ const name = selKeys[s]
if (group.name === name) {
+
+ // we're going to append our collection (`out`) with data
+ // for each of the selected facets. this will allow us to
+ // display a breadcrumb like 'Facet Group > Facet Value'
+ // using label values (for cleaner values) + retain the
+ // values used by the back-end
out = out.concat(selected[name].map(sk => {
+ const selVal = hasOwnProperty.call(sk, 'value') ? sk.value : sk
+ const selLabel = hasOwnProperty.call(sk, 'label') ? sk.label : sk
return {
group: {
- name: pool[p].name,
- label: pool[p].label
+ name: group.name,
+ label: group.label
},
facet: {
- value: sk.value,
- label: sk.label,
+ value: selVal,
+ label: selLabel,
}
}
}))
- selKeys.splice(s, 1)
- break
+ // selKeys.splice(s, 1)
+ // break
}
}
}
diff --git a/src/actions/search.js b/src/actions/search.js
index 933dcf7..f9856b2 100644
--- a/src/actions/search.js
+++ b/src/actions/search.js
@@ -15,14 +15,6 @@ import {
SEARCHING,
} from '../constants'
-const REQUIRED_OPTS = {
- search_field: 'search',
-}
-
-const DEFAULT_OPTS = {
- per_page: 10,
-}
-
const hasOwnProperty = Object.prototype.hasOwnProperty
function conductSearch (dispatch, query, facets, options, queryString) {
@@ -57,7 +49,7 @@ function conductSearch (dispatch, query, facets, options, queryString) {
export const searchCatalog = (query, facets, opts) => dispatch => {
// save ourselves the hassle of keeping track of these defaults
- const options = assign({}, REQUIRED_OPTS, opts)
+ const options = assign({}, opts)
if (!facets)
facets = {}
@@ -96,7 +88,7 @@ export const setSearchOption = (field, value) => (dispatch, getState) => {
const query = search.query || ''
const facets = assign({}, search.facets)
- const options = assign({}, DEFAULT_OPTS, REQUIRED_OPTS, search.options)
+ const options = assign({}, search.options)
// we'll pass null to remove the option
if (value === null) {
@@ -114,11 +106,11 @@ export const toggleSearchFacet = (field, facet, checked) => (dispatch, getState)
// recycling the previous search info
const query = search.query || ''
- const options = assign({}, DEFAULT_OPTS, REQUIRED_OPTS, search.options)
+ const options = assign({}, search.options)
const facets = assign({}, search.facets)
let dirty = false
- let idx
+ let idx = -1
if (facets[field]) {
idx = findIndex(facets[field], f => {
@@ -129,8 +121,6 @@ export const toggleSearchFacet = (field, facet, checked) => (dispatch, getState)
})
}
- else idx = -1
-
// add to selected-facets
if (checked) {
if (idx === -1) {
diff --git a/src/components/catalog/SearchBreadcrumbTrail.jsx b/src/components/catalog/SearchBreadcrumbTrail.jsx
index 4fb2a09..7db9026 100644
--- a/src/components/catalog/SearchBreadcrumbTrail.jsx
+++ b/src/components/catalog/SearchBreadcrumbTrail.jsx
@@ -6,24 +6,23 @@ const T = React.PropTypes
const SearchBreadcrumbTrail = React.createClass({
propTypes: {
onRemoveBreadcrumb: T.func.isRequired,
-
- facets: T.object,
+
+ breadcrumbs: T.array,
query: T.string,
},
- renderGroupBreadcrumbs: function (key) {
- const group = this.props.facets[key]
-
- return group.map((facet, index) => {
- const props = {
- key: key + index + facet.value,
- group: key,
- value: facet.label,
- onRemove: this.props.onRemoveBreadcrumb.bind(null, key, facet),
- }
-
- return React.createElement(SearchBreadcrumb, props)
- })
+ renderGroupBreadcrumbs: function (breadcrumb, index) {
+ const props = {
+ key: `bc${index}`,
+ group: breadcrumb.group.label,
+ value: breadcrumb.facet.label,
+ onRemove: this.props.onRemoveBreadcrumb.bind(null,
+ breadcrumb.group.name,
+ breadcrumb.facet,
+ ),
+ }
+
+ return
},
renderQuery: function () {
@@ -40,19 +39,13 @@ const SearchBreadcrumbTrail = React.createClass({
},
render: function () {
- if (!this.props.facets)
- return null
-
- const keys = Object.keys(this.props.facets)
-
- if (!keys.length)
- return null
+ const bc = this.props.breadcrumbs
return (
{this.renderQuery()}
- {keys.map(this.renderGroupBreadcrumbs)}
+ {!!bc.length && bc.map(this.renderGroupBreadcrumbs)}
)
}
diff --git a/src/pages/SearchResults.jsx b/src/pages/SearchResults.jsx
index 7ea7cd4..db58126 100644
--- a/src/pages/SearchResults.jsx
+++ b/src/pages/SearchResults.jsx
@@ -8,7 +8,6 @@ import FacetList from '../components/catalog/FacetList.jsx'
import FacetListWithViewMore from '../components/catalog/FacetListWithViewMore.jsx'
import FacetRangeLimitDate from '../components/catalog/FacetRangeLimitDate.jsx'
-import SearchBreadcrumb from '../components/catalog/SearchBreadcrumb.jsx'
import SearchBreadcrumbTrail from '../components/catalog/SearchBreadcrumbTrail.jsx'
import SearchResultsHeader from '../components/catalog/SearchResultsHeader.jsx'
@@ -19,11 +18,43 @@ import ResultsGalleryItem from '../components/catalog/ResultsGalleryItem.jsx'
import { getBreadcrumbList } from '../../lib/facet-helpers'
const SearchResults = React.createClass({
+ // TODO: clean this up a bit? this is a hold-over from when this component
+ // was handling the starting search form as well as the results
componentWillMount: function () {
const qs = this.props.location.search
- if (qs)
- this.props.searchCatalogByQueryString(qs).then(this.handleSearchResponse)
+ if (qs) {
+ this.props.searchCatalogByQueryString(qs)
+ }
+ },
+
+ componentWillReceiveProps: function (nextProps) {
+ // compare the queryString in the browser to the previously-searched
+ // one. if it differs, submit the new search. this allows the search
+ // to be updated when the user uses the back/forward buttons in the
+ // browser in addition to selecting facets/options
+ const queryString = window.location.search
+ const previousQueryString = this.props.search.queryString
+
+ // checking that `previousQueryString` is defined prevents this
+ // from being run on mount (when `queryString` will always not
+ // equal `undefined`).
+ if (previousQueryString && queryString !== previousQueryString)
+ return this.props.searchCatalogByQueryString(queryString)
+
+ // we're using `props.search.timestamp` as a unique identifier
+ // to signify that the new search results being passed as props
+ // differ than the ones previous. this could also be done with
+ // a shallow compare of the `search` object but since what would
+ // be changing is at a deeper level (the `search.facets` and
+ // `search.options` objects in particular), this could be costly
+ const timestamp = this.props.search.timestamp
+ const next = nextProps.search.timestamp
+
+ if (!next || timestamp === next)
+ return
+
+ this.handleSearchResponse(nextProps.search)
},
getInitialState: function () {
@@ -37,7 +68,6 @@ const SearchResults = React.createClass({
const options = this.props.search.options
this.props.searchCatalog(query, {}, this.props.search.options)
- .then(this.handleSearchResponse)
},
determineResultsComponent: function (which) {
@@ -66,28 +96,17 @@ const SearchResults = React.createClass({
}
},
- getFacetGroupInfo: function (pool, name) {
- for (let i = 0; i < pool.length; i++)
- if (pool[i].name === name)
- return {
- name: pool[i].name,
- label: pool[i].label
- }
-
- return null
- },
-
handleNextPage: function () {
const pages = this.state.pages
if (!pages.next_page)
return
- this.props.setSearchOption('page', pages.next_page).then(this.handleSearchResponse)
+ this.props.setSearchOption('page', pages.next_page)
},
handlePerPageChange: function (val) {
- this.props.setSearchOption('per_page', val).then(this.handleSearchResponse)
+ this.props.setSearchOption('per_page', val)
},
handlePreviousPage: function () {
@@ -98,24 +117,25 @@ const SearchResults = React.createClass({
const prev = pages.prev_page === 1 ? null : pages.prev_page
- this.props.setSearchOption('page', prev).then(this.handleSearchResponse)
+ this.props.setSearchOption('page', prev)
},
handleSearchResponse: function (res) {
if (!res) {
- console.log('no res!')
+ console.warn('no data passed to `SearchResults#handleSearchResponse')
return
}
- const facets = res.response.facets
+ const facets = res.results.facets
const breadcrumbs = getBreadcrumbList(facets, this.props.search.facets)
this.setState({
- results: res.response.docs,
- options: this.props.search.options,
- pages: res.response.pages,
- facets,
breadcrumbs,
+ facets,
+ options: this.props.search.options,
+ pages: res.results.pages,
+ results: res.results.docs,
+ timestamp: res.timestamp,
})
},
@@ -123,7 +143,6 @@ const SearchResults = React.createClass({
const { facets, options } = this.props.search
this.props.searchCatalog(query, facets, options)
- .then(this.handleSearchResponse)
},
maybeRenderLoadingModal: function () {
@@ -161,50 +180,26 @@ const SearchResults = React.createClass({
_onToggleFacet: function (which, key, facet) {
return this.props.toggleSearchFacet(key, facet, which)
- .then(this.handleSearchResponse)
- .catch(console.warn)
},
renderBreadcrumbs: function () {
- const bc = this.state.breadcrumbs
-
- if (!bc)
- return
+ if (!this.state.breadcrumbs)
+ return null
- const query = this.props.search.query
-
- const querybc = !query ? null : (
-
- )
-
- const crumbs = bc.map((crumb, index) => {
- const {label, name} = crumb.group
- const facet = crumb.facet
+ const onRemoveBreadcrumb = (key, value) => {
+ if (key === 'q')
+ return this.handleSubmitSearchQuery('')
- const props = {
- key: 'bc' + index + facet.value,
- group: label,
- value: facet.label,
- onRemove: this.onRemoveFacet.bind(null, name, facet)
- }
-
- return React.createElement(SearchBreadcrumb, props)
- })
+ return this.onRemoveFacet(key, value)
+ }
- const style = {
- marginBottom: '10px',
- marginTop: '-5px',
+ const props = {
+ breadcrumbs: this.state.breadcrumbs,
+ onRemoveBreadcrumb,
+ query: this.props.search.query,
}
- return (
-
- {[].concat(querybc, crumbs)}
-
- )
+ return React.createElement(SearchBreadcrumbTrail, props)
},
renderFacetSidebar: function () {
@@ -284,13 +279,15 @@ const SearchResults = React.createClass({
},
renderResults: function () {
- if (!this.state.results)
+ const results = this.state.results
+
+ if (typeof results === 'undefined')
return
const which = this.state.resultsView
const props = {
- data: this.state.results,
+ data: results,
displayComponent: this.determineResultsComponent(which),
offset: this.state.pages.offset_value,
containerProps: {
@@ -311,10 +308,6 @@ const SearchResults = React.createClass({
}
const styles = {
- container: {
- // backgroundColor: '#fafafa',
- },
-
sidebar: {
container: {
display: 'inline-block',
@@ -333,7 +326,7 @@ const SearchResults = React.createClass({
}
return (
-
+
{this.maybeRenderLoadingModal()}
diff --git a/src/reducers/search.js b/src/reducers/search.js
index 95da842..5caa204 100644
--- a/src/reducers/search.js
+++ b/src/reducers/search.js
@@ -7,22 +7,29 @@
* note: all of the heavy lifting is being done within the action/search creator
*
* {
- * // flagged when SEARCHING action is received
- * isSearching: bool
- *
- * // contains the actual search query
- * query: string
- *
* // facets grouped by field
* // { 'subject': [{value: 'art' ...}, {value: 'anthropology' ...} ]}
* facets: object
*
+ * // flagged when SEARCHING action is received
+ * isSearching: bool
+
* // search options
* // { 'per_page': 25 }
* options: object
*
+ * // contains the search query
+ * query: string
+ *
* // the actual formatted querystring (used for pushState)
* queryString: string
+ *
+ * // raw Blacklight results (specificially, the `response` object)
+ * results: object
+ *
+ * // Date.now() used to determine whether or not to update state on the
+ * // SearchResults page
+ * timestamp: number
* }
*/
@@ -81,45 +88,40 @@ function receiveError (/* state, action */) {
// the server.
function receiveResults (state, action) {
+ const results = action.results.response
+ const fullSet = results.facets
+ const selectedFacets = state.facets || {}
const facets = {}
- const selectedFacets = state.facets
-
- // if we previously don't have a `facets` to check against,
- // use an empty object to prevent from throwing
- const keys = Object.keys(selectedFacets || {})
- // bail early if no facet keys
- if (!keys.length) {
- return assign({}, state, {
- isSearching: false,
- })
- }
+ const keys = Object.keys(selectedFacets)
- const fullSet = action.results.response.facets
+ if (keys.length) {
+ keys.forEach(key => {
+ const facet = selectedFacets[key]
- keys.forEach(key => {
- const facet = selectedFacets[key]
+ facets[key] = facet.map(facetValue => {
+ // in most cases (read: not arriving from a link) the facets
+ // will be objects, so we'll just return them and deal with
+ // the minimal extra work
+ if (typeof facetValue === 'object' && facetValue !== null)
+ return facetValue
- facets[key] = facet.map(facetValue => {
- // in most cases (read: not arriving from a link) the facets
- // will be objects, so we'll just return them and deal with
- // the minimal extra work
- if (typeof facetValue === 'object' && facetValue !== null)
- return facetValue
+ // otherwise, loop through all of the facet-groups to find
+ // the appropriate one, and then loop through its items
+ // to locate the facet object
+ const group = arrayFind(fullSet, g => g.name === key)
+ return arrayFind(group.items, facetItem => facetItem.value === facetValue)
- // otherwise, loop through all of the facet-groups to find
- // the appropriate one, and then loop through its items
- // to locate the facet object
- const group = arrayFind(fullSet, g => g.name === key)
- return arrayFind(group.items, facetItem => facetItem.value === facetValue)
-
- // filter out any empty values that may have been returned
- // as `null`
- }).filter(Boolean)
- })
+ // filter out any empty values that may have been returned
+ // as `null`
+ }).filter(Boolean)
+ })
+ }
return assign({}, state, {
isSearching: false,
facets,
+ results,
+ timestamp: Date.now(),
})
}