devextreme
Version:
JavaScript/TypeScript Component Suite for Responsive Web Development
359 lines (354 loc) • 16.4 kB
JavaScript
/**
* DevExtreme (cjs/__internal/grids/grid_core/search/m_search.js)
* Version: 26.1.3
* Build date: Wed Jun 10 2026
*
* Copyright (c) 2012 - 2026 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.searchModule = exports.SearchPanelViewController = void 0;
var _message = _interopRequireDefault(require("../../../../common/core/localization/message"));
var _query = _interopRequireDefault(require("../../../../common/data/query"));
var _dom_adapter = _interopRequireDefault(require("../../../../core/dom_adapter"));
var _renderer = _interopRequireDefault(require("../../../../core/renderer"));
var _data2 = require("../../../../core/utils/data");
var _m_modules = _interopRequireDefault(require("../m_modules"));
var _m_utils = _interopRequireDefault(require("../m_utils"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : {
default: e
}
}
const SEARCH_PANEL_CLASS = "search-panel";
const SEARCH_TEXT_CLASS = "search-text";
const HEADER_PANEL_CLASS = "header-panel";
const FILTERING_TIMEOUT = 700;
const SEARCH_PANEL_ITEM_NAME = "searchPanel";
function allowSearch(column) {
return !!(column.allowSearch ?? column.allowFiltering)
}
function parseValue(column, text) {
const {
lookup: lookup
} = column;
if (!column.parseValue) {
return text
}
if (lookup) {
return column.parseValue.call(lookup, text)
}
return column.parseValue(text)
}
const dataController = base => class extends base {
optionChanged(args) {
switch (args.fullName) {
case "searchPanel.text":
case "searchPanel":
this._applyFilter();
args.handled = true;
break;
default:
super.optionChanged(args)
}
}
publicMethods() {
return super.publicMethods().concat(["searchByText"])
}
_calculateAdditionalFilter() {
var _this$_dataController, _this$_dataController2, _dataSource$loadOptio;
const dataSource = null === (_this$_dataController = this._dataController) || void 0 === _this$_dataController || null === (_this$_dataController2 = _this$_dataController.getDataSource) || void 0 === _this$_dataController2 ? void 0 : _this$_dataController2.call(_this$_dataController);
const langParams = null === dataSource || void 0 === dataSource || null === (_dataSource$loadOptio = dataSource.loadOptions) || void 0 === _dataSource$loadOptio || null === (_dataSource$loadOptio = _dataSource$loadOptio.call(dataSource)) || void 0 === _dataSource$loadOptio ? void 0 : _dataSource$loadOptio.langParams;
const filter = super._calculateAdditionalFilter();
const searchFilter = this.calculateSearchFilter(this.option("searchPanel.text"), langParams);
return _m_utils.default.combineFilters([filter, searchFilter])
}
searchByText(text) {
this.option("searchPanel.text", text)
}
calculateSearchFilter(text, langParams) {
let column;
const columns = this._columnsController.getColumns();
const searchVisibleColumnsOnly = this.option("searchPanel.searchVisibleColumnsOnly");
let lookup;
const filters = [];
if (!text) {
return null
}
function onQueryDone(items) {
const valueGetter = (0, _data2.compileGetter)(lookup.valueExpr);
for (let i = 0; i < items.length; i++) {
const value = valueGetter(items[i]);
filters.push(column.createFilterExpression(value, null, "search"))
}
}
for (let i = 0; i < columns.length; i++) {
column = columns[i];
if (searchVisibleColumnsOnly && !column.visible) {
continue
}
if (allowSearch(column) && column.calculateFilterExpression) {
var _lookup;
lookup = column.lookup;
const filterValue = parseValue(column, text);
if (null !== (_lookup = lookup) && void 0 !== _lookup && _lookup.items) {
(0, _query.default)(lookup.items, {
langParams: langParams
}).filter(column.createFilterExpression.call({
dataField: lookup.displayExpr,
dataType: lookup.dataType,
calculateFilterExpression: column.calculateFilterExpression
}, filterValue, null, "search")).enumerate().done(onQueryDone)
} else if (void 0 !== filterValue) {
filters.push(column.createFilterExpression(filterValue, null, "search"))
}
}
}
if (0 === filters.length) {
return ["!"]
}
return _m_utils.default.combineFilters(filters, "or")
}
};
class SearchPanelViewController extends _m_modules.default.ViewController {
init() {
this.headerPanel = this.getView("headerPanel");
this.dataController = this.getController("data");
const isSearchPanelVisible = this.option("searchPanel.visible");
if (isSearchPanelVisible) {
var _this$headerPanel;
const searchPanelToolbarItem = this.getSearchPanelToolbarItem();
null === (_this$headerPanel = this.headerPanel) || void 0 === _this$headerPanel || _this$headerPanel.registerToolbarItem("searchPanel", searchPanelToolbarItem)
}
}
optionChanged(args) {
if ("searchPanel" === args.name) {
if ("searchPanel.text" === args.fullName) {
const editor = this.getSearchTextEditor();
if (editor) {
editor.option("value", args.value)
}
} else {
this.syncSearchPanelItem()
}
args.handled = true
} else {
super.optionChanged(args)
}
}
syncSearchPanelItem() {
const isSearchPanelVisible = this.option("searchPanel.visible");
if (isSearchPanelVisible) {
var _this$headerPanel2;
const searchPanelToolbarItem = this.getSearchPanelToolbarItem();
null === (_this$headerPanel2 = this.headerPanel) || void 0 === _this$headerPanel2 || _this$headerPanel2.applyToolbarItem("searchPanel", searchPanelToolbarItem)
} else {
var _this$headerPanel3;
null === (_this$headerPanel3 = this.headerPanel) || void 0 === _this$headerPanel3 || _this$headerPanel3.removeToolbarItem("searchPanel")
}
}
getSearchPanelToolbarItem() {
const searchPanelOptions = this.option("searchPanel");
return {
template: (_data, _index, container) => {
if (this.headerPanel) {
const $search = (0, _renderer.default)("<div>").addClass(this.headerPanel.addWidgetPrefix("search-panel")).appendTo(container);
this.getController("editorFactory").createEditor($search, {
width: null === searchPanelOptions || void 0 === searchPanelOptions ? void 0 : searchPanelOptions.width,
placeholder: null === searchPanelOptions || void 0 === searchPanelOptions ? void 0 : searchPanelOptions.placeholder,
parentType: "searchPanel",
value: this.option("searchPanel.text"),
updateValueTimeout: 700,
setValue: value => {
var _this$dataController;
null === (_this$dataController = this.dataController) || void 0 === _this$dataController || _this$dataController.searchByText(value)
},
editorOptions: {
inputAttr: {
"aria-label": _message.default.format(`${this.component.NAME}-ariaSearchInGrid`)
}
}
});
this.headerPanel.resize()
}
},
name: "searchPanel",
location: "after",
locateInMenu: "never",
sortIndex: 50
}
}
getSearchTextEditor() {
var _this$headerPanel4;
const $element = null === (_this$headerPanel4 = this.headerPanel) || void 0 === _this$headerPanel4 ? void 0 : _this$headerPanel4.element();
if (!this.headerPanel || !$element) {
return null
}
const headerPanelClass = this.headerPanel.addWidgetPrefix("header-panel");
const $searchPanel = $element.find(`.${this.headerPanel.addWidgetPrefix("search-panel")}`).filter((_, el) => (0, _renderer.default)(el).closest(`.${headerPanelClass}`).is($element));
if ($searchPanel.length) {
return $searchPanel.dxTextBox("instance")
}
return null
}
}
exports.SearchPanelViewController = SearchPanelViewController;
const rowsView = Base => class extends Base {
init() {
super.init.apply(this, arguments);
this._searchParams = [];
this._dataController = this.getController("data")
}
dispose() {
clearTimeout(this._highlightTimer);
super.dispose()
}
_getFormattedSearchText(column, searchText) {
const value = parseValue(column, searchText);
const formatOptions = _m_utils.default.getFormatOptionsByColumn(column, "search");
return _m_utils.default.formatValue(value, formatOptions)
}
_getStringNormalizer() {
var _this$_dataController3, _this$_dataController4, _dataSource$loadOptio2;
const isCaseSensitive = this.option("searchPanel.highlightCaseSensitive");
const dataSource = null === (_this$_dataController3 = this._dataController) || void 0 === _this$_dataController3 || null === (_this$_dataController4 = _this$_dataController3.getDataSource) || void 0 === _this$_dataController4 ? void 0 : _this$_dataController4.call(_this$_dataController3);
const langParams = null === dataSource || void 0 === dataSource || null === (_dataSource$loadOptio2 = dataSource.loadOptions) || void 0 === _dataSource$loadOptio2 || null === (_dataSource$loadOptio2 = _dataSource$loadOptio2.call(dataSource)) || void 0 === _dataSource$loadOptio2 ? void 0 : _dataSource$loadOptio2.langParams;
return str => (0, _data2.toComparable)(str, isCaseSensitive, langParams)
}
_findHighlightingTextNodes(column, cellElement, searchText) {
var _$items;
const that = this;
let $parent = cellElement.parent();
let $items;
const stringNormalizer = this._getStringNormalizer();
const normalizedSearchText = stringNormalizer(searchText);
const resultTextNodes = [];
if (!$parent.length) {
$parent = (0, _renderer.default)("<div>").append(cellElement)
} else if (column) {
if (column.groupIndex >= 0 && !column.showWhenGrouped) {
$items = cellElement
} else {
const columnIndex = that._columnsController.getVisibleIndex(column.index);
$items = $parent.children("td").eq(columnIndex).find("*")
}
}
$items = null !== (_$items = $items) && void 0 !== _$items && _$items.length ? $items : $parent.find("*");
$items.each((_, element) => {
const $contents = (0, _renderer.default)(element).contents();
for (let i = 0; i < $contents.length; i++) {
const node = $contents.get(i);
if (3 === node.nodeType) {
const normalizedText = stringNormalizer(node.textContent ?? node.nodeValue ?? "");
if (normalizedText.includes(normalizedSearchText)) {
resultTextNodes.push(node)
}
}
}
});
return resultTextNodes
}
_highlightSearchTextCore($textNode, searchText) {
const that = this;
const $searchTextSpan = (0, _renderer.default)("<span>").addClass(that.addWidgetPrefix("search-text"));
const text = $textNode.text();
const firstContentElement = $textNode[0];
const stringNormalizer = this._getStringNormalizer();
const index = stringNormalizer(text).indexOf(stringNormalizer(searchText));
if (index >= 0) {
if (firstContentElement.textContent) {
firstContentElement.textContent = text.substr(0, index)
} else {
firstContentElement.nodeValue = text.substr(0, index)
}
$textNode.after($searchTextSpan.text(text.substr(index, searchText.length)));
$textNode = (0, _renderer.default)(_dom_adapter.default.createTextNode(text.substr(index + searchText.length))).insertAfter($searchTextSpan);
return that._highlightSearchTextCore($textNode, searchText)
}
}
_highlightSearchText(cellElement, isEquals, column) {
const that = this;
const stringNormalizer = this._getStringNormalizer();
let searchText = that.option("searchPanel.text");
if (isEquals && column) {
searchText = searchText && that._getFormattedSearchText(column, searchText)
}
if (searchText && that.option("searchPanel.highlightSearchText")) {
const textNodes = that._findHighlightingTextNodes(column, cellElement, searchText);
textNodes.forEach(textNode => {
if (isEquals) {
if (stringNormalizer((0, _renderer.default)(textNode).text()) === stringNormalizer(searchText ?? "")) {
(0, _renderer.default)(textNode).replaceWith((0, _renderer.default)("<span>").addClass(that.addWidgetPrefix("search-text")).text((0, _renderer.default)(textNode).text()))
}
} else {
that._highlightSearchTextCore((0, _renderer.default)(textNode), searchText)
}
})
}
}
_renderCore() {
const deferred = super._renderCore.apply(this, arguments);
if (this.option().rowTemplate || this.option("dataRowTemplate")) {
if (this.option("templatesRenderAsynchronously")) {
clearTimeout(this._highlightTimer);
this._highlightTimer = setTimeout(() => {
this._highlightSearchText(this.getTableElement())
})
} else {
this._highlightSearchText(this.getTableElement())
}
}
return deferred
}
_updateCell($cell, parameters) {
const {
column: column
} = parameters;
const dataType = column.lookup && column.lookup.dataType || column.dataType;
const isEquals = "string" !== dataType;
if (allowSearch(column) && !parameters.isOnForm) {
if (this.option("templatesRenderAsynchronously")) {
if (!this._searchParams.length) {
clearTimeout(this._highlightTimer);
this._highlightTimer = setTimeout(() => {
this._searchParams.forEach(params => {
this._highlightSearchText.apply(this, params)
});
this._searchParams = []
})
}
this._searchParams.push([$cell, isEquals, column])
} else {
this._highlightSearchText($cell, isEquals, column)
}
}
super._updateCell($cell, parameters)
}
};
const searchModule = exports.searchModule = {
defaultOptions: () => ({
searchPanel: {
visible: false,
width: 160,
placeholder: _message.default.format("dxDataGrid-searchPanelPlaceholder"),
highlightSearchText: true,
highlightCaseSensitive: false,
text: "",
searchVisibleColumnsOnly: false
}
}),
controllers: {
searchPanel: SearchPanelViewController
},
extenders: {
controllers: {
data: dataController
},
views: {
rowsView: rowsView
}
}
};