AutoComplete
import AutoComplete from '@concur/nui-widgets/lib/AutoComplete/AutoComplete';AutoComplete components allow users to select items from a defined list. The input field acts as a search box and will narrow the list of items presented based on matches against the entered text.
AutoComplete shows an expand/collapse toggle button to make the users aware that a list of values exists.
Examples
Basic Rendering
Basic rendering uses text strings from the data property to render list items and input text. The default is to use the id value for item keys and the text value for display text.
const data = [
{text: 'Seattle', id: '1', countryCode: 'a'},
{text: 'Mexico City', id: '2', countryCode: 'b'},
{text: 'Vancouver', id: '3', countryCode: 'c'},
{text: 'Minneapolis', id: '4', countryCode: 'a'},
{text: 'Tijuana', id: '5', countryCode: 'b'},
{text: 'Toronto', id: '6', countryCode: 'c'}
];
const mruData = [
{text: 'Seattle', id: '1', countryCode: 'a'},
{text: 'Mexico City', id: '2', countryCode: 'b'},
{text: 'Vancouver', id: '3', countryCode: 'c'}
];
class AutoCompleteExample02 extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data,
mruData: mruData,
searchText: (this.props.value ? this.props.value : '')
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.searchText) {
this.setState({
searchText: nextProps.value
});
}
}
filterByText = (text, itemData) => {
if (!text) {
return true;
} else if (itemData.text.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
return true;
}
return false;
}
handleChange = (text) => {
this.setState({
searchText: text,
data: data.filter(rec => {
return this.filterByText(text, rec);
}),
mruData: mruData.filter(rec => {
return this.filterByText(text, rec);
})
});
}
handleSelect = (itemData) => {
this.setState({
searchText: itemData.text,
data: data.filter(rec => {
return rec.id === itemData.id;
}),
mruData: mruData.filter(rec => {
return rec.id === itemData.id;
})
});
}
render() {
return (
<AutoComplete
{...this.props}
data={this.state.data}
mruData={this.state.mruData}
mruTitle='Most Recently Used'
onChange={(e) => this.handleChange(e.target.value)}
onSelect={this.handleSelect}
value={this.state.searchText} />
);
}
}Custom Rendering
To render custom content for list items and input text, use the dataRendering property.
const data = [
{fname: 'Adam', lname: 'Fedor', empid: '0601', email: 'adam.fedor@concur.com', location: 'Bellevue', dept: 'PM'},
{fname: 'Maria', lname: 'Gandarillas', empid: '0701', email: 'maria.gandarillas@concur.com', location: 'Bellevue', dept: 'PM'},
{fname: 'Campbell', lname: 'Gunn', empid: '0702', email: 'campbell.gunn@concur.com', location: 'Bellevue', dept: 'PM'},
{fname: 'Jeffrey', lname: 'Johnson', empid: '1001', email: 'jeffrey.johnson@concur.com', location: 'Bellevue', dept: 'DEV'},
{fname: 'Peter', lname: 'Kim', empid: '1101', email: 'peter.kim@concur.com', location: 'Vienna', dept: 'QA'},
{fname: 'Matthew', lname: 'Osborn', empid: '1501', email: 'matthew.osborn@concur.com', location: 'Fargo (remote)', dept: 'DEV'},
{fname: 'Greg', lname: 'Smith', empid: '1901', email: 'greg.a.smith@concur.com', location: 'Minneapolis (remote)', dept: 'DEV'},
{fname: 'Brad', lname: 'Ullman', empid: '2101', email: 'brad.ullman@concur.com', location: 'Bellevue', dept: 'DEV'},
{fname: 'Darin', lname: 'Warling', empid: '2301', email: 'darin.warling@concur.com', location: 'Minneapolis (remote)', dept: 'DEV'}
];
const mruData = dataUsers.slice(2, 5);
class AutoCompleteExample extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data,
mruData: mruData,
searchText: (this.props.value ? this.props.value : '')
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.searchText) {
this.setState({
searchText: nextProps.value
});
}
}
filterByText = (text, itemData) => {
if (!text) {
return true;
} else if (itemData.fname.toLowerCase().indexOf(text.toLowerCase()) >= 0 ||
itemData.lname.toLowerCase().indexOf(text.toLowerCase()) >= 0 ||
itemData.empid.toLowerCase().indexOf(text.toLowerCase()) >= 0 ||
itemData.email.toLowerCase().indexOf(text.toLowerCase()) >= 0 ||
itemData.location.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
return true;
}
return false;
}
handleChange = (text) => {
this.setState({
searchText: text,
data: data.filter(rec => {
return this.filterByText(text, rec);
}),
mruData: mruData.filter(rec => {
return this.filterByText(text, rec);
})
});
}
handleSelect = (itemData) => {
this.setState({
searchText: this._renderInputText(itemData),
data: data.filter(rec => {
return rec.empid === itemData.empid;
}),
mruData: mruData.filter(rec => {
return rec.id === itemData.id;
})
});
}
_renderInputText = (itemData) => {
return itemData.lname + ', ' + itemData.fname;
}
render() {
return (
<AutoComplete
{...this.props}
data={this.state.data}
dataRendering={{
inputRenderer: this._renderInputText,
listItemRenderer: (itemData) => {
return <Employee {...itemData} />;
}
}}
itemKey={'empid'}
mruData={this.state.mruData}
mruTitle='Most Recently Used'
onChange={(e) => this.handleChange(e.target.value)}
onSelect={this.handleSelect}
value={this.state.searchText} />
);
}
}Headers, Dividers and Disabled Items
To include a header, add an object in the data array with the header property set to the text of the header. To include a divider (separator), add an object in the data array with the divider property set to true. To make a list item disabled, add a disabled property set to true to the object in the data array.
const data = [
{header: '01-Transportation', id: '01'},
{text: 'Airfare', id: '1', headerId: '01'},
{text: 'Airfare Fees', id: '2', headerId: '01'},
{text: 'Car Rental', id: '3', headerId: '01'},
{text: 'Fuel', id: '4', headerId: '01'},
{text: 'Parking', id: '5', headerId: '01'},
{text: 'Taxi', id: '6', headerId: '01', disabled: true},
{header: '02-Lodging', id: '02'},
{text: 'Hotel', id: '7', headerId: '02'},
{text: 'Laundry', id: '8', headerId: '02', disabled: true},
{header: '03-Meals and Entertainment', id: '03'},
{text: 'Individual Breakast', id: '9', headerId: '03'},
{text: 'Individual Lunch', id: '10', headerId: '03'},
{text: 'Individual Dinner', id: '11', headerId: '03'},
{divider: true, headerId: '03'},
{text: 'Business Meal', id: '12', headerId: '03'},
{text: 'Beverages', id: '13', headerId: '03'},
{text: 'Entertainment', id: '14', headerId: '03'}
];
const mruData = data.slice(11, 13);
class AutoCompleteExample03 extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data,
mruData: mruData,
searchText: (this.props.value ? this.props.value : '')
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.searchText) {
this.setState({
searchText: nextProps.value
});
}
}
filterByText = (text, itemData) => {
if (!text || !itemData.text) {
return true;
} else if (itemData.text.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
return true;
}
return false;
}
filterHeaders = (filteredData) => {
let itemsToRemove = [];
filteredData.forEach(rec => {
if (!!rec.header) {
let itemsExist = filteredData.some(rec2 => {
return !rec2.divider && rec2.headerId === rec.id;
});
if (!itemsExist) {
itemsToRemove.push(rec.id);
}
}
});
if (itemsToRemove.length === 0) {
return filteredData;
}
// now, filter out any unneeded headers and/or dividers
let updatedData = filteredData.filter(rec => {
if (!!rec.header && itemsToRemove.indexOf(rec.id) >= 0) {
return false;
} else if (!!rec.divider && itemsToRemove.indexOf(rec.headerId) >= 0) {
return false;
}
return true;
});
return updatedData;
}
handleChange = (text) => {
let filteredData = data.filter(rec => {
return this.filterByText(text, rec);
});
filteredData = this.filterHeaders(filteredData);
this.setState({
searchText: text,
data: filteredData,
mruData: mruData.filter(rec => {
return this.filterByText(text, rec);
})
});
}
handleSelect = (itemData) => {
this.setState({
searchText: itemData.text,
data: data.filter(rec => {
return rec.id === itemData.id;
}),
mruData: mruData.filter(rec => {
return rec.id === itemData.id;
})
});
}
render() {
return (
<AutoComplete
{...this.props}
data={this.state.data}
mruData={this.state.mruData}
mruTitle='Most Recently Used'
onChange={(e) => this.handleChange(e.target.value)}
onSelect={this.handleSelect}
value={this.state.searchText} />
);
}
}Loading Message using Spinner
The loadingMsg property can be used to add a Spinner to the dropdown loading animation.
const loadingSpinner = (
<React.Fragment>
<Spinner
message='Loading...'
size='lg'
type='inline'
visible />
<span> Loading...</span>
</React.Fragment>
);
const AutocompleteExample = () => (
<AutoComplete
isLoading
loadingMsg={loadingSpinner}
noResultsMsg='No results found' />
);Infinite Scrolling
To use infinite scrolling, the consumer must provide and call the required data using the infinite object. The onLoadMore callback is triggered near the bottom of the dropdown to allow for more data to be fetched. The new data should be appended to the previous data property. To stop infinite scrolling or stop fetching data, change hasMore to false.
class HIGAutoCompleteEx06 extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data.slice(1, 50),
hasMore: true,
searchText: (this.props.value ? this.props.value : ''),
maxData: 50
};
}
getMoreData = () => {
if (this.state.maxData > data.length) {
this.setState({
hasMore: false
});
}
setTimeout(() => {
this.setState({
data: data.slice(0, this.state.maxData + 50),
maxData: this.state.maxData + 50
});
}, 750);
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.searchText) {
this.setState({
searchText: nextProps.value
});
}
}
filterByText = (text, rec) => {
return !text ? true : rec.text.toLowerCase().indexOf(text.toLowerCase()) >= 0;
}
handleChange = (text) => {
this.setState({
searchText: text,
data: this.state.data.filter(rec => {
return this.filterByText(text, rec);
})
});
}
handleSelect = (itemData) => {
this.setState({
searchText: itemData.text,
data: this.state.data.filter(rec => {
return rec.id === itemData.id;
})
});
}
render() {
const loadingSpinner = (
<React.Fragment>
<Spinner
message='Loading...'
size='lg'
type='inline'
visible />
<span> Loading...</span>
</React.Fragment>
);
return (
<AutoComplete
{...this.props}
data={this.state.data}
infinite={{
onLoadMore: this.getMoreData,
hasMore: this.state.hasMore
}}
loadingMsg={loadingSpinner}
onChange={(e) => this.handleChange(e.target.value)}
onSelect={this.handleSelect}
value={this.state.searchText} />
);
}
HIGAutoCompleteEx06.propTypes = AutoComplete.propTypes;
HIGAutoCompleteEx06.defaultProps = {
noResultsMsg: 'No results found'
};
export default HIGAutoCompleteEx06;Disabled State
Use the disabled property to disable the Autocomplete component. A disabled element is unusable and un-clickable.
const data = [
{text: 'Seattle', id: '1', countryCode: 'a'},
{text: 'Mexico City', id: '2', countryCode: 'b'},
{text: 'Vancouver', id: '3', countryCode: 'c'},
{text: 'Minneapolis', id: '4', countryCode: 'a'},
{text: 'Tijuana', id: '5', countryCode: 'b'},
{text: 'Toronto', id: '6', countryCode: 'c'}
];
const mruData = [
{text: 'Seattle', id: '1', countryCode: 'a'},
{text: 'Mexico City', id: '2', countryCode: 'b'},
{text: 'Vancouver', id: '3', countryCode: 'c'}
];
class AutoCompleteExample04 extends React.Component {
constructor(props) {
super(props);
this.state = {
data: data,
mruData: mruData,
searchText: (this.props.value ? this.props.value : '')
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.searchText) {
this.setState({
searchText: nextProps.value
});
}
}
filterByText = (text, itemData) => {
if (!text) {
return true;
} else if (itemData.text.toLowerCase().indexOf(text.toLowerCase()) >= 0) {
return true;
}
return false;
}
handleChange = (text) => {
this.setState({
searchText: text,
data: data.filter(rec => {
return this.filterByText(text, rec);
}),
mruData: mruData.filter(rec => {
return this.filterByText(text, rec);
})
});
}
handleSelect = (itemData) => {
this.setState({
searchText: itemData.text,
data: data.filter(rec => {
return rec.id === itemData.id;
}),
mruData: mruData.filter(rec => {
return rec.id === itemData.id;
})
});
}
render() {
return (
<AutoComplete
{...this.props}
data={this.state.data}
disabled
mruData={this.state.mruData}
mruTitle='Most Recently Used'
onChange={(e) => this.handleChange(e.target.value)}
onSelect={this.handleSelect}
value={this.state.searchText} />
);
}
}ReadOnly State
ReadOnly is not a supported state for the Autocomplete component.
Usage
Properties
| Property | Type | Default | Description |
|---|---|---|---|
noResultsMsg | String or Object | Required | Displayed when no data items match entered text. If a string is used, it will display that text. If an object is used, it has props for title (primary text) and description (secondary text). |
dropdownTriggerLabel | String | Required | Localized assistive text for the dropdown trigger button. |
active | Node | Node of the active item. | |
className | String | Custom classes to be added to the <input> tag. | |
data | Array | Raw data for the dropdown items. Each element in the array (in JSON format) represents an item in the dropdown. For default rendering of list items, each item should have keys for id and text. For custom rendering of list items, see dataRendering. | |
dataRendering | Object | Enables custom rendering. See dataRendering for details about the available properties. | |
disabled | Boolean | false | Controls whether the component is enabled or disabled. |
flipContainer | Element or Element[] | Used to create a boundary for the underlying Popper to stay within. | |
infinite | Object | Defines the properties for infinite scrolling. See infinite for more details. | |
inputRef | Ref or Function | Attach a ref to the <input> element. | |
isLoading | Boolean | false | When true, loadingMsg will be displayed. |
isOpen | Boolean | false | When true, the input dropdown will be opened automatically on creation. |
itemKey | String or Function | 'id' | Used to identify the unique key for a list item. If a string is used, it identifies the field in the data to use as the key value for the list item. If a function is used, it must return the key value for the list item. The function will be passed the row of data (in JSON format). |
loadingMsg | Node | Displayed while data is loading. Must be non-empty if isLoading is true. | |
mruData | Array | Raw data for the most-recently used (MRU) items (in JSON format). For simple rendering of list items, each item should have keys for id and text. For custom rendering of list items, see dataRendering. | |
mruTitle | String | Title of MRU section inside dropdown menu. | |
placeholder | String | Placeholder displayed when input field is empty. | |
popperClassName | String | Custom classes to be added to the popper <div> element. | |
popperPlacement | String or String[] | Placement of the dropdown relative to the input element. Options are 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'left-start', 'left-end', 'right', 'right-start', 'right-end'. See Popover Placement for a more detailed description of the values. | |
required | Boolean | false | Adds the required attribute to the <input> element. NOTE: This does not perform any validation, but rather just marks the field as required. |
size | String | 'lg' | Component size. Options are 'lg', 'md' and 'sm'. |
showAllOnFocus | Boolean | true | If true, dropdown menu opens automatically when component receives focus. If false, dropdown menu will not open until at least one character is entered (or more, depending on the value of showAllOnFocusThreshold. |
showAllOnFocusThreshold | Number | 0 | Minimum number of characters that must be entered before the autocomplete menu is displayed. |
targetClassName | String | Custom classes to be added to the target/reference <div> wrapper element. | |
validationMessage | Object | An object identifying a validation message. The object will include properties for state and text; e.g., { state: 'warning', text: 'This is your last warning' }. The options for state are 'error' and 'warning'. text is the message shown in the validation tooltip; as with all text, it must be localized. | |
validationMessageType | String | This will determine how the validation message will appear. Options are 'iconAligned', a tooltip from the warning or error icon, or 'inputAligned', an overlay displaying underneath the input. | |
value | String | Initial (default) value of input field. | |
widthSizingType | String | 'minTarget' | The width of the popper component relative to the target. Options are 'none','matchTarget','minTarget','maxTarget' |
Callbacks
| Property | Parameters | Description |
|---|---|---|
onBlur | event | Callback function for input field onBlur. Will be called only if input field loses focus and the Dropdown popover is closed. |
onChange | event | Callback function for input field onChange. |
onDropdownClose | event | Callback function for onDropdownClose. |
onDropdownTriggerClick | event | Callback function for onClick on the dropdown trigger button. |
onFocus | event | Callback function for input field onFocus. |
onKeyDown | event | Callback function for input field onKeyDown. |
onMouseDown | event | Callback function for input field onMouseDown. |
onSelect | item | Callback function for onSelect. |
Shape: dataRendering
Properties
| Property | Type | Default | Description |
|---|---|---|---|
inputRenderer | String or Function | 'text' | Used to render text in the input for a selected item. If a string is used, the value for that key (from data or mruData) will be shown. If a function is used, it must return the text to display in the input field. The function will be passed the row of data (in JSON format). |
listItemRenderer | String or Function | 'text' | Used to render content for each list item in the dropdown. If a string is used, the value for that key (from data or mruData) will be shown. If a function is used, it must return a valid node. The function will be passed the row of data (in JSON format). |
Shape: infinite
Properties
| Property | Type | Default | Description |
|---|---|---|---|
hasMore | Boolean | true | Sets whether there are more items to be loaded. Event listeners are removed if set to false. This can also be used to disable infinite loading. NOTE: Setting hasMore to true emits a loading message. Having bothisLoading and hasMore set to true will result in multiple loading messages. |
isInitialLoad | Boolean | true | Sets whether the component should load the first set of items when opened. |
pageStart | Number | 0 | The number of the first page to load. With the default of 0, the first page is loaded. |
threshold | Number | 250 | The distance in pixels from the bottom of the scroll area before a call to onLoadMore is triggered. |
Callbacks
| Property | Parameters | Description |
|---|---|---|
onLoadMore | pageNumber | A callback when more items are requested by the user. Provides a single parameter specifying the page to load. |