@uncharted.software/lex
v1.0.2
Published
A preact micro-framework for building token-based search bars
Keywords
Readme
Lex.js
A preact-based micro-framework for building token-based search bars
Introduction
Lex is a micro-framework for building token-based search bars. Rather than predefining how searches are performed, Lex provides developers with the tools they need to define their own query language, and to develop unique UI components for constructing queries, thus supporting the widest possible set of potential use cases.
Lex is built internally with Preact, ensuring a minimal library size and compatibility with any modern SPA framework (VueJS, Aurelia, React, etc.) by remaining framework-neutral.
API Documentation and Demos
Online
For current API documentation, please visit: https://unchartedsoftware.github.io/lex/
For demos of key features, visit: https://unchartedsoftware.github.io/lex/demo/
Local
For current API documentation, please clone the project and run:
$ npm install
$ npm run serve-docsFor demos of key features, refer to source in the demo directory, while running:
$ npm install
$ npm run serve-demosUsing Lex
Defining a Search Language
In
Lex, a search language is a finite-state machine.States represent "steps" towards successfully constructing a token through user-supplied values, and userstransition()between them until they reach a terminalState(one with no children).
Lex attempts to provide an environment in which developers can craft their own search language, rather than enforcing one. Despite this goal, the following assumptions are made for the sake of improving user experience:
- A query consists of a list of tokens (i.e.
[TOKEN1 TOKEN2 TOKEN3]) - The set of tokens in a
Lexbar is interpeted as being joined by eitherANDs orORs (i.e.[TOKEN1 & TOKEN2 & TOKEN3]). This connective is not represented visually within the search bar, and thus is left up to the interpretation of the developer (however so far all existing apps usingLexhave chosenAND). - Tokens consist of a list of
States - effectively, a path through the search language. EachStatestores one or more values and, together, the sequence represents a statement such as[Name, is, Sean],[Age, is not, 7]or[Location, is, (Toronto,Victoria)]. - Multi-value
States can represent anORof values which, together with an overall choice ofANDconnective, strongly encourage Conjunctive Normal Form as the basis for a search language.
Defining a search language in Lex is accomplished via method chaining. Let's start with a basic search language, which allows a user to select a column to search and supply a value. The state machine for this language consists of three states, with specific rules governing the transition between the root state and its two children:
Choose Column ----(if string)----> Enter String
\---(if numeric)---> Enter NumberHere is the implementation via Lex:
import { Lex, TransitionFactory, ValueState, ValueStateValue, TextEntryState, NumericEntryState } from 'lex';;
// Lex.from() starts a subtree of the language
const language = Lex.from('columnName', ValueState, {
name: 'Choose a column to search',
suggestions: [
// ValueStates allow users to choose from a list
// of values (or potentially create their own).
// We set metadata "type"s on these options
// to help us make transition decisions later.
new ValueStateValue('Name', {type: 'string'}),
new ValueStateValue('Age', {type: 'numeric'})
]
}).branch(
Lex.from('value', TextEntryState, {
// transitions to this State are considered legal
// if the parent State's value had a metadata
// type === 'string'
...TransitionFactory.valueMetaCompare({type: 'string'})
}),
Lex.from('value', NumericEntryState, {
// Similarly, checking for parentVal.meta.type === 'numeric'
...TransitionFactory.valueMetaCompare({type: 'numeric'})
}),
);Consuming the language is as accomplished via configuration when constructing a new instance of Lex.
// Now we can instantiate a search bar that will respect this language.
const lex = new Lex({
language: language
// other configuration goes here
});
lex.render(document.getElementById('LexContainer'));Lex supports far more complex languages, validation rules, state types etc. than are shown in this brief example. Check out the demo directory and API documentation for more details.
Extending Lex
Lex translates States from the search language into UI components as a user is creating or modifying a Token. These components fall into two categories:
- Builders - UI which is presented inline within a
Token. This is generally a text input that the user can type into to supply values to the currentState. - Assistants - UI which is presented as a drop-down below a
Token.Assistants provide an alternative, typically richer, mechanism for supplying values to the currentState.
There must be one Builder for each State in a search language. Assistants are optional.
Lex contains several built-in State types, which are associated with default Builders and Assistants:
State | Default Builder | Default Assistant
------ | --------------- | -----------------
LabelState | LabelBuilder | none
ValueState | ValueBuilder | ValueAssistant
RelationState | ValueBuilder | ValueAssistant
TerminalState | TerminalBuilder | none
TextEntryState | ValueBuilder | ValueAssistant
TextRelationState | ValueBuilder | ValueAssistant
NumericEntryState | ValueBuilder | ValueAssistant
NumericRelationState | ValueBuilder | ValueAssistant
CurrencyEntryState | ValueBuilder | ValueAssistant
DateTimeEntryState | DateTimeEntryBuilder | DateTimeEntryAssistant
DateTimeRelationState | ValueBuilder | ValueAssistant
Two things are evident in this table:
- Most
Statetypes extendValueState, which is a powerful component supporting selecting a value from a list of suggestions, entering custom values, accepting multiple values, etc. - Any
Statetype which is missing a directBuilderorAssistantwill attempt to use the corresponding components for its superclassState.
Lex may be extended, therefore, in the following ways (in descending order of likelihood):
- Via the implementation of new
States, extending existingStates (i.e. extendingValueStatebut usingValueBuilderandValueAssistant) - Via the implementation of new
Assistants for existingStates (i.e. implementing a custom drop-down UI for choosing dates and times) - Via the implementation of new
Builders for existingStates (mostly for formatting "finished"Tokens in unique ways by overridingrenderReadOnly()) - Via the implementation of entirely unique
States, with customBuilders andAssistants. (i.e. implementing aGeoBoundsEntryStatewith a customBuilder, and anAssistantfeaturing a map)
The States, Builders and Assistants within the library are well-documented examples of how these extension types are accomplished, and exist as a reference for this purpose.
Overrides must be registered with Lex before the search bar is rendered:
// ...
lex.registerBuilder(DateTimeEntryState, CustomDateTimeEntryBuilder);
lex.registerAssistant(CurrencyEntryState, CustomCurrencyEntryAssistant);
lex.render(document.getElementById('LexContainer'));Consuming Lex Within an Application
The following co-requisites must be part of your JS build in order to use Lex:
{
"element-resize-detector": "1.1.x", // developed against "1.1.15"
"preact": "8.x", // developed against: "8.5.2",
"moment-timezone": "0.5.x", // developed against "0.5.34"
"flatpickr": "4.6.x" // developed against: "4.6.3"
}The following polyfills are required for use in IE and are not provided by this library:
- ES6 Promise Polyfill
