+ {getLangText('We could not find any works related to you...')}
+
+
+ {getLangText('You\'re filtering by the search keyword: \'%s\' ', search)}
+
+
+
+
);
} else {
diff --git a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js
index 0890db00..b7db7ce6 100644
--- a/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js
+++ b/js/components/ascribe_piece_list_toolbar/piece_list_toolbar.js
@@ -4,16 +4,16 @@ import React from 'react';
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
import PieceListToolbarOrderWidget from './piece_list_toolbar_order_widget';
+import SearchBar from '../search_bar';
+
+import AppConstants from '../../constants/application_constants';
-import Input from 'react-bootstrap/lib/Input';
-import Glyphicon from 'react-bootstrap/lib/Glyphicon';
-import { getLangText } from '../../utils/lang_utils';
let PieceListToolbar = React.createClass({
-
propTypes: {
className: React.PropTypes.string,
searchFor: React.PropTypes.func,
+ searchQuery: React.PropTypes.string,
filterParams: React.PropTypes.arrayOf(
React.PropTypes.shape({
label: React.PropTypes.string,
@@ -39,11 +39,6 @@ let PieceListToolbar = React.createClass({
])
},
- searchFor() {
- let searchTerm = this.refs.search.getInputDOMNode().value;
- this.props.searchFor(searchTerm);
- },
-
getFilterWidget(){
if (this.props.filterParams){
return (
@@ -55,6 +50,7 @@ let PieceListToolbar = React.createClass({
}
return null;
},
+
getOrderWidget(){
if (this.props.orderParams){
return (
@@ -68,24 +64,21 @@ let PieceListToolbar = React.createClass({
},
render() {
- let searchIcon = ;
+ const { className, children, searchFor, searchQuery } = this.props;
return (
-
+
- {this.props.children}
-
-
-
+ {children}
+
{this.getOrderWidget()}
{this.getFilterWidget()}
diff --git a/js/components/piece_list.js b/js/components/piece_list.js
index 35dcaba0..05557418 100644
--- a/js/components/piece_list.js
+++ b/js/components/piece_list.js
@@ -157,6 +157,7 @@ let PieceList = React.createClass({
diff --git a/js/components/search_bar.js b/js/components/search_bar.js
new file mode 100644
index 00000000..e8382489
--- /dev/null
+++ b/js/components/search_bar.js
@@ -0,0 +1,140 @@
+'use strict';
+
+import React from 'react';
+
+import Input from 'react-bootstrap/lib/Input';
+import Glyphicon from 'react-bootstrap/lib/Glyphicon';
+import { getLangText } from '../utils/lang_utils';
+
+
+const { func, string, number } = React.PropTypes;
+
+const SearchBar = React.createClass({
+ propTypes: {
+ // a function that accepts a string as a search query and updates the
+ // propagated `searchQuery` after successfully retrieving the
+ // request from the server
+ searchFor: func.isRequired,
+ searchQuery: string.isRequired,
+
+ className: string,
+
+ // the number of milliseconds the component
+ // should wait before requesting search results from the server
+ threshold: number.isRequired
+ },
+
+ getInitialState() {
+ return {
+ timer: null,
+ searchQuery: '',
+ loading: false
+ };
+ },
+
+ componentDidUpdate(prevProps) {
+ const searchQueryProps = this.props.searchQuery;
+ const searchQueryPrevProps = prevProps.searchQuery;
+ const searchQueryState = this.state.searchQuery;
+ const { loading } = this.state;
+
+ /**
+ * 1. Condition: `loading` must be true, which implies that `evaluateTimer`,
+ * has already been called
+ *
+ * AND
+ *
+ * (
+ * 2. Condition: `searchQueryProps` and `searchQueryState` are true and equal
+ * (which means that the search query has been propagated to the inner
+ * fetch method of `fetchPieceList`, which in turn means that a fetch
+ * has completed)
+ *
+ * OR
+ *
+ * 3. Condition: `searchQueryProps` and `searchQueryState` can be any value (`true` or
+ * `false`, as long as they're equal). This case only occurs when the user
+ * has entered a `searchQuery` and deletes the query in one go, reseting
+ * `searchQueryProps` to empty string ('' === false) again.
+ * )
+ */
+
+ const firstCondition = !!loading;
+ const secondCondition = searchQueryProps && searchQueryState && searchQueryProps === searchQueryState;
+ const thirdCondition = !searchQueryPrevProps && searchQueryProps === searchQueryState;
+
+ if(firstCondition && (secondCondition || thirdCondition)) {
+ this.setState({ loading: false });
+ }
+ },
+
+ componentWillReceiveProps(nextProps) {
+ /**
+ * This enables the `PieceListStore` to override the state
+ * of that component in case someone is changing the `searchQuery` on
+ * another component.
+ *
+ * Like how it's being done in the 'Clear search' dialog.
+ */
+ if(this.props.searchQuery !== nextProps.searchQuery || !this.state.searchQuery) {
+ this.setState({ searchQuery: nextProps.searchQuery });
+ }
+ },
+
+ startTimer(searchQuery) {
+ const { timer } = this.state;
+ const { threshold } = this.props;
+
+ // The timer waits for the specified threshold time in milliseconds
+ // and then calls `evaluateTimer`.
+ // If another letter has been called in the mean time (timespan < `threshold`),
+ // the present timer gets cleared and a new one is added to `this.state`.
+ // This means that `evaluateTimer`, will only be called when the threshold has actually
+ // passed,
+ clearTimeout(timer); // apparently `clearTimeout` can be called with null, without throwing errors
+ const newTimer = setTimeout(this.evaluateTimer(searchQuery), threshold);
+
+ this.setState({ timer: newTimer });
+ },
+
+ evaluateTimer(searchQuery) {
+ return () => {
+ this.setState({ timer: null, loading: true }, () => {
+ // search for the query
+ this.props.searchFor(searchQuery);
+ });
+ };
+ },
+
+ handleChange({ target: { value }}) {
+ // On each letter entry we're updating the state of the component
+ // and start a timer, which we're also pushing to the state
+ // of the component
+ this.startTimer(value);
+ this.setState({ searchQuery: value });
+ },
+
+ render() {
+ let searchIcon = ;
+ const { className } = this.props;
+ const { loading, searchQuery } = this.state;
+
+ if(loading) {
+ searchIcon = ;
+ }
+
+ return (
+
+
+
+ );
+ }
+});
+
+
+export default SearchBar;
\ No newline at end of file
diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js
index ce893791..0fe5e210 100644
--- a/js/constants/application_constants.js
+++ b/js/constants/application_constants.js
@@ -78,7 +78,8 @@ let constants = {
'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA',
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV',
- 'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA']
+ 'SPA', 'GESTOR', 'VISaRTA', 'RAO', 'LITA', 'DALRO', 'VeGaP', 'BUS', 'ProLitteris', 'AGADU', 'AUTORARTE', 'BUBEDRA', 'BBDA', 'BCDA', 'BURIDA', 'ADAVIS', 'BSDA'],
+ 'searchThreshold': 500
};
export default constants;