Do you like this project? Please support my Mecha CMS project too. Thank you!

Option Picker 2.2.6

Accessible custom <select> (and <input list>) element.

Press Enter or Space key to open/close the options list. Use ArrowDown and ArrowUp keys to open the options list and then focus on an option, then press Enter, Space, or Tab to select it. On the โ€œstrictโ€ option picker presentation (the example below), you should be able to type away when you focus on it to select the first active option that contains text that matches the search query you are currently typing. On the โ€œlooseโ€ option picker presentation (this example), when you start to input a value, the available options get filtered instead of selected immediately.

Usage

Note: CSS variables have been removed since version 2.0.0. From now on, I will only provide a minimal style just to make viable interaction. Expect it to look ugly by default. The main goal of this application is to be able to integrate it seamlessly into your web design. By providing a minimal style, it is easy to add other styles to it to adapt it to your own web design. Have a look at the source code on this page to see how I adapted the style to resemble the default style of the previous version.

Browser

<!DOCTYPE html>
<html dir="ltr">
  <head>
    <meta charset="utf-8">
    <link href="./index.min.css" rel="stylesheet">
    <style>
      .option-picker {
        width: 100%;
      }
      .option-picker__options {
        background: #fff;
      }
    </style>
  </head>
  <body>
    <p>
      <select>
        <option>
          Option 1
        </option>
        <option>
          Option 2
        </option>
        <option value="3">
          Option 3
        </option>
      </select>
    </p>
    <script src="./index.min.js"></script>
    <script>
      const picker = new OptionPicker(document.querySelector('select'));
    </script>
  </body>
</html>

Node.js

Functions and methods in this application are mostly native JavaScript and are intended for use by the browser. Node.js doesnโ€™t know about the DOM, so this kind of practice will probably be used more often to build new browser packages than to be used directly in the Node.js server.

CommonJS

const OptionPicker = require('@taufik-nurrohman/option-picker').default;

const picker = new OptionPicker(document.querySelector('select'));

ECMAScript

import OptionPicker from '@taufik-nurrohman/option-picker';

const picker = new OptionPicker(document.querySelector('select'));

Tests

Tweaks

Constructors

OptionPicker(self, state)

const picker = new OptionPicker(self, strict = false);
const picker = new OptionPicker(self, state = {
    max: null,
    min: null,
    options: null,
    size: null,
    strict: false,
    time: {
        error: 1000,
        search: [10, 500]
    },
    with: []
});

OptionPicker.Options(picker, options)

This constructor is used internally. It is the return value of the picker.options property. It contains methods like get(), has(), let(), and set() to modify the current options.

const options = new OptionPicker.Options(picker, ['Option 1', 'Option 2', 'Option 3']);

Parameters

self

The <input> or <select> element.

strict

Do not allow users to enter a value other than those available in the options list on the โ€œlooseโ€ option picker presentation. Pressing the Tab key will automatically select the first active option.

state

The configuration data.

state.max

Determines the maximum number of options that can be selected on the option picker that are sourced from a <select multiple> element. It has a default value set to Infinity. If the source element is an <input> element, then its default value will be 1. This means that the multiple selection feature will apply only to <select> element.

const picker = new OptionPicker(document.querySelector('select'), {
    max: 25
});

state.min

Determines the minimum number of options that can be selected on the option picker. If the source element is a <select> element, the default value will be 1; if the source element is an <input> element, the default value will be 0.

const picker = new OptionPicker(document.querySelector('select'), {
    min: 5
});

state.options

A list of options data as an array, an object, a map, or a function that returns an array, an object, or a map.

Array is the easiest data structure to write, it guarantees that the order will remain the same when transformed into a series of options. With this kind of data structure, where each element is a scalar, the option label will also act as a value after it is transformed into an options list. This is equivalent to creating an <option> element without a value attribute.

const options = [
    'Option 1',
    'Option 2',
    'Option 3'
];

const picker = new OptionPicker(document.querySelector('select'), {options});
const options = [
    ['Option 1', {
        value: 1
    }],
    ['Option 2', {
        value: 2
    }],
    ['Option 3', {
        active: false,
        value: 3
    }]
];

const picker = new OptionPicker(document.querySelector('select'), {options});

