/*! * SAPUI5 * Copyright (c) 2025 SAP SE or an SAP affiliate company. All rights reserved. */ import HBox from "sap/m/HBox"; import Text from "sap/m/Text"; import Icon from "sap/ui/core/Icon"; import Table, { $TableSettings } from "sap/ui/table/Table"; import Column from "sap/ui/table/Column"; import { callConfigExit } from "../../error/callConfigExit"; import { MetadataOptions } from "sap/ui/base/ManagedObject"; import { initialValueUnicode } from "../../uiConstants"; import { ResultSetItem } from "../../ResultSetApi"; import SearchBasketModel, { SearchBasketApi } from "./SearchBasketModel"; import Context from "sap/ui/model/Context"; import RowActionItem from "sap/ui/table/RowActionItem"; import RowAction from "sap/ui/table/RowAction"; import Event from "sap/ui/base/Event"; import { RowActionType } from "sap/ui/table/library"; import Button from "sap/m/Button"; import { ButtonType } from "sap/m/library"; import i18n from "../../i18n"; import { updateSortIcons } from "./SearchBasketSortUtils"; import Link from "sap/m/Link"; export interface $SearchBasketSapUiTableSettings extends $TableSettings { basketConfigurator?: { adaptColumns: (columns: any[]) => any[]; adaptRow: (publicItem: ResultSetItem) => ResultSetItem; }; showDeleteAction?: boolean; } /** * @namespace sap.esh.search.ui.controls.basket */ export default class SearchBasketSapUiTable 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?: $SearchBasketSapUiTableSettings) { 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.clearSelection(); } }); // set basket model this.setModel(basketModel, "eshBasket"); // 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.bindColumns({ path: "eshBasket>/columns", factory: (id: string, context: Context) => { const label = context.getProperty("label"); const path = context.getProperty("path"); const hasIcon = context.getProperty("hasIcon"); const iconPath = context.getProperty("iconPath"); const width = context.getProperty("width"); let template; template = new Text({ text: { parts: [{ path: `eshBasket>${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>${iconPath}}` }).addStyleClass("sapUiTinyMarginEnd"), template, ], }); } const oSortLink = new Link(`${id}-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); const newColumn = new Column(`${id}`, { label: oSortLink, template: template, }); if (width) { newColumn.setWidth(width); } return newColumn; }, }); this.bindRows({ path: "eshBasket>/rows", }); if (typeof settings?.selectionMode !== "undefined") { // nothing to check here, as sap.ui.table.Table settings provide selectionMode property } this.attachRowSelectionChange((oEvent: Event) => { this.getBasketModel().setProperty("/selectAll", oEvent.getParameters()["selectAll"]); 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) { const rowAction = new RowAction({ items: [ new RowActionItem({ type: RowActionType.Delete, press: (oEvent: Event) => { const oBasketModel = this.getModel("eshBasket") as SearchBasketModel; if (oBasketModel.getProperty("/_skipRowSelectionChange")) { return; } const item = oEvent.getParameters()["item"]; const itemKey = item.getBindingContext("eshBasket").getObject()["key"]; oBasketModel.removeItemsFromBasket([itemKey], this); oBasketModel.logItemRemove(1); }, }), ], }); this.setRowActionTemplate(rowAction); this.setRowActionCount(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; } static renderer = { apiVersion: 2, }; }