vsm-autocomplete
is a web-page component for looking up terms that are
linked to identifiers. These are fetched from a given
vsm-dictionary
.
Its main purpose is to be a subcomponent of
vsm-box
.
But it can also be used in other applications that need an input-field
with term+ID lookup.
vsm-autocomplete
is a Vue.js-based input field component, that a web developer can embed inside one of these:- A larger Vue component. This is the use-case of
vsm-box
. - A project that bundles it with a particular vsm-dictionary.
E.g. one could make:vsm-autocomplete
+vsm-dictionary-bioportal
+ a webpack setup,
to create a standalone web-component:vsm-autocomplete-bioportal
. - A full Vue application.
- A larger Vue component. This is the use-case of
- It makes a new HTML-tag available: '
<vsm-autocomplete>
', which requires at least a 'vsm-dictionary="..."
' attribute.
This attribute points to an object of type 'VsmDictionary', which is an interface module that connects to a particular database server, in order to get terms and identifiers. The particular interface module could query e.g. BioPortal or PubDictionaries. - When a user starts typing, the component queries the vsm-dictionary for
matching terms,
and shows them in a selection panel underneath the input. - When the user selects a term (e.g. clicks on a selection-panel item),
an
item-select
signal is emitted, together with the term, ID, and other info.
This can be picked up with an event listener, as in the example below. - Terms, IDs, and styles:
For being useful in biological domains (as explained on the VSM-pages),vsm-autocomplete
supports whatvsm-dictionary
supports too, i.a.:- There can be multiple terms (synonyms) per ID. — These will get shown as separate selection-panel items (different terms, same ID).
- Any term can have multiple IDs. — These will get shown as separate selection-panel items (same term, different IDs). (Think e.g. of gene names: one term/string often represents multiple genes).
- There can be "fixed terms". These are 'preferred matches' that result in matching items that appear on top of the selection-panel (also for an empty search string).
- Terms can include any special character (like 'β-carotene').
- Terms can have some HTML-styling. E.g. italic style for human gene names,
or parts in superscript for charged ions
(see
string-style-html
).
To see an interactive demo (that uses example dictionary-data), run:
git clone [email protected]:vsm/vsm-autocomplete.git
cd vsm-autocomplete
npm install
npm start
Here we embed it in another Vue component, e.g. in a MyComp.vue
:
<template>
<div class="my-comp">
<vsm-autocomplete
:vsm-dictionary="vsmDictionary"
:autofocus="true"
initial-str="a"
@item-select="onItemSelect"
/>
</div>
</template>
<script>
// 1.) Create a VsmDictionary data source.
import VsmAutocomplete from 'vsm-autocomplete';
import VsmDictionaryLocal from 'vsm-dictionary-local'; // Or any other one.
import cacher from 'vsm-dictionary-cacher'; // Recommended for speed.
var CachedVsmDictionaryLocal = cacher(VsmDictionaryLocal);
var dict = new CachedVsmDictionaryLocal({ // Initialize with demo data:
dictData: [
{ id: 'Dict1',
name: 'Example subdictionary 1',
entries: [
{ id: 'D1:001', terms: ['aaa', 'aasynonym'], descr: 'description' },
{ id: 'D1:002', terms: 'aab', descr: 'descr2' },
{ id: 'D1:003', terms: 'abc', descr: 'descr3' },
{ id: 'D1:004', terms: [{ str: 'acd', style:'i' }], descr: 'descr4' }
]
}
]
});
// 2.) Create a parent component 'MyComp' that embeds a VsmAutocomplete.
export default {
name: 'MyComp',
components: {
'vsm-autocomplete': VsmAutocomplete // Use it as a sub-component of MyComp.
},
data: function() {
return {
vsmDictionary: dict
};
},
methods: {
onItemSelect(match) {
console.log(match.id, match.str, match.descr, JSON.stringify(match.terms));
}
}
};
</script>
<style scoped> /* Add CSS-styling around the vsm-autocomplete */
.my-comp {
width: 200px;
border: 1px solid #ddd;
}
</style>
The example shows that the <vsm-autocomplete>
HTML-tag accepts
two types of attributes:
- props:
These insert information, settings, or functions, into the component.
Here::vsm-dictionary="..."
,:autofocus="..."
, andinitial-str="..."
. - event-listeners:
These listen to 'event' signals and information, that come out of the component.
Here:@item-select="..."
.
An extra 'item-literal' can be shown at the bottom of the autocomplete selection-list.
- This list-item is only visible if a
@item-literal-select
event-listener is attached. - When the user selects the 'item-literal',
a parent component (e.g.
vsm-box
) can interpret this as a signal that the user wants to access some more advanced term-search dialog window. - It does not represent a match-object from the VsmDictionary, so it has no ID.
- It represents the input-field's literal string.
- It has its own CSS-style to make it visually distinct.
- One can generate custom content for it, via the
:custom-item-literal
prop.
Prop | Type | Required | Default | Description |
---|---|---|---|---|
vsm-dictionary | Object | true | A subclass of VsmDictionary: an interface to a provider of terms, IDs, styling info, subdictionary info, refTerms and fixedTerms |
|
autofocus | Boolean | false | When true , automatically focuses the input-field at page load |
|
placeholder | String|Boolean | false | • String: shows this placeholder message in an empty input-field | • false/absent: adds none |
|
initial-value | String | '' | The initial content for the input-field | |
query-options | Object | { perPage: 20 } | Options for filters, fixedTerms, etc., sent along with calls to vsmDictionary.getMatchesForString() |
|
max-string-lengths | Object | { str: 40, strAndDescr: 70 } | Limits the length of matches' str and descr shown in list-items(in number of characters) |
|
fresh-list-delay | Number | 0 | Prevents selection of matches in the list, until this amount of milliseconds has passed | |
custom-item | Function|Boolean | false | • Function: returns HTML-content for each part of normal list-items (see below) | • false/absent: default content is used |
|
custom-item-literal | Function|Boolean | false | • Function: returns HTML-content (see below) for the item-literal | • false/absent: the default item-literal is used |
Notes:
- The
placeholder
is hidden when the input-field is focused. - The
query-options
'sperPage
sets the length of the selection-list.- This includes S/T-type items only, while N/R/F/G-type items (see VsmDictionary's spec) and a item-literal may get added in addition.
- The
query-options
'sidts
specifies a list of fixedTerms (see vsm-dictionary's spec), only if this autocomplete component should use any. - In the selection-panel, N/R/F/G-type items get CSS-styling that makes them visually distinct.
- The
max-string-lengths
'sstrAndDescr
sets the maximum length of an already length-trimmedstr
, plus itsdescr
. - When a selection-list shows stale results (meaning that the list is expected
to be replaced with fresh results soon), then it will not respond to Enter
or Click until the new results appear.
But if a user decides to Enter/Click on stale results anyway, and the list would update with new results shortly before that Enter actually comes through, then the Enter would select a different list-item than intended: an item that freshly appeared there in a new list; and perhaps the user would not even notice that a different item was selected.
Therefore, by setting afresh-list-delay
(of e.g. a few 100 milliseconds), one can delay when the list starts to respond, and prevent an impatient user from mistakenly Entering/selecting an item that only just appeared. - The
custom-item
andcustom-item-literal
functions are described in the "Customized content" section, further below.
Event | Output | Description (-> output) |
---|---|---|
focus | When the input-field gets the page focus | |
blur | When the input-field loses the focus | |
input-change | String | When the input-field's content changes, after trimming (-> the latest value, trimmed) |
input | String | When the input-field's content changes (-> the latest value) |
key-esc | When Esc is pressed, while the selection-list is closed |
|
key-bksp | When Backspace is pressed, while the input-field is empty |
|
key-ctrl-enter | When Ctrl+Enter is pressed, while the input-field has no string-codes |
|
key-shift-enter | When Shift+Enter is pressed |
|
key-tab | String | When Tab or Shift+Tab is pressed (-> modifier key: '' or 'shift' ) |
dblclick | When the input-field is double-clicked | |
mouseover-input | When the input-field is mouse-hovered (not the selection-list) | |
list-open | When the selection-list opens | |
list-close | When the selection-list closes | |
item-active-change | false|Object|String | When the active item in the selection-list changes -> • false: none (on list-close) | • Object: a match-object from VsmDictionary | • String: the literal input string (for item-literal) |
item-select | Object | When an item is selected (-> a match-object from VsmDictionary) |
item-literal-select | String | When the item-literal is selected (-> the literal input string) |
Notes:
- When a
@key-tab
listener is attached, vsm-autocomplete prevents the default behaviour:
i.e. it does not let the focus move away from the input-field.
Because in that case it means that a parent component wants to decide what happens on Tab / Shift-Tab. - Please note the conditions stated above for
@key-esc
,@key-bksp
, and@key-ctrl-enter
. - The user can press
Ctrl+Enter
to convert certain string-codes in the input (if any) to special characters, e.g. '\beta' to β.
Only if no codes were replaced, it emitskey-ctrl-enter
, which an external listener may act upon. @item-select
is likely the most important event.
This is the architecture of vsm-autocomplete
in terms of its own subcomponents:
VsmAutocomplete
├── TheInput
├─┬ TheList
│ ├── ListItem (multiple items)
│ └── ListItemLiteral (only 1, or 0)
└── TheSpinner
If this autocomplete component should take into account some fixedTerms, i.e.
if it is given some queryOptions.idts[]
(see vsm-dictionary's spec) as prop,
then it makes sure that this fixedTerm data is pre-loaded in the given
vsmDictionary
, before making any requests for matching strings to it.
See the tests for details.
They are easiest to read in the following, bottom-up order:
- subcomponents/TheInput.test.js
- subcomponents/ListItem.test.js
- subcomponents/ListItemLiteral.test.js
- subcomponents/TheList.test.js
- (there are no tests for the subcomponents/TheSpinner, as it contains no logic)
- VsmAutocomplete.test.js
The selection-panel items are given a default content like:
match-string (description) (dictionary-ID)
,
and each of the three parts may show extra info when the user mouse-hovers it;
e.g. if "(descri...)" was cut short, then hovering shows the full description.
Any of these parts can be customized by giving a customizing function as the
custom-item
prop.
• E.g.: for items that represent a match from a Human Genes
subdictionary, it could insert a list of gene-name synonyms in the description
part.
• Or: for a list of items representing a mix of matches
from a Human Genes and a Mouse Genes subdictionary resp.,
it could add an image of the species so that users can
more quickly distinguish between identically named matches.
customItem()
(if given) is called during the construction of each ListItem.
It is called with one argument: an Object with useful properties, representing
useful information for building a custom ListItem:
customItem(data)
:
data
: {Object}:item
: the complete 'match'-object that this ListItem represents. (The 'match' data-type is described in VsmDictionary's spec).searchStr
: the string that the user typed to find this match.maxStringLengths
: themax-string-lengths
prop that was set on this<vsm-autocomplete>
.queryOptions
: the propquery-options
that was set on this<vsm-autocomplete>
.
(Use case: by evaluatingqueryOptions.filter.dictIDs[]
, a multi-purpose customization function could detect if this particular autocomplete is combining gene-IDs from multiple species, and if so, add an icon (image of the species) in front of gene-names for clarity).dictInfo
: the info-object of the subdictionary from which this match came (which containscustomItem()
).vsmDictionary
: the VsmDictionary instance being used.
(It can not be used for queries, as customItem() is synchronous. But it may be used for accessing e.g.vsmDictionary.numberMatchConfig
details).strs
: the default content for the different parts of the ListItem.
It is an Object with properties (Strings) that represent the different parts.
The parts may contain HTML tags, but will most commonly be just text.
All properties are guaranteed to be of type {String}.
This default content will be used ifcustomItem()
does not change it.str
: the match's term-string, which ismatchObj.str
,
but trimmed in length according tomaxStringLengths.str
,
and withmatchObj.style
's custom CSS-styling already applied.descr
: the match's description, which ismatchObj.descr
,
but trimmed in length according tomaxStringLengths.strAndDescr
.info
: the dictionary-ID, which ismatchObj.dictID
;
(or for number-string matches: the number-ID; or for refTerms: empty).strTitle
: thestr
-part's HTML'title=".."'
attribute,
which appears on mouse-hover.
(Ifstr
was length-trimmed, then this ismatchObj.str
, else empty).descrTitle
: thedescr
-part's HTML'title=".."'
attribute.
(Ifdescr
was length-trimmed, then this ismatchObj.descr
, else empty).infoTitle
: theinfo
-part's HTML'title=".."'
attribute.
(This is a text withmatchObj.id
anddictInfo.name
).extra
: an extra part that is added at the end of the ListItem, and which is empty by default.
customItem()
must return an Object like its argument data.strs
.
It may simply return the given strs
object, after directly modifying just
those properties it needs to.
- E.g.
customItem: data => { if (data.item.dictID == 'X') data.strs.descr += '!'; return data.strs; }
would add a "!" to the description-part of each ListItem from subdictionary 'X' (only), and leave these ListItems' other parts unchanged. - If any part is empty, it will be left out of the ListItem.
The item-literal can be customized by giving a customizing function as the
custom-item-literal
prop.
customItemLiteral()
(if given) is called during the construction of a
ListItemLiteral.
It is called with one argument: an Object with useful properties:
customItemLiteral(data)
:
data
: {Object}:searchStr
: the string that the user typed in TheInput.maxStringLengths
: themax-string-lengths
prop that was set on this<vsm-autocomplete>
.strs
: the default content for the different parts of the ListItemLiteral (similar tocustomItem
'sdata.strs
):str
: the default (text/HTML) content for the ListItemLiteral, which is:
the content of the input-field, trimmed in length according tomax-string-lengths
.strTitle
: its HTML-element's 'title=".."' attribute, which appears on mouse-hover.
customItemLiteral()
must return an Object like its argument data.strs
.
It may simply return the given strs
object, after directly modifying just
those properties it needs to.
- E.g.
customItemLiteral: data => { data.strs.strTitle = ''; return data.strs }
would only remove the item-literal's 'title' attribute.
One can change the look of a <vsm-autocomplete>
component by overriding
its CSS-classes.
As an example, the following 'CSS skin' makes everything a bit larger:
vsm-autocomplete-large.css
.
This project is licensed under the AGPL license - see LICENSE.md.