Object is another data structure that is also easy to write. The object item keys act as option values, and the object item values act as option labels. The only problem with this data structure is that it cannot guarantee that the order will remain the same when transformed into a series of options, especially if each object key is written as a number.

const options = {
    '1': 'Option 1',
    '2': 'Option 2',
    '3': 'Option 3'
};

const picker = new OptionPicker(document.querySelector('select'), {options});

The solution would be to set the object item key as text and then set the object item value in the attributes object.

const options = {
    'option-1': ['Option 1', {
        value: 1
    }],
    'option-2': ['Option 2', {
        value: 2
    }],
    'option-3': ['Option 3', {
        active: false,
        value: 3
    }]
};

const picker = new OptionPicker(document.querySelector('select'), {options});

Map is another type of data structure, similar to an object, but it guarantees that the order will remain the same when transformed into a series of options.

const options = new Map;

options.set(1, 'Option 1');
options.set(2, 'Option 2');
options.set(3, 'Option 3');

const picker = new OptionPicker(document.querySelector('select'), {options});
const options = new Map;

options.set(1, ['Option 1', {
    value: 1
}]);

options.set(2, ['Option 2', {
    value: 2
}]);

options.set(3, ['Option 3', {
    active: false,
    value: 3
}]);

const picker = new OptionPicker(document.querySelector('select'), {options});

Function that returns one of the three data structures above can also be used. The first argument of this function holds the typed search query. Useful for creating dynamic options list capabilities using AJAX.

const options = function (query) {
    return [
        'Option 1',
        'Option 2',
        'Option 3'
    ];
};

const picker = new OptionPicker(document.querySelector('select'), {options});
const options = function (query) {
    return fetch('./options.php?query=' + encodeURIComponent(query)).then(response => response.json());
};

const picker = new OptionPicker(document.querySelector('select'), {options});

state.size

Set the value to an integer greater than 1 to display the options list as a scrollable area that is always visible. This layout takes up space and is provided to mimic the behavior of a <select> element when given the size attribute. It has a default value of null. If self is a <select> element, then the application will try to use the size attribute value of that element. This does not apply if self is an <input> element, because size attribute on an <input> element has different semantics. 1 2

const picker = new OptionPicker(document.querySelector('select:not([size])'), {
    size: 5
});

state.strict

Do not allow users to enter a value other than those available in the options list on the โ€œlooseโ€ option picker presentation. Pressing the Tab key will automatically select the first active option.

const picker = new OptionPicker(document.querySelector('input[list]'), {
    strict: true
});

state.time

Stores configuration data related to time in milliseconds.

state.time.error

Determines how long the invalid state is presented to the user. If the value is less than or equal to 0, the built-in invalid state will be disabled.

const picker = new OptionPicker(document.querySelector('select'), {
    time: {
        error: 999999999 // As long as possible
    }
});
const picker = new OptionPicker(document.querySelector('select'), {
    time: {
        error: 0 // Disable the built-in invalid state
    }
});

It contains an array that determines how long a user needs to stop typing to begin filtering or searching and how long they need to stop typing to reset the search query in the โ€œstrictโ€ option picker presentation.

const picker = new OptionPicker(document.querySelector('select'), {
    time: {
        search: [
            // Begin filtering or searching one second after the user stops typing
            1000,
            // Reset the typed search query after one second if the user stops typing
            1000
        ]
    }
});

state.with

List of callable functions or objects containing an attach() method to be invoked each time the application is initialized. A very simple โ€œpluginโ€ system.

Methods

Instance Methods

Instance methods are methods available through the results of an OptionPicker construct.

picker.attach(self, state)

Re-initializes the application and its extensions after it has been detached.

picker.attach();

picker.blur()

Blurs from the option pickerโ€™s input or value.

picker.blur();

picker.detach()

Removes the application view and executes the detach() method of the extensions, if they are present.

picker.detach();

picker.enter(focus, mode = true)

Shows the option pickerโ€™s options.

picker.enter(); // Show the option pickerโ€™s options
picker.enter(true); // Show the option pickerโ€™s options and focus to the option pickerโ€™s input or value

picker.exit(focus, mode = true)

Hides the option pickerโ€™s options.

picker.exit(); // Hide the option pickerโ€™s options
picker.exit(true); // Hide the option pickerโ€™s options and focus to the option pickerโ€™s input or value

