diff --git a/wagtailmodelchoosers/client/components/AutoComplete.js b/wagtailmodelchoosers/client/components/AutoComplete.js index 693c2aa..3b8c8bd 100644 --- a/wagtailmodelchoosers/client/components/AutoComplete.js +++ b/wagtailmodelchoosers/client/components/AutoComplete.js @@ -30,6 +30,22 @@ class AutoComplete extends Component { this.onChange = this.onChange.bind(this); } + onSuggestionsUpdateRequested({ value }) { + const { onLoadStart } = this.props; + onLoadStart(); + this.loadSuggestions(value); + } + + onChange(event, { newValue }) { + const { onChange } = this.props; + + this.setState({ + value: newValue, + }, () => { + onChange(newValue); + }); + } + loadSuggestions(suggestionValue) { const { filter, endpoint, onLoadSuggestions } = this.props; const url = `${endpoint}/?search=${suggestionValue}${filter}`; @@ -38,7 +54,7 @@ class AutoComplete extends Component { credentials: 'same-origin', }) .then(res => res.json()) - .then(json => { + .then((json) => { this.setState({ suggestions: json.results, loading: false, @@ -48,22 +64,6 @@ class AutoComplete extends Component { }); } - onSuggestionsUpdateRequested({ value }) { - const { onLoadStart } = this.props; - onLoadStart(); - this.loadSuggestions(value); - } - - onChange(event, { newValue }) { - const { onChange } = this.props; - - this.setState({ - value: newValue, - }, () => { - onChange(newValue); - }); - } - render() { const { value, suggestions } = this.state; @@ -76,7 +76,7 @@ class AutoComplete extends Component { renderSuggestion={renderSuggestion} inputProps={{ placeholder: 'Type to search', - value: value, + value, onChange: this.onChange, }} /> diff --git a/wagtailmodelchoosers/client/components/BaseChooser.js b/wagtailmodelchoosers/client/components/BaseChooser.js index ceddf67..df04d56 100644 --- a/wagtailmodelchoosers/client/components/BaseChooser.js +++ b/wagtailmodelchoosers/client/components/BaseChooser.js @@ -13,23 +13,26 @@ const STR = { const defaultProps = { display: 'title', filters: [], - pk_name: 'uuid', translations: {}, + pk_name: 'uuid', + page_size: 10, + page_size_param: 'page_size', }; const propTypes = { initialValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, updateInputValue: PropTypes.func.isRequired, - label: PropTypes.string.isRequired, initial_display_value: PropTypes.string.isRequired, - endpoint: PropTypes.string.isRequired, - value: PropTypes.any, required: PropTypes.bool.isRequired, display: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired, - list_display: PropTypes.array.isRequired, - filters: PropTypes.array, pk_name: PropTypes.string, translations: PropTypes.object, + label: PropTypes.string.isRequired, + list_display: PropTypes.array.isRequired, + filters: PropTypes.array, + endpoint: PropTypes.string.isRequired, + page_size: PropTypes.number, + page_size_param: PropTypes.string, }; class BaseChooser extends React.Component { @@ -53,8 +56,8 @@ class BaseChooser extends React.Component { this.state = { pickerVisible: false, - selectedId: selectedId, - selectedItem: selectedItem, + selectedId, + selectedItem, initialUrl: null, }; @@ -68,12 +71,6 @@ class BaseChooser extends React.Component { this.clearPicker = this.clearPicker.bind(this); } - showPicker() { - this.setState({ - pickerVisible: true, - }); - } - onClose() { this.setState({ pickerVisible: false, @@ -94,7 +91,7 @@ class BaseChooser extends React.Component { getItemPk(item) { const { pk_name: pkName } = this.props; - return !!item ? item[pkName] : null; + return item ? item[pkName] : null; } getItemPreview() { @@ -107,7 +104,9 @@ class BaseChooser extends React.Component { // Return first non-empty field if `display` is an Array. if (Array.isArray(display)) { - for (const fieldName of display) { + let i; + for (i = 0; i < display.length; i + 1) { + const fieldName = display[i]; if (fieldName in selectedItem && selectedItem[fieldName]) { return selectedItem[fieldName]; } @@ -123,11 +122,6 @@ class BaseChooser extends React.Component { return this.getItemPk(selectedItem); } - isOptional() { - const { required } = this.props; - return !required; - } - getChooseButtons() { const { translations } = this.props; const { selectedId } = this.state; @@ -157,6 +151,17 @@ class BaseChooser extends React.Component { ); } + isOptional() { + const { required } = this.props; + return !required; + } + + showPicker() { + this.setState({ + pickerVisible: true, + }); + } + clearPicker(e) { e.preventDefault(); @@ -172,6 +177,16 @@ class BaseChooser extends React.Component { render() { const { pickerVisible, initialUrl } = this.state; + const { + list_display: listDisplay, + label, + endpoint, + filters, + pk_name: pkName, + page_size: pageSize, + page_size_param: pageSizeParam, + translations, + } = this.props; return (
@@ -186,7 +201,14 @@ class BaseChooser extends React.Component { url={initialUrl} onClose={this.onClose} onSelect={this.onSelect} - {...this.props} + label={label} + endpoint={endpoint} + filters={filters} + list_display={listDisplay} + pk_name={pkName} + page_size={pageSize} + page_size_param={pageSizeParam} + translations={translations} /> ) : null}
diff --git a/wagtailmodelchoosers/client/components/Buttons.js b/wagtailmodelchoosers/client/components/Buttons.js index ca8fb5b..a411919 100644 --- a/wagtailmodelchoosers/client/components/Buttons.js +++ b/wagtailmodelchoosers/client/components/Buttons.js @@ -27,7 +27,7 @@ Button.defaultProps = { Button.propTypes = { isActive: PropTypes.bool, - classes: PropTypes.array, + classes: PropTypes.arrayOf(PropTypes.string), label: PropTypes.string.isRequired, onClick: PropTypes.func.isRequired, }; diff --git a/wagtailmodelchoosers/client/components/ModelChooser.js b/wagtailmodelchoosers/client/components/ModelChooser.js index 7143c26..9d12f33 100644 --- a/wagtailmodelchoosers/client/components/ModelChooser.js +++ b/wagtailmodelchoosers/client/components/ModelChooser.js @@ -30,23 +30,25 @@ class ModelChooser extends React.Component { // TODO: Props mutation WTF? input.value = newValue; - } + } - render() { - const { options, input } = this.props; + render() { + const { options, input } = this.props; - return ( - - ); + return ( + + ); } } ModelChooser.propTypes = { + // eslint-disable-next-line react/forbid-prop-types options: PropTypes.object.isRequired, + // eslint-disable-next-line react/forbid-prop-types input: PropTypes.object.isRequired, }; diff --git a/wagtailmodelchoosers/client/components/ModelPicker.js b/wagtailmodelchoosers/client/components/ModelPicker.js index 6c260ff..d4cb592 100644 --- a/wagtailmodelchoosers/client/components/ModelPicker.js +++ b/wagtailmodelchoosers/client/components/ModelPicker.js @@ -19,9 +19,7 @@ const STR = { }; const defaultProps = { - display: 'title', filters: [], - pk_name: 'uuid', page_size: 10, page_size_param: 'page_size', translations: {}, @@ -32,9 +30,6 @@ const propTypes = { onClose: PropTypes.func.isRequired, label: PropTypes.string.isRequired, endpoint: PropTypes.string.isRequired, - value: PropTypes.any, - required: PropTypes.bool.isRequired, - display: PropTypes.oneOfType([PropTypes.string, PropTypes.array]).isRequired, list_display: PropTypes.array.isRequired, filters: PropTypes.array, pk_name: PropTypes.string.isRequired, @@ -90,11 +85,6 @@ class ModelPicker extends React.Component { this.onLoadStart = this.onLoadStart.bind(this); } - getDefaultUrl() { - const { endpoint, page_size: pageSize, page_size_param: pageSizeParam } = this.props; - return `${endpoint}/?${pageSizeParam}=${pageSize}`; - } - componentDidMount() { setTimeout(() => { this.navigate(this.getDefaultUrl()); @@ -109,178 +99,98 @@ class ModelPicker extends React.Component { document.body.style.width = ''; } - getPk(item) { - const { pk_name } = this.props; - - return !!item ? item[pk_name] : null; - } - - getModels() { - const { shouldShowSuggestions, suggestions, models } = this.state; - - return shouldShowSuggestions ? suggestions : models; - } - - select(pk) { - const { onSelect } = this.props; - const { url } = this.state; - const models = this.getModels(); - const item = models.find(m => this.getPk(m) === pk); - + onClose(e) { + const { onClose } = this.props; this.closeWithCallback(() => { - onSelect(this.getPk(item), item, url); + onClose(e); }); } - closeWithCallback(callback) { - this.elRef.classList.add(MODAL_EXIT_CLASS); - setTimeout(callback, MODAL_CLOSE_TIMEOUT); - } - - update(json) { - const { page_size: pageSize } = this.props; - - // If the API does not return the total number of page, - // try to calculate it from the number of result and the page size. - let numPage = 0; - if (json.num_pages) { - numPage = json.num_pages; - } else if (json.count) { - numPage = Math.ceil(json.count / pageSize); - } - + onLoadSuggestions(suggestions) { this.setState({ - numPages: numPage, - page: json.page, - models: json.results, - count: json.count, - next: json.next, - previous: json.previous, - loading: false, - }, () => { - this.contentRef.scrollTop = 0; + suggestions, + suggestionsCount: suggestions.length, + loadingSuggestions: false, }); } - addFilterParams(url) { - const { filters } = this.props; - let localUrl = url; - - if (filters) { - // TODO Redo with map and join. - filters.forEach((filter) => { - localUrl += `&${filter.field}=${filter.value}`; - }); - } - - return localUrl; - } + onValueChange(newValue) { + const shouldShowSuggestions = newValue.trim().length > 2; - navigate(url) { - const urlWithFilters = this.addFilterParams(url); this.setState({ - loading: true, - url: url, - }, () => { - // TODO There is no reason for this code to be in the setState callback. - // TODO This is not producing errors when status code is not 200, - // so the error handling likely does not work. - // TODO Use fetch API wrapper. - fetch(urlWithFilters, { - credentials: 'same-origin', - }) - .then(res => res.json()) - .then(this.update, this.handleError); + shouldShowSuggestions, }); } - onClose(e) { - const { onClose } = this.props; - this.closeWithCallback(() => { - onClose(e); - }); - } - - handleError() { + onLoadStart() { this.setState({ - loading: false, + loadingSuggestions: true, }); } - getPlaceholder() { - const { list_display: listDisplay } = this.props; - const { loading } = this.state; - - return ( - - - {loading ? 'Loading' : 'Sorry, no results'} - - - ); - } - - getTable() { - const { list_display: listDisplay } = this.props; - const models = this.getModels(); + getPaginationButtons() { + const { translations } = this.props; + const { next, previous, shouldShowSuggestions } = this.state; - return ( - - - - {listDisplay.map(this.getHeader)} - - - - {models.length ? models.map(this.getRow) : this.getPlaceholder()} - -
- ); - } + const prevLabel = tr(STR, translations, 'previous'); + const nextLabel = tr(STR, translations, 'next'); + const prevEnabled = shouldShowSuggestions ? false : !!previous; + const nextEnabled = shouldShowSuggestions ? false : !!next; - getHeader(field) { return ( - - {field.label} - + +