Lookup
Menu component with a text input and a menu of options.
Options will depend on the current value of the input. Typical use will involve listening for new-input events, fetching or otherwise computing options, then passing those options back to the Lookup for display.
TextInput props apply
This component contains a TextInput component. You can bind TextInput props to this component and they will be passed to the TextInput within.
Attributes passed to input
This component will pass any HTML attributes applied to it, except for CSS class, to the <input> element within the component.
Demos
Default
The Lookup component will emit new-input events on input, which the parent component can react to by computing or fetching options, then providing those options to the Lookup component for display.
Note that in this example, options are Wikidata items with a human-readable label and a Wikidata entity ID value.
<template>
<div>
<p>The current value is: {{ selection }}</p>
<cdx-lookup
v-model="selection"
:options="menuOptions"
@new-input="onInput"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { CdxLookup, MenuOption } from '@wikimedia/codex';
import vegetableItems from './data';
export default defineComponent( {
name: 'LookupDefault',
components: { CdxLookup },
setup() {
const selection = ref( '' );
const menuOptions = ref<MenuOption[]>( [] );
function onInput( value: string ) {
if ( value ) {
menuOptions.value = vegetableItems.filter( ( item ) =>
item.label.includes( value )
);
}
}
return {
selection,
menuOptions,
onInput
};
}
} );
</script>
<style scoped>
p {
margin: 0 0 16px;
font-weight: bold;
}
</style>
The menu-option slot can be used to set up custom option content and formatting.
<template>
<div>
<cdx-lookup
v-model="selection"
class="vp-lookup-custom-option"
:options="menuOptions"
@new-input="onInput"
>
<template #menu-option="{ option }">
<p class="vp-lookup-custom-option__label">
{{ option.label || option.value }}
</p>
<p v-if="option.description" class="vp-lookup-custom-option__description">
{{ option.description }}
</p>
</template>
</cdx-lookup>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { CdxLookup, MenuOption } from '@wikimedia/codex';
import vegetableItems from './data';
export default defineComponent( {
name: 'LookupCustomOption',
components: { CdxLookup },
setup() {
const selection = ref( '' );
const menuOptions = ref<MenuOption[]>( [] );
function onInput( value: string ) {
if ( value ) {
menuOptions.value = vegetableItems.filter( ( item ) =>
item.label.includes( value )
);
}
}
return {
selection,
menuOptions,
onInput
};
}
} );
</script>
<style lang="less" scoped>
.vp-lookup-custom-option {
p {
margin: 0;
}
&__label {
font-weight: bold;
}
&__description {
font-size: 0.875em;
line-height: 1.25;
}
}
</style>
The footer slot can be used to display non-interactive content below the final option. For example, a "no result found" message can be conditionally displayed.
<template>
<div>
<cdx-lookup
v-model="selection"
:options="menuOptions"
@new-input="onInput"
>
<template v-if="noResults" #footer>
No results found.
</template>
</cdx-lookup>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { CdxLookup, MenuOption } from '@wikimedia/codex';
import vegetableItems from './data';
export default defineComponent( {
name: 'LookupNoResults',
components: { CdxLookup },
setup() {
const selection = ref( '' );
const menuOptions = ref<MenuOption[]>( [] );
const noResults = ref( false );
function onInput( value: string ) {
if ( value ) {
menuOptions.value = vegetableItems.filter( ( item ) =>
item.label.includes( value )
);
noResults.value = menuOptions.value.length === 0;
} else {
noResults.value = false;
}
}
return {
selection,
menuOptions,
noResults,
onInput
};
}
} );
</script>
With fetched results
Often, a Lookup component is used to fetch results from an API endpoint. Parent components can react to the new-input event emitted by Lookup to search for results, then pass back to the Lookup either an array of results to display as options or an empty array if there are no results. Between those two events, a pending state animation will display in the input.
<template>
<div>
<cdx-lookup
v-model="selection"
:options="menuOptions"
@new-input="onInput"
>
<template v-if="noResults" #footer>
No results found.
</template>
</cdx-lookup>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { CdxLookup, MenuOption } from '@wikimedia/codex';
import { Result } from './types';
export default defineComponent( {
name: 'LookupWithFetch',
components: { CdxLookup },
setup() {
const selection = ref( '' );
const menuOptions = ref<MenuOption[]>( [] );
const noResults = ref( false );
const currentSearchTerm = ref( '' );
function onInput( value: string ) {
noResults.value = false;
currentSearchTerm.value = value;
if ( !value || value === '' ) {
return;
}
fetch(
`https://www.wikidata.org/w/api.php?origin=*&action=wbsearchentities&format=json&search=${encodeURIComponent( value )}&language=en&limit=10&props=url`
).then( ( resp ) => resp.json() )
.then( ( data ) => {
if ( currentSearchTerm.value === value ) {
const results: MenuOption[] = [];
if ( data.search && data.search.length > 0 ) {
data.search.forEach( ( result: Result ) => {
results.push( {
label: result.label,
value: result.id
} );
} );
}
if ( results.length === 0 ) {
noResults.value = true;
}
menuOptions.value = results;
}
} ).catch( () => {
menuOptions.value = [];
} );
}
return {
selection,
menuOptions,
noResults,
onInput
};
}
} );
</script>
Clearable, with start icon
Props of the TextInput component can be bound to Lookup and will be passed down to the TextInput component inside of it, so you can take advantage of features like the "clear" button and icons.
<template>
<div>
<cdx-lookup
v-model="selection"
:options="menuOptions"
:clearable="true"
:start-icon="cdxIconSearch"
@new-input="onInput"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { CdxLookup, MenuOption } from '@wikimedia/codex';
import { cdxIconSearch } from '@wikimedia/codex-icons';
import vegetableItems from './data';
export default defineComponent( {
name: 'LookupClearableStartIcon',
components: { CdxLookup },
setup() {
const selection = ref( '' );
const menuOptions = ref<MenuOption[]>( [] );
function onInput( value: string ) {
if ( value ) {
menuOptions.value = vegetableItems.filter( ( item ) =>
item.label.includes( value )
);
}
}
return {
selection,
menuOptions,
onInput,
cdxIconSearch
};
}
} );
</script>
With placeholder
Attributes (except for class) will fall through to the input element, so you can set things like placeholder on the Lookup component and they'll be applied to the input.
<template>
<div>
<cdx-lookup
v-model="selection"
:options="menuOptions"
placeholder="Start typing a vegetable name..."
@new-input="onInput"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import { CdxLookup, MenuOption } from '@wikimedia/codex';
import vegetableItems from './data';
export default defineComponent( {
name: 'LookupWithPlaceholder',
components: { CdxLookup },
setup() {
const selection = ref( '' );
const menuOptions = ref<MenuOption[]>( [] );
function onInput( value: string ) {
if ( value ) {
menuOptions.value = vegetableItems.filter( ( item ) =>
item.label.includes( value )
);
}
}
return {
selection,
menuOptions,
onInput
};
}
} );
</script>
Usage
Props
| Prop name | Description | Type | Values | Default |
|---|
modelValue(required) | Value of the selection provided by v-model binding in the parent component. | string|number|null | - | |
options | Menu options. | MenuOption[] | - | () => [] |
initialInputValue | Initial value of the text input. | string|number | - | '' |
disabled | Whether the entire component is disabled. | boolean | - | false |
Events
| Event name | Properties | Description |
|---|
| update:modelValue | value string | number - The new value | When the selected value changes. |
| new-input | value string | number - The new value | When the text input value changes. |
Slots
| Name | Description | Bindings |
|---|
| menu-option | Display of an individual option in the menu | option MenuOption - The current option |
| footer | Content to display at the end of the options list | |