went linting

This commit is contained in:
andrelandgraf 2019-06-12 23:16:27 +02:00
parent 7ef3ff88e5
commit c7a31b06e4
3 changed files with 222 additions and 140 deletions

View File

@ -1,18 +1,17 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import './DataListInput.css'; import './input.scss';
class DataListInput extends React.Component { class DataListInput extends React.Component {
constructor( props ) {
constructor(props) { super( props );
super(props);
this.state = { this.state = {
/* last valid item that was selected from the drop down menu */ /* last valid item that was selected from the drop down menu */
lastValidItem: undefined, lastValidItem: undefined,
/* current input text */ /* current input text */
currentInput: "", currentInput: '',
/* current set of matching items */ /* current set of matching items */
matchingItems: [], matchingItems: [],
/* visibility property of the drop down menu */ /* 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 * 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 currentInput = event.target.value;
const matchingItems = this.props.items.filter((item) => { const { items, match } = this.props;
if (typeof(this.props.match) === typeof(Function)) const matchingItems = items.filter( ( item ) => {
return this.props.match(currentInput, item); if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
return this.match(currentInput, item); return this.match( currentInput, item );
}); } );
this.setState({ this.setState( {
currentInput: currentInput, currentInput,
matchingItems: matchingItems, matchingItems,
focusIndex: 0, focusIndex: 0,
visible: true, 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 currentInput
* @param item * @param item
* @returns {boolean} * @returns {boolean}
*/ */
match = (currentInput, item) => { match = ( currentInput, item ) => item
return item.label.substr(0, currentInput.length).toUpperCase() === currentInput.toUpperCase(); .label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
};
/** /**
* function for getting the index of the currentValue inside a value of the values array * 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 * @param item
* @returns {number} * @returns {number}
*/ */
indexOfMatch = (currentInput, item) => { indexOfMatch = ( currentInput, item ) => item
return item.label.toUpperCase().indexOf(currentInput.toUpperCase()); .label.toUpperCase().indexOf( currentInput.toUpperCase() );
};
/** /**
* handle key events * handle key events
* @param event * @param event
*/ */
onHandleKeydown = (event) => { onHandleKeydown = ( event ) => {
const { visible, focusIndex, matchingItems } = this.state;
// only do something if drop-down div is visible // only do something if drop-down div is visible
if (!this.state.visible) return; if ( !visible ) return;
let currentFocusIndex = this.state.focusIndex; let currentFocusIndex = focusIndex;
if (event.keyCode === 40 || event.keyCode === 9) { if ( event.keyCode === 40 || event.keyCode === 9 ) {
// If the arrow DOWN key or tab is pressed increase the currentFocus variable: // If the arrow DOWN key or tab is pressed increase the currentFocus variable:
currentFocusIndex += 1; currentFocusIndex += 1;
if (currentFocusIndex >= this.state.matchingItems.length) currentFocusIndex = 0; if ( currentFocusIndex >= matchingItems.length ) currentFocusIndex = 0;
this.setState({ this.setState( {
focusIndex: currentFocusIndex, focusIndex: currentFocusIndex,
}); } );
// prevent tab to jump to the next input field if drop down is still open // prevent tab to jump to the next input field if drop down is still open
event.preventDefault(); event.preventDefault();
} else if (event.keyCode === 38) { } else if ( event.keyCode === 38 ) {
// If the arrow UP key is pressed, decrease the currentFocus variable: // If the arrow UP key is pressed, decrease the currentFocus variable:
currentFocusIndex -= 1; currentFocusIndex -= 1;
if (currentFocusIndex <= -1) currentFocusIndex = this.state.matchingItems.length - 1; if ( currentFocusIndex <= -1 ) currentFocusIndex = matchingItems.length - 1;
this.setState({ this.setState( {
focusIndex: currentFocusIndex, focusIndex: currentFocusIndex,
}); } );
} else if (event.keyCode === 13) { } else if ( event.keyCode === 13 ) {
// Enter pressed, similar to onClickItem // Enter pressed, similar to onClickItem
if (this.state.focusIndex > -1) { if ( focusIndex > -1 ) {
// Simulate a click on the "active" item: // Simulate a click on the "active" item:
const selectedItem = this.state.matchingItems[currentFocusIndex]; const selectedItem = matchingItems[ currentFocusIndex ];
this.onSelect(selectedItem); this.onSelect( selectedItem );
} }
} }
}; };
@ -99,11 +98,19 @@ class DataListInput extends React.Component {
* onClickItem gets called when onClick happens on one of the list elements * onClickItem gets called when onClick happens on one of the list elements
* @param event * @param event
*/ */
onClickItem = (event) => { onClickItem = ( event ) => {
const { matchingItems } = this.state;
// update the input value and close the dropdown again // update the input value and close the dropdown again
const selectedKey = event.currentTarget.children[1].value; const elements = event.currentTarget.children;
const selectedItem = this.state.matchingItems.find(item => item.key === selectedKey); let selectedKey;
this.onSelect(selectedItem); 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 * does nothing if the key has not changed since the last onSelect event
* @param selectedItem * @param selectedItem
*/ */
onSelect = (selectedItem) => { onSelect = ( selectedItem ) => {
if (this.state.lastValidItem !== undefined && selectedItem.key === this.state.lastValidItem.key){ console.log( selectedItem );
const { lastValidItem } = this.state;
if ( lastValidItem && selectedItem.key === lastValidItem.key ) {
// do not trigger the callback function // do not trigger the callback function
// but still change state to fit new selection // but still change state to fit new selection
this.setState({ this.setState( {
currentInput: selectedItem.label, currentInput: selectedItem.label,
visible: false, visible: false,
focusIndex: -1, focusIndex: -1,
}); } );
return; return;
} }
// change state to fit new selection // change state to fit new selection
this.setState({ this.setState( {
currentInput: selectedItem.label, currentInput: selectedItem.label,
lastValidItem: selectedItem, lastValidItem: selectedItem,
visible: false, visible: false,
focusIndex: -1, focusIndex: -1,
}); } );
// callback function onSelect // callback function onSelect
this.props.onSelect(selectedItem); const { onSelect } = this.props;
onSelect( selectedItem );
}; };
renderItems = ( items, focusIndex, activeItemClassName, itemClassName) => ( renderItemLabel = ( currentInput, item ) => (
<div className="datalist-items"> <React.Fragment>
{items.map((item, i) => { {item.label.substr( 0, this.indexOfMatch( currentInput, item ) )}
const isActive = focusIndex === i; <strong>
const itemActiveClasses = isActive ? `datalist-active-item ${activeItemClassName}` : '' {item.label.substr( this.indexOfMatch( currentInput, item ), currentInput.length )}
const itemClasses = `${itemClassName} ${itemActiveClasses};` </strong>
return ( {item.label.substr( this.indexOfMatch( currentInput, item ) + currentInput.length )}
<div onClick={this.onClickItem} </React.Fragment>
className={itemClasses}
key={item.key}>
{item.label.substr(0, this.indexOfMatch(currentInput, item))}
<strong>{item.label.substr(this.indexOfMatch(currentInput, item), currentInput.length)}</strong>
{item.label.substr(this.indexOfMatch(currentInput, item) + currentInput.length)}
<input type='hidden' value={item.key}/>
</div>
) )
})}
renderItems = ( currentInput, items, focusIndex, activeItemClassName, itemClassName ) => (
<div className="datalist-items">
{items.map( ( item, i ) => {
const isActive = focusIndex === i;
const itemActiveClasses = isActive ? `datalist-active-item ${ activeItemClassName }` : '';
const itemClasses = `${ itemClassName } ${ itemActiveClasses };`;
return (
<div
onClick={this.onClickItem}
className={itemClasses}
key={item.key}
tabIndex={0}
role="button"
onKeyUp={event => event.preventDefault()}
>
{ this.renderItemLabel( currentInput, item )}
<input type="hidden" value={item.key} />
</div>
);
} )}
</div> </div>
); );
renderInputField = ( placeholder, currentInput, inputClassName ) => ( renderInputField = ( placeholder, currentInput, inputClassName ) => (
<input onKeyDown={this.onHandleKeydown} onInput={this.onHandleInput} type="text" <input
className={ `autocomplete-input ${inputClassName}` } onChange={this.onHandleInput}
placeholder={placeholder} value={currentInput}/> onKeyDown={this.onHandleKeydown}
type="text"
className={`autocomplete-input ${ inputClassName }`}
placeholder={placeholder}
value={currentInput}
/>
) )
render() { render() {
const { currentInput, matchingItems, focusIndex, visible } = this.state; const {
const { placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength } = this.props; currentInput, matchingItems, focusIndex, visible,
} = this.state;
const {
placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength,
} = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength; const reachedRequiredLength = currentInput.length >= requiredInputLength;
return ( return (
<div className="datalist-input"> <div className="datalist-input">
{ this.renderInputField( placeholder, currentInput, inputClassName ) } { this.renderInputField( placeholder, currentInput, inputClassName ) }
{ reachedRequiredLength && visible && { reachedRequiredLength && visible
this.renderItems( matchingItems, focusIndex, activeItemClassName, itemClassName ) && this.renderItems( currentInput, matchingItems, focusIndex,
activeItemClassName, itemClassName )
} }
</div> </div>
); );
@ -175,7 +208,12 @@ class DataListInput extends React.Component {
} }
DataListInput.propTypes = { DataListInput.propTypes = {
items: PropTypes.array.isRequired, items: PropTypes.arrayOf(
PropTypes.shape( {
label: PropTypes.string.isRequired,
key: PropTypes.number.isRequired,
} ),
).isRequired,
placeholder: PropTypes.string, placeholder: PropTypes.string,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
match: PropTypes.func, match: PropTypes.func,

View File

@ -1,6 +1,6 @@
{ {
"name": "react-datalist-input", "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.", "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", "main": "build/index.js",
"repository": { "repository": {

View File

@ -29,35 +29,37 @@ class DataListInput extends React.Component {
this.onSelect = this.onSelect.bind(this); this.onSelect = this.onSelect.bind(this);
this.renderItems = this.renderItems.bind(this); this.renderItems = this.renderItems.bind(this);
this.renderInputField = this.renderInputField.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 * 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 currentInput = event.target.value;
const matchingItems = this.props.items.filter((item) => { const { items, match } = this.props;
if (typeof(this.props.match) === typeof(Function)) const matchingItems = items.filter( ( item ) => {
return this.props.match(currentInput, item); if ( typeof ( match ) === typeof ( Function ) ) { return match( currentInput, item ); }
return this.match(currentInput, item); return this.match( currentInput, item );
}); } );
this.setState({ this.setState( {
currentInput: currentInput, currentInput,
matchingItems: matchingItems, matchingItems,
focusIndex: 0, focusIndex: 0,
visible: true, 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 currentInput
* @param item * @param item
* @returns {boolean} * @returns {boolean}
*/ */
match(currentInput, item) { match( currentInput, item ) {
return item.label.substr(0, currentInput.length).toUpperCase() === currentInput.toUpperCase(); return item.label.substr( 0, currentInput.length ).toUpperCase() === currentInput.toUpperCase();
} }
/** /**
@ -66,40 +68,41 @@ class DataListInput extends React.Component {
* @param item * @param item
* @returns {number} * @returns {number}
*/ */
indexOfMatch(currentInput, item) { indexOfMatch( currentInput, item ) {
return item.label.toUpperCase().indexOf(currentInput.toUpperCase()); return item.label.toUpperCase().indexOf( currentInput.toUpperCase() );
} }
/** /**
* handle key events * handle key events
* @param event * @param event
*/ */
onHandleKeydown(event) { onHandleKeydown( event ) {
const { visible, focusIndex, matchingItems } = this.state;
// only do something if drop-down div is visible // only do something if drop-down div is visible
if (!this.state.visible) return; if ( !visible ) return;
let currentFocusIndex = this.state.focusIndex; let currentFocusIndex = focusIndex;
if (event.keyCode === 40 || event.keyCode === 9) { if ( event.keyCode === 40 || event.keyCode === 9 ) {
// If the arrow DOWN key or tab is pressed increase the currentFocus variable: // If the arrow DOWN key or tab is pressed increase the currentFocus variable:
currentFocusIndex += 1; currentFocusIndex += 1;
if (currentFocusIndex >= this.state.matchingItems.length) currentFocusIndex = 0; if ( currentFocusIndex >= matchingItems.length ) currentFocusIndex = 0;
this.setState({ this.setState( {
focusIndex: currentFocusIndex, focusIndex: currentFocusIndex,
}); } );
// prevent tab to jump to the next input field if drop down is still open // prevent tab to jump to the next input field if drop down is still open
event.preventDefault(); event.preventDefault();
} else if (event.keyCode === 38) { } else if ( event.keyCode === 38 ) {
// If the arrow UP key is pressed, decrease the currentFocus variable: // If the arrow UP key is pressed, decrease the currentFocus variable:
currentFocusIndex -= 1; currentFocusIndex -= 1;
if (currentFocusIndex <= -1) currentFocusIndex = this.state.matchingItems.length - 1; if ( currentFocusIndex <= -1 ) currentFocusIndex = matchingItems.length - 1;
this.setState({ this.setState( {
focusIndex: currentFocusIndex, focusIndex: currentFocusIndex,
}); } );
} else if (event.keyCode === 13) { } else if ( event.keyCode === 13 ) {
// Enter pressed, similar to onClickItem // Enter pressed, similar to onClickItem
if (this.state.focusIndex > -1) { if ( focusIndex > -1 ) {
// Simulate a click on the "active" item: // Simulate a click on the "active" item:
const selectedItem = this.state.matchingItems[currentFocusIndex]; const selectedItem = matchingItems[ currentFocusIndex ];
this.onSelect(selectedItem); this.onSelect( selectedItem );
} }
} }
} }
@ -108,11 +111,19 @@ class DataListInput extends React.Component {
* onClickItem gets called when onClick happens on one of the list elements * onClickItem gets called when onClick happens on one of the list elements
* @param event * @param event
*/ */
onClickItem(event) { onClickItem( event ) {
const { matchingItems } = this.state;
// update the input value and close the dropdown again // update the input value and close the dropdown again
const selectedKey = event.currentTarget.children[1].value; const elements = event.currentTarget.children;
const selectedItem = this.state.matchingItems.find(item => item.key === selectedKey); let selectedKey;
this.onSelect(selectedItem); 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 * does nothing if the key has not changed since the last onSelect event
* @param selectedItem * @param selectedItem
*/ */
onSelect(selectedItem) { onSelect( selectedItem ) {
if (this.state.lastValidItem !== undefined && selectedItem.key === this.state.lastValidItem.key){ const { lastValidItem } = this.state;
if ( lastValidItem && selectedItem.key === lastValidItem.key ) {
// do not trigger the callback function // do not trigger the callback function
// but still change state to fit new selection // but still change state to fit new selection
this.setState({ this.setState( {
currentInput: selectedItem.label, currentInput: selectedItem.label,
visible: false, visible: false,
focusIndex: -1, focusIndex: -1,
}); } );
return; return;
} }
// change state to fit new selection // change state to fit new selection
this.setState({ this.setState( {
currentInput: selectedItem.label, currentInput: selectedItem.label,
lastValidItem: selectedItem, lastValidItem: selectedItem,
visible: false, visible: false,
focusIndex: -1, focusIndex: -1,
}); } );
// callback function onSelect // callback function onSelect
this.props.onSelect(selectedItem); const { onSelect } = this.props;
onSelect( selectedItem );
} }
renderItems( items, focusIndex, activeItemClassName, itemClassName) { renderItemLabel( currentInput, item ) {
return (
<React.Fragment>
{item.label.substr( 0, this.indexOfMatch( currentInput, item ) )}
<strong>
{item.label.substr( this.indexOfMatch( currentInput, item ), currentInput.length )}
</strong>
{item.label.substr( this.indexOfMatch( currentInput, item ) + currentInput.length )}
</React.Fragment>
);
}
renderItems( currentInput, items, focusIndex, activeItemClassName, itemClassName ) {
return ( return (
<div className="datalist-items"> <div className="datalist-items">
{items.map((item, i) => { {items.map( ( item, i ) => {
const isActive = focusIndex === i; const isActive = focusIndex === i;
const itemActiveClasses = isActive ? `datalist-active-item ${activeItemClassName}` : '' const itemActiveClasses = isActive ? `datalist-active-item ${ activeItemClassName }` : '';
const itemClasses = `${itemClassName} ${itemActiveClasses};` const itemClasses = `${ itemClassName } ${ itemActiveClasses };`;
return ( return (
<div onClick={this.onClickItem} <div
onClick={this.onClickItem}
className={itemClasses} className={itemClasses}
key={item.key}> key={item.key}
{item.label.substr(0, this.indexOfMatch(currentInput, item))} tabIndex={0}
<strong>{item.label.substr(this.indexOfMatch(currentInput, item), currentInput.length)}</strong> role="button"
{item.label.substr(this.indexOfMatch(currentInput, item) + currentInput.length)} onKeyUp={event => event.preventDefault()}
<input type='hidden' value={item.key}/> >
{ this.renderItemLabel( currentInput, item )}
<input type="hidden" value={item.key} />
</div> </div>
) );
})} } )}
</div> ); </div> );
} }
renderInputField( placeholder, currentInput, inputClassName ) { renderInputField( placeholder, currentInput, inputClassName ) {
return ( return (
<input onKeyDown={this.onHandleKeydown} onInput={this.onHandleInput} type="text" <input
className={ `autocomplete-input ${inputClassName}` } onChange={this.onHandleInput}
placeholder={placeholder} value={currentInput} /> ); onKeyDown={this.onHandleKeydown}
type="text"
className={`autocomplete-input ${ inputClassName }`}
placeholder={placeholder}
value={currentInput}
/>
);
} }
render() { render() {
const { currentInput, matchingItems, focusIndex, visible } = this.state; const {
const { placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength } = this.props; currentInput, matchingItems, focusIndex, visible,
} = this.state;
const {
placeholder, inputClassName, activeItemClassName, itemClassName, requiredInputLength,
} = this.props;
const reachedRequiredLength = currentInput.length >= requiredInputLength; const reachedRequiredLength = currentInput.length >= requiredInputLength;
return ( return (
<div className="datalist-input"> <div className="datalist-input">
{ this.renderInputField( placeholder, currentInput, inputClassName ) } { this.renderInputField( placeholder, currentInput, inputClassName ) }
{ reachedRequiredLength && visible && { reachedRequiredLength && visible
this.renderItems( matchingItems, focusIndex, activeItemClassName, itemClassName ) && this.renderItems( currentInput, matchingItems, focusIndex,
activeItemClassName, itemClassName )
} }
</div> </div>
); );
@ -186,7 +225,12 @@ class DataListInput extends React.Component {
} }
DataListInput.propTypes = { DataListInput.propTypes = {
items: PropTypes.array.isRequired, items: PropTypes.arrayOf(
PropTypes.shape( {
label: PropTypes.string.isRequired,
key: PropTypes.number.isRequired,
} ),
).isRequired,
placeholder: PropTypes.string, placeholder: PropTypes.string,
onSelect: PropTypes.func.isRequired, onSelect: PropTypes.func.isRequired,
match: PropTypes.func, match: PropTypes.func,