lw-select-react

0.0.5 • Public • Published

lw-select: lighweight ReactJS and plain HTML select components

The goal is to provide a simple select control that does not require me to commit to a full platform and its dependencies, made of thousands of lines of unknown code that bloats the build.
I also wanted it to handle relatively lengthy lists with ease. The enclosed demos handles a 10K and a 40K items list. The idea is to allow passing relatively lengthy items list down to the client and to forget about banging the server once and again with paging as if we still were in 1999.

Additionally, I want to ensure the best developer experience. Not only the users deserve consideration.
This means not having to study a ream of docs, while being in control of what is happening inside of the applications.

There are two versions in this same project: a ReactJS component, and a plain HTML one. Both do almost the same.

Online demos

There is a short (7 items) regions demo, and a bulkier cars demo (~10K items).
Also, there is a ~40K cities demo that is a bit slower, shown only in the HTML version, that's used for testing performance features.

Dependencies

This feature is self-contained.

What does it do?

The control looks and works like the standard HTML select element.
Initially it displays a placeholder text (placeholder), or the pre-selected item if any (one of value, valueIndex, valueText).
When clicked, it displays a drop with the items list.
Typing text filters the list so it shows only the items containing the typed text or texts (space separated).
Clicking an item in the drop selects it, triggers an event (onSelectionChanged), and returns the associated data (value, valueText, valueIndex).
Selection can also be done using the keyboard arrows and the enter key.

The items array

The control consumes an array of objects (listSource).
It displays each array item as an item in the list; the items are sorted as they are in the array.
The text displayed in each item is either a property of the object associated to the list item, or is built by a function passed as a parameter (listDisplayProps).
For searching, the text entered by the user is matched against a string built using the element's properties enumerated in a parameter (listSearchProps).
This allows to search over properties that are not displayed. For example in the cars demo the "category" property is not displayed but is used in the search. Thus, if you type "coupe" the list will filter in the items containing Category: "coupe' (or the word "coupe" somewhere else).
At load time the control builds all the search texts by joining the selected properties converted to lowercase, with their accents stripped, and separated by two blank spaces. These strings are stored in a separate array, congruent with the items array.
Thus, if the properties of an item are changed after the control has loaded, its internal state might not reflect the new state of the items arrays.

Running the demos locally

There are two demos, one for tha ReactJS version, and the other for the plain HTML version.
Both do more or less the same.
The cars listing demos handle a list of nearly 10K elements.
The control was tested all along with this rather lengthy list because one of the goals was to make it more or less performant.
The cars listing is excellent data from https://www.back4app.com/database

To run the ReactJS demo, in the project top dir, type:
npm start
The demo is a regular Create React App project.
To run the HTML demo, cd to the project top directory and run the command:
npm run proto
This starts a browser-sync web server and opens a browser tab loaded with the demo index.html.
So you need browser-sync as a development dependency (normally installed global), or any other HTTP server.
The file displayed is ./public/proto.html, that must be run from a web server because it doesn't work with the file protocol.

Configuration

The control is configured through its properties ("props" in ReactJS parlance).
These props are:

Only one of the value... props needs to be set, if all are null no element is selected.

If one of the values is invalid, it is ignored with a warning in the console.
"Invalid" means that value is not an object or is not found in listSource, valueIndex is not a number or is out of the range of indexes for listSource, or that valueText is not equal to any of the item descriptions (not an exact match).

listSource

An array of objects.
In the examples that use the cars list, the elements look like this:

const carsListing = [
  { Year: 2022, Make: "Chevrolet", Model: "Bolt EV", Category: "Hatchback" },
  { Year: 2022, Make: "Chevrolet", Model: "Equinox", Category: "SUV" },
  { Year: 2022, Make: "Chevrolet", Model: "Traverse", Category: "SUV" },
  { Year: 2022, Make: "GMC", Model: "Hummer EV", Category: "Pickup" },
  ...
]

Obviously, your objects will have the property names that you want, there are no restrictions.
The invocation looks like:

<LWSelect 
  listSource = { carsListing }
  ...
>

onSelectionChanged

A function to be called whenever the selection is changed, either assigned a value, or nullified, due to user actions.
Not called on initial values, when the control is initialized.
It will be passed 3 args: value, valueText and valueIndex.

  • value is a reference to the selected item in the listSource.
  • valueIndex is the index of the selectred item in the array.
  • valueText is the text displayed in the UI, associated with the selected item.

listDisplayProps

The rule for formatting the texts displayed by the control in its drop list.
A string or an arrow function.

If it's a atring, it must be the name of a property present in the items of the list, like for example Model if using the cars listing.
Every item of the drop will contain the value of the "Model" property of the associated listSource item.
Items lacking the selected property will be listed as "undefined".

If it's a function, it has to be an arrow function that returns the string to be displayed in the drop.
At load time, the function will be called for each of the list elements, and the string it returns will be displayed associated to the item.
When called the function is passed two arguments, say item and i.
item is a reference to the element in the listSource array.
i is the numeric index of that element.

