diff --git a/lib/DataListInput.js b/lib/DataListInput.js
index e04d145..326e134 100644
--- a/lib/DataListInput.js
+++ b/lib/DataListInput.js
@@ -1,18 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
-import './DataListInput.css';
+import './input.scss';
class DataListInput extends React.Component {
-
- constructor(props) {
- super(props);
+ constructor( props ) {
+ super( props );
this.state = {
/* last valid item that was selected from the drop down menu */
lastValidItem: undefined,
/* current input text */
- currentInput: "",
+ currentInput: '',
/* current set of matching items */
matchingItems: [],
/* visibility property of the drop down menu */
@@ -24,32 +23,32 @@ class DataListInput extends React.Component {
/**
* gets called when someone starts to write in the input field
- * @param event
+ * @param value
*/
- onHandleInput = (event) => {
+ onHandleInput = ( event ) => {
const currentInput = event.target.value;
- const matchingItems = this.props.items.filter((item) => {
- if (typeof(this.props.match) === typeof(Function))
- return this.props.match(currentInput, item);
- return this.match(currentInput, item);
- });
- this.setState({
- currentInput: currentInput,
- matchingItems: matchingItems,
+ const { items, match } = this.props;
+ const matchingItems = items.filter( ( item ) => {
+ if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
+ return this.match( currentInput, item );
+ } );
+ this.setState( {
+ currentInput,
+ matchingItems,
focusIndex: 0,
visible: true,
- });
+ } );
};
/**
- * default function for matching the current input value (needle) and the values of the items array
+ * default function for matching the current input value (needle)
+ * and the values of the items array
* @param currentInput
* @param item
* @returns {boolean}
*/
- match = (currentInput, item) => {
- return item.label.substr(0, currentInput.length).toUpperCase() === currentInput.toUpperCase();
- };
+ match = ( currentInput, item ) => item
+ .label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
/**
* function for getting the index of the currentValue inside a value of the values array
@@ -57,40 +56,40 @@ class DataListInput extends React.Component {
* @param item
* @returns {number}
*/
- indexOfMatch = (currentInput, item) => {
- return item.label.toUpperCase().indexOf(currentInput.toUpperCase());
- };
+ indexOfMatch = ( currentInput, item ) => item
+ .label.toUpperCase().indexOf( currentInput.toUpperCase() );
/**
* handle key events
* @param event
*/
- onHandleKeydown = (event) => {
+ onHandleKeydown = ( event ) => {
+ const { visible, focusIndex, matchingItems } = this.state;
// only do something if drop-down div is visible
- if (!this.state.visible) return;
- let currentFocusIndex = this.state.focusIndex;
- if (event.keyCode === 40 || event.keyCode === 9) {
+ if ( !visible ) return;
+ let currentFocusIndex = focusIndex;
+ if ( event.keyCode === 40 || event.keyCode === 9 ) {
// If the arrow DOWN key or tab is pressed increase the currentFocus variable:
currentFocusIndex += 1;
- if (currentFocusIndex >= this.state.matchingItems.length) currentFocusIndex = 0;
- this.setState({
+ if ( currentFocusIndex >= matchingItems.length ) currentFocusIndex = 0;
+ this.setState( {
focusIndex: currentFocusIndex,
- });
+ } );
// prevent tab to jump to the next input field if drop down is still open
event.preventDefault();
- } else if (event.keyCode === 38) {
+ } else if ( event.keyCode === 38 ) {
// If the arrow UP key is pressed, decrease the currentFocus variable:
currentFocusIndex -= 1;
- if (currentFocusIndex <= -1) currentFocusIndex = this.state.matchingItems.length - 1;
- this.setState({
+ if ( currentFocusIndex <= -1 ) currentFocusIndex = matchingItems.length - 1;
+ this.setState( {
focusIndex: currentFocusIndex,
- });
- } else if (event.keyCode === 13) {
+ } );
+ } else if ( event.keyCode === 13 ) {
// Enter pressed, similar to onClickItem
- if (this.state.focusIndex > -1) {
+ if ( focusIndex > -1 ) {
// Simulate a click on the "active" item:
- const selectedItem = this.state.matchingItems[currentFocusIndex];
- this.onSelect(selectedItem);
+ const selectedItem = matchingItems[ currentFocusIndex ];
+ this.onSelect( selectedItem );
}
}
};
@@ -99,11 +98,19 @@ class DataListInput extends React.Component {
* onClickItem gets called when onClick happens on one of the list elements
* @param event
*/
- onClickItem = (event) => {
+ onClickItem = ( event ) => {
+ const { matchingItems } = this.state;
// update the input value and close the dropdown again
- const selectedKey = event.currentTarget.children[1].value;
- const selectedItem = this.state.matchingItems.find(item => item.key === selectedKey);
- this.onSelect(selectedItem);
+ const elements = event.currentTarget.children;
+ let selectedKey;
+ for ( let i = 0; i < elements.length; i += 1 ) {
+ if ( elements[ i ].tagName === 'INPUT' ) {
+ selectedKey = Number( elements[ i ].value );
+ break;
+ }
+ }
+ const selectedItem = matchingItems.find( item => item.key === selectedKey );
+ this.onSelect( selectedItem );
};
/**
@@ -111,63 +118,89 @@ class DataListInput extends React.Component {
* does nothing if the key has not changed since the last onSelect event
* @param selectedItem
*/
- onSelect = (selectedItem) => {
- if (this.state.lastValidItem !== undefined && selectedItem.key === this.state.lastValidItem.key){
+ onSelect = ( selectedItem ) => {
+ console.log( selectedItem );
+ const { lastValidItem } = this.state;
+ if ( lastValidItem && selectedItem.key === lastValidItem.key ) {
// do not trigger the callback function
// but still change state to fit new selection
- this.setState({
+ this.setState( {
currentInput: selectedItem.label,
visible: false,
focusIndex: -1,
- });
+ } );
return;
}
// change state to fit new selection
- this.setState({
+ this.setState( {
currentInput: selectedItem.label,
lastValidItem: selectedItem,
visible: false,
focusIndex: -1,
- });
+ } );
// callback function onSelect
- this.props.onSelect(selectedItem);
+ const { onSelect } = this.props;
+ onSelect( selectedItem );
};
- renderItems = ( items, focusIndex, activeItemClassName, itemClassName) => (
+ renderItemLabel = ( currentInput, item ) => (
+
+ {item.label.substr( 0, this.indexOfMatch( currentInput, item ) )}
+
+ {item.label.substr( this.indexOfMatch( currentInput, item ), currentInput.length )}
+
+ {item.label.substr( this.indexOfMatch( currentInput, item ) + currentInput.length )}
+
+ )
+
+ renderItems = ( currentInput, items, focusIndex, activeItemClassName, itemClassName ) => (
- {items.map((item, i) => {
+ {items.map( ( item, i ) => {
const isActive = focusIndex === i;
- const itemActiveClasses = isActive ? `datalist-active-item ${activeItemClassName}` : ''
- const itemClasses = `${itemClassName} ${itemActiveClasses};`
+ const itemActiveClasses = isActive ? `datalist-active-item ${ activeItemClassName }` : '';
+ const itemClasses = `${ itemClassName } ${ itemActiveClasses };`;
return (
-
- {item.label.substr(0, this.indexOfMatch(currentInput, item))}
-
{item.label.substr(this.indexOfMatch(currentInput, item), currentInput.length)}
- {item.label.substr(this.indexOfMatch(currentInput, item) + currentInput.length)}
-
+
event.preventDefault()}
+ >
+ { this.renderItemLabel( currentInput, item )}
+
- )
- })}
+ );
+ } )}
);
renderInputField = ( placeholder, currentInput, inputClassName ) => (
-
+
)
render() {
- const { currentInput, matchingItems, focusIndex, visible } = this.state;
- const { placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength } = this.props;
+ const {
+ currentInput, matchingItems, focusIndex, visible,
+ } = this.state;
+ const {
+ placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength,
+ } = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength;
return (
{ this.renderInputField( placeholder, currentInput, inputClassName ) }
- { reachedRequiredLength && visible &&
- this.renderItems( matchingItems, focusIndex, activeItemClassName, itemClassName )
+ { reachedRequiredLength && visible
+ && this.renderItems( currentInput, matchingItems, focusIndex,
+ activeItemClassName, itemClassName )
}
);
@@ -175,7 +208,12 @@ class DataListInput extends React.Component {
}
DataListInput.propTypes = {
- items: PropTypes.array.isRequired,
+ items: PropTypes.arrayOf(
+ PropTypes.shape( {
+ label: PropTypes.string.isRequired,
+ key: PropTypes.number.isRequired,
+ } ),
+ ).isRequired,
placeholder: PropTypes.string,
onSelect: PropTypes.func.isRequired,
match: PropTypes.func,
diff --git a/package.json b/package.json
index 2509737..b854ac8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-datalist-input",
- "version": "1.0.8",
+ "version": "1.0.11",
"description": "This package provides a react component as follows: an input field with a drop down menu to pick a possible option based on the current input.",
"main": "build/index.js",
"repository": {
diff --git a/src/index.js b/src/index.js
index 5f9dd90..0e0e996 100644
--- a/src/index.js
+++ b/src/index.js
@@ -29,35 +29,37 @@ class DataListInput extends React.Component {
this.onSelect = this.onSelect.bind(this);
this.renderItems = this.renderItems.bind(this);
this.renderInputField = this.renderInputField.bind(this);
+ this.renderItemLabel = this.renderItemLabel.bind(this);
}
/**
* gets called when someone starts to write in the input field
- * @param event
+ * @param value
*/
- onHandleInput(event) {
+ onHandleInput( event ){
const currentInput = event.target.value;
- const matchingItems = this.props.items.filter((item) => {
- if (typeof(this.props.match) === typeof(Function))
- return this.props.match(currentInput, item);
- return this.match(currentInput, item);
- });
- this.setState({
- currentInput: currentInput,
- matchingItems: matchingItems,
+ const { items, match } = this.props;
+ const matchingItems = items.filter( ( item ) => {
+ if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
+ return this.match( currentInput, item );
+ } );
+ this.setState( {
+ currentInput,
+ matchingItems,
focusIndex: 0,
visible: true,
- });
+ } );
}
/**
- * default function for matching the current input value (needle) and the values of the items array
+ * default function for matching the current input value (needle)
+ * and the values of the items array
* @param currentInput
* @param item
* @returns {boolean}
*/
- match(currentInput, item) {
- return item.label.substr(0, currentInput.length).toUpperCase() === currentInput.toUpperCase();
+ match( currentInput, item ) {
+ return item.label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
}
/**
@@ -66,40 +68,41 @@ class DataListInput extends React.Component {
* @param item
* @returns {number}
*/
- indexOfMatch(currentInput, item) {
- return item.label.toUpperCase().indexOf(currentInput.toUpperCase());
+ indexOfMatch( currentInput, item ) {
+ return item.label.toUpperCase().indexOf( currentInput.toUpperCase() );
}
/**
* handle key events
* @param event
*/
- onHandleKeydown(event) {
+ onHandleKeydown( event ) {
+ const { visible, focusIndex, matchingItems } = this.state;
// only do something if drop-down div is visible
- if (!this.state.visible) return;
- let currentFocusIndex = this.state.focusIndex;
- if (event.keyCode === 40 || event.keyCode === 9) {
+ if ( !visible ) return;
+ let currentFocusIndex = focusIndex;
+ if ( event.keyCode === 40 || event.keyCode === 9 ) {
// If the arrow DOWN key or tab is pressed increase the currentFocus variable:
currentFocusIndex += 1;
- if (currentFocusIndex >= this.state.matchingItems.length) currentFocusIndex = 0;
- this.setState({
+ if ( currentFocusIndex >= matchingItems.length ) currentFocusIndex = 0;
+ this.setState( {
focusIndex: currentFocusIndex,
- });
+ } );
// prevent tab to jump to the next input field if drop down is still open
event.preventDefault();
- } else if (event.keyCode === 38) {
+ } else if ( event.keyCode === 38 ) {
// If the arrow UP key is pressed, decrease the currentFocus variable:
currentFocusIndex -= 1;
- if (currentFocusIndex <= -1) currentFocusIndex = this.state.matchingItems.length - 1;
- this.setState({
+ if ( currentFocusIndex <= -1 ) currentFocusIndex = matchingItems.length - 1;
+ this.setState( {
focusIndex: currentFocusIndex,
- });
- } else if (event.keyCode === 13) {
+ } );
+ } else if ( event.keyCode === 13 ) {
// Enter pressed, similar to onClickItem
- if (this.state.focusIndex > -1) {
+ if ( focusIndex > -1 ) {
// Simulate a click on the "active" item:
- const selectedItem = this.state.matchingItems[currentFocusIndex];
- this.onSelect(selectedItem);
+ const selectedItem = matchingItems[ currentFocusIndex ];
+ this.onSelect( selectedItem );
}
}
}
@@ -108,11 +111,19 @@ class DataListInput extends React.Component {
* onClickItem gets called when onClick happens on one of the list elements
* @param event
*/
- onClickItem(event) {
+ onClickItem( event ) {
+ const { matchingItems } = this.state;
// update the input value and close the dropdown again
- const selectedKey = event.currentTarget.children[1].value;
- const selectedItem = this.state.matchingItems.find(item => item.key === selectedKey);
- this.onSelect(selectedItem);
+ const elements = event.currentTarget.children;
+ let selectedKey;
+ for ( let i = 0; i < elements.length; i += 1 ) {
+ if ( elements[ i ].tagName === 'INPUT' ) {
+ selectedKey = Number( elements[ i ].value );
+ break;
+ }
+ }
+ const selectedItem = matchingItems.find( item => item.key === selectedKey );
+ this.onSelect( selectedItem );
}
/**
@@ -120,65 +131,93 @@ class DataListInput extends React.Component {
* does nothing if the key has not changed since the last onSelect event
* @param selectedItem
*/
- onSelect(selectedItem) {
- if (this.state.lastValidItem !== undefined && selectedItem.key === this.state.lastValidItem.key){
+ onSelect( selectedItem ) {
+ const { lastValidItem } = this.state;
+ if ( lastValidItem && selectedItem.key === lastValidItem.key ) {
// do not trigger the callback function
// but still change state to fit new selection
- this.setState({
+ this.setState( {
currentInput: selectedItem.label,
visible: false,
focusIndex: -1,
- });
+ } );
return;
}
// change state to fit new selection
- this.setState({
+ this.setState( {
currentInput: selectedItem.label,
lastValidItem: selectedItem,
visible: false,
focusIndex: -1,
- });
+ } );
// callback function onSelect
- this.props.onSelect(selectedItem);
+ const { onSelect } = this.props;
+ onSelect( selectedItem );
}
- renderItems( items, focusIndex, activeItemClassName, itemClassName) {
+ renderItemLabel( currentInput, item ) {
return (
-
- {items.map((item, i) => {
- const isActive = focusIndex === i;
- const itemActiveClasses = isActive ? `datalist-active-item ${activeItemClassName}` : ''
- const itemClasses = `${itemClassName} ${itemActiveClasses};`
- return (
-
- {item.label.substr(0, this.indexOfMatch(currentInput, item))}
- {item.label.substr(this.indexOfMatch(currentInput, item), currentInput.length)}
- {item.label.substr(this.indexOfMatch(currentInput, item) + currentInput.length)}
-
-
- )
- })}
-
);
+
+ {item.label.substr( 0, this.indexOfMatch( currentInput, item ) )}
+
+ {item.label.substr( this.indexOfMatch( currentInput, item ), currentInput.length )}
+
+ {item.label.substr( this.indexOfMatch( currentInput, item ) + currentInput.length )}
+
+ );
+ }
+
+ renderItems( currentInput, items, focusIndex, activeItemClassName, itemClassName ) {
+ return (
+
+ {items.map( ( item, i ) => {
+ const isActive = focusIndex === i;
+ const itemActiveClasses = isActive ? `datalist-active-item ${ activeItemClassName }` : '';
+ const itemClasses = `${ itemClassName } ${ itemActiveClasses };`;
+ return (
+
event.preventDefault()}
+ >
+ { this.renderItemLabel( currentInput, item )}
+
+
+ );
+ } )}
+
);
}
renderInputField( placeholder, currentInput, inputClassName ) {
return (
-
);
+
+ );
}
render() {
- const { currentInput, matchingItems, focusIndex, visible } = this.state;
- const { placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength } = this.props;
+ const {
+ currentInput, matchingItems, focusIndex, visible,
+ } = this.state;
+ const {
+ placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength,
+ } = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength;
return (
{ this.renderInputField( placeholder, currentInput, inputClassName ) }
- { reachedRequiredLength && visible &&
- this.renderItems( matchingItems, focusIndex, activeItemClassName, itemClassName )
+ { reachedRequiredLength && visible
+ && this.renderItems( currentInput, matchingItems, focusIndex,
+ activeItemClassName, itemClassName )
}
);
@@ -186,7 +225,12 @@ class DataListInput extends React.Component {
}
DataListInput.propTypes = {
- items: PropTypes.array.isRequired,
+ items: PropTypes.arrayOf(
+ PropTypes.shape( {
+ label: PropTypes.string.isRequired,
+ key: PropTypes.number.isRequired,
+ } ),
+ ).isRequired,
placeholder: PropTypes.string,
onSelect: PropTypes.func.isRequired,
match: PropTypes.func,
@@ -205,4 +249,4 @@ DataListInput.defaultProps = {
requiredInputLength: 1,
};
-export default DataListInput;
+export default DataListInput;
\ No newline at end of file