JavaScript/jQuery DropDownBox - Search in Embedded Components
When using DevExtreme DropDownBox with an embedded DataGrid or TreeList component, you may want to allow users to search and filter records in the dropdown. DataGrid and TreeList include a built-in search and filter UI that you can activate. You can also make the DropDownBox input field editable, so that it acts as a search box for the embedded control. In this case, you need to implement custom code, because DropDownBox does not have built-in search functionality. The implementation depends on whether a lookup field is involved in the search.
When to Use Each Approach
| Implementation Aspect | Search by field values (no lookup column involved) | Search by display values (lookup column involved) |
|---|---|---|
| Embedded component | DataGrid / TreeList | DataGrid / TreeList |
| User input | Text that matches values in the main dataset (same record). | Text that matches values in the main dataset, a related dataset (lookup display text), or both. |
| Main API used for search | DataSource.searchValue(value) | DataSource.filter(filterExpr) |
| Extra data source required | No | Yes, a lookup data source |
| Filtering logic | The DataSource searches for values in fields specified in searchExpr | You query the lookup data source by display field (for example, Name contains text), map results to keys, then build an OR filter such as [EmployeeID, '=', 1] or [EmployeeID, '=', 5] |
| Reset search on close | dataSource.searchValue('') |
dataSource.filter(null) |
For complete working examples, see the following GitHub repositories:
- DropDownBox with embedded DataGrid (search by field values)
- DropDownBox with embedded TreeList (search by lookup column display value)
See Also
Search by Field Values (DataGrid)
This topic covers search by field values (no lookup column involved) in a DropDownBox with an embedded DataGrid. The approach uses DataSource.searchExpr and DataSource.searchValue to filter rows as the user types.
The full working code is available in the GitHub repository:
1) Configure DropDownBox to Accept User Input
- Activate
acceptCustomValueso the user can type text. - Set
valueChangeEventto an empty string to prevent the component from trying to interpret typing as a value change.
jQuery
$('#gridBox').dxDropDownBox({
acceptCustomValue: true,
valueChangeEvent: '',
openOnFieldClick: false,
// ...
});Angular
<dx-drop-down-box
[acceptCustomValue]="true"
valueChangeEvent=""
[openOnFieldClick]="false"
...
>
</dx-drop-down-box>Vue
<DxDropDownBox
:accept-custom-value="true"
value-change-event=""
:open-on-field-click="false"
...
/>React
<DropDownBox
acceptCustomValue={true}
valueChangeEvent=""
openOnFieldClick={false}
...
/>ASP.NET Core Controls
@(Html.DevExtreme().DropDownBox()
.AcceptCustomValue(true)
.ValueChangeEvent("")
.OpenOnFieldClick(false)
// ...
)2) Create a DataSource That Supports Search
Use the following DataSource members:
searchExpr— fields used for searchingsearchValue— current search string
jQuery
const dataSource = new DevExpress.data.DataSource({
store: DevExpress.data.AspNet.createStore({
key: 'OrderNumber',
loadUrl: 'https://js.devexpress.com/Demos/WidgetsGalleryDataService/api/orders',
}),
searchExpr: 'Employee',
});Angular
@Input() dataSource!: DataSource;
import AspNetData from 'devextreme-aspnet-data-nojquery';
import { DataSource } from 'devextreme-angular/common/data';
this.dataSource = new DataSource({
store: AspNetData.createStore({
key: 'OrderNumber',
loadUrl: `${url}/Orders`,
}),
searchExpr: 'Employee',
});Vue
import AspNetData from 'devextreme-aspnet-data-nojquery';
import { DataSource } from 'devextreme-vue/common/data';
const dataSource = new DataSource({
store: AspNetData.createStore({
key: 'OrderNumber',
loadUrl: `${url}/Orders`,
}),
searchExpr: 'Employee',
});React
import AspNetData from 'devextreme-aspnet-data-nojquery';
import { DataSource } from 'devextreme-react/common/data';
const dataSource = new DataSource({
store: AspNetData.createStore({
key: 'OrderNumber',
loadUrl: `${url}/Orders`,
}),
searchExpr: 'Employee',
});ASP.NET Core Controls
@(Html.DevExtreme().DataGrid<SampleOrder>()
.DataSource(d => d.Mvc().Controller("SampleData").LoadAction("Get").Key("OrderID"))
.DataSourceOptions(op => { op.SearchExpr(new[] { "CustomerName" }); })
// ...
)3) Configure displayExpr
Use displayExpr to define how a selected record is displayed in the input:
function displayExpr(item) {
if (!item || typeof item !== 'object') return '';
return `${item.Employee}: ${item.StoreState} - ${item.StoreCity} <${item.OrderNumber}>`;
}4) Configure the Embedded DataGrid in contentTemplate
Configure the DropDownBox component. Use contentTemplate to embed a DataGrid.
To activate focused row and single row selection, specify the following settings:
- Enable
focusedRowEnabledto allow keyboard navigation. - Use
focusedRowKeyso search can focus the first match. - Use single selection:
selection.mode.
jQuery
contentTemplate: (e, container) => {
const dropDownBox = e.component;
const value = dropDownBox.option('value');
const $dataGridContainer = $('<div>');
container.append($dataGridContainer);
$dataGridContainer.dxDataGrid({
dataSource,
paging: { enabled: true, pageSize: 10 },
focusedRowEnabled: true,
focusedRowKey: value,
autoNavigateToFocusedRow: false,
remoteOperations: true,
scrolling: { mode: 'virtual' },
selection: { mode: 'single' },
selectedRowKeys: [value],
height: '100%',
width: '100%',
columnAutoWidth: true,
onContentReady: () => {
if (!dropDownBox.option('gridFirstLoadCompleted')) {
dropDownBox.option('gridFirstLoadCompleted', true);
}
},
onSelectionChanged: (args) => {
if (!args.component.option('resetSelection')) {
const keys = args.selectedRowKeys;
dropDownBox.option('value', keys.length ? keys[0] : null);
dropDownBox.focus();
}
args.component.option('resetSelection', false);
},
columns: [
{ dataField: 'OrderNumber', caption: 'ID', dataType: 'number' },
{ dataField: 'OrderDate', dataType: 'date', format: 'shortDate' },
{ dataField: 'StoreCity', dataType: 'string' },
{ dataField: 'StoreState', dataType: 'string' },
{ dataField: 'Employee', dataType: 'string' },
{ dataField: 'SaleAmount', dataType: 'number', format: { type: 'currency', precision: 2 } },
],
});
dataGridInstance = $dataGridContainer.dxDataGrid('instance');
return container;
},Angular
<div *dxTemplate="let data of 'content'">
<dx-data-grid
#dataGrid
[dataSource]="dataSource"
height="100%"
width="100%"
[focusedRowEnabled]="true"
[(focusedRowKey)]="focusedRowKey"
[(selectedRowKeys)]="selectedRowKeys"
[autoNavigateToFocusedRow]="false"
[remoteOperations]="true"
[columnAutoWidth]="true"
(onKeyDown)="dataGridKeyDown($event)"
(onContentReady)="dataGridContentReady($event)"
(onSelectionChanged)="onSelectionChanged($event)"
>
<dxi-data-grid-column dataField="OrderNumber" caption="ID" dataType="number"></dxi-data-grid-column>
<dxi-data-grid-column dataField="OrderDate" dataType="date"></dxi-data-grid-column>
<dxi-data-grid-column dataField="StoreState" dataType="string"></dxi-data-grid-column>
<dxi-data-grid-column dataField="StoreCity" dataType="string"></dxi-data-grid-column>
<dxi-data-grid-column dataField="Employee" dataType="string"></dxi-data-grid-column>
<dxi-data-grid-column dataField="SaleAmount" dataType="number">
<dxo-data-grid-format type="currency" [precision]="2"></dxo-data-grid-format>
</dxi-data-grid-column>
<dxo-data-grid-paging [enabled]="true" [pageSize]="10"></dxo-data-grid-paging>
<dxo-data-grid-selection mode="single"></dxo-data-grid-selection>
<dxo-data-grid-scrolling mode="virtual"></dxo-data-grid-scrolling>
</dx-data-grid>
</div>Vue
<template #default>
<DxDataGrid
ref="dataGridRef"
:data-source="dataSource"
height="100%"
width="100%"
:focused-row-enabled="true"
v-model:focused-row-key="focusedRowKey"
:selected-row-keys="selectedRowKeys"
:auto-navigate-to-focused-row="false"
:remote-operations="true"
:column-auto-width="true"
@key-down="dataGridKeyDown"
@content-ready="dataGridContentReady"
@selection-changed="onSelectionChanged"
>
<DxColumn data-field="OrderNumber" caption="ID" data-type="number" />
<DxColumn data-field="OrderDate" data-type="date" />
<DxColumn data-field="StoreState" data-type="string" />
<DxColumn data-field="StoreCity" data-type="string" />
<DxColumn data-field="Employee" data-type="string" />
<DxColumn data-field="SaleAmount" data-type="number">
<DxFormat type="currency" :precision="2" />
</DxColumn>
<DxPaging :enabled="true" :page-size="10" />
<DxSelection mode="single" />
<DxScrolling mode="virtual" />
</DxDataGrid>
</template>React
<DataGrid
ref={dataGridRef}
dataSource={dataSource}
height="100%"
width="100%"
focusedRowEnabled={true}
focusedRowKey={selection.focusedRowKey}
selectedRowKeys={selection.selectedRowKeys}
autoNavigateToFocusedRow={false}
remoteOperations={true}
columnAutoWidth={true}
onKeyDown={dataGridKeyDown}
onFocusedRowChanged={onFocusedRowChanged}
onSelectionChanged={onSelectionChanged}
>
<Column dataField="OrderNumber" caption="ID" dataType="number" />
<Column dataField="OrderDate" dataType="date" />
<Column dataField="StoreState" dataType="string" />
<Column dataField="StoreCity" dataType="string" />
<Column dataField="Employee" dataType="string" />
<Column dataField="SaleAmount" dataType="number">
<Format type="currency" precision={2} />
</Column>
<Paging enabled={true} pageSize={10} />
<Selection mode="single" />
<Scrolling mode="virtual" />
</DataGrid>ASP.NET Core Controls
@using (Html.DevExtreme().NamedTemplate("EmbeddedDataGridSingle"))
{
@(Html.DevExtreme().DataGrid<SampleOrder>()
.ID("embedded-datagrid")
.DataSource(d => d.Mvc().Controller("SampleData").LoadAction("Get").Key("OrderID"))
.DataSourceOptions(op => { op.SearchExpr(new[] { "CustomerName" }); })
.Paging(p => p.PageSize(10))
.FocusedRowEnabled(true)
.FocusedRowKey(new JS("component.option('value')"))
.RemoteOperations(true)
.AutoNavigateToFocusedRow(false)
.Scrolling(s => s.Mode(GridScrollingMode.Virtual))
.Selection(s => s.Mode(SelectionMode.Single))
.SelectedRowKeys(new JS("[component.option('value')]"))
.Height("100%")
.Width("100%")
.ColumnAutoWidth(true)
.Columns(columns =>
{
columns.AddFor(m => m.OrderID).Caption("ID");
columns.AddFor(m => m.OrderDate).Format("shortDate");
columns.AddFor(m => m.CustomerName);
columns.AddFor(m => m.ShipCountry);
columns.AddFor(m => m.ShipCity);
})
.OnSelectionChanged("function(e){ dataGridSelectionChanged(e, component); }")
.OnKeyDown("dataGridKeyDown")
.OnContentReady("function(e){ dataGridContentReady(e, component); }")
.OnInitialized("dataGridInitialized")
)
}5) Implement Search in onInput
Handle the DropDownBox onInput event:
- Ensure the dropdown is open
- Apply
dataSource.searchValue(text) - Load results and move focus to the first match
jQuery
onInput: (e) => {
clearTimeout(searchTimerId);
searchTimerId = setTimeout(() => {
const dropDownBox = e.component;
if (!dropDownBox.option('opened')) dropDownBox.open();
const text = dropDownBox.option('text') || '';
dataSource.searchValue(text);
if (isSearchIncomplete(dropDownBox)) {
const onChanged = () => {
const items = dataSource.items();
if (items.length > 0) {
dataGridInstance.option('focusedRowKey', items[0].OrderNumber);
}
dropDownBox.focus();
dataSource.off('changed', onChanged);
};
dataSource.on('changed', onChanged);
dataSource.load();
}
}, searchTimeout);
},Angular
onInput(e: DxDropDownBoxTypes.InputEvent): void {
if (this.searchTimer) clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(() => {
if (!this.gridBoxOpened) this.gridBoxOpened = true;
const text = e.component.option('text');
this.dataSource.searchValue(text ?? null);
if (this.isSearchIncomplete(e.component)) {
const onChanged = (): void => {
const items = this.dataSource.items();
if (items.length > 0) {
this.focusedRowKey = items[0].OrderNumber;
}
this.focusInput();
this.dataSource.off('changed', onChanged);
};
this.dataSource.on('changed', onChanged);
this.dataSource.load().catch(() => {});
}
}, this.searchTimeout);
}Vue
function onInput(e: DxDropDownBoxTypes.InputEvent): void {
if (searchTimer.value) clearTimeout(searchTimer.value);
searchTimer.value = setTimeout(() => {
if (!gridBoxOpened.value) gridBoxOpened.value = true;
const text = e.component.option('text');
props.dataSource.searchValue(text ?? null);
if (isSearchIncomplete(e.component)) {
const onChanged = (): void => {
const items = props.dataSource.items();
if (items.length > 0) {
focusedRowKey.value = (items[0] as OrderItem).OrderNumber;
}
dropDownBoxRef.value?.instance?.focus();
props.dataSource.off('changed', onChanged);
};
props.dataSource.on('changed', onChanged);
props.dataSource.load().catch(() => {});
}
}, props.searchTimeout);
}React
const onChanged = useCallback(() => {
const items = dataSource.items();
if (items.length > 0) {
dispatch({ type: 'SET_FOCUSED_KEY', key: items[0].OrderNumber });
}
dropDownBoxRef.current?.instance().focus();
dataSource.off('changed', onChanged);
}, []);
const onInput = useCallback((e: DropDownBoxTypes.InputEvent) => {
if (searchTimer.current) clearTimeout(searchTimer.current);
searchTimer.current = setTimeout(() => {
if (!gridBoxOpened) setGridBoxOpened(true);
const text = e.component.option('text');
dataSource.searchValue(text ?? null);
if (isSearchIncomplete(e.component)) {
dataSource.on('changed', onChanged);
dataSource.load().catch(() => {});
}
}, searchTimeout);
}, [dataSource, gridBoxOpened, searchTimeout]);ASP.NET Core Controls
function onInput(e) {
clearTimeout(searchTimerId);
searchTimerId = setTimeout(() => {
const dropDownBox = e.component;
if (!dropDownBox.option('opened')) dropDownBox.open();
performSearch({
dropDownBox,
dataSource: getGridDataSource(),
grid: dataGridInstance,
});
}, searchTimeout);
}6) Detect Whether the User Is Searching (isSearchIncomplete)
The isSearchIncomplete function returns true if the user has changed the input text and a new search must be applied. It compares text (what the user typed) against the component's internal displayValue (the formatted display text of the currently selected value):
function isSearchIncomplete(dropDownBox) {
let displayValue = dropDownBox.option('displayValue');
let text = dropDownBox.option('text') || '';
displayValue = displayValue && displayValue.length && displayValue[0];
return text !== displayValue;
}7) Focus Management in onOpened
When the popup opens and the DropDownBox raises its onOpened event, move focus into the DataGrid.
The example implementation waits until the grid is ready (first open) or until the popup animation is complete (subsequent opens), then calls grid.focus().
jQuery
function onOpened(e) {
const dropDownBox = e.component;
const gridFirstLoadCompleted = dropDownBox.option('gridFirstLoadCompleted');
const handleOptionChanged = (args) => {
const grid = args.component;
const triggerCondition = gridFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedRowIndex';
if (triggerCondition) {
grid.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
grid.focus();
if (gridFirstLoadCompleted) grid.option('opened', false);
});
}
};
dataGridInstance.on('optionChanged', handleOptionChanged);
if (gridFirstLoadCompleted) {
dataGridInstance.option('opened', true);
}
const isTextEqualToDisplayValue =
dropDownBox.option('text') === dropDownBox.option('displayValue')[0];
const shouldClearSelection =
(dropDownBox.option('value') && !dropDownBox.option('text')) ||
!isTextEqualToDisplayValue;
if (shouldClearSelection && dataGridInstance.option('selectedRowKeys').length) {
dataGridInstance.option('resetSelection', true);
dataGridInstance.option('selectedRowKeys', []);
}
}Angular
onOpened(e: DxDropDownBoxTypes.OpenedEvent): void {
let gridFirstLoadCompleted = this.gridFirstLoadCompleted;
const dropDownBox = e.component;
const handleOptionChanged = (args: DxDataGridTypes.OptionChangedEvent): void => {
const grid = args.component;
const triggerCondition = gridFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedRowIndex';
if (triggerCondition) {
grid.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
grid.focus();
grid.option('opened', false);
});
}
};
this.dataGrid.instance.on('optionChanged', handleOptionChanged);
if (this.gridFirstLoadCompleted) {
this.dataGrid.instance.option('opened', true);
}
const displayValue = dropDownBox.option('displayValue') as string[];
const isTextEqualToDisplayValue = dropDownBox.option('text') === displayValue[0];
const shouldClearSelection =
(dropDownBox.option('value') && !dropDownBox.option('text')) ||
!isTextEqualToDisplayValue;
if (shouldClearSelection && this.selectedRowKeys?.length) {
this.resetSelection = true;
this.selectedRowKeys = [];
}
}Vue
function onOpened(e: DxDropDownBoxTypes.OpenedEvent): void {
const _gridFirstLoadCompleted = gridFirstLoadCompleted.value;
const dropDownBox = e.component;
function handleOptionChanged(args: DxDataGridTypes.OptionChangedEvent): void {
const grid = args.component;
const triggerCondition = _gridFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedRowIndex';
if (triggerCondition) {
grid.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
grid.focus();
grid.option('opened', false);
});
}
}
dataGridRef.value?.instance?.on('optionChanged', handleOptionChanged);
if (gridFirstLoadCompleted.value) {
dataGridRef.value?.instance?.option('opened', true);
}
const { text, value } = dropDownBox.option();
const displayValue = dropDownBox.option('displayValue') as string[];
const shouldClearSelection = (value && !text) || text !== displayValue[0];
if (shouldClearSelection && selectedRowKeys.value?.length) {
selectedRowKeys.value = [];
}
}React
const onOpened = useCallback((e: DropDownBoxTypes.OpenedEvent) => {
const isFirstLoadComplete = gridFirstLoadCompleted.current;
const dropDownBox = e.component;
function handleOptionChanged(args: DataGridTypes.OptionChangedEvent): void {
const grid = args.component;
const triggerCondition = isFirstLoadComplete
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedRowIndex';
if (triggerCondition) {
grid.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
grid.focus();
grid.option('opened', false);
});
}
}
dataGridRef.current?.instance().on('optionChanged', handleOptionChanged);
if (gridFirstLoadCompleted.current) {
dataGridRef.current?.instance().option('opened', true);
}
const displayValue = dropDownBox.option('displayValue') as string[];
const shouldClearSelection =
(dropDownBox.option('value') && !dropDownBox.option('text')) ||
dropDownBox.option('text') !== displayValue[0];
if (shouldClearSelection && selection.selectedRowKeys?.length) {
dispatch({ type: 'RESET' });
}
}, []);ASP.NET Core Controls
function onOpened(e) {
const dropDownBox = e.component;
const gridFirstLoadCompleted = dropDownBox.option('gridFirstLoadCompleted');
const handleOptionChanged = (args) => {
const grid = args.component;
const triggerCondition = gridFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedRowIndex';
if (triggerCondition) {
grid.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
grid.focus();
if (gridFirstLoadCompleted) grid.option('opened', false);
});
}
};
dataGridInstance.on('optionChanged', handleOptionChanged);
if (gridFirstLoadCompleted) {
dataGridInstance.option('opened', true);
}
const isTextEqualToDisplayValue =
dropDownBox.option('text') === dropDownBox.option('displayValue')[0];
const shouldClearSelection =
(dropDownBox.option('value') && !dropDownBox.option('text')) ||
!isTextEqualToDisplayValue;
if (shouldClearSelection && dataGridInstance.option('selectedRowKeys').length) {
dataGridInstance.option('resetSelection', true);
dataGridInstance.option('selectedRowKeys', []);
}
}8) Reset Component State in onClosed
When the popup closes, the onClosed event restores consistent state if the user typed something but did not confirm a selection.
- If nothing was loaded, reset the DropDownBox and clear
searchValue - If a search was in progress (
text !== displayValue), auto-select the first row
jQuery
function onClosed(e) {
const dropDownBox = e.component;
const hasLoadedItems = dataGridInstance.getVisibleRows().length;
const text = dropDownBox.option('text');
const displayValue = (dropDownBox.option('displayValue') || [])[0];
if (!hasLoadedItems) {
dropDownBox.reset(null);
dataSource.searchValue('');
dataSource.load();
return;
}
if (text && text !== displayValue) {
const firstKey = dataGridInstance.getKeyByRowIndex(0);
dataGridInstance.selectRows(firstKey);
dataGridInstance.option('focusedRowKey', firstKey);
}
}Angular
onClosed(e: DxDropDownBoxTypes.ClosedEvent): void {
const dropDownBox = e.component;
const hasLoadedItems = this.dataGrid.instance.getVisibleRows().length;
const text = dropDownBox.option('text');
const displayValue = dropDownBox.option('displayValue') as string[];
if (!hasLoadedItems) {
dropDownBox.reset('');
this.dataSource.searchValue('');
this.dataSource.load().catch(() => {});
return;
}
if (text && text !== displayValue[0]) {
const firstKey = this.dataGrid.instance.getKeyByRowIndex(0);
this.selectedRowKeys = [firstKey];
this.focusedRowKey = firstKey;
}
}Vue
function onClosed(e: DxDropDownBoxTypes.ClosedEvent): void {
const dropDownBox = e.component;
const hasLoadedItems = dataGridRef.value?.instance?.getVisibleRows().length;
const text = dropDownBox.option('text');
const displayValue = dropDownBox.option('displayValue') as string[];
if (!hasLoadedItems) {
dropDownBox.reset('');
props.dataSource.searchValue('');
props.dataSource.load().catch(() => {});
return;
}
if (text && text !== displayValue[0]) {
const firstKey = dataGridRef.value?.instance?.getKeyByRowIndex(0);
dropDownValue.value = firstKey ?? null;
selectedRowKeys.value = firstKey ? [firstKey] : [];
focusedRowKey.value = firstKey ?? null;
}
}React
const onClosed = useCallback((e: DropDownBoxTypes.ClosedEvent) => {
const dropDownBox = e.component;
const hasLoadedItems = dataGridRef.current?.instance().getVisibleRows().length;
const text = dropDownBox.option('text');
const displayValue = dropDownBox.option('displayValue') as string[];
if (!hasLoadedItems) {
dropDownBox.reset('');
dataSource.searchValue('');
dataSource.load().catch(() => {});
return;
}
if (text && text !== displayValue[0]) {
const firstKey = dataGridRef.current?.instance().getKeyByRowIndex(0);
dispatch({ type: 'SELECT_VALUE', value: firstKey });
}
}, [dataSource]);ASP.NET Core Controls
function onClosed(e) {
const dropDownBox = e.component;
const hasLoadedItems = dataGridInstance.getVisibleRows().length;
const text = dropDownBox.option('text');
const displayValue = (dropDownBox.option('displayValue') || [])[0];
if (!hasLoadedItems) {
dropDownBox.reset(null);
dataSource.searchValue('');
dataSource.load();
return;
}
if (text && text !== displayValue) {
const firstKey = dataGridInstance.getKeyByRowIndex(0);
dataGridInstance.selectRows(firstKey);
dataGridInstance.option('focusedRowKey', firstKey);
}
}See Also
Search by Lookup Column (TreeList)
This topic covers search by a lookup column display value in a DropDownBox with an embedded TreeList. Because the search target is a display value from a related dataset, the approach resolves the typed text to matching IDs via the lookup data source, then applies a DataSource.filter on the main data source.
The full working code is available in the GitHub repository:
1) Configure DropDownBox to Accept User Input
- Activate
acceptCustomValue. - Set
valueChangeEventto an empty string to prevent the component from trying to interpret typing as a value change.
jQuery
$('#treeBox').dxDropDownBox({
acceptCustomValue: true,
valueChangeEvent: '',
openOnFieldClick: false,
// ...
});Angular
<dx-drop-down-box
[acceptCustomValue]="true"
valueChangeEvent=""
[openOnFieldClick]="false"
...
>
</dx-drop-down-box>Vue
<DxDropDownBox
:accept-custom-value="true"
value-change-event=""
:open-on-field-click="false"
...
/>React
<DropDownBox
acceptCustomValue={true}
valueChangeEvent=""
openOnFieldClick={false}
...
/>ASP.NET Core Controls
@(Html.DevExtreme().DropDownBox()
.AcceptCustomValue(true)
.ValueChangeEvent("")
.OpenOnFieldClick(false)
// ...
)2) Create the Main DataSource for TreeList
Declare a data source for the TreeList.
jQuery
const dataSource = new DevExpress.data.DataSource({
store: makeAsyncDataSource('Task_ID', `${url}/Tasks`),
});Angular
this.dataSource = new DataSource({
store: AspNetData.createStore({
key: 'Task_ID',
loadUrl: `${url}/Tasks`,
}),
});Vue
const dataSource = new DataSource({
store: AspNetData.createStore({
key: 'Task_ID',
loadUrl: `${url}/Tasks`,
}),
});React
const dataSource = new DataSource({
store: AspNetData.createStore({
key: 'Task_ID',
loadUrl: `${url}/Tasks`,
}),
});ASP.NET Core Controls
@(Html.DevExtreme().TreeList()
.DataSource(d => d.Mvc()
.Controller("SampleData")
.LoadAction("GetTasks")
.Key("Task_ID")
)
// ...
)3) Configure displayExpr
Use displayExpr to define how a selected item appears in the input field.
Because the displayed text depends on lookup data (employee name from a related dataset), pre-load that data and resolve it in displayExpr:
jQuery
let lookupItems = [];
const lookupDataSource = makeAsyncDataSource('ID', `${url}/TaskEmployees`);
lookupDataSource.load().then((items) => {
lookupItems = items;
$('#treeBox').dxDropDownBox('instance')?.repaint();
});
function displayExpr(item) {
if (!lookupItems || !lookupItems.length) return 'Loading...';
if (!item) return '';
const employeeData = lookupItems.find(
(employee) => employee.ID === item.Task_Assigned_Employee_ID,
);
if (!employeeData) return item.Task_Subject || '';
return `${employeeData.Name}: ${item.Task_Subject} (${item.Task_Status})`;
}Angular
getDisplayExpr(item: Task, lookupItems: Employee[]): string {
if (!lookupItems?.length) return 'Loading...';
if (!item) return '';
const employee = lookupItems.find(e => e.ID === item.Task_Assigned_Employee_ID);
if (!employee) return item.Task_Subject || '';
return `${employee.Name}: ${item.Task_Subject} (${item.Task_Status})`;
}
(this.service.lookupStore.load() as Promise<Employee[]>).then((items) => {
this.displayExpr = (item: Task): string =>
this.service.getDisplayExpr(item, items);
});Vue
const lookupItems = ref<Employee[]>([]);
onMounted(() => {
lookupStore.load().then((items: Employee[]) => {
lookupItems.value = items;
dropDownBoxRef.value?.instance?.repaint();
});
});
function displayExpr(item: Task): string {
if (!lookupItems.value.length) return 'Loading...';
if (!item) return '';
const employee = lookupItems.value.find(
(e) => e.ID === item.Task_Assigned_Employee_ID
);
if (!employee) return item.Task_Subject || '';
return `${employee.Name}: ${item.Task_Subject} (${item.Task_Status})`;
}React
const [lookupItems, setLookupItems] = useState<Employee[]>([]);
useEffect(() => {
lookupStore.load().then((items: Employee[]) => {
setLookupItems(items);
dropDownBoxRef.current?.instance().repaint();
});
}, []);
const displayExpr = useCallback((item: Task | null): string => {
if (!item) return '';
if (!lookupItems.length) return 'Loading...';
const employee = lookupItems.find(
(e) => e.ID === item.Task_Assigned_Employee_ID
);
if (!employee) return item.Task_Subject || '';
return `${employee.Name}: ${item.Task_Subject} (${item.Task_Status})`;
}, [lookupItems]);ASP.NET Core Controls
function displayExpr(item) {
if (!lookupItems || !lookupItems.length) return 'Loading...';
if (!item) return '';
const employeeData = lookupItems.find(
(employee) => employee.ID === item.Task_Assigned_Employee_ID,
);
if (!employeeData) return item.Task_Subject || '';
return `${employeeData.Name}: ${item.Task_Subject} (${item.Task_Status})`;
}4) Configure the Embedded TreeList in contentTemplate
Configure the DropDownBox component. Use contentTemplate to render the TreeList. In the TreeList component, activate focusedRowEnabled and set single selection.mode.
jQuery
contentTemplate(templateData, container) {
const dropDownInstance = templateData.component;
const value = dropDownInstance.option('value');
const treeListContainer = $('<div>').dxTreeList({
dataSource,
hasItemsExpr: 'Has_Items',
remoteOperations: { filtering: true, sorting: true, grouping: true },
parentIdExpr: 'Task_Parent_ID',
columnAutoWidth: true,
wordWrapEnabled: true,
showBorders: true,
height: 400,
width: '100%',
focusedRowEnabled: true,
selection: { mode: 'single' },
scrolling: { mode: 'virtual' },
selectedRowKeys: [value],
focusedRowKey: value,
onContentReady: () => {
if (!dropDownInstance.option('listFirstLoadCompleted')) {
dropDownInstance.option('listFirstLoadCompleted', true);
}
},
onSelectionChanged(args) {
const { resetSelection } = args.component.option();
if (!resetSelection) {
const keys = args.selectedRowKeys;
dropDownInstance.option('value', keys.length ? keys[0] : null);
dropDownInstance.focus();
}
args.component.option('resetSelection', false);
},
columns: [
{ dataField: 'Task_ID' },
{
dataField: 'Task_Assigned_Employee_ID',
caption: 'Employee',
minWidth: 120,
lookup: { dataSource: lookupDataSource, valueExpr: 'ID', displayExpr: 'Name' },
},
{ dataField: 'Task_Subject', width: 300 },
{ dataField: 'Task_Start_Date', caption: 'Start Date', dataType: 'date' },
{ dataField: 'Task_Status', caption: 'Status' },
{ dataField: 'Task_Due_Date', caption: 'Due Date', dataType: 'date' },
],
});
container.append(treeListContainer);
treeList = treeListContainer.dxTreeList('instance');
return container;
},Angular
<div *dxTemplate="let data of 'content'">
<dx-tree-list
#treeListRef
[dataSource]="dataSource"
hasItemsExpr="Has_Items"
parentIdExpr="Task_Parent_ID"
[columnAutoWidth]="true"
[wordWrapEnabled]="true"
[showBorders]="true"
[height]="400"
[width]="'100%'"
[focusedRowEnabled]="true"
[(focusedRowKey)]="focusedRowKey"
[(selectedRowKeys)]="selectedRowKeys"
(onContentReady)="treeListOnContentReady($event)"
(onKeyDown)="treeListOnKeyDown($event)"
(onSelectionChanged)="onSelectionChanged($event)"
>
<dxo-tree-list-remote-operations
[filtering]="true" [sorting]="true" [grouping]="true">
</dxo-tree-list-remote-operations>
<dxo-tree-list-selection mode="single"></dxo-tree-list-selection>
<dxo-tree-list-scrolling mode="virtual"></dxo-tree-list-scrolling>
<dxi-tree-list-column dataField="Task_ID"></dxi-tree-list-column>
<dxi-tree-list-column
dataField="Task_Assigned_Employee_ID"
caption="Employee"
[minWidth]="120"
>
<dxo-tree-list-lookup
[dataSource]="service.lookupStore"
valueExpr="ID"
displayExpr="Name"
></dxo-tree-list-lookup>
</dxi-tree-list-column>
<dxi-tree-list-column dataField="Task_Subject" [width]="300"></dxi-tree-list-column>
<dxi-tree-list-column dataField="Task_Start_Date" caption="Start Date" dataType="date"></dxi-tree-list-column>
<dxi-tree-list-column dataField="Task_Status" caption="Status"></dxi-tree-list-column>
<dxi-tree-list-column dataField="Task_Due_Date" caption="Due Date" dataType="date"></dxi-tree-list-column>
</dx-tree-list>
</div>Vue
<template #content>
<DxTreeList
ref="treeListRef"
:data-source="dataSource"
has-items-expr="Has_Items"
parent-id-expr="Task_Parent_ID"
:column-auto-width="true"
:word-wrap-enabled="true"
:show-borders="true"
:height="400"
width="100%"
:focused-row-enabled="true"
v-model:focused-row-key="focusedRowKey"
v-model:selected-row-keys="selectedRowKeys"
@content-ready="treeListOnContentReady"
@key-down="treeListOnKeyDown"
@selection-changed="onSelectionChanged"
>
<DxRemoteOperations :filtering="true" :sorting="true" :grouping="true" />
<DxSelection mode="single" />
<DxScrolling mode="virtual" />
<DxColumn data-field="Task_ID" />
<DxColumn data-field="Task_Assigned_Employee_ID" caption="Employee" :min-width="120">
<DxLookup :data-source="lookupStore" value-expr="ID" display-expr="Name" />
</DxColumn>
<DxColumn data-field="Task_Subject" :width="300" />
<DxColumn data-field="Task_Start_Date" caption="Start Date" data-type="date" />
<DxColumn data-field="Task_Status" caption="Status" />
<DxColumn data-field="Task_Due_Date" caption="Due Date" data-type="date" />
</DxTreeList>
</template>React
<TreeList
ref={treeListRef}
dataSource={dataSource}
hasItemsExpr="Has_Items"
parentIdExpr="Task_Parent_ID"
columnAutoWidth
wordWrapEnabled
showBorders
height={400}
width="100%"
focusedRowEnabled
focusedRowKey={selection.focusedRowKey}
selectedRowKeys={selection.selectedRowKeys}
onContentReady={treeListOnContentReady}
onKeyDown={treeListOnKeyDown}
onFocusedRowChanged={onFocusedRowChanged}
onSelectionChanged={onSelectionChanged}
>
<RemoteOperations filtering sorting grouping />
<Selection mode="single" />
<Scrolling mode="virtual" />
<Column dataField="Task_ID" />
<Column dataField="Task_Assigned_Employee_ID" caption="Employee" minWidth={120}>
<Lookup dataSource={lookupStore} valueExpr="ID" displayExpr="Name" />
</Column>
<Column dataField="Task_Subject" width={300} />
<Column dataField="Task_Start_Date" caption="Start Date" dataType="date" />
<Column dataField="Task_Status" caption="Status" />
<Column dataField="Task_Due_Date" caption="Due Date" dataType="date" />
</TreeList>ASP.NET Core Controls
@(Html.DevExtreme().TreeList()
.DataSource(d => d.Mvc().Controller("SampleData").LoadAction("GetTasks").Key("Task_ID"))
.ParentIdExpr("Task_Parent_ID")
.HasItemsExpr("Has_Items")
.RemoteOperations(r => r.Filtering(true).Sorting(true).Grouping(true))
.ColumnAutoWidth(true)
.WordWrapEnabled(true)
.ShowBorders(true)
.Height(400)
.Width("100%")
.FocusedRowEnabled(true)
.FocusedRowKey(new JS("component.option('value')"))
.Selection(s => s.Mode(SelectionMode.Single))
.SelectedRowKeys(new JS("[component.option('value')]"))
.Scrolling(s => s.Mode(TreeListScrollingMode.Virtual))
.Columns(c => {
c.Add().DataField("Task_ID");
c.Add().DataField("Task_Assigned_Employee_ID").Caption("Employee").MinWidth(120)
.Lookup(l => l.DataSource(ds => ds.Mvc().Controller("SampleData").LoadAction("GetEmployees"))
.ValueExpr("ID").DisplayExpr("Name"));
c.Add().DataField("Task_Subject").Width(300);
c.Add().DataField("Task_Start_Date").Caption("Start Date").DataType(GridColumnDataType.Date);
c.Add().DataField("Task_Status").Caption("Status");
c.Add().DataField("Task_Due_Date").Caption("Due Date").DataType(GridColumnDataType.Date);
})
.OnContentReady("treeListOnContentReady")
.OnFocusedRowChanged("treeListOnFocusedRowChanged")
.OnKeyDown("treeListOnKeyDown")
.OnSelectionChanged("treeListOnSelectionChanged")
.OnInitialized("treeListOnInitialized")
)5) Implement Search in onInput
Use the onInput event to open the dropdown and trigger the search. Because the search targets a lookup column display value, the typed text must first be resolved to matching IDs via the lookup data source, then applied as a DataSource.filter to the main data source:
function applySearchFilter(text, lookupField, dataField, searchExprVal, lookupDataSource, dataSource) {
// Step 1: find employees whose Name contains the typed text
lookupDataSource.load({ filter: [lookupField, 'contains', text] }).done((items) => {
const filterParts = [];
// Step 2: optionally also search in a non-lookup field (for example, Task_Subject)
if (Array.isArray(searchExprVal)) {
filterParts.push([searchExprVal[1], 'contains', text]);
}
// Step 3: add an OR condition for each matched employee ID
items.forEach((item, index) => {
if (filterParts.length > 0 || index > 0) filterParts.push('or');
filterParts.push([dataField, '=', item.ID]);
});
// Step 4: apply the filter (use a "no-results" sentinel if nothing matched)
const filterExpr = filterParts.length > 0 ? filterParts : [dataField, '=', -1];
dataSource.filter(filterExpr);
dataSource.load();
});
}jQuery
onInput(e) {
clearTimeout(searchTimerId);
const instance = e.component;
if (!instance.option('opened')) instance.open();
searchTimerId = performSearch({
e, lookupDataSource, dataSource, searchTimeout, searchExprVal,
});
},Angular
onInput(e: DxDropDownBoxTypes.InputEvent): void {
if (this.searchTimerId) clearTimeout(this.searchTimerId);
const instance = e.component;
if (!this.dropDownBoxOpened) this.dropDownBoxOpened = true;
if (this.service.isSearchIncomplete(instance)) {
const text = instance.option('text');
if (text) {
this.searchTimerId = setTimeout(() => {
this.service.applySearchFilter(text, this.searchExprValue, this.dataSource);
}, this.searchTimeout);
} else {
this.dataSource.filter(null);
}
this.focusInput();
}
}Vue
function onInput(e: DxDropDownBoxTypes.InputEvent): void {
if (searchTimerId) clearTimeout(searchTimerId);
const instance = e.component;
if (!dropDownBoxOpened.value) dropDownBoxOpened.value = true;
if (isSearchIncomplete(instance)) {
const text = instance.option('text');
if (text) {
searchTimerId = setTimeout(() => {
applySearchFilter(text, props.searchExprValue, props.dataSource);
}, searchTimeoutValue.value);
} else {
props.dataSource.filter(null);
}
focusInput();
}
}React
const onInput = useCallback((e: DropDownBoxTypes.InputEvent): void => {
if (searchTimerIdRef.current) clearTimeout(searchTimerIdRef.current);
const instance = e.component;
if (!dropDownBoxOpened) setDropDownBoxOpened(true);
if (isSearchIncomplete(instance)) {
const text = instance.option('text');
if (text) {
searchTimerIdRef.current = setTimeout(() => {
applySearchFilter(text, searchExprValue, dataSource);
}, searchTimeout);
} else {
dataSource.filter(null);
}
focusInput();
}
}, [searchTimerIdRef, dropDownBoxOpened, searchExprValue, dataSource, searchTimeout]);ASP.NET Core Controls
function dropDownBoxOnInput(e) {
clearTimeout(searchTimerId);
const instance = e.component;
if (!instance.option('opened')) instance.open();
const dataSource = treeList.getDataSource();
searchTimerId = performSearch(e, dataSource);
}6) Detect Whether the User Is Searching (isSearchIncomplete)
The isSearchIncomplete function returns true if the user has changed the input text and a new search must be applied. It compares text (what the user typed) against the component's internal displayValue (the formatted display text of the currently selected value):
function isSearchIncomplete(dropDownBox) {
let displayValue = dropDownBox.option('displayValue');
let text = dropDownBox.option('text') || '';
displayValue = displayValue && displayValue.length && displayValue[0];
return text !== displayValue;
}7) Focus Management in onOpened
When the popup opens and the DropDownBox raises its onOpened event, move focus into the TreeList.
The example implementation waits until the TreeList is ready (first open) or until the popup animation completes (subsequent opens), then calls list.focus(). It also clears the TreeList selection when the input text no longer matches the selected value.
jQuery
function onOpened(e) {
const dropDownBox = e.component;
const listFirstLoadCompleted = dropDownBox.option('listFirstLoadCompleted');
const handleOptionChanged = (args) => {
const list = args.component;
const triggerCondition = listFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedColumnIndex';
if (triggerCondition) {
list.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
list.focus();
if (listFirstLoadCompleted) list.option('opened', false);
});
}
};
treeList.on('optionChanged', handleOptionChanged);
if (listFirstLoadCompleted) {
treeList.option('opened', true);
}
const { text, value } = dropDownBox.option();
const isTextEqualToDisplayValue = text === dropDownBox.option('displayValue')[0];
const shouldClearSelection = (value && !text) || !isTextEqualToDisplayValue;
if (shouldClearSelection && treeList.option('selectedRowKeys').length) {
treeList.option('resetSelection', true);
treeList.selectRows([]);
treeList.pageIndex(0).then(() => {
treeList.option('focusedRowIndex', 0);
treeList.option('focusedRowKey', firstRowKey);
});
}
}Angular
onOpened(e: DxDropDownBoxTypes.OpenedEvent): void {
const treeListInstance = this.treeListRef?.instance;
const dropDownBox = e.component;
const handleOptionChanged = (args: DxTreeListTypes.OptionChangedEvent): void => {
const list = args.component;
const triggerCondition = this.listFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedColumnIndex';
if (triggerCondition) {
list.off('optionChanged', handleOptionChanged);
setTimeout(() => {
list.focus();
list.option('opened', false);
}, 100);
}
};
treeListInstance.on('optionChanged', handleOptionChanged);
if (this.listFirstLoadCompleted && !this.service.isSearchIncomplete(dropDownBox)) {
treeListInstance.option('opened', true);
}
const { text, value } = dropDownBox.option();
const displayValue = dropDownBox.option('displayValue') as string[] | undefined;
const shouldClearSelection = (value && !text) || text !== displayValue?.[0];
if (shouldClearSelection && this.selectedRowKeys.length) {
this.resetSelectionFlag = true;
this.selectedRowKeys = [];
treeListInstance.pageIndex(0).then(() => {
this.focusedRowIndex = 0;
this.focusedRowKey = firstRowKey;
this.focusInput();
}).catch(() => {});
}
}Vue
function onOpened(e: DxDropDownBoxTypes.OpenedEvent): void {
const treeListInstance = treeListRef.value?.instance;
const dropDownBox = e.component;
function handleOptionChanged(args: DxTreeListTypes.OptionChangedEvent): void {
const list = args.component;
const triggerCondition = listFirstLoadCompleted
? args.name === 'openTrigger'
: args.name === 'focusedRowKey' || args.name === 'focusedColumnIndex';
if (triggerCondition) {
list.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
list.focus();
list.option('openTrigger', 'closed');
});
}
}
treeListInstance?.on('optionChanged', handleOptionChanged);
if (listFirstLoadCompleted) {
treeListInstance?.option('openTrigger', 'opened');
}
const { text, value: dropDownValue } = dropDownBox.option();
const displayValue = dropDownBox.option('displayValue') as string[] | undefined;
const shouldClearSelection = (dropDownValue && !text) || text !== displayValue?.[0];
if (shouldClearSelection) {
selectedRowKeys.value = [];
treeListInstance?.pageIndex(0).then(() => {
treeListInstance?.option('focusedRowIndex', 0);
focusedRowKey.value = FIRST_ROW_KEY;
focusInput();
}).catch(() => {});
}
}React
const onOpened = useCallback((e: DropDownBoxTypes.OpenedEvent): void => {
const treeListInstance = treeListRef.current?.instance();
const dropDownBox = e.component;
function handleOptionChanged(args: TreeListTypes.OptionChangedEvent): void {
const list = args.component;
const triggerCondition = listFirstLoadCompletedRef.current
? args.name === 'openTrigger'
: args.name === 'focusedRowKey' || args.name === 'focusedColumnIndex';
if (triggerCondition) {
list.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
list.focus();
list.option('openTrigger', 'closed');
});
}
}
treeListInstance?.on('optionChanged', handleOptionChanged);
if (listFirstLoadCompletedRef.current) {
treeListInstance?.option('openTrigger', 'opened');
}
const { text, value: dropDownValue } = dropDownBox.option();
const dropDownDisplayValue = dropDownBox.option('displayValue') as string[] | undefined;
const shouldClearSelection = (dropDownValue && !text) || text !== dropDownDisplayValue?.[0];
if (shouldClearSelection) {
dispatch({ type: 'RESET' });
treeListInstance?.pageIndex(0).then(() => {
treeListInstance?.option('focusedRowIndex', 0);
dispatch({ type: 'SET_FOCUSED_KEY', key: FIRST_ROW_KEY });
focusInput();
}).catch(() => {});
}
}, [focusInput, listFirstLoadCompletedRef, treeListRef]);ASP.NET Core Controls
function dropDownBoxOnOpened(e) {
handleDropDownOpened(e);
}
function handleDropDownOpened(e) {
if (!treeList) return;
const dropDownBox = e.component;
const listFirstLoadCompleted = dropDownBox.option('listFirstLoadCompleted');
const handleOptionChanged = (args) => {
const list = args.component;
const triggerCondition = listFirstLoadCompleted
? args.name === 'opened'
: args.name === 'focusedRowKey' || args.name === 'focusedColumnIndex';
if (triggerCondition) {
list.off('optionChanged', handleOptionChanged);
requestAnimationFrame(() => {
list.focus();
if (listFirstLoadCompleted) list.option('opened', false);
});
}
};
treeList.on('optionChanged', handleOptionChanged);
if (listFirstLoadCompleted) {
treeList.option('opened', true);
}
const { text, value } = dropDownBox.option();
const isTextEqualToDisplayValue = text === dropDownBox.option('displayValue')[0];
const shouldClearSelection = (value && !text) || !isTextEqualToDisplayValue;
if (shouldClearSelection && treeList.option('selectedRowKeys').length) {
treeList.option('resetSelection', true);
treeList.selectRows([]);
treeList.pageIndex(0).then(() => {
treeList.option('focusedRowIndex', 0);
treeList.option('focusedRowKey', firstRowKey);
});
}
}8) Reset Component State in onClosed
When the popup closes, the onClosed event restores consistent state if the user typed something but did not confirm a selection. The search state is cleared by calling dataSource.filter(null).
jQuery
function onClosed(e) {
const dropDownBox = e.component;
const { text } = dropDownBox.option();
const displayValue = dropDownBox.option('displayValue')[0];
const resetValue = text && text !== displayValue;
if (!hasLoadedItems) {
dropDownBox.reset(null);
dataSource.filter(null);
dataSource.load();
}
if (resetValue && !treeList.option('selectedRowKeys').length) {
treeList.option('autoSelection', true);
const firstKey = treeList.getKeyByRowIndex(0);
treeList.selectRows([firstKey]);
treeList.option('focusedRowKey', firstKey);
}
}Angular
onClosed(e: DxDropDownBoxTypes.ClosedEvent): void {
const treeListInstance = this.treeListRef?.instance;
const dropDownBox = e.component;
const text = dropDownBox.option('text');
const displayValue = (dropDownBox.option('displayValue') as string[] | undefined)?.[0];
const resetValue = text && text !== displayValue;
if (!this.hasLoadedItems) {
this.value = null;
this.selectedRowKeys = [];
this.dataSource.filter(null);
this.dataSource.load().then(() => {}).catch(() => {});
}
if (resetValue && !this.selectedRowKeys.length) {
this.autoSelectionFlag = true;
const firstKey = treeListInstance.getKeyByRowIndex(0) as number;
this.selectedRowKeys = [firstKey];
this.focusedRowKey = firstKey;
}
}Vue
function onClosed(e: DxDropDownBoxTypes.ClosedEvent): void {
const treeListInstance = treeListRef.value?.instance;
const dropDownBox = e.component;
const text = dropDownBox.option('text');
const displayValue = (dropDownBox.option('displayValue') as string[] | undefined)?.[0];
const resetValue = text && text !== displayValue;
if (!hasLoadedItems) {
value.value = null;
selectedRowKeys.value = [];
props.dataSource.filter(null);
props.dataSource.load().catch(() => {});
}
if (resetValue && !selectedRowKeys.value.length && treeListInstance) {
const firstKey = treeListInstance.getKeyByRowIndex(0) as number;
value.value = firstKey;
selectedRowKeys.value = [firstKey];
focusedRowKey.value = firstKey;
}
}React
const onClosed = useCallback((e: DropDownBoxTypes.ClosedEvent): void => {
const treeListInstance = treeListRef.current?.instance();
const dropDownBox = e.component;
const text = dropDownBox.option('text');
const dropDownDisplayValue = (dropDownBox.option('displayValue') as string[] | undefined)?.[0];
const resetValue = text && text !== dropDownDisplayValue;
if (!hasLoadedItemsRef.current) {
dispatch({ type: 'SELECT_VALUE', value: null });
dataSource.filter(null);
dataSource.load().catch(() => {});
}
if (resetValue && !selection.selectedRowKeys.length && treeListInstance) {
const firstKey = treeListInstance.getKeyByRowIndex(0) as number;
dispatch({ type: 'SELECT_VALUE', value: firstKey });
}
}, [selection.selectedRowKeys.length, dataSource]);ASP.NET Core Controls
function dropDownBoxOnClosed(e) {
const dataSource = treeList.getDataSource();
resetSearchState(e, dataSource);
}
function resetSearchState(e, dataSource) {
if (!treeList) return;
const dropDownBox = e.component;
const { text } = dropDownBox.option();
const displayValue = dropDownBox.option('displayValue')[0];
const resetValue = text && text !== displayValue;
if (!hasLoadedItems) {
dropDownBox.reset(null);
dataSource.filter(null);
dataSource.load();
}
if (resetValue && !treeList.option('selectedRowKeys').length) {
treeList.option('autoSelection', true);
const firstKey = treeList.getKeyByRowIndex(0);
treeList.selectRows([firstKey]);
treeList.option('focusedRowKey', firstKey);
}
}Example
See this example for more details: DropDownBox with embedded TreeList.