Skip to content

Commit

Permalink
Merge pull request #2 from HubSpot/message-for-more-results
Browse files Browse the repository at this point in the history
display a message if the results list is truncated
  • Loading branch information
Zack committed May 19, 2016
2 parents 2afa2c9 + 7556346 commit f237a47
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 19 deletions.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,16 @@ Type: `Number`

Limit the number of options rendered in the results list.

#### props.resultsTruncatedMessage

Type: `String`

If `maxVisible` is set, display this custom message at the bottom of the list of results when the result are truncated.

#### props.customClasses

Type: `Object`
Allowed Keys: `input`, `results`, `listItem`, `listAnchor`, `hover`
Allowed Keys: `input`, `results`, `listItem`, `listAnchor`, `hover`, `resultsTruncated`

An object containing custom class names for child elements. Useful for
integrating with 3rd party UI kits.
Expand Down
34 changes: 17 additions & 17 deletions src/typeahead/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var Typeahead = React.createClass({
name: React.PropTypes.string,
customClasses: React.PropTypes.object,
maxVisible: React.PropTypes.number,
resultsTruncatedMessage: React.PropTypes.string,
options: React.PropTypes.array,
allowCustomValues: React.PropTypes.number,
initialValue: React.PropTypes.string,
Expand Down Expand Up @@ -79,14 +80,15 @@ var Typeahead = React.createClass({
inputDisplayOption: null,
defaultClassNames: true,
customListComponent: TypeaheadSelector,
showOptionsWhenEmpty: false
showOptionsWhenEmpty: false,
resultsTruncatedMessage: null
};
},

getInitialState: function() {
return {
// The currently visible set of options
visible: this.getOptionsForValue(this.props.initialValue, this.props.options),
// The options matching the entry value
searchResults: this.getOptionsForValue(this.props.initialValue, this.props.options),

// This should be called something else, "entryValue"
entryValue: this.props.value || this.props.initialValue,
Expand All @@ -108,11 +110,7 @@ var Typeahead = React.createClass({
if (this._shouldSkipSearch(value)) { return []; }

var searchOptions = this._generateSearchFunction();
var result = searchOptions(value, options);
if (this.props.maxVisible) {
result = result.slice(0, this.props.maxVisible);
}
return result;
return searchOptions(value, options);
},

setEntryText: function(value) {
Expand All @@ -127,7 +125,7 @@ var Typeahead = React.createClass({
_hasCustomValue: function() {
if (this.props.allowCustomValues > 0 &&
this.state.entryValue.length >= this.props.allowCustomValues &&
this.state.visible.indexOf(this.state.entryValue) < 0) {
this.state.searchResults.indexOf(this.state.entryValue) < 0) {
return true;
}
return false;
Expand Down Expand Up @@ -158,7 +156,9 @@ var Typeahead = React.createClass({

return (
<this.props.customListComponent
ref="sel" options={this.state.visible}
ref="sel" options={this.state.searchResults.slice(0, this.props.maxVisible)}
areResultsTruncated={this.state.searchResults.length > this.props.maxVisible}
resultsTruncatedMessage={this.props.resultsTruncatedMessage}
onOptionSelected={this._onOptionSelected}
allowCustomValues={this.props.allowCustomValues}
customValue={this._getCustomValue()}
Expand All @@ -178,7 +178,7 @@ var Typeahead = React.createClass({
index--;
}
}
return this.state.visible[index];
return this.state.searchResults[index];
},

_onOptionSelected: function(option, event) {
Expand All @@ -192,15 +192,15 @@ var Typeahead = React.createClass({
var formInputOptionString = formInputOption(option);

nEntry.value = optionString;
this.setState({visible: this.getOptionsForValue(optionString, this.props.options),
this.setState({searchResults: this.getOptionsForValue(optionString, this.props.options),
selection: formInputOptionString,
entryValue: optionString});
return this.props.onOptionSelected(option, event);
},

_onTextEntryUpdated: function() {
var value = this.refs.entry.value;
this.setState({visible: this.getOptionsForValue(value, this.props.options),
this.setState({searchResults: this.getOptionsForValue(value, this.props.options),
selection: '',
hasRendered: true,
entryValue: value});
Expand All @@ -227,7 +227,7 @@ var Typeahead = React.createClass({
_onTab: function(event) {
var selection = this.getSelection();
var option = selection ?
selection : (this.state.visible.length > 0 ? this.state.visible[0] : null);
selection : (this.state.searchResults.length > 0 ? this.state.searchResults[0] : null);

if (option === null && this._hasCustomValue()) {
option = this._getCustomValue();
Expand Down Expand Up @@ -255,7 +255,7 @@ var Typeahead = React.createClass({
return;
}
var newIndex = this.state.selectionIndex === null ? (delta == 1 ? 0 : delta) : this.state.selectionIndex + delta;
var length = this.state.visible.length;
var length = this.state.searchResults.slice(0, this.props.maxVisible).length;
if (this._hasCustomValue()) {
length += 1;
}
Expand Down Expand Up @@ -306,7 +306,7 @@ var Typeahead = React.createClass({

componentWillReceiveProps: function(nextProps) {
this.setState({
visible: this.getOptionsForValue(this.state.entryValue, nextProps.options)
searchResults: this.getOptionsForValue(this.state.entryValue, nextProps.options)
});
},

Expand Down Expand Up @@ -385,7 +385,7 @@ var Typeahead = React.createClass({
},

_hasHint: function() {
return this.state.visible.length > 0 || this._hasCustomValue();
return this.state.searchResults.length > 0 || this._hasCustomValue();
}
});

Expand Down
18 changes: 17 additions & 1 deletion src/typeahead/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ var TypeaheadSelector = React.createClass({
selectionIndex: React.PropTypes.number,
onOptionSelected: React.PropTypes.func,
displayOption: React.PropTypes.func.isRequired,
defaultClassNames: React.PropTypes.bool
defaultClassNames: React.PropTypes.bool,
areResultsTruncated: React.PropTypes.bool,
resultsTruncatedMessage: React.PropTypes.string
},

getDefaultProps: function() {
Expand Down Expand Up @@ -70,6 +72,20 @@ var TypeaheadSelector = React.createClass({
);
}, this);

if (this.props.areResultsTruncated && this.props.resultsTruncatedMessage !== null) {
var resultsTruncatedClasses = {
"results-truncated": this.props.defaultClassNames
};
resultsTruncatedClasses[this.props.customClasses.resultsTruncated] = this.props.customClasses.resultsTruncated;
var resultsTruncatedClassList = classNames(resultsTruncatedClasses);

results.push(
<li key="results-truncated" className={resultsTruncatedClassList}>
{this.props.resultsTruncatedMessage}
</li>
);
}

return (
<ul className={classList}>
{ customValue }
Expand Down
10 changes: 10 additions & 0 deletions test/typeahead-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,16 @@ describe('Typeahead Component', function() {
var results = simulateTextInput(component, 'o');
assert.equal(results.length, 1);
});

it('limits the result set based on the maxVisible option, and shows resultsTruncatedMessage when specified', function() {
var component = TestUtils.renderIntoDocument(<Typeahead
options={ BEATLES }
maxVisible={ 1 }
resultsTruncatedMessage='Results truncated'
></Typeahead>);
var results = simulateTextInput(component, 'o');
assert.equal(TestUtils.findRenderedDOMComponentWithClass(component, 'results-truncated').textContent, 'Results truncated');
});
});

context('displayOption', function() {
Expand Down

0 comments on commit f237a47

Please sign in to comment.