picker.fire(event, data, that)

Fires an event.

picker.fire('change', []);

picker.focus(mode = true)

Focuses to the option pickerโ€™s input or value.

picker.focus(); // Focus and select the text
picker.focus(-1); // Focus and put the caret to the start of the text
picker.focus(+1); // Focus and put the caret to the end of the text

picker.off(event, task)

Removes an event.

picker.off('change'); // Remove all events from the `change` event container
picker.off('change', onChange); // Remove `onChange` event from the `change` event container

picker.on(event, task)

Adds a new event.

picker.on('change', function () {
    console.log(this.value);
});
function onChange() {
    console.log(this.value);
}

picker.on('change', onChange);

picker.reset(focus, mode = true)

Resets the option pickerโ€™s value or values to its initial value or values.

picker.reset(); // Reset the option pickerโ€™s value or values
picker.reset(true); // Reset the option pickerโ€™s value or values and focus to the option pickerโ€™s input or value

Static Methods

Static methods are methods available directly on the OptionPicker object.

OptionPicker.from(self, state)

Creates a new OptionPicker instance.

const picker = OptionPicker.from(document.querySelector('select'));

OptionPicker.of(self)

Gets OptionPicker instance of an element.

document.querySelectorAll('input[list], select').forEach(self => {
    const picker = OptionPicker.of(self);
});

Properties

Instance Properties

Instance properties are properties available through the results of an OptionPicker construct.

picker.active

Gets or sets the active state of the option picker. By setting the value to false or true, the disabled state of the source element will also be set automatically.

picker.active = false; // // Make the option picker โ€œdisabledโ€
picker.active = true; // // Make the option picker โ€œenabledโ€

picker.fix

Gets or sets the read-only state of the option picker. By setting the value to false or true, the read-only state of the source element will also be set automatically. This only applies to the option picker that is sourced from an <input> element.

picker.fix = true; // Make the option picker โ€œread-onlyโ€

picker.hooks

Returns the events data.

console.log(picker.hooks);

picker.mask

Returns the option pickerโ€™s mask.

picker.mask.classList.add(picker.state.n + '--dark');

picker.max

Proxy that passes to the picker.state.max property, with additional actions that are executed while the value is being set.

Note: This does not apply to an option picker that is sourced from an <input> element.

console.log(picker.max); // Returns the `picker.state.max` value
picker.max = 4; // Add `multiple` attribute to the `<select>` element and allow the user to select up to 4 options. If the option picker is sourced from a `<select multiple>` element and it has more than 4 options selected, the form submission will be disabled and the `max.options` hook will be fired.

picker.min

Proxy that passes to the picker.state.min property, with additional actions that are executed while the value is being set.

Note: If the value is set to 0 and the option picker is sourced from a <select> element, it will allow the user to remove the only selection by focusing on the option picker value and then pressing the Backspace or Delete key, or by focusing on the selected option and then clicking/tapping or pressing the Space key on it to remove the selection.

console.log(picker.min); // Returns the `picker.state.min` value
picker.min = 4; // If the option picker is sourced from a `<select multiple>` element and it has less than 4 options selected, the form submission will be disabled and the `min.options` hook will be fired.

picker.options

Gets the current options list data, or sets (overwrites) the current options list with a new data.

console.log(picker.options);
picker.options = [
    'Option 1',
    'Option 2',
    'Option 3'
];

// Orโ€ฆ
picker.options.set('Option 1');
picker.options.set('Option 2');
picker.options.set('Option 3');
picker.options = [
    ['Option 1', {
        value: 1
    }],
    ['Option 2', {
        value: 2
    }],
    ['Option 3', {
        active: false,
        value: 3
    }]
];

// Orโ€ฆ
picker.options.set(1, 'Option 1');
picker.options.set(2, 'Option 2');
picker.options.set(3, ['Option 3', {
    active: false
}]);
picker.options = {
    '1': 'Option 1',
    '2': 'Option 2',
    '3': 'Option 3'
};

// Orโ€ฆ
picker.options.set(1, 'Option 1');
picker.options.set(2, 'Option 2');
picker.options.set(3, 'Option 3');
picker.options = {
    'option-1': ['Option 1', {
        value: 1
    }],
    'option-2': ['Option 2', {
        value: 2
    }],
    'option-3': ['Option 3', {
        active: false,
        value: 3
    }]
};

