/*!
* SAPUI5
* Copyright (c) 2025 SAP SE or an SAP affiliate company. All rights reserved.
*/
import ErrorHandler from "./error/ErrorHandler";
import i18n from "./i18n";
import ResourceModel from "sap/ui/model/resource/ResourceModel";
import SearchFieldGroup from "./controls/searchfieldgroup/SearchFieldGroup";
import SearchModel from "sap/esh/search/ui/SearchModel";
import SearchLayoutResponsive from "sap/esh/search/ui/controls/SearchLayoutResponsive";
import SearchResultContainer from "./controls/resultview/SearchResultContainer";
import SearchResultTable from "./controls/resultview/SearchResultTable";
import SearchResultList from "./controls/resultview/SearchResultList";
import SearchResultGrid from "./controls/resultview/SearchResultGrid";
import SearchSpreadsheetExport from "./controls/SearchSpreadsheetExport";
import { SearchFocusHandler } from "sap/esh/search/ui/SearchHelper";
import SearchFilterBar from "sap/esh/search/ui/controls/SearchFilterBar";
import SearchNlqExplainBar from "sap/esh/search/ui/controls/SearchNlqExplainBar";
import * as SearchHelper from "sap/esh/search/ui/SearchHelper";
import Control, { $ControlSettings } from "sap/ui/core/Control";
import InvisibleText from "sap/ui/core/InvisibleText";
import VerticalLayout from "sap/ui/layout/VerticalLayout";
import Button from "sap/m/Button";
import { ButtonType } from "sap/m/library";
import SegmentedButton from "sap/m/SegmentedButton";
import Bar from "sap/m/Bar";
import IconTabBar from "sap/m/IconTabBar";
import OverflowToolbar from "sap/m/OverflowToolbar";
import CustomListItem from "sap/m/CustomListItem";
import FlexBox from "sap/m/FlexBox";
import ViewSettingsDialog from "sap/m/ViewSettingsDialog";
import MessagePopover from "sap/m/MessagePopover";
import VBox from "sap/m/VBox";
import { ComplexCondition } from "./sinaNexTS/sina/ComplexCondition";
import { ProgramError } from "./error/errors";
import { callConfigExit } from "./error/callConfigExit";
import { Filter } from "./sinaNexTS/sina/Filter";
import SearchConfigurationSettings from "./SearchConfigurationSettings";
import DragDropBase from "sap/ui/core/dnd/DragDropBase";
import ResizeHandler from "sap/ui/core/ResizeHandler";
import RoutingHashChanger from "sap/ui/core/routing/HashChanger";
import UIEvents, {
IUiEventParametersResultViewSelectionChanged,
IUiEventParametersResultViewTypeChanged,
IUiEventParametersShowResultDetail,
IUiEventParametersBasketChanged,
IUiEventParametersPublicModelChanged,
} from "./UIEvents";
import { NavigationTarget } from "./sinaNexTS/sina/NavigationTarget";
import { Value } from "./sinaNexTS/sina/types";
import { DataSource } from "./sinaNexTS/sina/DataSource";
import { ResultSet } from "./ResultSetApi";
import EventConsumer from "./eventlogging/EventConsumer";
import SearchCompositeControlDragDropConfig from "./SearchCompositeControlDragDropConfig";
import assert from "sap/base/assert";
import PublicSearchModel from "./PublicSearchModel";
import Model from "sap/ui/model/Model";
import ToolbarAssembler from "./compositecontrol/ToolbarAssembler";
import ResultViewsAssembler from "./compositecontrol/ResultViewsAssembler";
import SearchPageAssembler from "./compositecontrol/SearchPageAssembler";
import SearchConfiguration from "./SearchConfiguration";
import SearchSelectionBar from "./controls/SearchSelectionBar";
import FixFlex from "sap/ui/layout/FixFlex";
import ActionSheet from "sap/m/ActionSheet";
import Dialog from "sap/m/Dialog";
import Element from "sap/ui/core/Element";
import { PublicSina } from "./sinaNexTS/sina/PublicSina";
import { Condition } from "./sinaNexTS/sina/Condition";
import * as core from "./sinaNexTS/core/core";
import OverflowToolbarToggleButton from "sap/m/OverflowToolbarToggleButton";
import { SelectionMode } from "./SelectionMode";
import OverflowToolbarButton from "sap/m/OverflowToolbarButton";
import Title from "sap/m/Title";
import SearchBasketSapMList from "./controls/basket/SearchBasketSapMList";
import SearchBasketSapMTable from "./controls/basket/SearchBasketSapMTable";
import SearchBasketSapUiTable from "./controls/basket/SearchBasketSapUiTable";
import SearchBasketModel, { SearchBasketApi } from "./controls/basket/SearchBasketModel";
// =============== WARNING ===============================================================
// do not use async/await in this file
// --> async/await does work locally but not in barrier
// because buildNPM does not transpile to Control.extend
// questions:
// -why
// -why do we have different builds local/central
// =============== WARNING ===============================================================
/**
* Search control (input for search terms, suggestions, facets, result views "list", "table", "grid") powered by AI
*
*/
/**
* Constructs a new SearchCompositeControl to interact with SAP Enterprise Search Services.
*
* @param {string} [sId] ID for the new control, generated automatically if no ID is given
* @param {object} [mSettings] Initial settings for the new control
*
* @class
*
* This is the SAPUI5 composite control by the Enterprise Search Team which helps to make full use of the Enterprise Search Engine
* features built into ABAP and HANA (including natural language processing).
* It includes a search input box with a suggestion dropdown, a result view which can have different visualisation, including tiles, list and table, filtering facets and more.
* This control is ready to use with enterprise search backend services but also allows deep extension to match requirements of adopting applications.
*
* @extends sap.ui.layout.VerticalLayout
*
* @author SAP SE
* @version 1.150.1
*
* @see https://help.sap.com/viewer/691cb949c1034198800afde3e5be6570/2.0.08/en-US/ce86ef2fd97610149eaaaa0244ca4d36.html SAP HANA Search Developer Guide
* @see https://help.sap.com/docs/SAP_S4HANA_ON-PREMISE/90b263323d0a4976bcda415e6bd20ea4/d93d53ef27074bbf9a4849c4a4c3e360.html Enterprise Search in SAP S/4HANA
*
*
* @constructor
* @public
* @alias sap.esh.search.ui.SearchCompositeControl
* @since 1.93.0
* @name sap.esh.search.ui.SearchCompositeControl
*
*/
/**
* @namespace sap.esh.search.ui
*/
export default class SearchCompositeControl extends VerticalLayout {
static readonly metadata = {
library: "sap.esh.search.ui",
dnd: { draggable: true, droppable: false }, // result view (list/table/grid) support dragging (dropping not supported)
properties: {
/**
* An additional CSS class to add to this control
* @since 1.93.0
*/
cssClass: {
type: "string",
},
/**
* Defines the initial search term for the search input.
* @since 1.93.0
*/
searchTerm: {
type: "string",
group: "Misc",
defaultValue: "",
},
/**
* Defines if the search composite control will send a search request directly after loading, using given search term, data source and filter root condition settings.
* @since 1.93.0
*/
searchOnStart: {
type: "boolean",
group: "Behavior",
defaultValue: true,
},
/**
* Defines the filter root condition of a filter tree which shall be applied to the search request.
* This control only allows filter trees which have a the following structure:
* complex condition (root level)
* \
* complex condition (attribute level)
* \
* simple condition (attribute value level)
* Filter root conditions which do not follow this structure won't be accepted and an error will be thrown.
* Please see the below for a more in-depth example.
*
* @since 1.98.0
* @example A simple use case for filter conditions
* sap.ui.require(
* [
* // Adjust the path to the .js files accordingly!
* "sap/esh/search/ui/sinaNexTS/sina/LogicalOperator",
* "sap/esh/search/ui/sinaNexTS/sina/ComparisonOperator",
* "sap/esh/search/ui/sinaNexTS/sina/ComplexCondition",
* "sap/esh/search/ui/sinaNexTS/sina/SimpleCondition",
* ], function (
* LogicalOperatorModule,
* ComparisonOperatorModule,
* ComplexConditionModule,
* SimpleConditionModule
* ) {
* ("use strict");
*
* // Root condition must always be of type ComplexCondition!
* const rootCondition = new ComplexConditionModule.ComplexCondition({
* operator: LogicalOperatorModule.LogicalOperator.And,
* });
*
* // Conditions of root condition must always be of type ComplexCondition!
* // Create one of those for each attribute.
* // This condition will hold all values for attribute 'FOLKLORIST':
* const complexChildCondition = new ComplexConditionModule.ComplexCondition({
* operator: LogicalOperatorModule.LogicalOperator.Or,
* });
*
* // Conditions of complexChildCondition have to be simple conditions!
* // This filter specfies the value of the attributes.
* // The result is an attribute filter like 'FOLKLORIST' = 'Douglas Milford':
* const simpleGrandChildCondition = new SimpleConditionModule.SimpleCondition({
* operator: ComparisonOperatorModule.ComparisonOperator.Eq, // results should be equal to the filter value
* attribute: "FOLKLORIST", // example: name of the attribute
* value: "Douglas Milford", // example: value of the filter
* });
* complexChildCondition.addCondition(simpleGrandChildCondition); // Add the conditions to the condition tree
* rootCondition.addCondition(complexChildCondition);
*
* // The filter tree now looks like this:
* // rootCondition
* // / And \
* // complexChildCondition
* // / Or
* // simpleGrandChildCondition ('FOLKLORIST' Eq 'Douglas Milford')
* // Additional complex child conditions would be linked by an "And" operator, additional simple attribute
* // filter conditions will be linked by an "Or":
*
* // If you would like to apply an additional filter to the 'FOLKLORIST' attribute you can do that, too:
* const simpleGrandChildCondition2 = new SimpleConditionModule.SimpleCondition({
* operator: ComparisonOperatorModule.ComparisonOperator.Eq, // results should be equal to the filter value
* attribute: "FOLKLORIST", // example: name of the attribute
* value: "Cynthia MacDonald", // example: value of the filter
* });
*
* complexChildCondition.addCondition(simpleGrandChildCondition2);
*
* // The filter tree now looks like this:
* // rootCondition
* // / And
* // complexChildCondition
* // / Or \
* // simpleGrandChildCondition simpleGrandChildCondition2
*
* // create a new search ui:
* const searchUI = new SearchCompositeControl({
* filterRootCondition: rootCondition,
* });
*
* // or if it already exists:
* // const searchUI = Element.getElementById("eshCompGenId_0");
* // searchUI.setFilterRootCondition(rootCondition);
* });
*/
filterRootCondition: {
type: "object",
group: "Misc",
},
/**
* Configuration for the Enterprise Search Client API.
* @since 1.93.0
*/
sinaConfiguration: {
type: "object",
group: "Misc",
},
/**
* The id of the data source in which it will search right after initialization.
* @since 1.98.0
*/
dataSource: {
type: "string",
group: "Misc",
},
/**
* Data source id which is set when the UI is loaded and cannot be changed at run time.
* The following UI parts will be hidden:
* - data source select (dropdown)
* - data source tab bar
* - data source facet (facet panel)
* - data source name/link on result list (header section of item)
* @since 1.121.0
*/
exclusiveDataSource: {
type: "string",
group: "Misc",
},
/**
* Defines selectable search result view types.
* The value can be set/get in attach event "searchFinished".
* Case 1: Search in Apps: result is displayed in a mandatory view type ["appSearchResult"], and it is not switchable.
* Case 2: Search in All or other Category: result is switchable between different view types.
* Possible values for the array items are "searchResultList" and "searchResultGrid".
* Case 3, Search in Business Object: result is switchable between different view types.
* Possible values for the array items are "searchResultList", "searchResultTable" and "searchResultGrid".
* Note: The value of resultViewTypes and resultViewType must be compatible to each other.
*
* After the result view type has been changed, the event 'resultViewTypeChanged' is fired.
*
* @since 1.98.0
*/
resultViewTypes: {
type: "string[]",
group: "Misc",
defaultValue: ["searchResultList", "searchResultTable"],
// Case 2.1, Search in All or other Category (configuration.isUshell !== true): result is switchable between different view types.
// Possible values for the array items are "searchResultList" and "searchResultGrid".
// Case 2.2, Search in All or other Category (configuration.isUshell === true): result is displayed in a mandatory view type ["searchResultList"].
},
/**
* Defines active search result view type.
* The value can be set/get in attach event "searchFinished", and it must be contained in resultViewTypes.
* Case 1, Search in Apps: result is displayed in a mandatory view type "appSearchResult".
* Case 2.1, Search in All or other Category (configuration.isUshell !== true): result is switchable between different view types.
* Possible value is "searchResultList", or "searchResultGrid".
* Case 2.2, Search in All or other Category (configuration.isUshell === true): result is displayed in a mandatory view type "searchResultList".
* Case 3, Search in Business Object: result is switchable between different view types.
* Possible value is "searchResultList", "searchResultTable" or "searchResultGrid".
* Note: The value of resultViewTypes and resultViewType must be compatible to each other.
*
* After the result view type has been changed, the event 'resultViewTypeChanged' is fired.
*
* @since 1.98.0
*/
resultViewType: {
type: "string",
group: "Misc",
defaultValue: "searchResultList",
},
/**
* Defines a pair of search result view settings.
* The value is an object of properties resultViewTypes and resultViewType.
* An example: {resultViewTypes: ["searchResultList", "searchResultTable"], resultViewType: "searchResultList"}
* Find more detail in the definition of each child property.
* The value can be set/get in attached event "searchFinished".
* Function setResultViewSettings prevents incompatibility of sequential execution of functions setResultViewTypes and setResultViewType.
* Note: The value of resultViewTypes and resultViewType must be compatible to each other.
*
* After the result view type has been changed, the event 'resultViewTypeChanged' is fired.
*
* @since 1.100.0
*/
resultViewSettings: {
settings: {
resultViewTypes: "string[]",
resultViewType: "string",
},
group: "Misc",
defaultValue: {
resultViewTypes: ["searchResultList", "searchResultTable"],
resultViewType: "searchResultList",
},
},
/**
* The result views are displayed in a master-detail mode.
* The event showResultDetail is fired whenever the detail button/indicator is clicked.
*
* @since 1.140.0
*/
resultviewMasterDetailMode: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Set default number of visible columns of search result table.
* This is important when adding custom columns by means of formatters.
* @since 1.142.0
*/
searchResultTableMaxNumberOfInitiallyVisibleColumns: {
type: "float",
group: "Misc",
defaultValue: 6,
},
/**
* Function callback for formatting the datasource tabstrips in the top toolbar.
* To the callback function a list of datasources is passed. The callback functions return a modified list of datasources
* to be displayed in the tabstrips.
*
* @since 1.103.0
*/
tabStripsFormatter: {
type: "function",
group: "Misc",
},
/**
* Activates the folder mode. Precondition for folder mode is
* 1) Search model:
* In the search model for the current datasource a hierarchy attribute (representing the folders) is defined
* 1.1) the hierarchy attribute is annotated with displayType=TREE and for the hierarchy there is a helper
* connector representing the hierarchy or
* 1.2) the current datasource is the helper datasource representing the folder hierarchy. The hierarchy attribute
* is annotated with displayType=FLAT
* 2) Search query:
* The folder mode is only active in case the search query has an empty search term and no filter conditions
* (except the hierarchy attribute) are set.
*
* In folder mode and in case a folder filter is set the result view only shows direct children of a folder.
* In contrast the counts in the facets are calculated by counting direct and not direct children.
* In case the folder mode is not active the UI uses the search mode: The result list shows direct and
* not direct children of a folder.
* @since 1.106.0
*/
folderMode: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* In case folderMode is set to true this boolean indicates whether for the initial search the folder mode is enabled.
* @since 1.114.0
*/
folderModeForInitialSearch: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* In case folder mode is active:
* Automatically switch result view type to list in search mode and to table in folder mode.
* @since 1.106.0
*/
autoAdjustResultViewTypeInFolderMode: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Enables the query language for the hana_odata provider.
* With query language it is possible for the end user to enter complex search
* queries with logical operators.
* @since 1.107.0
*/
enableQueryLanguage: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Relevant for SAP partners and SAP, the "Application Component" you expect customers to create incidents.
* @since 1.108.0
*/
applicationComponent: {
type: "string",
group: "Misc",
defaultValue: "HAN-AS-INA-UI",
},
/**
* Display a splitter bar to resize the left hand panel, containing all facets and filter criteria.
* @since 1.108.0
*/
facetPanelResizable: {
type: "boolean",
group: "Misc",
defaultValue: true,
},
/**
* Default size (percent) of the left hand panel, containing all facets and filter criteria. If "facetPanelResizable" is true, the width of the facet panel can be changed by the user.
* @since 1.108.0
*/
facetPanelWidthInPercent: {
type: "float",
group: "Misc",
defaultValue: 25,
},
/**
* A boolean which indicates whether the basket panel with selected items is initially opened or closed.
* This affects only the initial state of the basket panel.
* @since 1.146.0
*/
basketPanelVisibility: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Display a splitter bar to resize the right hand panel, containing the basket with selected items.
* @since 1.142.0
*/
basketPanelResizable: {
type: "boolean",
group: "Misc",
defaultValue: true,
},
/**
* Default size (percent) of the right hand panel, containing the basket with selected items. If "basketPanelResizable" is true, the width of the basket panel can be changed by the user.
* @since 1.142.0
* @default 100
*/
basketPanelWidthInPercent: {
type: "float",
group: "Misc",
defaultValue: 100,
},
/**
* Whenever a search has no results, a 'No Results Screen' is displayed. You can provide a custom control to be more specific and add some hints, links, buttons or other content.
* @since 1.94.0
*/
getCustomNoResultScreen: {
type: "function",
group: "Misc",
},
/**
* Define your own rendering of result items (we recommend to use 'sap.m.GridContainer' and 'sap.m.GenericTile').
* @since 1.93.0
*/
customGridView: {
type: "function",
group: "Misc",
},
/**
* Define a custom toolbar.
* @since 1.93.0
*/
getCustomToolbar: {
type: "function",
group: "Misc",
},
/**
* A callback which returns customized "label" for placeholder text of search box in case there is no search term.
* The placeholder text (en) will be "Search In: ".
*/
getSearchInputPlaceholderLabel: {
type: "function",
group: "Misc",
},
/**
* Location of the search input box.
* The search input can be placed on the top of the control (SearchCompositeControl) or as part of the search bar.
* By default the location is at the top, values are "Top" and "Searchbar".
* @since 1.140.0
*/
searchInputLocation: {
type: "string",
group: "Misc",
defaultValue: "Top",
},
/**
* Shall the window title be overwritten by this control?
* If true, the control will set the current search condition as window title.
* If false, it will not set or update the window title.
* @since 1.93.0
*/
overwriteBrowserTitle: {
type: "boolean",
group: "Misc",
},
/**
* Data source id which is set when the UI is loaded or filter is reset.
* If dataSource is also set, dataSource will be used during UI load and this
* parameter will used only after filter is reset. When setting 'exclusiveDataSource' to 'true', do not set this parameter (will be ignored).
* @since 1.93.0
*/
defaultDataSource: {
type: "string",
group: "Misc",
},
/**
* The layout is optimized for object selection / value help (narrow view w/o facet panel).
* @since 1.111.0
*/
optimizeForValueHelp: {
type: "boolean",
group: "Misc",
},
/**
* Callback for filtering the datasources displayed in the datasource dropdown listbox.
* The callback gets a list of datsources and returns the filtered list of datasources.
* @since 1.112.0
*/
filterDataSources: {
type: "function",
group: "Misc",
},
/**
* Show a text/title at the start(left) of the search bar.
*
* @since 1.143.0
*/
searchBarTitle: {
type: "string",
group: "Misc",
},
/**
* Controls whether the facet functionality is available or not.
* @since 1.133.0
*/
facets: {
type: "boolean",
group: "Misc",
defaultValue: true,
},
/**
* A boolean which indicates whether the facet panel is initially opened or closed.
* This affects only the initial state of the facet panel.
* This initial state of the facet panel can be overwritten by the user, depending
* on the user's personalization settings.
* @since 1.113.0
*/
facetVisibility: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Location of the button to show/hide facet panel. The button (filter icon) can be placed on the left (begin) or on the right (end) of the search bar.
* By default the button's location is at the beginning (left), values are "Begin" and "End".
* The property is not evaluated if facet panel is disabled, see property 'facets'.
* @since 1.140.0
*/
facetToggleButtonLocation: {
type: "string",
group: "Misc",
defaultValue: "Begin",
},
/**
* Location of the buttons to show/hide basket and add items to the basket. The buttons can be placed on the left (begin) or on the right (end) of the search bar.
* By default the location is at the end (right), values are "Begin", "Middle" and "End".
* @since 1.143.0
*/
basketButtonsLocation: {
type: "string",
group: "Misc",
defaultValue: "End",
},
/**
* Items of the basket are automatically added/removed when selecting/deselecting items on result views. Moreover all items of the basket are automatically selected on result views, if they are visible on the current result view page.
* The basket show/hide button is rendered as a toggle button. The left button shows available items (label: "Available"), the right button shows selected items (label: "Selected"). You either see the result view (table, list or grid) or the basket.
* With release 1.148.0 the label, icon and tooltip of the right button are controlled by the properties 'basketShowHideButtonText', 'basketShowHideButtonIcon' and 'basketShowHideButtonTooltip'.
* @since 1.143.0
*/
basketLinkByResultViewItemSelection: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Custom icon of button 'add items to basket'. By default the icon is "sap-icon://cart-4".
* @since 1.143.0
*/
basketAddButtonIcon: {
type: "string",
group: "Misc",
},
/**
* Custom text of button 'add items to basket'. By default the text is empty and the button will show the icon, only.
* @since 1.143.0
*/
basketAddButtonText: {
type: "string",
group: "Misc",
},
/**
* Custom tooltip of button 'add items to basket'. By default there tooltip is 'Add to Basket'.
* @since 1.143.0
*/
basketAddButtonTooltip: {
type: "string",
group: "Misc",
},
/**
* Custom icon of the show/hide basket button. Only applies when {@link #basketLinkByResultViewItemSelection} is true.
* In that case, the icon is applied to the right button part ("Selected") of the segmented toggle button.
* @since 1.148.0
*/
basketShowHideButtonIcon: {
type: "string",
group: "Misc",
},
/**
* Custom text of the show/hide basket button. Only applies when {@link #basketLinkByResultViewItemSelection} is true.
* In that case, the text is applied to the right button part ("Selected") of the segmented toggle button.
* @since 1.148.0
*/
basketShowHideButtonText: {
type: "string",
group: "Misc",
},
/**
* Custom tooltip of the show/hide basket button. Only applies when {@link #basketLinkByResultViewItemSelection} is true.
* In that case, the tooltip is applied to the right button part ("Selected") of the segmented toggle button.
* @since 1.148.0
*/
basketShowHideButtonTooltip: {
type: "string",
group: "Misc",
},
/**
* When basket gets initialized, this callback is called with the columns of the (future) basket table.
* You can add more columns or adjust the sequence of columns.
* @since 1.144.0
*/
basketInit: {
type: "function",
group: "Misc",
},
/**
* Before new item is added to basket, this callback is called with the public item (search result) and the new basket item
* You can adjust the model data of the basket item.
* @since 1.144.0
*/
basketBeforeAddItem: {
type: "function",
group: "Misc",
},
/**
* Location of the basket. When creating your own basket (see function 'createAndRegisterBasket'), set this property to "CustomBasketLocation".
* By default the basket is a panel at the right of the result view.
*
* Values:
* - "SidePanelRight" (default): basket is a panel at the right of the result view
* - "CustomBasketLocation": basket is created by function 'createAndRegisterBasket' and can be placed anywhere in the UI
*
* @since 1.145.0
*/
basketLocation: {
type: "string",
group: "Misc",
defaultValue: "SidePanelRight",
},
/**
* Define your own view for the basket. The basket is displayed on a panel at the right/end of result views (by default, details see 'basketLocation'). There is buttons to show/hide it and add items to the basket (see config properties 'basketVisibilityButton' and 'basketAddButton').
* For the basket items collection, we recommend to use function 'createAndRegisterBasket' to create a table/list, so the selection (incl. 'add to basket') of result views with table/list is managed by SearchCompositeControl.
* @since 1.144.0
*/
customBasket: {
type: "function",
group: "Misc",
},
/**
* A boolean for enabling chart visualizations (pie chart / bar chart facets).
* If set to true, facets can be viewed as a pie or bar chart (this cannot be deactivated at runtime).
* @since 1.140.0
*/
enableCharts: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* A boolean for enabling (business) object suggestions.
* @since 1.113.0
*/
boSuggestions: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* When set to true the facet panel is displayed also in case there are no search results.
* @since 1.113.0
* @deprecated As of version 1.134.0, this setting is ignored and the visibility of the facet panel is controlled by the end user.
*/
displayFacetPanelInCaseOfNoResults: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* A boolean indicating that the search state is written to the URL.
* @since 1.113.0
*/
updateUrl: {
type: "boolean",
group: "Misc",
defaultValue: true,
},
/**
* A callback for rendering the search URL. The callback gets a list of url encoded parameters and returns the URL string.
* Typically you need to register this callback in case of 'updateUrl = true'.
* @since 1.113.0
* @example
*
* const renderSearchUrl = function (properties){
return (
"#Action-search&/top=" +
properties.top +
(properties.orderby ? "&orderBy=" + properties.orderby : "") +
(properties.sortorder ? "&sortOrder=" + properties.sortorder : "") +
"&filter=" +
properties.filter
);
};
*/
renderSearchUrl: {
type: "function",
group: "Misc",
},
/**
* A callback for checking whether a URL is a search-relevant URL. The callback receives a URL and returns 'true' in case the URL is a search-relevant URL.
* Typically you need to register this callback in case of 'updateUrl = true'.
* @since 1.113.0
* @example
*
* const isSearchUrl =function (url) {
return url.indexOf("#Action-search") === 0;
};
*/
isSearchUrl: {
type: "function",
group: "Misc",
},
/**
* A callback for parsing URL parameters. The callback receices URL parameters and returns modified URL parameters.
* This is an optional callback. In case you set 'updateUrl = true' typcically this callback is not needed.
* @since 1.113.0
*/
parseSearchUrlParameters: {
type: "function",
group: "Misc",
},
/**
* A list of data sources to be displayed in the facet panel in the section "Collection".
* @since 1.113.0
*/
quickSelectDataSources: {
type: "object",
group: "Misc",
},
/**
* An asynchronues callback which is called after the initialization of the search composite control.
* @since 1.113.0
*/
initAsync: {
type: "function",
group: "Misc",
},
/**
* Enables the multiselection mode of search result items.
* A checkbox is provided for each result item if the value is set to 'true'.
* @public
* @since 1.96.0
* @deprecated As of version 1.141.0, use 'resultviewSelectionMode' instead.
*/
enableMultiSelectionResultItems: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* To select result view items, enable multi-selection or single-selection mode. Shows checkboxes ('MultipleItems') or enables item press ('OneItem'). Values of configuration parameter `resultviewSelectionMode` are 'None', 'OneItem' and 'MultipleItems'.
* To show a button for switching selection mode on/off, see 'showSelectionToggleButton'.
* A checkbox is provided for each result item if the value is true.
* The setting is related to `resultviewMasterDetailMode` (master-detail mode). The event `showResultDetail` is fired whenever the detail button is clicked.
* @public
* @since 1.141.0
* @param {"None" | "OneItem" | "MultipleItems"} selectionMode The mode to control how many result view items can be selected (checkboxes are displayedfor multi-selection).
*/
resultviewSelectionMode: {
type: "string",
group: "Misc",
defaultValue: "None",
values: [
{ name: "None", key: "None" },
{ name: "OneItem", key: "OneItem" },
{ name: "MultipleItems", key: "MultipleItems" },
],
},
/**
* Enables the selection toggle button in the search result list.
* The button is displayed on the top of search result view and allows to toggle the selection of all result items.
* The button is only displayed if the property 'resultviewSelectionMode' is not set to 'None'.
* @public
* @since 1.140.0
* @param {boolean} [showSelectionToggleButton=false] If true, shows a toggle button to show/hide selection indicators (checkboxes).
*/
showSelectionToggleButton: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* Enables a button in the search result list to control basket panel visibility.
* Depending on the configuration, this is either a show/hide button or an 'Available'/'Selected' toggle button (when basketLinkByResultViewItemSelection=true).
* The button is only rendered when the basket feature is active (i.e. createAndRegisterBasket was called).
* @public
* @since 1.143.0
* @param {boolean} [basketVisibilityButton=true] If true, shows a button to control visibility of the basket (when basket feature is active).
*/
basketVisibilityButton: {
type: "boolean",
group: "Misc",
defaultValue: true,
},
/**
* Enables the basket add button in the search result list.
* The button is displayed on the top of search result view and allows to add result view items to the basket.
* Has no effect when basketLinkByResultViewItemSelection=true — in that case items are added automatically
* via result view item selection and a manual add button is redundant (a console warning is logged).
* @public
* @since 1.143.0
* @param {boolean} [basketAddButton=false] If true, shows a button to add result view items to the basket.
*/
basketAddButton: {
type: "boolean",
group: "Misc",
defaultValue: false,
},
/**
* The maximum count of search result items displayed on a page after a search.
* By clicking 'Show More' button, another page of result items of the same count (if available) will be displayed.
* @since 1.96.0
*/
pageSize: {
type: "int",
group: "Misc",
defaultValue: 10,
},
/**
* Callback for formatting the filter string to be displayed on the filter bar (for closed facet panel).
* @since 1.120
*/
formatFilterBarText: {
type: "function",
group: "Misc",
},
/**
* Callback for checking whether the filter bar is visible.
* @since 1.120
*/
isFilterBarVisible: {
type: "function",
group: "Misc",
},
/**
* Adds a callback function which is called whenever there is a user or a technical event.
* A user event is an event which is triggered by the user interaction with the UI, such as clicking.
* A technical event is an event which is triggered by the internal events or events which can also
* be triggered by API.
* @since 1.120
* @example
*
*/
eventConsumers: {
singularName: "eventConsumer",
multiple: true,
group: "Misc",
},
/**
* Limit for length of searchterm.
*/
searchTermLengthLimit: {
type: "int",
group: "Misc",
defaultValue: 1000,
},
/**
* Prevents too many ajax requests in a short time.
*/
limitAjaxRequests: {
type: "boolean",
group: "Misc",
defaultValue: true,
},
},
events: {
/**
* Event is fired when search is started.
* @since 1.121
*/
searchStarted: {},
/**
* Event is fired when search is finished.
* @since 1.121
*/
searchFinished: {},
/**
* Event is fired when selection on result view (list, table or grid) has changed.
* @since 1.143
*/
resultViewSelectionChanged: {},
/**
* Event is fired when selection on result view (list, table or grid) has changed.
* @since 1.121
* Will be deprecated as of version 1.143.0, use 'resultViewSelectionChanged' instead. The new event comes with more details about selected/deselected items.
*/
selectionChanged: {},
/**
* Event is fired after result view type got changed (list, table or grid).
* Parameters of the event (since 1.143):
* - newValue: current result view type
* - oldValue: previous result view type
* @since 1.124
*/
resultViewTypeChanged: {},
/**
* Event is fired after result view detail button is pressed (list, table or grid).
* To use this event, set the property 'resultviewMasterDetailMode' to 'true'.
* @since 1.140.0
*/
showResultDetail: {},
/**
* After items are added/removed from basket, this callback is called
* Parameters of the event:
* - count: current number of items in basket
* @since 1.144.0
*/
basketChanged: {},
},
};
errorHandler: ErrorHandler;
oSearchCompositeControlDragDropConfig: SearchCompositeControlDragDropConfig;
searchContainer: SearchLayoutResponsive;
// search field group
oSearchFieldGroup: SearchFieldGroup;
// filter bar
oFilterBar: SearchFilterBar;
// nlq explain bar
oNlqExplainBar: SearchNlqExplainBar;
// search bar
oSearchBar: OverflowToolbar;
oSearchBarTitle: Title;
oFilterButton: OverflowToolbarToggleButton;
oSelectionButton: OverflowToolbarToggleButton;
oBasketButtons: Array;
oBasketAddButton: OverflowToolbarButton;
oDataSourceTabBar: IconTabBar;
oGenericItemsToolbar: OverflowToolbar;
resultViewSwitch: SegmentedButton;
// search results container(s)
oSearchResultContainer: SearchResultContainer;
oSearchResultPanel: FixFlex;
oSearchBaskets: Array = [];
resultView: Array;
countBreadcrumbsHiddenElement: InvisibleText;
showMoreFooter: FlexBox;
sortDialog: ViewSettingsDialog;
// context bar
oContextBar: Bar;
oContextBarContainer: VerticalLayout;
oSearchCountBreadcrumbs: Control;
// selection bar
oSearchSelectionBar: SearchSelectionBar;
// no results
noResultScreen: VBox; // needed for 'Destroy'
noResultScreenFolder: VBox; // needed for 'Destroy'
// result views
searchResultTable: SearchResultTable;
searchResultList: SearchResultList;
appResultListItem: CustomListItem;
searchResultGrid: SearchResultGrid;
searchSpreadsheetExport: SearchSpreadsheetExport;
appSearchResult: VerticalLayout;
oAppView: Control;
// footer
oFooter: OverflowToolbar;
oErrorPopover: MessagePopover;
oFocusHandler: SearchFocusHandler;
model: SearchModel;
toolbarAssembler: ToolbarAssembler;
resultViewsAssembler: ResultViewsAssembler;
searchPageAssembler: SearchPageAssembler;
private static eshCompCounter = 0;
subscribeDone_SearchStarted: boolean;
subscribeDone_SearchFinished: boolean;
subscribeDone_SelectionChanged: boolean;
subscribeDone_ResultViewTypeChanged: boolean;
subscribeDone_ShowResultDetail: boolean;
subscribeDone_BasketChanged: boolean;
subscribeDone_PublicModelChanged: boolean;
private _cssClass: string;
private initSearchPromise: Promise;
private _onHashChangedHandler: () => void;
private isHashChangedAttached = false;
constructor(sId?: string, settings?: Partial) {
const initialErrorHandler = ErrorHandler.getInstance({ label: sId });
let searchModel: SearchModel;
let createContentRunning = false;
try {
// unify input parameters
const unifiedParameters = SearchCompositeControl.unifyInputParameters(sId, settings);
sId = unifiedParameters.sId;
settings = unifiedParameters.settings;
// application component shall be provided (needed for error messages and supportability)
assert(
settings?.applicationComponent !== undefined && settings?.applicationComponent !== "",
`Provide property 'applicationComponent' (example: "LOD-ANA-LS").\nRelevant for SAP partners and SAP, the "Application Component" you expect customers to create incidents.`
);
// if 'exclusiveDataSource' is provided, we (internally) set 'dataSource' and 'defaultDataSource' to same value.
// to prevent confusion, DO NOT provide 'dataSource' and 'defaultDataSource'
assert(
settings.exclusiveDataSource === undefined ||
settings.exclusiveDataSource === "" ||
((settings.dataSource === undefined || settings.dataSource === "") &&
(settings.defaultDataSource === undefined || settings.defaultDataSource === "")),
`Property 'exclusiveDataSource' is provided ('${settings.exclusiveDataSource}').\nDo NOT provide properties 'dataSource' or 'defaultDataSource'.`
);
// create search model (within search model the SearchConfiguration is created and initialized with settings)
searchModel = settings["model"] || settings["searchModel"]; // ToDo, adjust flp/cflp, user 'searchModel' instead of 'model' (renamed because of syntax check errors with type of existing property 'model')
if (!searchModel) {
searchModel = new SearchModel({
configuration: settings,
});
} else {
searchModel.config.id = sId; // FLP use case
}
initialErrorHandler.setSearchModel(searchModel);
// call super constructor with all UI5 relevant settings
const settingsKnownToUI5 = SearchCompositeControl.getUI5ControlSettings(searchModel.config);
super(sId, settingsKnownToUI5);
this.errorHandler = initialErrorHandler;
// init composite control
this.addStyleClass("sapUshellSearchInputHelpPage"); // obsolete, but could be still used in some applications
this.addStyleClass("sapElisaSearchCompositeControl");
if (searchModel.config.optimizeForValueHelp) {
this.addStyleClass("sapUshellSearchInputHelpPageValueHelp");
}
this.setModelInternal(searchModel);
this.setModelInternal(searchModel.publicSearchModel, searchModel.publicSearchModel.modelName);
this.setModelInternal(new ResourceModel({ bundle: i18n }), "i18n");
const scc = this as SearchCompositeControl;
this.oFocusHandler = new SearchHelper.SearchFocusHandler(scc);
searchModel.focusHandler = this.oFocusHandler;
this.toolbarAssembler = new ToolbarAssembler(scc);
this.resultViewsAssembler = new ResultViewsAssembler(scc);
this.searchPageAssembler = new SearchPageAssembler(scc);
// create views
searchModel.config.performanceLogger?.enterMethod({ name: "createContent" }, { isSearch: true });
createContentRunning = true;
this.createContent();
searchModel.config.performanceLogger?.leaveMethod({ name: "createContent" });
createContentRunning = false;
// console.log("searchCompositeControl ID: " + this.getId());
// drag&drop config (helper)
this.oSearchCompositeControlDragDropConfig = new SearchCompositeControlDragDropConfig({
eshComp: this,
});
ResizeHandler.register(this, () => {
this.resizeHandler();
});
this._onHashChangedHandler = () => {
this.closePopovers();
};
if (!this.isHashChangedAttached) {
RoutingHashChanger.getInstance().attachEvent("hashChanged", this._onHashChangedHandler, this);
this.isHashChangedAttached = true;
}
// trigger async search init
this.initSearchPromise = this.initSearch();
} catch (error) {
initialErrorHandler.onError(error);
// control failed to construct — will never render, so flush buffered messages immediately as MessageBox
initialErrorHandler.flushBufferedMessagesAsMessageBox();
throw error;
} finally {
if (createContentRunning) {
searchModel.config.performanceLogger?.leaveMethod({
name: "createContent",
});
createContentRunning = false;
}
}
}
public getPublicSina(): PublicSina {
return (this.getModelInternal() as SearchModel).sinaNext.getPublicSina();
}
getModel(sName?: string) {
// for internal use, see function 'getModelInternal'
const callStack = new Error().stack;
if (
sName !== undefined ||
callStack.includes("._setModelInternal") ||
callStack.includes("setModelInternal") ||
callStack.includes("onAllSearchFinished") ||
callStack.includes(".updateBindingContext") || // update of existing binding
callStack.includes(".updatePropertyValue") ||
callStack.includes("addContent")
) {
// OK, called by SearchCompositeControl, function setModelInternal
} else {
assert(
false,
`The main model of this control is private.\nTo access the public search model, call function 'getPublicSearchModel' or bind to it (model name 'publicSearchModel').\nThe main model of this control is private and cannot be accessed (for internal use, see function 'getModelInternal').`
);
}
// for now, return the search model (remove this as soon as stakeholders have migrated)
return this.getModelInternal(sName);
}
setModel(oModel: Model, sName?: string): this {
const oSearchModelInternal = this.getModelInternal() as SearchModel;
if (!sName) {
assert(false, `Main model cannot be set (for internal use, see function 'setModelInternal').`);
return this;
} else if (sName === oSearchModelInternal?.publicSearchModel?.modelName) {
assert(
false,
`Model cannot be set, name '${oSearchModelInternal?.publicSearchModel?.modelName}' is reserved for public search model (for internal use, see function 'setModelInternal').`
);
return this;
} else {
return super.setModel(oModel, sName);
}
}
private setModelInternal(oModel: Model, sName?: string) {
return super.setModel(oModel, sName);
}
private browserHasValidSearchUrl(): boolean {
const searchModel = this.getModelInternal() as SearchModel;
if (
!callConfigExit("isSearchUrl", searchModel.config.applicationComponent, () =>
searchModel.config.isSearchUrl(SearchHelper.getHashFromUrl())
)
) {
return false;
}
const oParametersLowerCased = SearchHelper.getUrlParameters();
if (core.isEmptyObject(oParametersLowerCased)) {
return false;
}
return true;
}
private async initSearch() {
// log method entry
const searchModel = this.getModelInternal() as SearchModel;
searchModel.config.performanceLogger?.enterMethod({ name: "init search" }, { isSearch: true });
// init model
const isAlreadyInitialized = !!searchModel.sinaNext;
await searchModel.initAsync();
// on back navigation the model is already initialized and cached data resolves as
// microtasks, batching structure and content into one render cycle. Yield to a
// macro-task so the Splitter can size its panes before the result list renders.
if (isAlreadyInitialized) {
await new Promise((resolve) => setTimeout(resolve, 0));
}
// start search
if (searchModel.config.updateUrl && this.browserHasValidSearchUrl()) {
// 1) via URL
await searchModel.searchUrlParser.parse();
} else if (this.getProperty("searchOnStart") && !searchModel.config.isUshell) {
// 2) initial search
await this.search();
}
// log method leave
if (searchModel) {
searchModel.config.performanceLogger?.leaveMethod({
name: "init search",
});
}
}
private static getUI5ControlSettings(settings: SearchConfigurationSettings): $ControlSettings {
const settingsKnownToUI5: $ControlSettings = {}; // this is a subset of settings which contain only parameters and events (aggregations not relevant, there is only 'content') which are also in this controls metadata
// properties
const metadataProperties = SearchCompositeControl.getMetadata().getProperties();
for (const metadataProperty in metadataProperties) {
if (typeof settings[metadataProperty] === "undefined") {
continue;
}
settingsKnownToUI5[metadataProperty] = settings[metadataProperty];
}
// events (added 1.121)
const metadataEvents = SearchCompositeControl.getMetadata().getEvents();
for (const metadataEvent in metadataEvents) {
if (typeof settings[metadataEvent] === "undefined") {
continue;
}
settingsKnownToUI5[metadataEvent] = settings[metadataEvent];
}
// aggregations
// -> not relevant, we only have aggregation 'content' and this shall not be taken over
return settingsKnownToUI5;
}
private static unifyInputParameters(
sId: string,
settings: Partial
): { sId: string; settings: Partial } {
// shift arguments in case sId was missing, but mSettings was given
if (typeof sId !== "string" && sId !== undefined && settings === undefined) {
settings = sId;
sId = settings && settings.id;
} else if (typeof settings === "undefined") {
settings = {};
}
// add sId to mSettings
if (typeof sId === "string" && sId.length > 0) {
settings.id = sId;
}
// no id -> create one
if (!sId || sId.length === 0) {
sId = "eshComp" + "GenId_" + SearchCompositeControl.eshCompCounter++;
settings.id = sId;
}
// check sId === mSettings.id
if (typeof sId === "string" && sId.length > 0 && typeof settings.id !== "undefined") {
if (sId !== settings.id) {
throw new Error(
"Constructor of component 'sap.esh.search.ui.SearchCompositeControl' has failed\n\n" +
"sId and mSettings.id are not the same. It is sufficient to set either 'id' (sId) or 'settings.id' (mSettings.id)."
);
}
}
return { sId, settings };
}
private closePopovers(): void {
// close popover opened by SearchResultTablePersonalizer reset button
// close it firstly, because it is popover over another popover
const domDialogs = document.getElementsByClassName("sapMDialogOpen") || []; // DOM elements
for (const domDialog of domDialogs) {
const ui5Dialog = Element?.getElementById(domDialog.id) as Dialog; // UI5 instance
if (
ui5Dialog &&
ui5Dialog?.getParent()?.getId()?.endsWith("search-result-table-personalizer-resetBtn")
) {
const buttons = ui5Dialog?.getButtons() || [];
for (const btn of buttons) {
if (btn?.getType() === ButtonType.Default) {
// cancel button is Default type
btn?.firePress();
break;
}
}
}
}
// list of all possiblely opened dialogs / actionsheets
const selectors = [
"sapUshellSearchFacetDialog",
"sapUshellSearchResultSortDialog",
"sapUshellSearchResultTablePersonalizationDialog",
"sapUshellSearchResultExportDialog",
"sapUshellSearchResultShareActionSheet",
"sapUshellSearchResultExportActionSheet",
"bookmarkDialog", // unified.shell AddBookmarkButton hard coded id
"sapUshellSearchMessageBox", // instance of Dialog
];
for (const selector of selectors) {
const domElement =
document.getElementsByClassName(selector)[0] || document.getElementById(selector); // DOM element
const ui5Element = Element?.getElementById(domElement?.id); // UI5 instance
if (ui5Element) {
if (ui5Element instanceof Dialog) {
const buttons = ui5Element?.getButtons() || [];
for (const btn of buttons) {
if (btn?.getType() === ButtonType.Default) {
// cancel button is Default type
btn?.firePress();
break;
}
}
}
if (ui5Element instanceof ActionSheet) {
ui5Element?.close();
}
}
}
// Related Apps Action Sheet in Result Table closed after new search starts
// Related Apps Action Sheet in Result List in narrow window closed after new search starts
// Do nothing
}
exit(): void {
if (this.isHashChangedAttached) {
RoutingHashChanger.getInstance().detachEvent("hashChanged", this._onHashChangedHandler, this);
this.isHashChangedAttached = false;
}
if (this.oFilterBar) {
// ToDo: ugly workaround for "ap22: duplicate id 'eshCompGenId_0-searchFilterBar'"
// -> after adding a stable id to the filter bar, the existing memory clean-up issue became a duplicate id issue
this.oFilterBar.destroy();
}
if (this.oSearchBarTitle) {
// ToDo: ugly workaround for "ap22: duplicate id 'eshCompGenId_0-searchBarTitle'"
// -> after adding a stable id to the search bar title, the existing memory clean-up issue became a duplicate id issue
this.oSearchBarTitle.destroy();
}
if (this.oBasketButtons) {
// ToDo: ugly workaround for "ap22: duplicate id 'eshCompGenId_0-searchBarBasketToggleButton'"
// -> after adding a stable id to the search bar title, the existing memory clean-up issue became a duplicate id issue
for (const btn of this.oBasketButtons) {
btn.destroy();
}
}
if (this.oSearchBaskets.length > 0) {
// ToDo: ugly workaround for "ap22: duplicate id ...
// -> after adding a stable id to the search bar title, the existing memory clean-up issue became a duplicate id issue
for (const basket of this.oSearchBaskets) {
(basket as Control).destroy();
}
}
if (this.noResultScreen) {
// in case a custom result screen is used, we need to explicitely destroy the default no result screen
// in the shadow dom of UI5. Otherwise a duplicate id exception will be thrown if search composite control
// is instantiated next time.
this.noResultScreen.destroy();
}
if (this.noResultScreenFolder) {
// in case a custom result screen is used, we need to explicitely destroy the default no result screen
// in the shadow dom of UI5. Otherwise a duplicate id exception will be thrown if search composite control
// is instantiated next time.
this.noResultScreenFolder.destroy();
}
// avoid to create same-id-TablePersoDialog by oTablePersoController.activate()
// destroy TablePersoDialog when exit search app
this.searchResultTable?.tablePersonalizer?.destroyControllerAndDialog();
if (this["oFacetDialog"]) {
// oFacetDialog doesn't have id
// destroy oFacetDialog when exit search app anyway
this["oFacetDialog"].destroy(); // ToDo
this["oFacetDialog"] = null;
}
const oModel = this.getModelInternal() as SearchModel;
oModel.unsubscribe(UIEvents.ESHSearchStarted, this.onAllSearchStarted, this);
oModel.unsubscribe(UIEvents.ESHSearchFinished, this.onAllSearchFinished, this);
oModel.unsubscribe(UIEvents.ESHSelectionChanged, this.onSelectionChanged, this);
oModel.unsubscribe(UIEvents.ESHResultViewTypeChanged, this.onResultViewTypeChanged, this);
oModel.unsubscribe(UIEvents.ESHShowResultDetail, this.onShowResultDetail, this);
oModel.unsubscribe(UIEvents.ESHBasketChanged, this.onBasketChanged, this);
oModel.unsubscribe(UIEvents.ESHPublicModelChanged, this.onPublicModelChanged, this);
}
//#region create UI elements
static renderer = {
apiVersion: 2,
};
createContent(): void {
this.searchPageAssembler.assembleContent();
}
createSearchFieldGroup(): SearchFieldGroup {
return this.searchPageAssembler.createSearchFieldGroup();
}
createClassicSearchFieldGroup(): SearchFieldGroup {
return this.searchPageAssembler.createClassicSearchFieldGroup();
}
onAfterRendering(): void {
this.errorHandler.setIsRendered(true);
this.searchPageAssembler.onAfterRendering();
}
isShowMoreFooterVisible(): boolean {
const oModel = this.getModelInternal() as SearchModel;
return oModel.getProperty("/boCount") > oModel.getProperty("/boResults").length;
}
// #endregion
// #region drag and drop
assignDragDropConfig(): void {
this.oSearchCompositeControlDragDropConfig.assignDragDropConfig();
}
override addDragDropConfig(oDragDropConfig: DragDropBase): this {
this.oSearchCompositeControlDragDropConfig.addDragDropConfig(oDragDropConfig);
return this;
}
override insertDragDropConfig(oDragDropConfig: DragDropBase, iIndex: number): this {
this.oSearchCompositeControlDragDropConfig.insertDragDropConfig(oDragDropConfig, iIndex);
return this;
}
override indexOfDragDropConfig(oDragDropConfig: DragDropBase): number {
return this.oSearchCompositeControlDragDropConfig.indexOfDragDropConfig(oDragDropConfig);
}
override getDragDropConfig(): Array {
return this.oSearchCompositeControlDragDropConfig.getDragDropConfig();
}
override removeDragDropConfig(vDragDropConfig: DragDropBase | string | number): DragDropBase {
return this.oSearchCompositeControlDragDropConfig.removeDragDropConfig(vDragDropConfig);
}
override removeAllDragDropConfig(): Array {
return this.oSearchCompositeControlDragDropConfig.removeAllDragDropConfig();
}
override destroyDragDropConfig(): this {
this.oSearchCompositeControlDragDropConfig.destroyDragDropConfig();
return this;
}
// #endregion
// #region ESH event handlers
onAllSearchStarted(eventId: string, parameters: unknown): void {
this.searchPageAssembler.onAllSearchStarted(eventId, parameters);
}
onAllSearchFinished(eventId: string, parameters: unknown): void {
this.searchPageAssembler.onAllSearchFinished(eventId, parameters);
}
onSelectionChanged(eventId: string, parameters: IUiEventParametersResultViewSelectionChanged): void {
this.searchPageAssembler.onSelectionChanged(eventId, parameters);
}
onResultViewTypeChanged(eventId: string, parameters: IUiEventParametersResultViewTypeChanged): void {
this.searchPageAssembler.onResultViewTypeChanged(eventId, parameters);
}
onShowResultDetail(eventId: string, parameters: IUiEventParametersShowResultDetail): void {
this.searchPageAssembler.onShowResultDetail(eventId, parameters);
}
onBasketChanged(eventId: string, parameters: IUiEventParametersBasketChanged): void {
this.searchPageAssembler.onBasketChanged(eventId, parameters);
}
onPublicModelChanged(eventId: string, parameters: IUiEventParametersPublicModelChanged): void {
this.searchPageAssembler.onPublicModelChanged(eventId, parameters);
}
// #endregion
createSearchPage(idPrefix: string): void {
this.searchPageAssembler.createSearchPage(idPrefix);
}
createNoResultScreen(idPrefix: string, type: "default" | "folder"): VBox {
return this.searchPageAssembler.createNoResultScreen(idPrefix, type);
}
createSearchContainer(idPrefix: string): SearchLayoutResponsive {
return this.searchPageAssembler.createSearchContainer(idPrefix);
}
isSearchFieldGroupLocatedInsideSearchComposite(currentControlDomRef?: HTMLElement | ParentNode): boolean {
return this.searchPageAssembler.isSearchFieldGroupLocatedInsideSearchComposite(currentControlDomRef);
}
adjustSearchbarCustomGenericButtonWidth(): void {
this.searchPageAssembler.adjustSearchbarCustomGenericButtonWidth();
}
resizeHandler() {
this.searchPageAssembler.resizeHandler();
}
createFooter(idPrefix: string): OverflowToolbar {
return this.searchPageAssembler.createFooter(idPrefix);
}
chooseNoResultScreen(): void {
this.searchPageAssembler.chooseNoResultScreen();
}
openSortDialog() {
this.searchPageAssembler.openSortDialog(this.sortDialog);
}
openShowMoreDialog(dimension?: string) {
this.searchPageAssembler.openShowMoreDialog(dimension);
}
// #region public API
/**
* Parse the browser URL, update properties and trigger search call (if needed)
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.122.0
* @returns this
*/
parseURL(): SearchCompositeControl {
(this.getModelInternal() as SearchModel).parseURL();
return this;
}
/**
* Get the public part of the search model (JSON)
* In general the model is read-only but a few properties can be modified.
* Modifiable properties (i.e. consumtion via two-way binding):
* - 'results[i].data.selected'
* For binding use model 'publicSearchModel' (i.e. "publicSearchModel>results/...")
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.122.0
* @returns {object} A JSON model containing public artefacts of internal search model
*/
public getPublicSearchModel(): PublicSearchModel {
return this.getModelInternal("publicSearchModel") as PublicSearchModel;
}
/**
* Create an instance of a basket and register it for interaction with the result view (items/selection indicator).
* See property 'basketLocation' in search model configuration to define where the basket panel is located.
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.145.0
* @param {string} idPostfix The ID postfix for the basket instance
* @param {"sapMList" | "sapMTable" | "sapUiTable"} type The UI type table/list ("sapMList" | "sapMTable" | "sapUiTable")
* @returns {object} A basket control of type sap.m.Table or sap.m.List containing items of the search basket ('selected items')
*/
public createAndRegisterBasket(
idPostfix: string,
type: "sapMList" | "sapMTable" | "sapUiTable"
): SearchBasketSapUiTable | SearchBasketSapMTable | SearchBasketSapMList {
return this.searchPageAssembler.createAndRegisterBasket(idPostfix, type);
}
/**
* Get the basket model ('selected items', 'count' : JSON model)
* The model is managed by SearchCompositeControl, do not change it manually.
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.145.0
* @returns {object} A JSON model containing items of the search basket ('selected items', 'count')
*/
public getSearchBasketModel(): SearchBasketModel | null {
if (this.oSearchBaskets.length > 0) {
return (this.oSearchBaskets[this.oSearchBaskets.length - 1] as SearchBasketApi).getBasketModel();
}
return null;
}
/**
* Get the active/current search basket (see interfaceSearchBasketApi)
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.145.0
* @returns {object} A control containing other controls. At least one of the controls is the basket-list/table, containg the 'selected items'.
*/
public getBasket(): SearchBasketApi | undefined {
let oBasket;
if (this.oSearchBaskets.length > 0) {
oBasket = this.oSearchBaskets[this.oSearchBaskets.length - 1] as SearchBasketApi;
}
return oBasket;
}
/**
* Get items of the latest search basket ('selected items', JSON model)
* The model is read-only.
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.143.0
* @returns {object} A JSON model containing items of the search basket ('selected items')
*/
public getBasketItems(): Array {
let basketItems = []; // empty array also returned when there is no basket instance
if (this.oSearchBaskets.length > 0) {
const basketModel = (
this.oSearchBaskets[this.oSearchBaskets.length - 1] as SearchBasketApi
).getBasketModel();
basketItems = basketModel.getProperty("/rows").map((publicItem) => {
return {
key: publicItem.key,
title: publicItem.title,
selected: publicItem.selected,
data: publicItem.data,
};
});
}
return basketItems;
}
/**
* Get visibility status of basket panel
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.146.0
* @returns boolean The visibility status of basket panel
*/
public getBasketPanelVisibility(): boolean {
const oModel = this.getModelInternal() as SearchModel;
if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return undefined;
}
return oModel.getProperty("/basketPanelVisibility");
}
/**
* Set visibility status of basket panel
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.146.0
* @returns boolean The visibility status of basket panel
*/
public setBasketPanelVisibility(basketPanelVisibility: boolean): boolean {
const oModel = this.getModelInternal() as SearchModel;
if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return undefined;
}
oModel.setProperty("/basketPanelVisibility", basketPanelVisibility);
return oModel.getProperty("/basketPanelVisibility");
}
/**
* Get activation status of master detail mode
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.141.0
* @returns {boolean} The activation status of master detail mode
*/
public getResultviewMasterDetailMode(): boolean {
const oModel = this.getModelInternal() as SearchModel;
if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return undefined;
}
return oModel.getProperty("/config/resultviewMasterDetailMode");
}
/**
* Set activation status of master detail mode
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.141.0
* @returns this
*/
public setResultviewMasterDetailMode(bMasterDetailMode: boolean): SearchCompositeControl {
const oModel = this.getModelInternal() as SearchModel;
if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return this;
}
oModel.setProperty("/config/resultviewMasterDetailMode", bMasterDetailMode);
return this;
}
/**
* Get result view selection mode ('None', 'OneItem', 'MultipleItems')
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.141.0
* @returns {string} The result view selection mode
*/
public getResultviewSelectionMode(): SelectionMode {
const oModel = this.getModelInternal() as SearchModel;
if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return undefined;
}
return oModel.getProperty("/config/resultviewSelectionMode");
}
/**
* Set result view selection mode ('None', 'OneItem', 'MultipleItems')
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.141.0
* @returns this
*/
public setResultviewSelectionMode(selectionMode: SelectionMode): SearchCompositeControl {
const oModel = this.getModelInternal() as SearchModel;
if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return this;
}
oModel.setProperty("/config/resultviewSelectionMode", selectionMode);
return this;
}
/**
* Get the internal search model (JSON)
* In general the internal search model shall not be accessed by consumers of SearchCompositeControl
* The preferred consumption of search model data is to use public search model 'publicSearchModel' (i.e. "publicSearchModel>results/..."), also see function getPublicSearchModel
* @public
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.122.0
* @returns {object} A JSON model containing public artefacts of internal search model
*/
public getModelInternal(sName?: string) {
return super.getModel(sName);
}
/**
* Gets the set of result items
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.114.0
* @returns {object} The result set containg 'items'.
*/
getSearchResultSet(): ResultSet {
// search never finished (search running, searchOnStart = false or no sinaConfiguration.provider n.a.)
// -> return empty result set
let resultSet;
const searchModel = this.getModelInternal() as SearchModel;
if (typeof searchModel["resultSet"] === "undefined" || searchModel["resultSet"] === null) {
resultSet = { items: [] };
} else {
resultSet = this.getModelInternal("publicSearchModel").getProperty("/results");
}
return resultSet;
}
/**
* Gets search result view types
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.98.0
* @returns {string[]} The array of active search result view types.
*/
getResultViewTypes(): Array {
const oModel = this.getModelInternal() as SearchModel;
return oModel?.getResultViewTypes();
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
}
/**
* Sets search result view types
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.98.0
* @param {string[]} resultViewTypes The array of active search result view types.
* @example
* setResultViewTypes(["searchResultList", "searchResultTable"])
* @returns this
*/
setResultViewTypes(resultViewTypes: Array): SearchCompositeControl {
const oModel = this.getModelInternal() as SearchModel;
if (typeof resultViewTypes === "undefined") {
throw Error(
"Search Result View Type Error In SearchCompositeControl:\n\n" +
"The function parameter 'resultViewTypes' is mandatory.\n" +
'Valid example: setResultViewTypes(["searchResultList"])'
);
} else if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return this;
}
this.setResultViewSettings({
resultViewTypes: resultViewTypes,
resultViewType: oModel.getResultViewType(),
});
return this;
}
/**
* Gets search result view type
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.98.0
* @returns {"appSearchResult" | "searchResultList" | "searchResultTable" | "searchResultGrid" | "" | string} The active search result view type.
*/
getResultViewType(): string {
const oModel = this.getModelInternal() as SearchModel;
return oModel?.getResultViewType();
}
/**
* Sets search result view type
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.98.0
* @param {"appSearchResult" | "searchResultList" | "searchResultTable" | "searchResultGrid"} resultViewType The active search result view type.
* @example
* setResultViewType("searchResultList")
* @returns this
*/
setResultViewType(resultViewType: string): SearchCompositeControl {
const oModel = this.getModelInternal() as SearchModel;
if (typeof resultViewType === "undefined") {
throw Error(
"Search Result View Type Error In SearchCompositeControl:\n\n" +
"The function parameter 'resultlViewType' is mandatory.\n" +
'Valid example: setResultViewType("searchResultList")'
);
} else if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
return this;
}
this.setResultViewSettings({
resultViewTypes: oModel.getResultViewTypes(),
resultViewType: resultViewType,
});
return this;
}
/**
* Gets search result view type settings
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.100.0
* @returns {object} The object of resultViewTypes and resultViewType.
*/
getResultViewSettings(): { resultViewTypes: Array; resultViewType: string } {
const oModel = this.getModelInternal() as SearchModel;
if (oModel) {
return {
resultViewTypes: oModel.getResultViewTypes(),
resultViewType: oModel.getResultViewType(),
};
}
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl)
// -> do nothing!
}
/**
* Sets search result view type settings
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.100.0
* @param {object} settings The object of resultViewTypes and resultViewType.
* @example
* setResultViewSettings({resultViewTypes: ["searchResultList", "searchResultTable"], resultViewType: "searchResultList"})
* @returns this
*/
setResultViewSettings(resultlViewSettings: {
resultViewTypes: Array;
resultViewType: string;
}): SearchCompositeControl {
const oModel = this.getModelInternal() as SearchModel;
if (typeof resultlViewSettings === "undefined") {
throw Error(
"Search Result View Type Error In SearchCompositeControl:\n\n" +
"The function parameter 'resultlViewSettings' is mandatory.\n" +
'Valid example: setResultViewSettings({resultViewTypes: ["searchResultList", "searchResultTable"], resultViewType: "searchResultList"})'
);
} else if (typeof oModel === "undefined" || typeof oModel.config === "undefined") {
// if search model hasn't been instantiated yet (see constructor of SearchCompositeControl) -> do nothing!
return this;
}
if (
typeof resultlViewSettings.resultViewTypes === "undefined" ||
typeof resultlViewSettings.resultViewType === "undefined"
) {
throw Error(
"Search Result View Type Error In SearchCompositeControl:\n\n" +
"One of properties of function parameter is undefined.\n" +
'Valid example: setResultViewSettings({resultViewTypes: ["searchResultList", "searchResultTable"], resultViewType: "searchResultList"})'
);
}
oModel.calculateResultViewSwitchVisibility({
resultViewTypes: resultlViewSettings.resultViewTypes,
resultViewType: resultlViewSettings.resultViewType,
});
this.showMoreFooter.setVisible(this.isShowMoreFooterVisible());
oModel.enableOrDisableMultiSelection();
this.search(); // search will not be retriggered if model /isQueryInvalidated
this.assignDragDropConfig(); // reassign drag&drop config (onAllSearchFinished not called, when search query unchanged)
return this;
}
getControllerName(): string {
return "sap.esh.search.ui.container.Search";
}
/**
* Gets the additional CSS class which was applied to this control
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @returns {string} The additional CSS class which was applied to this control
*/
getCssClass(): string {
return this._cssClass;
}
/**
* Adds an additional CSS class to this control
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @param {string} cssClass The new CSS class
* @returns this
*/
setCssClass(cssClass: string): SearchCompositeControl {
if (cssClass && !this.hasStyleClass(cssClass)) {
this._cssClass = cssClass;
this.addStyleClass(cssClass);
}
return this;
}
/**
* Gets the id of the active data source which is selected in the data source drop down list.
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @this sap.esh.search.ui.SearchCompositeControl
* @since 1.98.0
* @returns {string} The id of currently selected data source.
*/
getDataSource(): string {
if (typeof this.getModelInternal() !== "undefined") {
const oModel = this.getModelInternal() as SearchModel;
const ds = oModel.getDataSource();
return ds.id;
}
}
/**
* Sets the id of the active data source which is selected in the data source drop down list.
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @param {string} dataSource The id of the new to be selected data source
* @param {boolean} [fireQuery=true] If true, fires a search query right away. Set to false for batch updates.
* @param {boolean} [resetTop=true] If true, top will be reset to the default number of visible results.
*/
setDataSource(dataSourceId: string, fireQuery: boolean, resetTop: boolean): SearchCompositeControl {
if (typeof this.getModelInternal() !== "undefined") {
const oModel = this.getModelInternal() as SearchModel;
oModel.setDataSourceById(dataSourceId, fireQuery, resetTop);
}
return this;
}
/**
* Resets the active data source which is selected in the data source drop down list to its default (see 'defaultDataSource'/'exclusiveDataSource').
* @public
* @since 1.124.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @param {boolean} [fireQuery=true] If true, fires a search query right away. Set to 'false' for batch updates.
* @returns {this}
*/
async resetDataSource(fireQuery = true): Promise {
if (typeof this.getModelInternal() !== "undefined") {
const oModel = this.getModelInternal() as SearchModel;
return oModel.resetDataSource(fireQuery); // ToDo: Make 'await' nescessary
}
return this;
}
/**
* Invalidate the search cache and optionally fire a new search.
* Also see functions 'retriggerSearch' and 'invalidateSearchResultCache'.
* @public
* @since 1.124.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @param {boolean} [invalidateQuery=false] Indicates wether the search query should be resetted. Set to 'true' to reset the query.
* @param {boolean} [fireQuery=true] Indicates wether the search query should be sent to the search service or not. Set to 'false', to prevent auto-update of search results.
* @returns {Promise} Returns 'true' if the search query was fired, otherwise 'void'.
*/
async search(invalidateQuery = false, fireQuery = true): Promise {
const internalSearchModel = this.getModelInternal() as SearchModel;
if (invalidateQuery) {
internalSearchModel.invalidateQuery();
}
if (fireQuery) {
return internalSearchModel.fireSearchQuery();
}
}
/**
* Resets the UI / search results (reset search query and fire a new search).
* @public
* @since 1.125.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {Promise}
*/
async resetSearch(): Promise {
const internalSearchModel = this.getModelInternal() as SearchModel;
internalSearchModel.resetQuery();
this.retriggerSearch();
}
/**
* Fire a new search (includes invalidation of search cache).
* To invalidate the search cache w/o triggering a new search, use function 'invalidateSearchResultCache' instead.
* @public
* @since 1.124.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {Promise}
*/
async retriggerSearch(): Promise {
this.search(true, true); // invalidateQuery: true, fireQuery: true
}
/**
* Invalidate the search cache.
* To trigger a search in addition, use function 'retriggerSearch' instead.
* @public
* @since 1.124.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {Promise}
*/
async invalidateSearchResultCache(): Promise {
this.search(true, false); // invalidateQuery: true, fireQuery: false
}
/**
* Gets the search term which is displayed in the search box.
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @returns {string} The search term
*/
getSearchTerm(): string {
if (typeof this.getModelInternal() !== "undefined") {
const oModel = this.getModelInternal() as SearchModel;
return oModel.getSearchBoxTerm();
}
return SearchCompositeControl.getMetadata().getPropertyDefaults()["searchTerm"];
}
/**
* Sets the search term which is displayed in the search box.
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @param {string} searchTerm Sets the new search term
* @param {boolean} [fireQuery=true] Indicates wether the search query should be sent to the search service or not.
* Not sending it could be useful for batch updates, for example if the data source shall also be changed.
* This way, the search service will not have to deal with outdated queries which will improve overall performance.
* @returns this
*/
setSearchTerm(searchTerm: string, fireQuery: boolean): SearchCompositeControl {
if (typeof this.getModelInternal() !== "undefined") {
const oModel = this.getModelInternal() as SearchModel;
oModel.setSearchBoxTerm(searchTerm, fireQuery);
}
return this;
}
/**
* Gets the settings.
* @public
* @since 1.125.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {object} The settings (search configuration)
*/
getConfig(): SearchConfiguration {
if (this.getModelInternal()) {
const oModel = this.getModelInternal() as SearchModel;
return oModel.config;
}
return;
}
/**
* Gets the filter containing all attribute filters currently set and functions to manipulate the filter.
* @public
* @since 1.124.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {object} The filter
*/
getFilter(): Filter {
if (this.getModelInternal()) {
const oModel = this.getModelInternal() as SearchModel;
return oModel.getProperty("/uiFilter");
}
return;
}
/**
* Gets the filter root condition which contains all attribute filters currently set.
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @returns {object} The filter root condition
*/
getFilterRootCondition(): ComplexCondition {
if (this.getModelInternal()) {
const oModel = this.getModelInternal() as SearchModel;
return oModel.getFilterRootCondition();
}
return SearchCompositeControl.getMetadata().getPropertyDefaults()["filterRootCondition"];
}
/**
* Update hierarchy facet
* Updates the one and only (static) hierarchy facet (tree control on facet panel).
* If no hierarchy facet exists, nothing will happen.
* @public
* @since 1.126.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {Promise}
*/
async updateStaticHierarchyFacet(): Promise {
const oModel = this.getModelInternal() as SearchModel;
const dataSourceId = this.getDataSource();
const hierarchyAttributeId = oModel.sinaNext
.getDataSource(dataSourceId)
.getStaticHierarchyAttributeMetadata().id;
if (hierarchyAttributeId) {
const staticHierarchyFacet = oModel
.getProperty("/facets")
.filter((item) => item.attributeId === hierarchyAttributeId);
if (staticHierarchyFacet.length > 0) {
await staticHierarchyFacet[0]?.updateTree();
}
}
}
/**
* Recalculate the placeholder text of search input field. If implemented, this will also call function 'getSearchInputPlaceholderLabel' to get the label for the placeholder text.
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.136.0
* @returns void
*/
recalculateSearchInputPlaceholder(): void {
if (this.getModelInternal()) {
const oModel = this.getModelInternal() as SearchModel;
oModel.calculatePlaceholder();
}
}
/**
* Sets the filter root condition for the search query.
*
* The filter root condition can contain multiple attribute filter which are then combined using AND or OR operators.
* For a more comprehensive explanation and examples, see the description of the filterRootCondition property of this control.
*
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.98.0
* @this sap.esh.search.ui.SearchCompositeControl
* @param {object} filterRootCondition Sets the new filter root condition.
* @param {boolean} [fireQuery=true] Indicates wether the search query should be sent to the search service or not.
* Not sending it could be useful for batch updates, for example if the data source shall also be changed.
* This way, the search service will not have to deal with outdated queries which will improve overall performance.
* @returns this
*/
setFilterRootCondition(
filterRootCondition: ComplexCondition,
fireQuery: boolean
): SearchCompositeControl {
if (this.getModelInternal()) {
const oModel = this.getModelInternal() as SearchModel;
oModel.setFilterRootCondition(filterRootCondition, fireQuery);
}
return this;
}
/**
* Assembles a search URL.
*
* Use this method for the creation of links to the search UI.
*
* @public
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @since 1.103.0
* @this sap.esh.search.ui.SearchCompositeControl
* @param {number} top Number of search results to be displayed.
* @param {object} filter A sina filter object storing the searchterm, datasource and the filter conditions.
* @param {boolean} encodeFilter A boolean whether to encode the URL parameters or not.
* @returns {string} search URL.
* @deprecated As of version 1.120.0, use {@link sap.esh.search.ui.SearchCompositeControl#createSearchNavigationTarget createSearchNavigationTarget} instead.
*/
renderSearchUrlFromParameters(top: number, filter: Filter, encodeFilter: boolean): string {
const model = this.getModelInternal() as SearchModel;
if (!model) {
throw new ProgramError(null, "cannot construct URL because model is undefined");
}
return model.createSearchNavigationTarget({
top: top,
filter: filter,
encodeFilter: encodeFilter,
updateUrl: true, // --> always create navigation target with url (overwrites config.updateUrl)
}).targetUrl;
}
/**
* Create a NavigationTarget instance for filtering/search.
*
* Use this method for the creation a NavigationTarget instance with filter and label.
*
* @public
* @since 1.114.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @param {object} filter A sina filter object storing the searchterm, datasource and the filter conditions.
* @param {string} label The label of the navigation target.
* @returns {object} The NavigationTarget instance. If the configuration parameter updateUrl is true, it contains targetUrl, otherwise it contains targetFunction.
*/
createSearchNavigationTarget(filter: Filter, label: string): NavigationTarget {
const model = this.getModelInternal() as SearchModel;
return model.createSearchNavigationTarget(filter, label);
}
/**
* Create a NavigationTarget instance for hierarchy.
*
* Use this method for the creation a NavigationTarget instance by hierarchical node id value and its label.
*
* @public
* @since 1.114.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @param {object} hierarchyNodeId The hiearchical node id value of the giving datasource or its helper hierarchical datasource.
* @param {string} hierarchyNodeLabel The label of the hiearchical node id.
* @param {object} dataSource The datasource instance.
* @param {string} navigationTargetLabel The label of the NavigatonTarget instance.
* @returns {object} The NavigationTarget instance. If the configuration parameter updateUrl is true, it contains targetUrl, otherwise it contains targetFunction.
*/
createStaticHierarchySearchNavigationTarget(
// Goto Folder
hierarchyNodeId: Value,
hierarchyNodeLabel: string,
dataSource: DataSource,
navigationTargetLabel: string
): NavigationTarget {
const model = this.getModelInternal() as SearchModel;
return model.sinaNext.createStaticHierarchySearchNavigationTarget(
hierarchyNodeId,
hierarchyNodeLabel,
dataSource,
navigationTargetLabel
);
}
/**
* Adds a callback function which is called whenever there is a user triggered event.
*
* @public
* @since 1.120.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @param {object} eventConsumer - This function will be called when the user triggered an UI event. As arguments this
* function will receive the UI event along with details of the event.
* @returns {void}
* @example A simple event consumer which could be passed to the addEventConsumer function
* const logEventConsumer = {
* label: "my log event consumer",
* logEvent: (event) => {
* console.log("user event happened: " + event.type)
* }
* }
* mySearchCompositeControl.addEventConsumer(logEventConsumer);
*/
addEventConsumer(eventConsumer: EventConsumer): void {
const model = this.getModelInternal() as SearchModel;
model?.eventLogger?.addConsumer(eventConsumer);
}
/**
* Returns all event consumers.
*
* @public
* @since 1.120.0
* @this sap.esh.search.ui.SearchCompositeControl
* @memberof sap.esh.search.ui.SearchCompositeControl.prototype
* @returns {Array