/*! * SAPUI5 * Copyright (c) 2025 SAP SE or an SAP affiliate company. All rights reserved. */ import { MetadataOptions } from "sap/ui/base/ManagedObject"; import { ResultSetItem } from "../../ResultSetApi"; import SearchBasketModel, { SearchBasketApi } from "./SearchBasketModel"; import Context from "sap/ui/model/Context"; import { initialValueUnicode } from "../../uiConstants"; import HBox from "sap/m/HBox"; import Icon from "sap/ui/core/Icon"; import Column from "sap/m/Column"; import { callConfigExit } from "../../error/callConfigExit"; import ColumnListItem from "sap/m/ColumnListItem"; import Text from "sap/m/Text"; import Table, { $TableSettings } from "sap/m/Table"; import Event from "sap/ui/base/Event"; import CustomListItem from "sap/m/CustomListItem"; import { ButtonType, ListMode } from "sap/m/library"; import Link from "sap/m/Link"; import OverflowToolbar from "sap/m/OverflowToolbar"; import Button from "sap/m/Button"; import i18n from "../../i18n"; import { updateSortIcons } from "./SearchBasketSortUtils"; export interface $SearchBasketSapMTableSettings extends $TableSettings { basketConfigurator?: { adaptColumns: (columns: any[]) => any[]; adaptRow: (publicItem: ResultSetItem) => ResultSetItem; }; showDeleteAction?: boolean; selectionMode?: string; // maps to ListMode of sap.m.Table ('Multi' → MultiSelect, 'None' → None) } /** * @namespace sap.esh.search.ui.controls.basket */ export default class SearchBasketSapMTable extends Table implements SearchBasketApi { static readonly metadata: MetadataOptions = { library: "sap.esh.search.ui.controls.basket", aggregations: {}, properties: { /** * An object containing the basket configurator functions 'adaptColumns' and 'adaptRow'. * @since 1.145.0 */ basketConfigurator: { type: "object", group: "Data", }, /** * Whether to show an X (delete) button per row to remove items from the basket. * When true, clicking X removes the item from the basket and, when basketLinkByResultViewItemSelection * is active, also deselects it in the result view. * @since 1.145.0 */ showDeleteAction: { type: "boolean", group: "Appearance", defaultValue: false, }, }, }; constructor(sId?: string, settings?: $SearchBasketSapMTableSettings) { super(sId, settings); let columns = []; if (settings?.basketConfigurator) { columns = callConfigExit("basketInit (basketConfigurator.adaptColumns)", "HAN-AS-INA-UI", () => settings?.basketConfigurator?.adaptColumns([]) ); } const basketModel = new SearchBasketModel({ columns: columns ? columns : [], rows: [], publicItems: [], basketCount: 0, config: { basketConfigurator: settings?.basketConfigurator ? settings.basketConfigurator : null, }, customData: {}, }); // add binding for basket model property "/selectAll" to select/deselect all items in the table basketModel.bindProperty("/selectAll").attachChange((oEvent) => { const selectAll = oEvent.getSource().getValue(); if (selectAll) { this.selectAll(); } else if (typeof selectAll !== "undefined") { this.removeSelections(true); } }); // set basket model this.setModel(basketModel, "eshBasket"); // add default headerToolbar with Remove All button if none provided via settings if (!settings?.headerToolbar) { this.setHeaderToolbar(this._createDefaultToolbar()); } // define group for F6 handling this.data("sap-ui-fastnavgroup", "true", true /* write into DOM */); this.setAlternateRowColors(true); this.addStyleClass("sapUiSmallMarginTop"); this.addStyleClass("sapElisaSearchBasketTable"); this._sortLinks = new Map(); this.bindAggregation("columns", { path: "eshBasket>/columns", factory: (sId: string, context: Context) => { const label = context.getProperty("label"); const path = context.getProperty("path"); const oSortLink = new Link(sId + "-sortLink", { text: label, press: () => { const oBasketModel = this.getModel("eshBasket") as SearchBasketModel; const currentPath = oBasketModel.getProperty("/_sortPath") as string; const currentOrder = oBasketModel.getProperty("/_sortOrder") as string; const nextOrder = currentPath === path && currentOrder === "Ascending" ? "Descending" : "Ascending"; const rows = oBasketModel.getProperty("/rows") as Array>; const getVal = (row: Record, p: string): string => { const val = p.split("/").reduce((obj: unknown, key) => { return obj && typeof obj === "object" ? (obj as Record)[key] : undefined; }, row as unknown); return String(val ?? "").toLowerCase(); }; const sorted = [...rows].sort((a, b) => { const aVal = getVal(a, path); const bVal = getVal(b, path); if (aVal < bVal) return nextOrder === "Ascending" ? -1 : 1; if (aVal > bVal) return nextOrder === "Ascending" ? 1 : -1; return 0; }); oBasketModel.setProperty( "/rows", sorted.map((row) => ({ ...row })) ); oBasketModel.setProperty("/_sortPath", path); oBasketModel.setProperty("/_sortOrder", nextOrder); updateSortIcons(this._sortLinks, path, nextOrder); oBasketModel.logSort(path, nextOrder); }, }); this._sortLinks.set(path, oSortLink); return new Column(sId, { header: oSortLink, }); }, }); this.bindItems({ path: "eshBasket>/rows", factory: (id: string, rowContext: Context) => { const newItem = new ColumnListItem("", {}); newItem.bindAggregation("cells", { path: "eshBasket>/columns", factory: (subPath: string, columnContext: any) => { // const label = columnContext.getProperty("label"); const path = columnContext.getProperty("path"); const hasIcon = columnContext.getProperty("hasIcon"); const iconPath = columnContext.getProperty("iconPath"); // const width = columnContext.getProperty("width"); let template; template = new Text("", { text: { parts: [{ path: `eshBasket>${rowContext.getPath()}/${path}` }], formatter: (text: unknown) => { if (text === null || text === undefined || text === "") { return initialValueUnicode; } return String(text).replace(//g, "").replace(/<\/b>/g, ""); }, }, }); if (hasIcon && iconPath) { template = new HBox({ items: [ new Icon({ src: `{eshBasket>${rowContext.getPath()}/${iconPath}}`, }).addStyleClass("sapUiTinyMarginEnd"), template, ], }); } return template; }, }); /* if (width) { newItem.setWidth(width); } */ return newItem; }, }); if (typeof settings?.selectionMode !== "undefined") { if (settings.selectionMode === "Multi") { this.setMode(ListMode.MultiSelect); } else if (settings.selectionMode === "None") { this.setMode(ListMode.None); } } this.attachSelectionChange(() => { const oBasketModel = this.getModel("eshBasket") as SearchBasketModel; if (oBasketModel.getProperty("/_skipRowSelectionChange")) { return; } /* const selectedIndices = this.getSelectedIndices(); for (let i = 0; i < oBasketModel.getProperty("/rows").length; i++) { oBasketModel.setProperty(`/rows/${i}/selected`, selectedIndices.indexOf(i) !== -1); } */ oBasketModel.syncSearchCompControl(this); }); if (settings?.showDeleteAction) { // add delete action to each row this.setMode(ListMode.Delete); } this.attachDelete((oEvent: Event) => { const oBasketModel = this.getModel("eshBasket") as SearchBasketModel; if (oBasketModel.getProperty("/_skipRowSelectionChange")) { return; } const listItem = oEvent.getParameters()["listItem"] as CustomListItem; const itemKey = listItem.getBindingContext("eshBasket").getObject()["key"]; oBasketModel.removeItemsFromBasket([itemKey], this); oBasketModel.logItemRemove(1); }); } getBasketModel(): SearchBasketModel { return this.getModel("eshBasket") as SearchBasketModel; } // registry of sort links per column path, used to update icons imperatively private _sortLinks: Map; private _clearBasketButton: Button; createClearBasketButton(): Button { if (!this._clearBasketButton) { this._clearBasketButton = new Button(`${this.getId()}-clearBasketButton`, { text: i18n.getText("clearBasket"), tooltip: i18n.getText("clearBasket_tooltip"), icon: "sap-icon://clear-all", type: ButtonType.Transparent, enabled: { parts: [{ path: "eshBasket>/count" }], formatter: (count: number) => count > 0, }, press: () => { const oBasketModel = this.getBasketModel(); const itemCount = oBasketModel.getProperty("/count") as number; oBasketModel.clearBasket(this); updateSortIcons(this._sortLinks, "", ""); oBasketModel.logClear(itemCount); }, }); } return this._clearBasketButton; } private _createDefaultToolbar(): OverflowToolbar { return new OverflowToolbar(`${this.getId()}-defaultToolbar`, { content: [this.createClearBasketButton()], }); } static renderer = { apiVersion: 2, }; }