// Orโ€ฆ
picker.options.set('option-1', ['Option 1', {
    value: 1
}]);

picker.options.set('option-2', ['Option 2', {
    value: 2
}]);

picker.options.set('option-3', ['Option 3', {
    active: false,
    value: 3
}]);
const options = new Map;

options.set(1, 'Option 1');
options.set(2, 'Option 2');
options.set(3, 'Option 3');

picker.options = options;
const options = new Map;

options.set(1, ['Option 1', {
    value: 1
}]);

options.set(2, ['Option 2', {
    value: 2
}]);

options.set(3, ['Option 3', {
    active: false,
    value: 3
}]);

picker.options = options;
picker.options.at(key)

Returns the raw option data from its key.

console.log(picker.options.at('option-1'));
picker.options.count()

Returns the total number of options.

console.log(picker.options.count());
picker.options.get(key)

Returns the option position in the list, starting from 0. If it is in a group, it will return an array containing the option position in the group and the group position in the list.

let index = picker.options.get('option-1');
if (-1 !== ('number' === typeof index ? index : index[0])) { โ€ฆ }
picker.options.has(key)

Returns true if the option is present.

if (picker.options.has('option-1')) { โ€ฆ }
picker.options.let(key)

Removes an option by its key.

picker.options.let('option-1'); // Remove โ€œoption-1โ€ option
picker.options.let(); // Remove all options
picker.options.open

Returns true if options is visible.

if (picker.options.open) { โ€ฆ }
picker.options.set(key, value)

Adds a new option with the given value or key and value.

picker.options.set('option-1');
picker.options.set('option-2', 'Option 2');
picker.options.set('option-3', ['Option 3', {
    active: false
}]);

picker.self

Returns the <input> or <select> element.

console.log(picker.self.getAttribute('name'));

picker.size

Changes the layout of a drop-down select box to a scrollable area select box. This is valid only if the value is an integer greater than 1. Otherwise, it will be considered as an invalid value and will revert the layout to its default layout, which is a drop-down select box.

picker.size = 5;

picker.state

Returns the application states if any.

console.log(picker.state);

picker.text

Gets or sets the current text in the โ€œlooseโ€ option pickerโ€™s input.

console.log(picker.text);
picker.text = 'Option 3';

picker.value

Proxy that passes to the picker.self.value property, with additional actions that are executed while the value is being set.

console.log(picker.value);
picker.on('change', function () {
    console.log(this.value);
});

picker.value = '3';

picker.vital

Gets or sets the required state of the option picker. By setting the value to false or true, the required state of the source element will also be set automatically. If the value is set to true and the current picker.min value is 0, then it will change the current picker.min value to 1.

picker.vital = true; // Make the option picker โ€œrequiredโ€

Static Properties

Static properties are properties available directly on the OptionPicker object.

OptionPicker._

Alias for OptionPicker.prototype.

OptionPicker._.clear = function () {
    if ('input' !== this.self.tagName.toLowerCase()) {
        return this;
    }
    return (this.value = ""), this;
};

const picker = new OptionPicker(document.querySelector('input'));

picker.clear(); // Clear value

OptionPicker.state

Returns the default values of picker.state.

const picker = new OptionPicker(document.querySelector('select'), {
    foo: ['bar', 'baz', 'qux']
});

console.log([OptionPicker.state, picker.state]);

OptionPicker.version

Returns the application version.

Extensions

Anatomy of an Extension

Extension as a function:

function Extension(self, state = {}) {
    this.test = 1;
    return this;
}

Object.defineProperty(Extension, 'name', {
    value: 'Extension'
});

Extension as an object:

const Extension = {
    attach: function (self, state = {}) {
        this.test = 1;
        return this;
    },
    detach: function (self, state = {}) {
        delete this.test;
        return this;
    },
    name: 'Extension'
};

Usage of an Extension

As a core extension:

OptionPicker.state.with.push(Extension);

As an optional extension:

const picker = new OptionPicker(document.querySelector('select'), {
    with: [Extension]
});

List of Extensions

License

This project is licensed under the terms of the MIT license. If this project has saved you time, please donate. Thank you! โค๏ธ