1
0
mirror of https://github.com/ascribe/onion.git synced 2024-06-26 11:16:28 +02: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:
TimDaubenschuetz 2015-10-15 15:16:33 +02:00
commit 9dec5eab1d
6 changed files with 192 additions and 28 deletions

View File

@ -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)

View File

@ -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 {

View File

@ -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()}

View File

@ -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
View 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;

View File

@ -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;