1a. Rich row template (highlight + ui-disable-choice)
markup
Example directives, not a supported wrapper. Copy
llselect-angularjs.js into your project and adapt it; a
ui-select migration adds llselect-ui-select.js beside it.
The reasoning, the source references, and
the list of deliberate gaps are in
angularjs/README.md.
Run npm run build, then npm run serve:site, and open
/demo/angularjs/examples.html. AngularJS itself comes from a CDN; nothing
AngularJS-related is in the package.
<llselect-single> /
<llselect-multiple> from section 2 on instead.
A separate directive (llselect-ui-select.js, module
llselect.uiCompat) that takes
ui-select's
call-site markup, so an existing ui-select codebase can migrate without
rewriting every call site.
Migrating a call site: rename the elements
(ui-select family to ui-llselect family) and
add ll-item-text - ui-select has no item-to-string concept,
and llselect needs one for the option's accessible name. The template
content carries over untouched. CSS does not - llselect owns the DOM, so
this is styled by the llselect theme.
As in real ui-select, a chosen person leaves the dropdown:
remove-selected defaults to true (1g shows the opt-out).
The template has three shapes: elements (above), a bare text node, or
none at all - rows then render the ll-item-text string
itself, which ui-select cannot do (its template is its only source of
item text).
The | filter: chain IS the custom filter, re-evaluated once
per typed query. Try vlan2 - the ifaceMatch
filter (app.js) ignores spaces.
Interop with uib-tooltip from
angular-ui-bootstrap
- the component kit most AngularJS apps already carry: both directives
sit on the same element, no conflict. ng-disabled carries
over here (ui-select parity, via attrs.$observe; the main
directives use ll-disabled). The attribute is inert on
this host, so hover works in BOTH states - the tooltip keeps working
while disabled, which a native disabled control forbids.
Top: the default. A chosen person leaves the list, exactly like
ui-select (remove-selected defaults to true; bridged onto
hideChosenRows). Its allow-clear puts everyone
back at once - the deviation the API page documents. Bottom:
remove-selected="false" keeps chosen rows listed.
Not bridged, by rule (llselect has no such concept, so it is ignored rather than half-implemented): tagging, sortable, append-to-body, async refresh, limit, themes.
ll-options takes an ng-options expression. Every
clause maps onto an llselect setting; see the table in the README.
The trigger has two displays: the default 'count' summary and
ll-trigger-display="'tags'" chips. Rows carry live checkbox
icons by default (ll-checkboxes="false" opts out), and
ll-choose-all-row="true" adds the tri-state row acting on the
visible enabled subset.
ll-checkboxes (default true) draws SVG checkboxes on the
rows and this row; ll-checkboxes="false" strips them all,
leaving plain text - which is also the core's own default (core demo
5.7).
Chosen items leave the popup; removing a tag puts them back, and the
clear button puts everything back at once. Pairs with the tags trigger,
and with ll-checkboxes="false" - a listed row here is
always unchecked, so checkboxes would say nothing.
The point of this page. myForm.fruit resolves because
AngularJS reads name off this element as a plain
attribute - there is no native <select> and no hidden
input anywhere. Clear the selection to watch the form go invalid.
select as is the ngModel value projection. llselect has no
equivalent setting on purpose - the app declares it, exactly as
ng-options does.
Setting attributes are read once at construction; ll-disabled gets
a $watch because it maps onto the setDisabled() method.
ll-arrow takes llselect's two built-in arrows and nothing
else; a custom one means editing your copy of the file. The house style
comes from llselectConfigProvider in .config()
(see app.js) - this demo sets arrow: 'chevron'
app-wide, which is why the selects above have one without saying so. A
per-element ll-arrow wins over it.
An expression evaluating to a function (item) => HTMLElement | null,
passed straight to llselect's createItemContentElFn. It runs
per rendered row per render, outside any digest, and the element is never
$compiled - custom rows with zero per-row scope or watcher.
The accessible name and filter text stay the item text from
ll-options (icons and hints never reach a screen reader).
All renderers are in
app.js. For real per-row Angular templates, that is
<ui-llselect> below.
renderLangRow builds the row with createElement;
feeding the SAME renderer to ll-trigger-content-fn is what
makes the trigger show the chosen row - there is no auto-projection.
renderLangRowFromTpl clones the <template>
and fills it - the row markup lives in HTML, still with no scope and no
$compile. On a multiple the element renders beside the
default checkbox.
renderUserRow: primary text plus a faded hint pushed to the
row's right edge (.user-row), with disable when
dimming suspended users on top. The name highlights the filter match
via the core createHighlightedTextEl (8f's pattern).
ONE renderer feeds rows AND chips (ll-tag-content-fn);
ll-tag-remove-button-content-fn swaps the remove icon, else
the theme's CSS glyph draws the x.
What <option style="background-color"> does on a
native select (Chromium only): renderTintedLangRow is 8a's
row tinted from its icon color, with createCheckmarkSvgEl
on the chosen row (rows re-render on chosen changes - never stale).
All inline styles, plus one demo CSS line (lang-tinted)
zeroing the option element's padding. The whole trigger takes the
chosen colors: demo-tint-trigger requires the published
controller and paints instance().triggerEl (API.md
"Reaching the instance from your own directive").
ll-highlight="true" wraps each query match in the default
item text in <mark> (the core
createHighlightedTextEl); the marks follow the typing.
With a custom ll-item-content-fn call the helper yourself,
the way 8c does.
A third-party validation library, included because it is the case that
started this: it throws unless the field has a name
(validation-common.js:460). It reads that name off the
ng-model element like everything else in AngularJS, so it
needs no native <select> either. Its
validation directive is restrict: 'A',
require: 'ngModel', and it revalidates from a watch on
ctrl.$modelValue - all element-agnostic.
Three integration facts worth knowing: it hard-depends on
pascalprecht.translate; its messages need
$translateProvider pointed at its locale files or you get
"Could not translate: 'INVALID_REQUIRED'"; and validation is debounced by
typingLimit (default 1000 ms), so it is not instant. Its
elm.bind('blur') trigger is inert here - blur does not bubble
and the focusable element is llselect's inner trigger - but the
$modelValue watch is the path that matters for a select.
Native <label for> cannot target these divs.
ll-label-el points at your label's id: the label names the
field, and clicking it focuses the trigger - focus only, like a native
label. (Core demo 12, as an attribute.)
Items are {interfaceType: 'vlan', interfaceNo: '2'}, shown
as VLAN 2. The default filter matches the shown text
(vlan 2 works out of the box); the custom fn also matches
the space-less vlan2.
Content that stays put while the list scrolls: a header between the
filter input and the items, a footer under them. Each fn runs once at
link time and the returned node is yours to update, for example from
ng-change. The same four examples as the core demo's
section 14, done with attributes.
The trigger shows chips, so the header takes over the count.
ng-change rewrites it; ll-on-open and
ll-on-filter-query-change rewrite the match count. The
button reaches the widget through instance().
Rows show a name and a role; the header repeats the same layout in bold. Rows re-render per keystroke, the header never does.
A pinned link under the list. Tab reaches it while the popup is open; Esc on it returns to the trigger. The app closes and navigates.
The same two commands as 13a with the same row look, only pinned. Each
is a real button wearing the theme's item class from
ctx.classIdMap. Tab reaches them, but they sit outside the
arrow-key ring.
Commands rendered as option rows above or below the items, so the arrow
keys reach them. onActivate receives the widget instance;
calling it counts as the user's own change, so ng-change
runs like after a click. textFn and disabledFn
run again after every change. The same four examples as the core demo's
section 15, done with attributes.
The leading row follows the choose-all row; its disabled state reads the live model. The trailing row sits after the last item.
The row asks for a name, adds it to the array behind
ll-options, writes the model and closes. A row never
closes the popup by itself.
The row flips the array behind ll-options, which
$watchCollection hands to the widget. A filter query
re-applies to the new order, and the chosen item stays chosen.
13a's leading rows with ll-popup-list-leading-rows-pinned="true":
the choose-all row and the command stay in view while the items scroll
under them, and keep their place in the arrow-key ring.
llselect's own strings come from a language pack. That covers the
placeholder, the search box, the clear and remove buttons, the count
summary, the choose-all row and the no-results status. An app picks one
once, in .config():
llselectConfigProvider.defaults({ uiTranslationPack: llselectI18n.zhTW }).
Widgets already on screen are the app's job. This page runs the recipe
from API.md, "Switching the UI
language at runtime", verbatim in app.js. Pick a language:
every widget on this page follows, chosen values survive, and 2a keeps
its explicit ll-placeholder. The page's own copy stays
English. RTL lives in the core demo, section 16.
The whole of what this page loads. Nothing is generated and nothing is minified: this is the file you copy.