mirror of
https://github.com/ascribe/onion.git
synced 2025-02-01 20:39:57 +01:00
Merged in AD-744-search-filter-not-working-i-type- (pull request #97)
Ad 744 search filter not working i type
This commit is contained in:
commit
9dec5eab1d
@ -5,6 +5,7 @@ import Q from 'q';
|
||||
|
||||
import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
||||
|
||||
|
||||
class PieceListActions {
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
@ -21,17 +22,16 @@ class PieceListActions {
|
||||
this.actions.updatePieceList({
|
||||
page,
|
||||
pageSize,
|
||||
search,
|
||||
orderBy,
|
||||
orderAsc,
|
||||
filterBy,
|
||||
search: '',
|
||||
pieceList: [],
|
||||
pieceListCount: -1,
|
||||
unfilteredPieceListCount: -1
|
||||
});
|
||||
|
||||
// afterwards, we can load the list
|
||||
|
||||
return Q.Promise((resolve, reject) => {
|
||||
PieceListFetcher
|
||||
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
|
||||
|
@ -9,21 +9,49 @@ let AccordionList = React.createClass({
|
||||
className: React.PropTypes.string,
|
||||
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
|
||||
loadingElement: React.PropTypes.element,
|
||||
count: React.PropTypes.number
|
||||
count: React.PropTypes.number,
|
||||
itemList: React.PropTypes.arrayOf(React.PropTypes.object),
|
||||
search: React.PropTypes.string,
|
||||
searchFor: React.PropTypes.func
|
||||
},
|
||||
|
||||
|
||||
clearSearch() {
|
||||
this.props.searchFor('');
|
||||
},
|
||||
|
||||
render() {
|
||||
const { search } = this.props;
|
||||
|
||||
if(this.props.itemList && this.props.itemList.length > 0) {
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
} else if(this.props.count === 0) {
|
||||
} else if(this.props.count === 0 && !search) {
|
||||
return (
|
||||
<div className="ascribe-accordion-list-placeholder">
|
||||
<p className="text-center">{getLangText('We could not find any works related to you...')}</p>
|
||||
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
||||
<p className="text-center">
|
||||
{getLangText('We could not find any works related to you...')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{getLangText('To register one, click')}
|
||||
<a href="register_piece">{getLangText('here')}</a>!
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else if(this.props.count === 0 && search) {
|
||||
return (
|
||||
<div className="ascribe-accordion-list-placeholder">
|
||||
<p className="text-center">
|
||||
{getLangText('We could not find any works related to you...')}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
{getLangText('You\'re filtering by the search keyword: \'%s\' ', search)}
|
||||
</p>
|
||||
<p className="text-center">
|
||||
<button className="btn btn-sm btn-default" onClick={this.clearSearch}>{getLangText('Clear search')}</button>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
@ -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 = <Glyphicon glyph='search' className="filter-glyph"/>;
|
||||
const { className, children, searchFor, searchQuery } = this.props;
|
||||
|
||||
return (
|
||||
<div className={this.props.className}>
|
||||
<div className={className}>
|
||||
<div className="row">
|
||||
<div className="col-xs-12 col-sm-10 col-md-8 col-lg-8 col-sm-offset-1 col-md-offset-2 col-lg-offset-2">
|
||||
<div className="row">
|
||||
<span className="pull-left">
|
||||
{this.props.children}
|
||||
</span>
|
||||
<span className="pull-right search-bar ascribe-input-glyph">
|
||||
<Input
|
||||
type='text'
|
||||
ref="search"
|
||||
placeholder={getLangText('Search%s', '...')}
|
||||
onChange={this.searchFor}
|
||||
addonAfter={searchIcon} />
|
||||
{children}
|
||||
</span>
|
||||
<SearchBar
|
||||
className="pull-right search-bar ascribe-input-glyph"
|
||||
searchFor={searchFor}
|
||||
searchQuery={searchQuery}
|
||||
threshold={AppConstants.searchThreshold}/>
|
||||
<span className="pull-right">
|
||||
{this.getOrderWidget()}
|
||||
{this.getFilterWidget()}
|
||||
|
@ -157,6 +157,7 @@ let PieceList = React.createClass({
|
||||
<PieceListToolbar
|
||||
className="ascribe-piece-list-toolbar"
|
||||
searchFor={this.searchFor}
|
||||
searchQuery={this.state.search}
|
||||
filterParams={this.props.filterParams}
|
||||
orderParams={this.props.orderParams}
|
||||
filterBy={this.state.filterBy}
|
||||
@ -177,6 +178,7 @@ let PieceList = React.createClass({
|
||||
orderBy={this.state.orderBy}
|
||||
orderAsc={this.state.orderAsc}
|
||||
search={this.state.search}
|
||||
searchFor={this.searchFor}
|
||||
page={this.state.page}
|
||||
pageSize={this.state.pageSize}
|
||||
loadingElement={loadingElement}>
|
||||
|
140
js/components/search_bar.js
Normal file
140
js/components/search_bar.js
Normal file
@ -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 = <Glyphicon glyph='search' className="filter-glyph"/>;
|
||||
const { className } = this.props;
|
||||
const { loading, searchQuery } = this.state;
|
||||
|
||||
if(loading) {
|
||||
searchIcon = <span className="glyph-ascribe-spool-chunked ascribe-color spin"/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
<Input
|
||||
type='text'
|
||||
value={searchQuery}
|
||||
placeholder={getLangText('Search%s', '...')}
|
||||
onChange={this.handleChange}
|
||||
addonAfter={searchIcon} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export default SearchBar;
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user