Select
Dropdown selection of a list of choices.
NOTE
Please be sure to read the Basic concepts part of the documentation first.
Overview
The role of the select is to offer the user a choice between a limited set of options. You have three ways of assigning these options:
- via the
choices
prop, - by creating
bt-form-select-choice
components in the appropriate slots, - by requesting a remote server.
TIP
You can combine all of these ways, we'll see that later in the documentation.
The choices can be any type of data. They are normalized by the component to be manageable. You have a say in this normalization step that'll see later on.
When you select an item, a copy is made and stored in the list of selected choices.
NOTE
It's important to note that the selected choices are not required to be part of the list of available choices. The two are uncorrelated.
You can totally assign a value to the select that is not part of the dropdown.
You can see why this is important in the remote part of the documentation.
States
Here a demo that illustrates the different states a select can take:
TIP
By default, errors are "floating" (meaning they show as a popover), you can make them show under the field by setting floating-errors
to false
.
But, the setting is ignored if the control is inside an addon of another component. In this case the errors will always be floating.
Choices creation
Here we'll look at the different ways you can add choices.
From prop
The choices
prop expects an array of items. The array can contain any value you want and can change at any time.
From slots
Because you can combine the different ways of defining available choices, you have multiple slots at your disposal to control where the choices will go in the dropdown:
choices-before
: these choices will always be first in the dropdown, before any remote choice or choice set via thechoices
prop,choices
: these choices will appear after any remote choice or choice set via thechoices
prop.choices-after
: these choices will always appear last.
WARNING
Please beware of always putting your choices in one of these slots. DO NOT put them in the default slot:
<!-- BAD, the default slot is for the label. -->
<bt-form-select>
<bt-form-select-choice>A</bt-form-select-choice>
<bt-form-select-choice>B</bt-form-select-choice>
</bt-form-select>
Do this instead:
<!-- GOOD, the choices will appear in the dropdown. -->
<bt-form-select>
Hey, I'm the label!
<template #choices>
<bt-form-select-choice>A</bt-form-select-choice>
<bt-form-select-choice>B</bt-form-select-choice>
</template>
</bt-form-select>
Each bt-form-select-choice
can take the following props:
Option | Description | Default |
---|---|---|
The value to assign to the control when the choice is selected. If undefined , the text of the slot is used. | undefined | |
The text to show in selection resume of the select. If null , the text of the slot is used. | null | |
To force the item to be disabled. | undefined | |
To force the item to be selected on initialization. Only works if the control has no value. | undefined |
Here's a demo that illustrates the usage of these props:
From server
You can also load your choices from a remote location.
The select component is fully integrated with the HttpService and ApiService of Banquette
.
If you're not familiar with them, I encourage you to look it up first, as there are many useful concepts to know about.
Here's a basic select with remote choices:
Using a model:
A remote choice can be transformed into a Model automatically if you set the remote-model
prop:
<bt-form-select remote-url="..." remote-model="Country">[...]</bt-form-select>
The server's response will be automagically converted into an array of Country
object using the TransformService.
Here's an example:
Using a global endpoint:
You can also use an Endpoint instead of an url:
import { ApiEndpointStorageService } from "@banquette/api";
import { Injector } from "@banquette/dependency-injection";
import { HttpMethod } from "@banquette/http";
Injector.Get(ApiEndpointStorageService).registerEndpoint(
'get_users',
'/users/{type}',
HttpMethod.GET,
{type: '*'}
);
<bt-form-select remote-endpoint="get_users" :remote-url-params="{type: 'admin'}">
Choose an administrator
</bt-form-select>
Using a model's endpoint:
import { Endpoint } from "@banquette/api";
@Alias('Country')
@Endpoint('get_all', '/countries')
class Country {
@Api() public name!: string;
@Api() public code!: string;
}
<bt-form-select remote-endpoint="get_all" remote-model="Country">
Choose a country
</bt-form-select>
Choices grouping
You can group your choices in separate sections for better readability:
Choices normalization
No matter how the choices are given to the select component, they always go through a normalization phase, that converts them into a Choice
object that is used internally.
For each choice, the component needs to figure out the following data from the input:
- what's the label?
- what's the identifier?
- what's the value?
- what's the group?
- is it disabled?
- is it selected?
For each of these questions, the answer will depend on the input and the configuration.
1) What's the label?
- If the prop choices-label is a function, it will always be called to get the label.
- Else if the input is a , it is used as label.
- Else if the input is an
object
, the prop choices-label is expected to contain the name of the attribute to get the label from.
<!-- Tell the select that the "name" attribute should be used as label. -->
<bt-form-select choices-label="name" :choices="[obj1, obj2]"></bt-form-select>
<!-- OR -->
<!-- Tell the select to execute a function for each item, that will return the label. -->
<bt-form-select :choices-label="(o) => o.name" :choices="[obj1, obj2]"></bt-form-select>
<!-- ALSO -->
<!-- "choices-label" can contain replaceable segments. -->
<!-- Here "{fullName}" and "{adherentId}" will be replaced by the value in each object. -->
<bt-form-select choices-label="{fullName} (n°{adherentId})" :choices="[userObj1, userObj2]"></bt-form-select>
NOTE
The label is used in the dropdown (if the choice is not defined in a slot) and in the selection resume. A label is always a simple string, it cannot contain HTML nor components.
2) What's the identifier?
- If the prop choices-identifier is a function, it will always be called to get the identifier.
- Else if the input is a , it is used as identifier.
- Otherwise, choices-identifier is expected to contain the name of the attribute to get the identifier from.
<!-- Tell the select that the "id" attribute should be used as identifier. -->
<bt-form-select choices-identifier="id" :choices="[obj1, obj2]"></bt-form-select>
<!-- OR -->
<!-- Tell the select to execute a function for each item, that will return the identifier. -->
<bt-form-select :choices-identifier="(o) => o.id" :choices="[obj1, obj2]"></bt-form-select>
WARNING
The identifier must be unique between the whole list of choices (even from different sources). Items with duplicate identifier will be ignored and a warning will show in the console.
3) What's the value?
- If the prop choices-value is a function, it will always be called to get the value.
- Else if the input is a OR if choices-value is not defined, the input is used as value.
- Else, choices-value is expected to contain the name of the attribute to get the value from.
<!-- Tell the select that the "id" attribute should be used as value. -->
<bt-form-select choices-value="id" :choices="[obj1, obj2]"></bt-form-select>
<!-- OR -->
<!-- Tell the select to execute a function for each item, that will return the value. -->
<bt-form-select :choices-label="(o) => o.id" :choices="[obj1, obj2]"></bt-form-select>
<!-- OR -->
<!-- The input is used as value, as is, no matter its type. -->
<bt-form-select :choices="[obj1, obj2]"></bt-form-select>
4) What's the group?
- If the prop choices-group is a function, it will always be called to get the group name.
- Else if the prop choices-group is a
string
AND if the input is anobject
, it is expected to be the name of the attribute that holds the name of the group in the object. - Otherwise, the item will not be part of a group.
<!-- Tell the select that the "category" attribute should be used as group. -->
<bt-form-select choices-group="category" :choices="[obj1, obj2]"></bt-form-select>
<!-- OR -->
<!-- Tell the select to execute a function for each item, that will return the group. -->
<bt-form-select :choices-group="(o) => o.group" :choices="[obj1, obj2]"></bt-form-select>
5) Is it disabled?
- If the prop choices-disabled is a function, it will always be called to get a
boolean
. - Else if the prop choices-disabled is a
string
AND if the input is anobject
, it is expected to be the name of the attribute that holds a boolean answering the question. - Otherwise, the item will not be disabled.
<!-- Tell the select that the "disabled" attribute should -->
<!-- be used to know if the choice is disabled. -->
<bt-form-select choices-disabled="disabled" :choices="[obj1, obj2]"></bt-form-select>
<!-- OR -->
<!-- Tell the select to execute a function for each item, that will return a boolean. -->
<bt-form-select :choices-disabled="(o) => !o.name" :choices="[obj1, obj2]"></bt-form-select>
6) Is it selected?
To know if an item should be selected, the select will look at the current selection (because remember, the selection is uncorrelated to the choices).
If a selected item has the same id of the item being normalized, it will be selected automatically.
This means that the selection can precede the choices (very important for remote choices).
Multiple selection
You can allow multiple values to be selected by setting the multiple
prop to true
:
You can constrain the select height to prevent it from growing when the number of items selected doesn't fit via the lock-height
prop:
Clearable
You can allow the user to clear the value of the select via the clearable
prop:
Search
To enable filtering, use the seach-type
prop. It can take the following values:
none
: the search is disabled (default value),local
: filters the local list of choices,remote
: no choices are visible in the dropdown until the user searches something. At which point a request is made to fetch the corresponding choices.
Local search:
Below an example of a local
search:
NOTE
Please note that the search type local
also works for remote choices. In the example above the choices are fetched form a remote server and the search type is local
.
A local
search simply means it is performed on the local list of choices.
Remote search:
When the search type is set to remote
, the choices are not fetched until the user searches something.
This is intended for large data sets.
You have two more parameters that come with it:
search-remote-param-name
(default:search
): the name of the parameter that holds the search text to include in the ajax request. You can set an array, in which case each item of the array will create a parameter that contains the search text.search-min-length
*(default:0
): minimum number of characters in the search before a request is sent.
Below an example of a remote
search:
Allow creation
You can allow the user to add new choices with allow-creation
:
Debug
If you add the debug
attribute to the component, a little icon shows on the right, allowing you to inspect the whole viewData object exposed by the component.
If you don't know what the viewData object is, please refer to this part of the documentation.
Props
Method | Description | Default |
---|---|---|
Bidirectional binding for the value of the control. | undefined | |
The original value given by the end-user through the html element. | undefined | |
The label of the field. Overridden by the default slot. | null | |
A placeholder value to show when there is no value selected. | null | |
A help text to show to the user. Overridden by the help slot. | null | |
If true the label will float above the control and act as a placeholder is there is none. | true | |
If true the errors will appear as an icon on the right side of the input that shows a popover.This value is overridden to true internally if the control is in a group to preserve layout integrity. | true | |
If true the help text will appear as an icon on the right side of the input that shows a popover.This value is overridden to true internally if the control is in a group to preserve layout integrity. | false | |
If true , a little asterisk extra is shown, indicating to the user that the field is mandatory. | false | |
For external control of the disabled state of the control. | false | |
For external control of the busy state of the control. | false | |
If true , show the debug overlay. | false | |
HTML tab index (for keyboard navigation). | 0 | |
If true , the control will try to gain focus when mounted. | false | |
Array of elements to use as choices. They are cumulative with other sources (like the choices slot or a remote source). | null | |
Define how to resolve the choice's label. See Nodes normalization for detail. | 'label' | |
Define how to resolve the choice's identifier. See Nodes normalization for detail. | 'id' | |
Define how to resolve the choice's value. See Nodes normalization for detail. | null | |
Define how to resolve the choice's disable state. See Nodes normalization for detail. | 'disabled' | |
Define how to resolve the choice's group name. See Nodes normalization for detail. | 'group' | |
If true the select allow for multiple values. | false | |
If true , the height of the select will not grow when multiple choices are selected. | false | |
If true , the user can enter custom values using the input, they will be added to the selected values even if not available in the list of choices. | false | |
If true , the user can clear the value of the select. | false | |
If true : the dropdown is always closed when a selection is made.If false : the dropdown is never closed when a selection is made.If auto : the dropdown is only closed when the select is not multiple. | 'auto' | |
An optional model identifier. If defined the choices will be transformed into an entity of the model. | null | |
A raw url to call to get available choices. | null | |
Name of the endpoint to call to get the choices. | null | |
The HTTP method to use when performing the request. | 'GET' | |
Parameters to add to the url. If a variable with the same name is found in the url (surrounded by { } ), it will replace it. Otherwise, it will be added to the query string (after ? ). | {} | |
Additional headers to add to the request. | {} | |
Different strategies to search on the set of choices.
| 'none' | |
Name of the parameter to add to the search request. If an array is given, a parameter with the value of the search will be created for each of them. Only applicable if search-type is remote . | 'search' | |
Minimum length of the search string before a search is performed. | 0 | |
Target to teleport the dropdown to. | null | |
CSS z-index value for the dropdown. | undefined |
Slots
Name | Description |
---|---|
choice | A slot that is called for each slot that needs to be rendered, no matter its origin. Example:
Choices defined here are shown just before the choices added in the Props:
|
choices | A slot where inline choices can be defined. Example:
Choices defined here are shown just before the choices added in the Props: none |
choices-after | A slot where inline choices can be defined. Choices defined here are shown after any other choice, no matter their origin. Props: none |
choices-before | A slot where inline choices can be defined. Choices defined here are shown before any other choice, no matter their origin. Props: none |
default or label | Defines the label of the field. This slot takes priority over the Props: none |
help | Defines the help text of the field. This slot takes priority over the Props: none |
before | Defines the content of the addon to show before the field. Props: none |
before-raw | Defines the content of the un-styled addon to show before the field. Props: none |
after | Defines the content of the addon to show after the field. Props: none |
after-raw | Defines the content of the un-styled addon to show after the field. Props: none |
extras-before | Defines custom floating extras that appear before the built-in ones. Props: none |
extras-after | Defines custom floating extras that appear after the built-in ones. Props: none |
Theming
Below the list of all CSS variables and selectors available to customize the appearance of the component. If you're not familiar on how the theming works, please refer to the VueThemes documentation.
Variables
Variable | Default | Comment |
---|---|---|
fontFamily | var(--bt-font-family-base) | - |
fontSize | var(--bt-font-size-base) | - |
fontWeight | var(--bt-font-weight-normal) | - |
textColor | var(--bt-text-color-base) | - |
backgroundColor | var(--bt-color-white) | - |
backgroundDisabledColor | Same as backgroundColor | - |
borderColor | var(--bt-color-gray-200) | - |
borderRadius | var(--bt-border-radius-base) | - |
borderWidth | var(--bt-border-width-base) | - |
borderStyle | var(--bt-border-style-base) | - |
borderFocusWidth | Same as borderWidth | - |
borderFocusColor | var(--bt-color-primary) | - |
borderErrorWidth | Same as borderWidth | - |
borderErrorColor | var(--bt-color-red-500) | - |
borderDisabledColor | var(--bt-color-gray-200) | - |
placeholderX | 0.8rem | - |
placeholderY | 0.75rem | - |
labelTextColor | var(--bt-text-color-light) | - |
labelTransitionDuration | 0.2s | - |
helpTextColor | var(--bt-text-color-light) | - |
helpFontSize | var(--bt-font-size-sm) | - |
errorTextColor | var(--bt-color-red-500) | - |
errorFontSize | var(--bt-font-size-sm) | - |
disabledOpacity | 0.5 | - |
addonBackgroundColor | var(--bt-color-gray-50) | - |
addonTextColor | var(--bt-text-color-light) | - |
inputMinWidth | 150px | - |
choiceTextColor | var(--bt-color-primary) | - |
choiceCheckIconColor | var(--bt-color-primary) | - |
choiceTrashIconColor | var(--bt-color-red-500) | - |
choiceBackgroundFocusColor | var(--bt-color-gray-50) | - |
groupPaddingX | 1em | - |
groupPaddingY | 0.8em | - |
groupLabelTextColor | var(--bt-color-gray-450) | - |
groupLabelFontWeight | var(--bt-font-weight-semibold) | - |
groupLabelFontSize | var(--bt-font-size-sm) | - |
groupSeparatorColor | var(--bt-color-gray-100) | - |
tagTextColor | var(--bt-color-gray-550) | Tags are for multiple selection. |
tagBorderColor | var(--bt-color-gray-150) | - |
tagBackgroundColor | var(--bt-color-gray-50) | - |
tagMaxWidth | 200px | - |
tagCloseFillColor | Same as tagTextColor | - |
tagCloseFillHoverColor | Same as tagTextColor | - |
tagCloseBackgroundHoverColor | Same as tagBorderColor | - |
clearIconColor | var(--bt-text-color-light) | - |
Selectors
Name | Comment |
---|---|
root | The root container. |
label | The container of the label. |
help | The non floating help text container. |
before | The before slot container. |
after | The after slot container. |
floatingExtras | The container of the floating extras. |
inputGroup | A container that only include the form field. |
input | The actual form control. |
Examples:
cssSelectors: {
input: {
background: '...'
},
floatingExtras: {
color: '...'
}
}