In the cars example it's coded as:

listDisplayProps = {
  ( item, i ) => { return item.Year + ' ' + item.Make + ' ' + item.Model }
}

Thus, the drop will show the values of Year, Make and Model for each item, separated by spaces.

listSearchProps

The names of the properties used for searching.
An array of strings, each one being the name of a property of the items list listSource array.
In the cars example, it is like so:

listSearchProps = { [ 'Make', 'Model', 'Year', 'Category' ] }

Notice that the "Category" property is present for searching, but not displayed (it is not in listDisplayProps.
So it's possible to search for "sedan" albeit this property does not show in the visible list.

At load time the control builds a "search array", congruent with the items list, containing the values of the fialds named here, separated by two spaces.

placeholder

A string.
It is used verbatim as the placeholder property of the HTML <input> element of the control.

value

A reference to an element of the listSource array to be selected at the touset.
You need to set only one of the three value... properties, if no initial value then set all to null.
The value prop is checked first. If it's valid then the others are ignored, if it's invalid then a warning is displayed on the console and the next option is checked.

valueIndex

The numeric index of an element of the listSource array to be selected at the touset.
You need to set only one of the three value... properties, if no initial value then set all to null.
This prop is checked after value. If it is valid then valueText is ignored, if it's invalid then a warning is displayed on the console and the next option is checked.

valueText

A string. The displayed text associated with the item of the list to be selected at start.
To be valid, it must be identical to the actual text displayed in one of the items, as if it was built by the listDisplayProps function.
If it's invalid then a warning is displayed on the console and no initial value is set.
You need to set only one of the three value... properties, if no initial value then set all to null.

disabled

A value that will be regarded as boolean. If truthy the lw-select intance is rendered disabled.
The default is false.

`readonly

One of true, false or disabled, meaning that the control is to be rendered in a disabled state.
It looks normal, and can be focused, but can't be changed. The default is false.

dropDownArrowIcon

Optional replacement for the drop-down arrow image, an SVG or IMG up to 24x24. For example, to emulate MUI:

dropDownArrowIcon: `
  <svg class="lwButtonIcon" aria-hidden="true" viewBox="0 0 24 24" data-testid="ArrowDropDownIcon" stroke="#fff" fill="#fff">
    <path d="M7 10l5 5 5-5z"></path>
  </svg> `,

It can be an <svg> or <img> tag with a data: URI.
⚠️ What you code in this prop will replace the innerHTML of the disclose button, so be very careful not to allow malware in.
Also, the class attribute seems to be needed for the SVGs to be clickable, this is kind of a magic hack because I can't explain it.
It can be only one icon, or two if you want it to swap when the list visibility changes. In this case they must have the classes lwButtonOpen and lwButtonClose, and display="block' and display="none.

targetElementId

Only for the HTML version: id of the DOM element where this instance of lw-select has to be appended.

Styles

The styling relies on a set of "CSS variables" (CSS custom properties).
This is a work in progress, aiming at externalizing almost everything, so developers can integrate this control nicely.

The variables are defined in the file LWSelect.module.css. The names are fairly descriptive. At the time of this writing (Nov'21) thet are:

  --control-height:28px;
  --control-border:1px solid #bbbbbb;
  --control-border-radius:7px;
  --control-inner-border-radius:6px; /* subtract border width */
  --control-bg-color:rgb(51,51,51); /* #333333 */
  --control-bg-color-hover:rgb(80,80,80); /* #505050 */
  --control-text-color:rgb(238, 238, 238); /* #eeeeee */
  --control-text-interim-color:rgb(160,160,255); /* #a0a0ff */
  --control-text-not-valid:underline wavy;
  --control-text-color-hover:white;
  --control-input-text-size:16px;

These names are referenced along the CSS files.

ReactJS directory structure

LWSelect.js
LWSelect.module.css

LWSelect.js is the main program, and LWSelect.module.css contains the styles.
All the other files come from a Create react App bootstrap.
In the public directory there are several files that comprise the HTML version, rootad at proto.html.

Security alert

One of the props, listDisplayProps, is a function, entered in plain text.
ESLint complained about it and was silenced with the // eslint-disable-next-line no-new-func declaration.
If evils users were able to edit it, they could wreack havoc in your syste.
Anyway, if the eval feature wasn't used, and those evil users were able to reach your code, thay would anyway be able to do lots of harm, so the precautins are the usual.

Disclaimer

If something fails I will worry a lot, but won't take amy responsibility.

Keywords

select, dropdown, autocomplete, autosuggest, typeahead, combobox, dropdown, choices, select replacement, listbox, picker, select box, web component, ReactJS, HTML5

Dependencies (0)

    Dev Dependencies (0)

      Package Sidebar

      Install

      npm i lw-select-react

      Weekly Downloads

      1

      Version

      0.0.5

      License

      MIT

      Unpacked Size

      49.6 kB

      Total Files

      6

      Last publish

      Collaborators

      • jlanus