1.0.0-rc.10 • Published 3 years ago

@ayfie/datalist v1.0.0-rc.10

Weekly downloads
-
License
MIT
Repository
github
Last release
3 years ago

Datalist module (vanilla)

Description

This is a javascript based datalist replacement library. It addresses most (if not all) the shortcomings of the HTML standard <datalist>element.

It is written in typescript, meaning that both javascript and typescript developers will get intellisense in modern IDEs.

Quick start

Installation

  • npm:

    npm install @ayfie/datalist

  • yarn:

    yarn add @ayfie/datalist

Sample

HTML:

<html>
    <head>
        ...
    </head>
    <body>
        <input id="my-input-field" />
    </body>
</html>

Code:

import { Datalist } from 'datalist'

const inputElement = document.getElementById('query')

const datalist = new Datalist(inputElement)

datalist.setOptions(['element 1', 'element 2'])

Table of Contents

1. Motivation

The Datalist module makes it easy to show an autocomplete suggestion-list along with your input element.

Very much like the HTML <datalist> element, in fact.

So, why not use a normal <datalist> then?

The answer is that the <datalist> tag has some important limitations, at least at the time of writing this, (May, 2021). Here are some of the features missing in the <datalist> element that motivated this library, along with a comparison:

2. W3C <datalist> compliance

Navigation of the datalist elements is according to the W3C specifications, with an addition of being able to set the min-length before actuation as well as to customize the filter-method:

  • The datalist elements to show are given using these rules:
    • The elements given programmatically, filtered as follows:
      • When inputElement has content longer than the given minLength, the list is filtered based on the given algorithm (default indexOf(text) > -1)
  • When the inputElement is not focused:
    • When navigating to the inputElement via keys, thus giving focus to the element, the datalist remains HIDDEN.
    • When the inputElement is clicked on thus giving it focus however, the datalist is SHOWN (if has matching items).
  • When the inputElement is focused:
    • When the datalist is in a hidden state:
      • When the inputElement looses focus, the datalist remains HIDDEN.
      • When using UP or DOWN arrows the datalist IS shown, making the first/last element "selected".
    • When the datalist is in a visible state:
      • When the inputElement looses focus, the datalist is HIDDEN.
      • When using UP or DOWN arrows the datalist remains shown, making the higher or lower element "selected". When at the top or bottom the selected item will cycle.
      • When clicking ESCAPE the datalist becomes HIDDEN.
      • When clicking ENTER the selected datalist entry is written to the inputElement and the datalist is HIDDEN.
      • When hovering the mouse over the list, the current changes according to the mouse hover.
      • When selecting an item by clicking on it with the mouse the datalist entry is written to the inputElement and the datalist is HIDDEN.

3. Usage

const datalist = new Datalist(inputElement, {
    settings,
    callbacks,
    options: [
        'Alabama',
        'Alaska',
        'Arizona',
        'Arkansas',
        // ...
    ],
})

If you want the Datalist to return a different value than what is displayed that is also possible:

const datalist = new Datalist(inputElement, {
    settings,
    callbacks,
    options: [
        { display: 'Alabama', value: 'AL' },
        { display: 'Alaska', value: 'AK' },
        { display: 'Arizona', value: 'AZ' },
        { display: 'Arkansas', value: 'AR' },
        // ...
    ],
})

This simulates the way the <datalist> tag allows for the same feature: (<option value=”AL”>Alabama</option>...)

4. Features

4.1 Settings

There are many ways that you can adjust how the Datalist instance is created, by adjusting the settings property object in the constructor.

Also, if you want to change the styling of the Datalist instance via inline-styles, then this can easily be done via the [IStyling | settings.styling] property.

Please see the [ISettings] section to learn more about the settings you can pass when creating the Datalist instance.

4.2 Callbacks

Please see the [ICallbacks] section to learn more about callbacks you can register when creating the Datalist instance.

4.3 Instance API

  • You can change the options to render at runtime using the [Datalist.setOptions] method.
  • You can force the options to hide with the [Datalist.hide] method.
  • You can force the options to show with the [Datalist.show] method (given that there are any matching options to render).

5. Caveats & Gotchas

5.1. Input type

The Datalist module does not care about the input type (<input type="...">). It will literally try to match the input field value with whatever options that have been added. Which only makes sense for textual properties. It will probably get very confused when used with for example an <input type="range"> element. The range element will render something similar to an progress-bar, and the options passed will match the value, when it is set. Thus rendering the option list (with one option) as each of the range values matches. There is nothing in the code that checks the type, so this is something that might give unpredictable results.

5.2 DOM manipulation

This library does manipulate the DOM in order to create the visual elements that are to mimic the <datalist> and the <option> elements. Also, in order to manipulate the positioning of the options a wrapper is also added as a parent of the input element.

More specifically, the DOM is augmented as follows (for position="DOWN" - which is default):

Before:

<input type="input">

Note: The above <input> can also be a <textarea>.

After:

<div class="DL-wrapper>
  <input type="input">
  <div class="DL-datalist-container">
    <ol class="DL-datalist">
      <li class="option>...</li>
      ...
    </ol>
  </div>
</div>

Note: The above example shows the modification for position == "DOWN". For other positions the datalist element is placed before the <input>-field.

5.3 Using with frameworks/libraries

This library does not render the items using a html-like template or JSX and it does not handle the events like for instance React expects it to. It directly manipulates the DOM by adding some extra elements. But, even though the DOM is manipulated, it does not automatically mean that it cannot be used with any of the frameworks and libraries. Most of these libraries/frameworks allows you to get a reference to the actual DOM instance of the object, which you can then use to call this library. Note though that if the i.e. React element is re-rendered, and the input field-is recreated the object reference will have changed. Thus the Datalist instance would also need to be recreated. Since it is recreated the state would not survive and the user would potentially be interrupted while navigating the options. It is normally considered bad practice to change the input field in such a way that it has to re-render. That would also introduce other problems, like if you were navigating the input field with left and right arrow-keys. That state would be lost and reset.

Because of this you should be safe. Just remember to get a reference to the field after the normal rendering is done. Then construct the Datalist instance using that reference. The reference element is moved inside a wrapper, but it is still valid after rendering.

The primary use of this library is actually as an add on to a Preact rendered textarea-field, where we use hooks to get the reference to its DOM instance. We have never had a problem with this library doing javascript based DOM autocomplete options.

5.4 Styling

Because the DOM is manipulated, any existing CSS rules that target your input-field or elements around it may be adversely affected. Fixing these issues are often fairly trivial though as you just have to change the rules to take into account that the input-field is now in this wrapper, and that surrounding elements are no longer siblings of the the input-field.

Notes:Since the datalist-options are rendered with style position:absolute it doesn't out-of-the-box restrict to the width of the input-element. To make sure that the width is correct we monitor the input-element and make sure that it has the same width. This behavior can be turned off (see [autoWidth]).

5.5. Input key events

The Datalist module depends on monitoring all keys pressed while the input-element is focused. In particular the module is handling escape, enter and key up and down events. When appropriate it will steal the event from the control itself. Like, when using Enter to select an option, then the Enter key should not be handled by the input-element. This is done by stopping the event in the bubbling phase and thus making the event never happen in the input-element. If you however catch events in the bubbling phase yourself on the input-element, the Datalist event-handler might not be first to be executed so. This means that you may or may not get the Enter event, even if the Datalist cancels it. Also, if you get the event first - and you cancel it, the Datalist will not be able to convert the Enter event to an "option-select" command.

6. License

The MIT License

Copyright © 2021 Ayfie Group

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.