mirror of
https://github.com/ascribe/onion.git
synced 2025-02-14 21:10:27 +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';
|
import PieceListFetcher from '../fetchers/piece_list_fetcher';
|
||||||
|
|
||||||
|
|
||||||
class PieceListActions {
|
class PieceListActions {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.generateActions(
|
this.generateActions(
|
||||||
@ -21,17 +22,16 @@ class PieceListActions {
|
|||||||
this.actions.updatePieceList({
|
this.actions.updatePieceList({
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
search,
|
|
||||||
orderBy,
|
orderBy,
|
||||||
orderAsc,
|
orderAsc,
|
||||||
filterBy,
|
filterBy,
|
||||||
|
search: '',
|
||||||
pieceList: [],
|
pieceList: [],
|
||||||
pieceListCount: -1,
|
pieceListCount: -1,
|
||||||
unfilteredPieceListCount: -1
|
unfilteredPieceListCount: -1
|
||||||
});
|
});
|
||||||
|
|
||||||
// afterwards, we can load the list
|
// afterwards, we can load the list
|
||||||
|
|
||||||
return Q.Promise((resolve, reject) => {
|
return Q.Promise((resolve, reject) => {
|
||||||
PieceListFetcher
|
PieceListFetcher
|
||||||
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
|
.fetch(page, pageSize, search, orderBy, orderAsc, filterBy)
|
||||||
|
@ -9,21 +9,49 @@ let AccordionList = React.createClass({
|
|||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
|
children: React.PropTypes.arrayOf(React.PropTypes.element).isRequired,
|
||||||
loadingElement: React.PropTypes.element,
|
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() {
|
render() {
|
||||||
|
const { search } = this.props;
|
||||||
|
|
||||||
if(this.props.itemList && this.props.itemList.length > 0) {
|
if(this.props.itemList && this.props.itemList.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<div className={this.props.className}>
|
||||||
{this.props.children}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if(this.props.count === 0) {
|
} else if(this.props.count === 0 && !search) {
|
||||||
return (
|
return (
|
||||||
<div className="ascribe-accordion-list-placeholder">
|
<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">
|
||||||
<p className="text-center">{getLangText('To register one, click')} <a href="register_piece">{getLangText('here')}</a>!</p>
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,16 +4,16 @@ import React from 'react';
|
|||||||
|
|
||||||
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
|
import PieceListToolbarFilterWidget from './piece_list_toolbar_filter_widget';
|
||||||
import PieceListToolbarOrderWidget from './piece_list_toolbar_order_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({
|
let PieceListToolbar = React.createClass({
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
className: React.PropTypes.string,
|
className: React.PropTypes.string,
|
||||||
searchFor: React.PropTypes.func,
|
searchFor: React.PropTypes.func,
|
||||||
|
searchQuery: React.PropTypes.string,
|
||||||
filterParams: React.PropTypes.arrayOf(
|
filterParams: React.PropTypes.arrayOf(
|
||||||
React.PropTypes.shape({
|
React.PropTypes.shape({
|
||||||
label: React.PropTypes.string,
|
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(){
|
getFilterWidget(){
|
||||||
if (this.props.filterParams){
|
if (this.props.filterParams){
|
||||||
return (
|
return (
|
||||||
@ -55,6 +50,7 @@ let PieceListToolbar = React.createClass({
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
getOrderWidget(){
|
getOrderWidget(){
|
||||||
if (this.props.orderParams){
|
if (this.props.orderParams){
|
||||||
return (
|
return (
|
||||||
@ -68,24 +64,21 @@ let PieceListToolbar = React.createClass({
|
|||||||
},
|
},
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let searchIcon = <Glyphicon glyph='search' className="filter-glyph"/>;
|
const { className, children, searchFor, searchQuery } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<div className={className}>
|
||||||
<div className="row">
|
<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="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">
|
<div className="row">
|
||||||
<span className="pull-left">
|
<span className="pull-left">
|
||||||
{this.props.children}
|
{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} />
|
|
||||||
</span>
|
</span>
|
||||||
|
<SearchBar
|
||||||
|
className="pull-right search-bar ascribe-input-glyph"
|
||||||
|
searchFor={searchFor}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
threshold={AppConstants.searchThreshold}/>
|
||||||
<span className="pull-right">
|
<span className="pull-right">
|
||||||
{this.getOrderWidget()}
|
{this.getOrderWidget()}
|
||||||
{this.getFilterWidget()}
|
{this.getFilterWidget()}
|
||||||
|
@ -157,6 +157,7 @@ let PieceList = React.createClass({
|
|||||||
<PieceListToolbar
|
<PieceListToolbar
|
||||||
className="ascribe-piece-list-toolbar"
|
className="ascribe-piece-list-toolbar"
|
||||||
searchFor={this.searchFor}
|
searchFor={this.searchFor}
|
||||||
|
searchQuery={this.state.search}
|
||||||
filterParams={this.props.filterParams}
|
filterParams={this.props.filterParams}
|
||||||
orderParams={this.props.orderParams}
|
orderParams={this.props.orderParams}
|
||||||
filterBy={this.state.filterBy}
|
filterBy={this.state.filterBy}
|
||||||
@ -177,6 +178,7 @@ let PieceList = React.createClass({
|
|||||||
orderBy={this.state.orderBy}
|
orderBy={this.state.orderBy}
|
||||||
orderAsc={this.state.orderAsc}
|
orderAsc={this.state.orderAsc}
|
||||||
search={this.state.search}
|
search={this.state.search}
|
||||||
|
searchFor={this.searchFor}
|
||||||
page={this.state.page}
|
page={this.state.page}
|
||||||
pageSize={this.state.pageSize}
|
pageSize={this.state.pageSize}
|
||||||
loadingElement={loadingElement}>
|
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',
|
'copyrightAssociations': ['ARS', 'DACS', 'Bildkunst', 'Pictoright', 'SODRAC', 'Copyright Agency/Viscopy', 'SAVA',
|
||||||
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
|
'Bildrecht GmbH', 'SABAM', 'AUTVIS', 'CREAIMAGEN', 'SONECA', 'Copydan', 'EAU', 'Kuvasto', 'GCA', 'HUNGART',
|
||||||
'IVARO', 'SIAE', 'JASPAR-SPDA', 'AKKA/LAA', 'LATGA-A', 'SOMAAP', 'ARTEGESTION', 'CARIER', 'BONO', 'APSAV',
|
'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;
|
export default constants;
|
||||||
|
Loading…
Reference in New Issue
Block a user