- Notifications
You must be signed in to change notification settings - Fork6
A modern, flexible, Angular 1.x autocomplete library with limited assumptions.
License
UrbanCompass/ngc-omnibox
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
The Omnibox is a lightweight implementation of an autocomplete field and menu that gives theimplementor as much control as possible over the markup and data structure used, and makes as fewUI and style assumptions as possible. It is currently implemented in AngularJS 1.x.
The primary goal of Omnibox is to give app makers as close to full control as possible. This meansthat this component is not ready to be used in a project out of the box: itrequires configurationand styling. However, this means that it can be the basis for just about any autocompleteimplementation possible given the following assumptions:
- You need a field in which you type text in to
- You exepect that field to return a list of suggestions to choose from
- You can choose one (or more, optionaly) of the suggestions
And that's it. If your autocomplete needs are compatible with those assumptions, you can useOmnibox.
A simple implementation that renders a list of suggestions:
<ngc-omniboxng-model="myCtrl.model"source="myCtrl.getSuggestions(query)"><ngc-omnibox-field></ngc-omnibox-field><ngc-omnibox-suggestions><ngc-omnibox-suggestions-item>{{suggestion.title}}</ngc-omnibox-suggestions-item><ngc-omnibox-suggestion-loading>Loading...</ngc-omnibox-suggestion-loading><ngc-omnibox-suggestion-empty>No results...</ngc-omnibox-suggestion-empty></ngc-omnibox-suggestions></ngc-omnibox>
A more complicated implementation that supports multiple choices and suggestions broken down bycategory:
<ngc-omniboxng-model="myCtrl.model"source="myCtrl.getSuggestions(query)"multiple="true"><ngc-omnibox-choices><span> {{choice.title}}<buttonng-click="omnibox.unchoose(choice)">×</button></span></ngc-omnibox-choices><ngc-omnibox-field><inputtype="text"autofocus="true"placeholder="Enter your search..."></ngc-omnibox-field><ngc-omnibox-suggestions><ngc-omnibox-category><ngc-omnibox-suggestion-header> Category: {{suggestion.title}}</ngc-omnibox-suggestion-header><ngc-omnibox-suggestions-item> {{suggestion.title}} - {{suggestion.subtitle}}<ngc-omnibox-suggestions-item></ngc-omnibox-category><ngc-omnibox-suggestion-loading>Loading...</ngc-omnibox-suggestion-loading><ngc-omnibox-suggestion-empty>No results...</ngc-omnibox-suggestion-empty></ngc-omnibox-suggestions></ngc-omnibox>
For more complicated and realistic use cases, including some that implement CSS, check out theexamples.
The way to configure the markup for the Omnibox is by use of its subcomponents. The Omnibox librarydoes not have its own template, so all of the markup that is displayed is completely under yourcontrol. Each subcomponent provides you a slot for custom markup and access to your data.
This is the main, base-level component. All of the other subcomponents must beincluded inside of this element. Since the component does not provide a template of its own, most ofthe markup you provide inside (with some slight exceptions) will be maintained. All subcomponentsof the Omnibox have access to theomnibox
object in their markup, which is a reference to thetop-levelOmnibox Controller
.
The Omnibox field controls the position of the input field markup inside the Omnibox component. Ifyou want to customize the actual input field element, you can add an<input>
inside thiscomponent. This allows you to change attributes such astype
,placeholder
, etc. as well as addcss classes.
Omnibox Suggestions is a container for your lists of suggestions to be rendered in. It hasadditional subcomponents that allow you to slot in markup for different parts needed for thesuggestions to render. You can include at most one instance of an individual subcomponent. If youinclude more than one, an Error will be thrown.
The Suggestion Item component gives you a slot for markup for a single suggestion. This markupwill be repeated once for each suggestion in your list of suggestions. It has access to asuggestion
, which represents a single suggestion in your list of suggestions.
The Suggestion Loading component gives you a slot for markup for when suggestions are loadingasynchronously.
The Suggestion Empty component gives you a slot for markup for when there are no suggestions. Thelist of suggestions is considered empty when it is either falsy or an array with no length.
The Suggestion Category component is a container for markup for when a suggestion haschildren
and needs to loop through them recursively. If the category component is not provided in the markupthen the suggestions will be rendered as a flat list, ignoring any children.
The Suggestion Header is an optional component when using in conjunction with a Suggestion Category.It provides a slot for markup and access to asuggestion
that contains thechildren
.
Omnibox Choices controls the markup for rendering multiple choices from the list of suggestions(think tokens or tags). This component is somewhat unique in that it assumes the first child elementis what you want the markup to be for each choice and will repeat over it. Any siblings of thatfirst child will be maintained, but placed after the choices.
One of the things that makes Omnibox so flexible is that it has only two assumptions about how yourdata is structured:
- Your list of suggestions must be an array. An array of what is up to you, but it must be an array
- If you need to render items in a group or recursively in a tree structure, you must provide akey on your suggestion named
children
, and that must be an array.
Everything else is completely up to you. To populate the list of suggestions, you return aPromise
in thesource
&-bound callback binding on the Omnibox that resolves to an array of suggestions.The Omnibox will render whatever is in that array. All filtering, data manipulation, slicing, anddicingmust be done before resolving this promise. The Omnibox component provides no searchalgorithm of it's own: it expects the suggestions it receives to already be sorted and formattedcorrectly.
When your markup receives asuggestion
or achoice
, it is simply a reference to either one ofthe items in the top-level array, or one of the items in the children array. No other modificationsare made to your data.
The following bindings are available on the main<ngc-omnibox>
component:
source({query, suggestions, omnibox}) {Promise}
(Required): An expression that populates thelist of suggestions by returning aPromise
that resolves to an array of suggestions. Theindividual suggestions can be of any type, but the list of suggestions must be an array. If you wishto provide nested suggestions (such as category headers, or a tree structure) then you must providea key calledchildren
on your suggestion which will then be looped through to find more children,or the end of the list. In its locals it has access to a string calledquery
with the currentquery in the input field, and an array calledsuggestions
, which is the current list ofsuggestions being displayed.To display a hint to the user in addition to suggestions, resolve the promise with an object thathas keys forhint
andsuggestions
:{hint: 'My hint', suggestions: [...]}
. A hint is displayedto the right of the text that has been input by the user. Pressing RIGHT on the keyboard replacesthe input text query with the hint. The hint should include the entirety of the query, plus whateverelse you want to hint with. When displayed, only the addition to the query is shown as a hint, butif the user presses RIGHT to complete it, it will use the entirety of the hint provided. Forexample, if the query is 'my query' and you want to hinted text to display ' is awesome', then youshould pass 'my query is awesome' as the hint. However, if you want the completed hint to beformatted as 'My Query is Awesome', you should set that as the hint. When hinted, it will bedisplayed as 'my query is Awesome', but when completed via RIGHT on the keyboard, it will replacethe query with the submitted formatting: 'My Query is Awesome'.ngModel {Any}
(Required): This is a one-way binding to the ngModel for the Omnibox. When themultiple
option is set totrue
, then thengModel
should be an array. Each item in the arraywill be populated with choices from the objects passed via thesource
function. If themultiple
option isfalse
(default), then thengModel
will be set to the singular chosen suggestion.ngDisabled() {Boolean}
: This expression should evaluate to a boolean that determines if theOmnibox component should be disabled.multiple {Boolean}
: Whether to allow multiple choices from the list of suggestions. This optioncontrols whether thengModel
will be an array (multiple is on) or a single choice (off). Note thatif multiple is set totrue
, thenrequireMatch
will behave as if it has been set totrue
. Ifyou need to support a "free-text" suggestion with multiple on, be sure to add it in your sourcefunction as a suggestion.hideOnBlur {Boolean}
: Whether the list of suggestions should automatically hide when thecomponent itself loses focus. Hitting ESC will always close the list of suggestions.hideOnChosen {Boolean}
: Whether the list of suggestions should automatically hide when theuser chooses a suggestion. Defaults to true.isSelectable({suggestion, omnibox}) {Boolean}
: An expression that should evaluate to a Booleanthat determines if a suggestion is able to be interacted with. This expression will be executedwhenever a suggestion is attempted to be highlighted either by the keyboard or mouse. In its localsit has access to an object calledsuggestion
which is the current suggestion that is beinginteracted with. A non-selectable suggestion cannot be clicked on, hovered over, or interacted withvia the keyboard.canShowSuggestions({query, omnibox}) {Boolean}
: An expression that should evaluate to a Booleanthat determines whether or not the list of suggestions can be displayed. In its locals it has accessto a string calledquery
which is the current query being searched on.requireMatch {Boolean}
: An expression that should evaluate to a Boolean that determines if amatched suggestion is required for the field (defaults tofalse
). This has a few effects on thebehavior of the omnibox:requireMatch = false
:- Suggestions are not automatically highlighted.
- Hitting enter keeps whatever text the user has typed and closes the list of suggestions.
- When using the keyboard to highlight suggestions, going to the end and hitting down or thebeginning and hitting up will highlight nothing.
- Hitting ESC when there is a match highlighted will un-highlight it. Hitting ESC again willclose the list of suggestions.
- If multiple is
false
, then the entered becomes the model value when chosen. If multiple istrue
, thenrequireMatch
is treated astrue
.
requireMatch = true
:- A suggestion is always higlighted (as long as there are some available)
- Hitting ENTER or TAB will always choose a suggestion.
- When using the keyboard to highlight suggestions, going to the end and hitting down will thenhighlight the first suggestion, and going to the beginning and hitting up will highlight thelast.
- Hitting ESC will close the list of suggestions and clear the field.
scrollIntoViewAlignToTop {Boolean}
: An expression that should evaluate to a Boolean thatdetermines the value of AlignToTop that is given into scrollIntoView() when scrolling Omniboxsuggestions into view. Defaults to false. It's useful to configure this when using the Omniboxinside of a scrollable container.shouldScrollIntoView {Boolean}
: An expression that should evaluate to a Boolean thatdetermines whether to scroll suggestions into view at all. Defaults to true.
The following &-callback functions are available for binding on thengc-omnibox
component inresponse to events:
onFocus({event, omnibox})
: An expression that's called when the component gets focus. Thisincludes not just the input field, but the component in general. This is necessary since selectingthe choices might blur the input field, but not the component itself. If you need to know when justthe input field receives focus, you can use thetarget
property of the event object to figure itout.onBlur({event, omnibox})
: An expression that's called when the component loses focus. This alsoincludes the entire component, not just the field. This blur event also has logic to reduce thenoise that sometimes happens where it'll lose focus then immediately regain it, so the blur iscalled only after a timeout to make sure it doesn't re-receive focus first.onChosen({choice, $event, omnibox})
: An expression that's called when a suggestion is chosen. Inits locals it has access tochoice
, which is the item that was chosen, and an$event
object. The$event
object has the following properties:isDefaultPrevented
,preventDefault()
, andperformDefault()
. IfisDefaultPrevented
is set to true by callingpreventDefault()
from thiscallback function, then the choice is not automatically added to the ngModel. If you then do wantthe choice to be added, you can callperformDefault()
to do so.onUnchosen({choice, $event, omnibox})
: An expression that's called when a suggestion is unchosen(removed as a choice). In its locals it has access tochoice
, which is the item that was unchosen,and an$event
object. The$event
object has the following properties:isDefaultPrevented
,preventDefault()
, andperformDefault()
. IfisDefaultPrevented
is set to true by callingpreventDefault()
from this callback function, then the choice is not automatically removed fromthe ngModel. If you then do want the choice to be removed, you can callperformDefault()
to do so.onShowSuggestions({suggestions, omnibox})
: An expression that's called when the suggestions UIis shown. In its locals it has access tosuggestions
.onHideSuggestions({suggestions, omnibox})
: An expression that's called when the suggestions UIis hidden. In its locals it has access tosuggestions
.
The Omnibox Controller handles most of the behavior for the Omnibox Component. It provides a setof functions and properties that can be accessed from subcomponents inside the Omnibox Componentvia theomnibox
object in their locals.
Any options not documented here should be considered private. Accessing or using undocumentedmethods could break at any time, and changes to those methods or properties will not be consideredwhen semantic versioning. Please refrain from using them.
omnibox.fieldElement {HTML Element}
: A reference to the DOM node of the input fieldomnibox.suggestions {Array}
readonly: A reference to the array of suggestions. This valueshould never be updated or modified directly. To update the list of suggestions, use thesource
function.omnibox.hasSuggestions {Boolean}
: Whether the Omnibox has any suggestions loaded.omnibox.hasChoices {Boolean}
: Whether the Omnibox has any suggestions chosen. This is onlyavailable whenmultiple
is set totrue
.omnibox.isLoading {Boolean}
: Whether the Omnibox is waiting for thesource
function to resolveitsPromise
and load the suggestions.omnibox.ngModel {Any}
: Reference to the current choice or choices for the Omnibox.
omnibox.focus()
: Focuses the input field.omnibox.blur()
: Blurs the input field.
omnibox.choose(suggestion, shouldFocusField)
: Chooses a suggestion item and adds it to the listof choices. Ifmultiple
is off, then only one choice can be chosen at a time, and the choicebecomes thengModel
.shouldFocusField
defaults totrue
, but if set tofalse
, then the inputfield isn't automatically re-focused after choosing a suggestion.omnibox.unchoose(suggestion, shouldFocusField)
: Removes a suggestion item from the list ofchoices. Ifmultiple
is off, then thengModel
is cleared.shouldFocusField
defaults totrue
,but if set tofalse
, then the input field isn't automatically re-focused after unchoosing asuggestion.omnibox.shouldShowSuggestions()
: Whether or not the suggestions menu should be visible.omnibox.highlightSuggestion(suggestion)
: Highlights a particular suggestion item in the list ofsuggestions. If the suggestion is not visible on screen, it will be scrolled into view.omnibox.highlightPreviousSuggestion(startIndex)
: Highlights the previous suggestion before thecurrent one. If no suggestion is highlighted, then the last suggestion is highlighted. If the firstsuggestion was highilighted, then it highlights the last suggestion. You can optionally pass astartIndex override what the current suggestion should be.omnibox.highlightNextSuggestion(startIndex)
: Highlights the next suggestion after thecurrent one. If no suggestion is highlighted, then the first suggestion is highlighted. If the lastsuggestion was highilighted, then it highlights the first suggestion. You can optionally pass astartIndex override what the current suggestion should be.omnibox.highlightNone()
: Un-highlights any highlighted suggestion.omnibox.isHighlighted(suggestion)
: Whether the provided suggestion is currently highlighted.
omnibox.highlightChoice(choice)
: Highlights a choice from the list of choices.omnibox.highlightPreviousChoice()
: Highlights the previous suggestion before the currentlyhiglighted one. If the first one is highlighted then the field is focused.omnibox.highlightNextChoice()
: Highlights the next suggestion after the currently higlightedone. If the last one is highlighted then the field is focused.omnibox.highlightFirstChoice()
: Highlights the first choice in the list of choices.omnibox.highlightLastChoice()
: Highlights the last choice in the list of choices.omnibox.highlightNoChoice()
: Un-highlights all choices.omnibox.isChoiceHighlighted()
: Whether the submitted choice is currently highlighted.
ThengcOmniboxHighlightMatch
Angular filter can be used to highlight some text in your suggestionthat matches the query being searched against. It uses a regular expression to search for the exactquery being passed, and wraps that match in HTML. By default the filter will wrap it in a<strong>
tag, but you can customize this to be any HTML you like.
Note that since we're parsing a String as HTML, the filter will throw a warning if you're not usingngSanitize
to make your string safe to convert to HTML.
<ngc-omniboxng-model="myCtrl.model"source="myCtrl.getSuggestions(query)"><ngc-omnibox-field></ngc-omnibox-field><ngc-omnibox-suggestions><ngc-omnibox-suggestions-itemng-bind-html="suggestion.title | ngcOmniboxHighlightMatch:omnibox.query"></ngc-omnibox-suggestions-item></ngc-omnibox-suggestions></ngc-omnibox>
If you wish to customize the markup used to wrap your text, you can do so in the second parameterpassed to the filter. The parameter is a string replacement pattern, which should follow thereplacement rules for JavaScript'sString.prototype.replace()
function. More information canbefound on MDN
<ngc-omniboxng-model="myCtrl.model"source="myCtrl.getSuggestions(query)"><ngc-omnibox-field></ngc-omnibox-field><ngc-omnibox-suggestions><ngc-omnibox-suggestions-itemng-bind-html="suggestion.title | ngcOmniboxHighlightMatch:omnibox.query:'<em class=\'my-highlighted-text\'>$&</em>'"></ngc-omnibox-suggestions-item></ngc-omnibox-suggestions></ngc-omnibox>
About
A modern, flexible, Angular 1.x autocomplete library with limited assumptions.