**TITLE**: Table Selection Example

**INTRODUCTION**: The Table building block API enables you to select or deselect line items in tables.

**TAGS**: selection, APIs

**STEP**: 1. Display the Selected Items in a Dedicated Grid Control

**DESCRIPTION**: In this example, the selected items from the table are displayed in a grid control below the table. This control is bound to a dedicated JSON model named 'selectionModel'.

**LANGUAGE**: XML

**CODE**:
```xml
<core:FragmentDefinition
	xmlns="sap.m"
	xmlns:customdata="http://schemas.sap.com/sapui5/extension/sap.ui.core.CustomData/1"
	xmlns:f="sap.f"
	xmlns:u="sap.ui.unified"
	xmlns:core="sap.ui.core"
	xmlns:macros="sap.fe.macros"
>
	<VBox>
		<Title text="Select some travels from this list:" level="H2" />
		<macros:Table
			metaPath="/Travel/@com.sap.vocabularies.UI.v1.PresentationVariant#TableSelection"
			readOnly="true"
			type="GridTable"
			selectionMode="ForceMulti"
			threshold="10"
			enableExport="false"
			disableCopyToClipboard="true"
			beforeRebindTable=".onBeforeRebindTable"
			selectionChange=".onTableSelectionChange"
			id="tableForSelection"
		/>
		<Title class="sapUiMediumMarginTop sapUiSmallMarginBottom" text="Your selection (click on a tile to remove):" level="H2" />
		<f:GridContainer items="{selectionModel>/items}">
			<f:layout>
				<f:GridContainerSettings columnSize="12rem" gap="1rem" />
			</f:layout>
			<GenericTile header="{selectionModel>Description}" press=".onTilePress" customdata:ID="{selectionModel>ID}">
				<TileContent unit="{selectionModel>Currency}">
					<NumericContent value="{selectionModel>Price}" withMargin="false" />
				</TileContent>
			</GenericTile>
		</f:GridContainer>
	</VBox>
</core:FragmentDefinition>
```

**STEP**: 2. Update the Selection Model When the Table Selection Changes

**DESCRIPTION**: The 'onTableSelectionChange' event handler is used to update the JSON model when the table selection changes. We iterate over all table items, check if they are selected or not, and update the selection model accordingly.

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import PageController from "sap/fe/core/PageController";
import type CollectionBindingInfo from "sap/fe/macros/CollectionBindingInfo";
import type Table from "sap/fe/macros/Table";
import { type GenericTile$PressEvent } from "sap/m/GenericTile";
import type UI5Event from "sap/ui/base/Event";
import JSONModel from "sap/ui/model/json/JSONModel";

interface SelectionItem {
	ID: string;
	Description: string;
	Price: number;
	Currency: string;
}

export default class TableSelectionController extends PageController {
	onInit(): void {
		super.onInit();

		// Initial selection
		const selectionModel = new JSONModel({
			items: [
				{
					ID: "53657221A8E4645C17002DF03754AB66",
					Description: "Lars Jensen site visit for system rollout support",
					Price: 3164,
					Currency: "USD"
				},
				{
					ID: "6F657221A8E4645C17002DF03754AB66",
					Description: "Sheffield annual review.",
					Price: 1450,
					Currency: "GBP"
				}
			]
		});
		this.getView().setModel(selectionModel, "selectionModel");
	}

	onBeforeRebindTable(event: UI5Event<{ collectionBindingInfo: CollectionBindingInfo }>): void {
		const collectionBindingInfo = event.getParameter("collectionBindingInfo");
		collectionBindingInfo.attachEvent(
			"dataReceived",
			() => {
				this.syncTableSelectionFromJSONModel();
			},
			this
		);
	}

	/**
	 * Synchronizes the selection in the table with the selection in the JSON model.
	 * This is required when new rows are loaded in the table such as after filtering or scrolling
	 * or when the selection in the JSON model is changed such as when a tile is removed.
	 */
	syncTableSelectionFromJSONModel(): void {
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		const selectedItems = selectionModel.getProperty("/items") as SelectionItem[];

		const table = this.byId("tableForSelection") as Table;
		table.getAllCurrentContexts()?.forEach((context) => {
			const id = context.getProperty("TravelUUID");
			const selected = selectedItems.some((item) => item.ID === id);
			context.setSelected(selected);
		});
	}

	/**
	 * When a tile is pressed, remove the corresponding item from the JSON model and deselect it in the table
	 * @param event UI5 event
	 */
	onTilePress(event: GenericTile$PressEvent): void {
		const tilePressed = event.getSource();
		const travelId = tilePressed.data("ID");
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		let selectedItems = selectionModel.getProperty("/items") as SelectionItem[];
		selectedItems = selectedItems.filter((item) => item.ID !== travelId);
		selectionModel.setProperty("/items", selectedItems);
		this.syncTableSelectionFromJSONModel();
	}

	/**
	 * When a row is selected or deselected in the table, update the JSON model accordingly.
	 * @param event UI5 event
	 */
	onTableSelectionChange(event: UI5Event): void {
		const table = event.getSource() as Table;
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		let selectedItems = selectionModel.getProperty("/items") as SelectionItem[];
		table.getAllCurrentContexts()?.forEach((context) => {
			const id = context.getProperty("TravelUUID");
			if (context.isSelected()) {
				if (!selectedItems.some((item) => item.ID === id)) {
					selectedItems.push({
						ID: id,
						Description: context.getProperty("Description"),
						Currency: context.getProperty("CurrencyCode_code"),
						Price: context.getProperty("TotalPrice")
					});
				}
			} else {
				selectedItems = selectedItems.filter((item) => item.ID !== id);
			}
		});
		selectionModel.setProperty("/items", selectedItems);
	}
}
```

**STEP**: 3. Update the Selection Model and the Table Selection When an Item Is Removed from the Selection Grid

**DESCRIPTION**: The 'onTilePress' event handler removes the corresponding item from the JSON model. The table selection is then synchronized with the selection model by calling 'syncTableSelectionFromJSONModel'.

**STEP**: 4. Make Sure the Table Rows Are Properly Selected When the Data Is Loaded

**DESCRIPTION**: Whenever additional data is loaded into the table, make sure that the table selection is synchronized with the selection model. Call 'syncTableSelectionFromJSONModel' when new data is received.

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import PageController from "sap/fe/core/PageController";
import type CollectionBindingInfo from "sap/fe/macros/CollectionBindingInfo";
import type Table from "sap/fe/macros/Table";
import { type GenericTile$PressEvent } from "sap/m/GenericTile";
import type UI5Event from "sap/ui/base/Event";
import JSONModel from "sap/ui/model/json/JSONModel";

interface SelectionItem {
	ID: string;
	Description: string;
	Price: number;
	Currency: string;
}

export default class TableSelectionController extends PageController {
	onInit(): void {
		super.onInit();

		// Initial selection
		const selectionModel = new JSONModel({
			items: [
				{
					ID: "53657221A8E4645C17002DF03754AB66",
					Description: "Lars Jensen site visit for system rollout support",
					Price: 3164,
					Currency: "USD"
				},
				{
					ID: "6F657221A8E4645C17002DF03754AB66",
					Description: "Sheffield annual review.",
					Price: 1450,
					Currency: "GBP"
				}
			]
		});
		this.getView().setModel(selectionModel, "selectionModel");
	}

	onBeforeRebindTable(event: UI5Event<{ collectionBindingInfo: CollectionBindingInfo }>): void {
		const collectionBindingInfo = event.getParameter("collectionBindingInfo");
		collectionBindingInfo.attachEvent(
			"dataReceived",
			() => {
				this.syncTableSelectionFromJSONModel();
			},
			this
		);
	}

	/**
	 * Synchronizes the selection in the table with the selection in the JSON model.
	 * This is required when new rows are loaded in the table such as after filtering or scrolling
	 * or when the selection in the JSON model is changed such as when a tile is removed.
	 */
	syncTableSelectionFromJSONModel(): void {
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		const selectedItems = selectionModel.getProperty("/items") as SelectionItem[];

		const table = this.byId("tableForSelection") as Table;
		table.getAllCurrentContexts()?.forEach((context) => {
			const id = context.getProperty("TravelUUID");
			const selected = selectedItems.some((item) => item.ID === id);
			context.setSelected(selected);
		});
	}

	/**
	 * When a tile is pressed, remove the corresponding item from the JSON model and deselect it in the table
	 * @param event UI5 event
	 */
	onTilePress(event: GenericTile$PressEvent): void {
		const tilePressed = event.getSource();
		const travelId = tilePressed.data("ID");
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		let selectedItems = selectionModel.getProperty("/items") as SelectionItem[];
		selectedItems = selectedItems.filter((item) => item.ID !== travelId);
		selectionModel.setProperty("/items", selectedItems);
		this.syncTableSelectionFromJSONModel();
	}

	/**
	 * When a row is selected or deselected in the table, update the JSON model accordingly.
	 * @param event UI5 event
	 */
	onTableSelectionChange(event: UI5Event): void {
		const table = event.getSource() as Table;
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		let selectedItems = selectionModel.getProperty("/items") as SelectionItem[];
		table.getAllCurrentContexts()?.forEach((context) => {
			const id = context.getProperty("TravelUUID");
			if (context.isSelected()) {
				if (!selectedItems.some((item) => item.ID === id)) {
					selectedItems.push({
						ID: id,
						Description: context.getProperty("Description"),
						Currency: context.getProperty("CurrencyCode_code"),
						Price: context.getProperty("TotalPrice")
					});
				}
			} else {
				selectedItems = selectedItems.filter((item) => item.ID !== id);
			}
		});
		selectionModel.setProperty("/items", selectedItems);
	}
}
```

**STEP**: 5. Provide an Initial Selection (Optional)

**DESCRIPTION**: Fill the selection model with the initial selection.

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import PageController from "sap/fe/core/PageController";
import type CollectionBindingInfo from "sap/fe/macros/CollectionBindingInfo";
import type Table from "sap/fe/macros/Table";
import { type GenericTile$PressEvent } from "sap/m/GenericTile";
import type UI5Event from "sap/ui/base/Event";
import JSONModel from "sap/ui/model/json/JSONModel";

interface SelectionItem {
	ID: string;
	Description: string;
	Price: number;
	Currency: string;
}

export default class TableSelectionController extends PageController {
	onInit(): void {
		super.onInit();

		// Initial selection
		const selectionModel = new JSONModel({
			items: [
				{
					ID: "53657221A8E4645C17002DF03754AB66",
					Description: "Lars Jensen site visit for system rollout support",
					Price: 3164,
					Currency: "USD"
				},
				{
					ID: "6F657221A8E4645C17002DF03754AB66",
					Description: "Sheffield annual review.",
					Price: 1450,
					Currency: "GBP"
				}
			]
		});
		this.getView().setModel(selectionModel, "selectionModel");
	}

	onBeforeRebindTable(event: UI5Event<{ collectionBindingInfo: CollectionBindingInfo }>): void {
		const collectionBindingInfo = event.getParameter("collectionBindingInfo");
		collectionBindingInfo.attachEvent(
			"dataReceived",
			() => {
				this.syncTableSelectionFromJSONModel();
			},
			this
		);
	}

	/**
	 * Synchronizes the selection in the table with the selection in the JSON model.
	 * This is required when new rows are loaded in the table such as after filtering or scrolling
	 * or when the selection in the JSON model is changed such as when a tile is removed.
	 */
	syncTableSelectionFromJSONModel(): void {
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		const selectedItems = selectionModel.getProperty("/items") as SelectionItem[];

		const table = this.byId("tableForSelection") as Table;
		table.getAllCurrentContexts()?.forEach((context) => {
			const id = context.getProperty("TravelUUID");
			const selected = selectedItems.some((item) => item.ID === id);
			context.setSelected(selected);
		});
	}

	/**
	 * When a tile is pressed, remove the corresponding item from the JSON model and deselect it in the table
	 * @param event UI5 event
	 */
	onTilePress(event: GenericTile$PressEvent): void {
		const tilePressed = event.getSource();
		const travelId = tilePressed.data("ID");
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		let selectedItems = selectionModel.getProperty("/items") as SelectionItem[];
		selectedItems = selectedItems.filter((item) => item.ID !== travelId);
		selectionModel.setProperty("/items", selectedItems);
		this.syncTableSelectionFromJSONModel();
	}

	/**
	 * When a row is selected or deselected in the table, update the JSON model accordingly.
	 * @param event UI5 event
	 */
	onTableSelectionChange(event: UI5Event): void {
		const table = event.getSource() as Table;
		const selectionModel = this.getView().getModel("selectionModel") as JSONModel;
		let selectedItems = selectionModel.getProperty("/items") as SelectionItem[];
		table.getAllCurrentContexts()?.forEach((context) => {
			const id = context.getProperty("TravelUUID");
			if (context.isSelected()) {
				if (!selectedItems.some((item) => item.ID === id)) {
					selectedItems.push({
						ID: id,
						Description: context.getProperty("Description"),
						Currency: context.getProperty("CurrencyCode_code"),
						Price: context.getProperty("TotalPrice")
					});
				}
			} else {
				selectedItems = selectedItems.filter((item) => item.ID !== id);
			}
		});
		selectionModel.setProperty("/items", selectedItems);
	}
}
```

Table Selection Example


**TITLE**: Tree Table

**INTRODUCTION**: The TreeTable building block enables you to render a table with hierarchical data.

**TAGS**: tree table, hierarchy, nodes, expand, collapse, nested, RecursiveHierarchy, TreeTable

**STEP**: Hierarchy Definition

**DESCRIPTION**: The Aggregation.RecursiveHierarchy and @Hierarchy.RecursiveHierarchy defines the hierarchy of the tree table.

**LANGUAGE**: CDS

**CODE**:
```cds
service Service {
  @Aggregation.RecursiveHierarchy #ProductsHierarchy: {
    NodeProperty            : ID,
    ParentNavigationProperty: Parent
  }
  @Hierarchy.RecursiveHierarchy #ProductsHierarchy  : {
    ExternalKey           : ID,
    LimitedDescendantCount: LimitedDescendantCount,
    DistanceFromRoot      : DistanceFromRoot,
    DrillState            : DrillState,
    Matched               : Matched,
    MatchedDescendantCount: MatchedDescendantCount
  }
  @Capabilities.FilterRestrictions                  : {NonFilterableProperties: [
    LimitedDescendantCount,
    DistanceFromRoot,
    DrillState,
    Matched,
    MatchedDescendantCount
  ]}
  @Capabilities.SortRestrictions                    : {NonSortableProperties: [
    LimitedDescendantCount,
    DistanceFromRoot,
    DrillState,
    Matched,
    MatchedDescendantCount
  ]}
  entity Products                        @(Common: {SemanticKey: [ID]}) {
    key ID                     : String  @title: 'Identifier'  @Core.Computed;
        Title                  : String  @title: 'Product Title';
        Description            : String  @title: 'Product Description';
        ParentID               : String;
        Parent                 : Association to one Products
                                   on Parent.ID = ParentID;

        @Core.Computed: true
        LimitedDescendantCount : Integer64;

        @Core.Computed: true
        DistanceFromRoot       : Integer64;

        @Core.Computed: true
        DrillState             : String;

        @Core.Computed: true
        Matched                : Boolean;

        @Core.Computed: true
        MatchedDescendantCount : Integer64;
  }

  annotate Products with @UI: {
    LineItem                 : [
      {
        $Type: 'UI.DataField',
        Value: ID,
      },
      {
        $Type: 'UI.DataField',
        Value: Title,
      },
      {
        $Type: 'UI.DataField',
        Value: Description,
      },
    ],
    PresentationVariant      : {
      $Type         : 'UI.PresentationVariantType',
      Visualizations: ['@UI.LineItem', ],
      Text          : 'Hierarchical',
    },
    PresentationVariant #Flat: {
      $Type         : 'UI.PresentationVariantType',
      Visualizations: ['@UI.LineItem#Flat', ],
      Text          : 'Flat list',
    },
    LineItem #Flat           : [
      {
        $Type: 'UI.DataField',
        Value: ID,
      },
      {
        $Type: 'UI.DataField',
        Value: Title,
      },
      {
        $Type: 'UI.DataField',
        Value: Description,
      },
    ],
    HeaderInfo               : {
      $Type         : 'UI.HeaderInfoType',
      TypeName      : 'Product',
      TypeNamePlural: 'Products',
      Title         : {
        $Type: 'UI.DataField',
        Value: ID,
      },
    },
    FieldGroup               : {
      $Type: 'UI.FieldGroupType',
      Data : [
        {
          $Type: 'UI.DataField',
          Value: Description,
        },
        {
          $Type: 'UI.DataField',
          Value: Title,
        },
      ],
    },
    Facets                   : [{
      $Type : 'UI.ReferenceFacet',
      Target: '@UI.FieldGroup',
      Label : 'General',
    }, ],
  };
}
```

**LANGUAGE**: XML

**CODE**:
```xml
<Annotations Target="Service.Products">
	<Annotation Term="Aggregation.RecursiveHierarchy" Qualifier="ProductsHierarchy">
		<Record Type="Aggregation.RecursiveHierarchyType">
			<PropertyValue Property="NodeProperty" PropertyPath="ID" />
			<PropertyValue Property="ParentNavigationProperty" NavigationPropertyPath="Parent" />
		</Record>
	</Annotation>
	<Annotation Term="Hierarchy.RecursiveHierarchy" Qualifier="ProductsHierarchy">
		<Record Type="Hierarchy.RecursiveHierarchyType">
			<PropertyValue Property="ExternalKey" Path="ID" />
			<PropertyValue Property="LimitedDescendantCount" Path="LimitedDescendantCount" />
			<PropertyValue Property="DistanceFromRoot" Path="DistanceFromRoot" />
			<PropertyValue Property="DrillState" Path="DrillState" />
			<PropertyValue Property="Matched" Path="Matched" />
			<PropertyValue Property="MatchedDescendantCount" Path="MatchedDescendantCount" />
		</Record>
	</Annotation>
</Annotations>
```

**STEP**: LineItem Definition

**DESCRIPTION**: The UI.LineItem annotation defines the columns of the table.

**LANGUAGE**: CDS

**CODE**:
```cds
service Service {
  @Aggregation.RecursiveHierarchy #ProductsHierarchy: {
    NodeProperty            : ID,
    ParentNavigationProperty: Parent
  }
  @Hierarchy.RecursiveHierarchy #ProductsHierarchy  : {
    ExternalKey           : ID,
    LimitedDescendantCount: LimitedDescendantCount,
    DistanceFromRoot      : DistanceFromRoot,
    DrillState            : DrillState,
    Matched               : Matched,
    MatchedDescendantCount: MatchedDescendantCount
  }
  @Capabilities.FilterRestrictions                  : {NonFilterableProperties: [
    LimitedDescendantCount,
    DistanceFromRoot,
    DrillState,
    Matched,
    MatchedDescendantCount
  ]}
  @Capabilities.SortRestrictions                    : {NonSortableProperties: [
    LimitedDescendantCount,
    DistanceFromRoot,
    DrillState,
    Matched,
    MatchedDescendantCount
  ]}
  entity Products                        @(Common: {SemanticKey: [ID]}) {
    key ID                     : String  @title: 'Identifier'  @Core.Computed;
        Title                  : String  @title: 'Product Title';
        Description            : String  @title: 'Product Description';
        ParentID               : String;
        Parent                 : Association to one Products
                                   on Parent.ID = ParentID;

        @Core.Computed: true
        LimitedDescendantCount : Integer64;

        @Core.Computed: true
        DistanceFromRoot       : Integer64;

        @Core.Computed: true
        DrillState             : String;

        @Core.Computed: true
        Matched                : Boolean;

        @Core.Computed: true
        MatchedDescendantCount : Integer64;
  }

  annotate Products with @UI: {
    LineItem                 : [
      {
        $Type: 'UI.DataField',
        Value: ID,
      },
      {
        $Type: 'UI.DataField',
        Value: Title,
      },
      {
        $Type: 'UI.DataField',
        Value: Description,
      },
    ],
    PresentationVariant      : {
      $Type         : 'UI.PresentationVariantType',
      Visualizations: ['@UI.LineItem', ],
      Text          : 'Hierarchical',
    },
    PresentationVariant #Flat: {
      $Type         : 'UI.PresentationVariantType',
      Visualizations: ['@UI.LineItem#Flat', ],
      Text          : 'Flat list',
    },
    LineItem #Flat           : [
      {
        $Type: 'UI.DataField',
        Value: ID,
      },
      {
        $Type: 'UI.DataField',
        Value: Title,
      },
      {
        $Type: 'UI.DataField',
        Value: Description,
      },
    ],
    HeaderInfo               : {
      $Type         : 'UI.HeaderInfoType',
      TypeName      : 'Product',
      TypeNamePlural: 'Products',
      Title         : {
        $Type: 'UI.DataField',
        Value: ID,
      },
    },
    FieldGroup               : {
      $Type: 'UI.FieldGroupType',
      Data : [
        {
          $Type: 'UI.DataField',
          Value: Description,
        },
        {
          $Type: 'UI.DataField',
          Value: Title,
        },
      ],
    },
    Facets                   : [{
      $Type : 'UI.ReferenceFacet',
      Target: '@UI.FieldGroup',
      Label : 'General',
    }, ],
  };
}
```

**LANGUAGE**: XML

**CODE**:
```xml
<Annotation Term="UI.LineItem">
	<Collection>
		<Record Type="UI.DataField">
			<PropertyValue Property="Value" Path="ID" />
		</Record>
		<Record Type="UI.DataField">
			<PropertyValue Property="Value" Path="Title" />
		</Record>
		<Record Type="UI.DataField">
			<PropertyValue Property="Value" Path="Description" />
		</Record>
	</Collection>
</Annotation>
```

Tree Table

tree table
hierarchy
nodes
expand
collapse
nested
RecursiveHierarchy
TreeTable


**TITLE**: Draft Handling

**INTRODUCTION**: You can implement draft handling on a custom page using the editFlow controller extension.

**TAGS**: custom page, edit flow, draft

**STEP**: Using the editFlow Controller Extension

**DESCRIPTION**: You must extend the PageController to implement draft handling on a custom page. You can then access the editFlow controller extension through 'this.editFlow'. The editFlow controller provides methods to manage the draft state, such as creating drafts, saving drafts, and canceling drafts.

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import PageController from "sap/fe/core/PageController";
import type TableAPI from "sap/fe/macros/Table";
import type Button from "sap/m/Button";
import MessageBox from "sap/m/MessageBox";
import type UI5Event from "sap/ui/base/Event";
import type ODataV4Context from "sap/ui/model/odata/v4/Context";

export default class SampleController extends PageController {
	public onSelectionChange(event: UI5Event<{ bindingContext: ODataV4Context }, TableAPI>): void {
		const currentContext = event.getParameter("bindingContext") ?? event.getSource().getSelectedContexts()[0];
		if (currentContext?.getObject("IsActiveEntity") === false) {
			this.getView().getModel("ui").setProperty("/isEditable", true);
		} else {
			this.getView().getModel("ui").setProperty("/isEditable", false);
		}
		const model = currentContext.getModel();
		const contextBinding = model.bindContext(currentContext.getPath());
		const targetContext = contextBinding.getBoundContext();

		this.getView().setBindingContext(targetContext);
	}

	public async onEditPressed(this: PageController): Promise<void> {
		const context = this.getView().getBindingContext();
		if (!context) {
			MessageBox.show("You must first select a row before you can press edit!");
		}
		const targetContext = await this.editFlow.editDocument(this.getView().getBindingContext());
		if (targetContext) {
			this.getView().setBindingContext(targetContext);
			(this.getExtensionAPI().byId("travelTable") as TableAPI).refresh();
		}
	}

	public async onSavePressed(this: PageController): Promise<void> {
		await this.editFlow.saveDocument(this.getView().getBindingContext(), {});
		(this.getExtensionAPI().byId("travelTable") as TableAPI).refresh();
	}

	public async onCancelPressed(this: PageController, event: UI5Event<{}, Button>): Promise<void> {
		await this.editFlow.cancelDocument(this.getView().getBindingContext(), { cancelButton: event.getSource() });
		(this.getExtensionAPI().byId("travelTable") as TableAPI).refresh();
	}

	public async onDeletePressed(this: PageController, event: UI5Event<{}, Button>): Promise<void> {
		const context = this.getView().getBindingContext();
		if (!context) {
			MessageBox.show("You must first select a row before you can delete it!");
		}
		await this.editFlow.deleteDocument(context, { skipNavigation: true });
		(this.getExtensionAPI().byId("travelTable") as TableAPI).refresh();
	}
}
```

**STEP**: Implementing a Hook

**DESCRIPTION**: You can implement the hooks provided by the editFlow controller extensions, for example, the onBeforeEdit hook, as described in the following topic.

**ADDITIONAL RELATED CODE BLOCKS**:

**FILE**: Sample.view.xml

**LANGUAGE**: XML

**CODE**:
```xml
<mvc:View
	id="customEditFlow"
	height="100%"
	controllerName="sap.fe.core.fpmExplorer.customEditFlow.Sample"
	xmlns="sap.m"
	xmlns:mvc="sap.ui.core.mvc"
	xmlns:l="sap.ui.layout"
	xmlns:macros="sap.fe.macros"
>
	<Page>
		<content>
			<VBox>
				<MessageStrip
					text="Selecting a table line populates the form fields and sets the edit button to visible."
					class="sapUiSmallMargin"
					type="Information"
				/>
				<l:HorizontalLayout
					id="macroTableContainer"
					allowWrapping="true"
					class="sapUiSmallMarginTopBottom sapUiMediumMarginBeginEnd"
				>
					<macros:Table
						metaPath="/Travel/@com.sap.vocabularies.UI.v1.LineItem"
						readOnly="true"
						selectionMode="None"
						id="travelTable"
						selectionChange=".onSelectionChange"
						rowPress=".onSelectionChange"
					/>
				</l:HorizontalLayout>

				<Panel id="myFormContent" class="sapUiSmallMarginTopBottom sapUiMediumMarginBeginEnd" width="90%">
					<headerToolbar>
						<OverflowToolbar>
							<Title text="Editing the object" />
							<ToolbarSpacer />
							<Button text="Edit" press=".onEditPressed" visible="{= !${ui>/isEditable} }" />
							<Button text="Delete" press=".onDeletePressed" visible="{= !${ui>/isEditable} }" />
						</OverflowToolbar>
					</headerToolbar>
					<content>
						<macros:Form metaPath="/Travel/@com.sap.vocabularies.UI.v1.FieldGroup#ApprovalData" id="formContent" />
						<Toolbar>
							<ToolbarSpacer />
							<Button text="Save" press=".onSavePressed" visible="{ui>/isEditable}" />
							<Button text="Cancel" press=".onCancelPressed" visible="{ui>/isEditable}" />
						</Toolbar>
					</content>
				</Panel>
			</VBox>
		</content>
	</Page>
</mvc:View>

```

**FILE**: service.cds

**LANGUAGE**: CDS

**CODE**:
```cds
using {
  sap,
  managed,
  Currency,
  Country
} from '@sap/cds/common';

using from '../service/annotations';

@cds.autoexpose  @readonly
aspect MasterData {}

service TravelService {
  @odata.draft.enabled
  @Common.SemanticKey: [TravelID]

  entity Travel : managed {
    key TravelUUID    : UUID;

        @Common.Label          : 'ID'
        @Common.Text           : Description
        TravelID      : Integer default 0                         @readonly;

        @Common.Label          : 'Begin Date'
        BeginDate     : Date;

        @Common.Label          : 'End Date'
        EndDate       : Date;

        @Common.Label          : 'Booking Fee'
        BookingFee    : Decimal(10, 2) default 0                  @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label          : 'Total Price'
        TotalPrice    : Decimal(10, 2)                            @readonly  @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label          : 'Currency'
        CurrencyCode  : Association to CurrencyCode default 'EUR' @mandatory;

        @Common.Label          : 'Description'
        Description   : String(1024);

        @Common.Label          : 'Travel Details'
        @UI.MultiLineText
        TravelDetails : String(1024);

        @title                 : 'Status'
        @Common.Text           : TravelStatus.name
        @Common.TextArrangement: #TextOnly
        TravelStatus  : Association to TravelStatus default 'O'   @readonly;

        @title                 : 'Agency'
        @Common.Text           : to_Agency.Name
        @Common.TextArrangement: #TextFirst
        to_Agency     : Association to TravelAgency               @mandatory;

        to_Booking    : Composition of many Booking
                          on to_Booking.to_Travel = $self;
  };

  entity Booking : managed {
    key BookingUUID   : UUID;

        @Common.Label: 'Booking ID'
        BookingID     : Integer                                  @Core.Computed;

        @Common.Label: 'Booking Date'
        BookingDate   : Date;

        @Common.Label: 'Connection'
        ConnectionID  : String(4)                                @mandatory;

        @Common.Label: 'Flight Date'
        FlightDate    : Date                                     @mandatory;

        @Common.Label: 'Flight Price'
        FlightPrice   : Decimal(10, 2)                           @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label: 'Currency'
        CurrencyCode  : Currency default 'EUR'                   @mandatory;

        @Common.Label: 'Status'
        BookingStatus : Association to BookingStatus default 'N' @mandatory;
        to_Travel     : Association to Travel                    @mandatory;

        PaymentMethod : String(1) default 'B'                    @Common              : {
          Label                   : 'Payment Method',
          Text                    : _Payment.Method,
          TextArrangement         : #TextOnly,
          ValueListWithFixedValues: true,
          ValueList               : {
            Label         : 'Payment Method',
            CollectionPath: 'Payment',
            Parameters    : [{
              $Type            : 'Common.ValueListParameterInOut',
              LocalDataProperty: PaymentMethod,
              ValueListProperty: 'code'
            }]
          }
        };
        _Payment      : Association to one Payment
                          on _Payment.code = PaymentMethod;
  };

  entity Payment {
    key code   : String(1) @(Common: {
          Label          : 'Payment Code',
          Text           : Method,
          TextArrangement: #TextFirst
        });
        Method : String    @(
          Core.Immutable: true,
          Common.Label  : 'Payment Method'
        );
  }

  entity TravelStatus : sap.common.CodeList {
    key code        : String(1) @Common.Text: descr;
        name        : String(255);
        descr       : String(1000);
        criticality : Int16     @odata.Type : 'Edm.Byte' enum {
          Neutral  = 0;
          Negative = 1;
          Critical = 2;
          Positive = 3;
          NewItem  = 5
        };
  };

  entity CurrencyCode : sap.common.CodeList {
        @Common.Label: 'Currency Code'
    key code  : String(3);

        @Common.Label: 'Currency Description'
        descr : String(255);
  };

  entity BookingStatus : sap.common.CodeList {
    key code : BookingStatusCode
  };

  entity TravelAgency : MasterData {
        @Common.Label: 'Agency ID'
        @Common.Text : Name
    key AgencyID       : String(6);

        @Common.Label: 'Agency Name'
        Name           : String(80);
        Street         : String(60);

        @Common.Label: 'Postal Code'
        PostalCode     : String(10);

        @Common.Label: 'City'
        City           : String(40);

        @Common.Label: 'Country Code'
        CountryCode    : Country;

        @Common.Label: 'Phone Number'
        PhoneNumber    : String(30);

        @Common.Label: 'Email Address'
        EMailAddress   : String(256);

        @Common.Label: 'Web Address'
        WebAddress     : String(256);
        Rating         : Integer;
        Recommendation : Integer;
  };

  type TravelStatusCode  : String(1) enum {
    Open     = 'O';
    Accepted = 'A';
    Canceled = 'X';
  };

  type BookingStatusCode : String(1) enum {
    New      = 'N';
    Booked   = 'B';
    Canceled = 'X';
  };
}

```

**FILE**: annotations.cds

**LANGUAGE**: CDS

**CODE**:
```cds
using TravelService as service from '../service/service';

annotate service.Travel {
  TravelStatus @Common.ValueListWithFixedValues;
  CurrencyCode @Common.ValueList: {
    CollectionPath: 'Currencies',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: CurrencyCode_code,
        ValueListProperty: 'code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'descr'
      }
    ]
  };
}

annotate service.Travel with @UI.LineItem: [
  {Value: TravelID},
  {
    $Type : 'UI.DataFieldForAnnotation',
    Target: '@UI.ConnectedFields#TripDates'
  },
  {Value: to_Agency_AgencyID},
  {
    Value             : TravelStatus_code,
    Criticality       : TravelStatus.criticality,
    @UI.Importance    : #High,
    @HTML5.CssDefaults: {width: '10em'}
  },
  {Value: BookingFee},
  {Value: TotalPrice}
];

annotate service.Travel with @(UI: {SelectionFields: [
  TravelID,
  to_Agency_AgencyID,
  TravelStatus_code,
  BeginDate,
  EndDate
]});

annotate service.Travel with @Capabilities.FilterRestrictions.FilterExpressionRestrictions: [
  {
    Property          : 'BeginDate',
    AllowedExpressions: 'SingleRange'
  },
  {
    Property          : 'EndDate',
    AllowedExpressions: 'SingleRange'
  }
];

annotate service.Travel with @(UI: {
  FieldGroup #TravelData    : {Data: [
    {Value: TravelID},
    {Value: to_Agency_AgencyID},
    {Value: BeginDate},
    {Value: EndDate}
  ]},
  FieldGroup #ApprovalData  : {Data: [
    {
      $Type      : 'UI.DataField',
      Value      : TravelStatus_code,
      Criticality: TravelStatus.criticality,
      Label      : 'Status'
    },
    {Value: BookingFee},
    {Value: TotalPrice}
  ]},
  FieldGroup #HeaderMain    : {Data: [
    {Value: BeginDate},
    {Value: EndDate},
    {Value: TotalPrice}
  ]},
  ConnectedFields #TripDates: {
    Label   : 'Business Trip Dates',
    Template: '{BeginDate} / {EndDate}',
    Data    : {
      BeginDate: {
        $Type: 'UI.DataField',
        Value: BeginDate
      },
      EndDate  : {
        $Type: 'UI.DataField',
        Value: EndDate
      }
    }
  },
  Facets                    : [
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'TravelData',
      Label : 'Travel Information',
      Target: '@UI.FieldGroup#TravelData',
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'ApprovalData',
      Label : 'Approval Data',
      Target: '@UI.FieldGroup#ApprovalData'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'BookingList',
      Label : 'Bookings',
      Target: 'to_Booking/@UI.LineItem'
    }
  ],
  HeaderFacets              : [{
    $Type : 'UI.ReferenceFacet',
    Target: '@UI.FieldGroup#HeaderMain'
  }],
  HeaderInfo                : {
    TypeName      : 'Travel',
    TypeNamePlural: 'Travels',
    Title         : {
      $Type: 'UI.DataField',
      Value: Description
    },
    Description   : {
      $Type: 'UI.DataField',
      Value: TravelID
    },
    TypeImageUrl  : 'sap-icon://header',
  }
});

annotate service.Travel {
  to_Agency @Common.ValueList: {
    CollectionPath: 'TravelAgency',
    Label         : '',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: to_Agency_AgencyID,
        ValueListProperty: 'AgencyID'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'Name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'Street'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'PostalCode'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'City'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'CountryCode_code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'PhoneNumber'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'EMailAddress'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'WebAddress'
      }
    ]
  };
}

annotate service.TravelAgency with @(UI: {
  DataPoint #Recommendation: {
    Value        : Recommendation,
    TargetValue  : 100.0,
    Title        : 'Recommendation',
    Visualization: #Progress
  },
  DataPoint #Rating        : {
    Value        : Rating,
    TargetValue  : 5.0,
    Title        : 'Rating',
    Visualization: #Rating
  },
  FieldGroup #Contact      : {Data: [{
    $Type : 'UI.DataFieldForAnnotation',
    Target: '@Communication.Contact',
    Label : 'Travel Agency'
  }]}
});

annotate service.TravelAgency with @(
  Communication.Contact : {
    email: [{
      type   : #work,
      address: EMailAddress
    }],
    fn   : Name,
    adr  : [{
      type    : #work,
      code    : PostalCode,
      country : CountryCode_code,
      locality: City
    }]
  },
  Common.IsNaturalPerson: false
);

annotate service.Booking {
  CurrencyCode @Common.ValueList: {
    CollectionPath: 'Currencies',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: CurrencyCode_code,
        ValueListProperty: 'code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'descr'
      }
    ]
  };
}

annotate service.Booking with @(UI: {
  HeaderInfo: {
    TypeName      : 'Booking',
    TypeNamePlural: 'Bookings',
    TypeImageUrl  : 'sap-icon://alert',
    Title         : {
      $Type: 'UI.DataField',
      Value: BookingID
    }
  },
  Facets    : [{
    $Type : 'UI.ReferenceFacet',
    ID    : 'FacetIdentifier1',
    Target: '@UI.FieldGroup'
  }],
  LineItem  : [
    {Value: BookingID},
    {Value: BookingDate},
    {Value: ConnectionID},
    {Value: FlightDate},
    {Value: FlightPrice}
  ],
  FieldGroup: {Data: [
    {
      $Type: 'UI.DataField',
      Value: BookingID
    },
    {
      $Type: 'UI.DataField',
      Value: BookingDate
    },
    {
      $Type: 'UI.DataField',
      Value: FlightDate
    },
    {
      $Type: 'UI.DataField',
      Value: FlightPrice
    }
  ]}
});

annotate service.TravelAgency with @(UI: {FieldGroup #Link: {Data: [{
  $Type: 'UI.DataFieldWithUrl',
  Value: Name,
  Url  : WebAddress
}]}});

```

**FILE**: Travel.json

**LANGUAGE**: JSON

**CODE**:
```json
[
	{
		"TravelUUID": "52657221A8E4645C17002DF03754AB66",
		"TravelID": "00000001",
		"BeginDate": "2025-11-04",
		"EndDate": "2025-11-06",
		"BookingFee": "20.00",
		"TotalPrice": "900.00",
		"CurrencyCode_code": "USD",
		"Description": "Business Trip for Christine, Pierre",
		"TravelDetails": "This business trip includes a two-day customer workshop at the airport. The goal is the evaluation of the customer's use cases and the alignment on the road map for the upcoming year.",
		"TravelStatus_code": "A",
		"to_Agency_AgencyID": "070001"
	},
	{
		"TravelUUID": "53657221A8E4645C17002DF03754AB66",
		"TravelID": "00000002",
		"BeginDate": "2025-12-04",
		"EndDate": "2025-12-16",
		"BookingFee": "80.00",
		"TotalPrice": "3164.00",
		"CurrencyCode_code": "USD",
		"Description": "Lars Jensen site visit for system rollout support",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "O",
		"to_Agency_AgencyID": "070002"
	},
	{
		"TravelUUID": "54657221A8E4645C17002DF03754AB66",
		"TravelID": "00000003",
		"BeginDate": "2025-08-04",
		"EndDate": "2025-08-08",
		"BookingFee": "40.00",
		"TotalPrice": "1871.00",
		"CurrencyCode_code": "USD",
		"Description": "John Smith customer workshop at Acme Corp headquarters",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "O",
		"to_Agency_AgencyID": "070003"
	},
	{
		"TravelUUID": "55657221A8E4645C17002DF03754AB66",
		"TravelID": "00000004",
		"BeginDate": "2025-09-04",
		"EndDate": "2025-09-05",
		"BookingFee": "40.00",
		"TotalPrice": "1240.00",
		"CurrencyCode_code": "USD",
		"Description": "Anna Müller onboarding with new project team in Berlin",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "X",
		"to_Agency_AgencyID": "070003"
	}
]

```

Draft Handling


**TITLE**: Draft Handling

**INTRODUCTION**: A draft is an interim version of a business entity that has not yet been explicitly saved as an active version. SAP Fiori elements supports the creation of apps using draft handling.

 The editFlow controller extension implements all the draft actions (such as creating a draft and saving a draft). It provides hooks you can implement to add your custom logic. It can also be used to implement draft handling in custom pages.

**TAGS**: global patterns, controller extensions, draft handling, extensibility, custom logic

**STEP**: Defining a Controller Extension in the Manifest

**DESCRIPTION**: To implement hooks provided by the editFlow controller extension, you must register a custom controller extension in the manifest.json file for the object page instance you want to extend.

**LANGUAGE**: JSON

**CODE**:
```json
{
	"_version": "1.12.0",
	"sap.app": {
		"id": "sap.fe.core.fpmExplorer",
		"type": "application",
		"i18n": {
			"bundleUrl": "i18n/i18n.properties",
			"supportedLocales": [""],
			"fallbackLocale": ""
		},
		"applicationVersion": {
			"version": "1.0.0"
		},
		"dataSources": {
			"mainService": {
				"uri": "/sap/fe/core/mock/service/",
				"type": "OData",
				"settings": {
					"annotations": [],
					"odataVersion": "4.0"
				}
			}
		}
	},
	"sap.ui5": {
		"dependencies": {
			"minUI5Version": "1.111.1",
			"libs": {
				"sap.ui.core": {},
				"sap.fe.templates": {}
			}
		},
		"extends": {
			"extensions": {
				"sap.ui.controllerExtensions": {
					"sap.fe.templates.ObjectPage.ObjectPageController#sap.fe.core.fpmExplorer.editFlow::Default": {
						"controllerName": "sap.fe.core.fpmExplorer.editFlow.TravelExtend"
					}
				}
			}
		},
		"contentDensities": {
			"compact": true,
			"cozy": true
		},
		"models": {
			"": {
				"dataSource": "mainService",
				"settings": {
					"operationMode": "Server",
					"autoExpandSelect": true,
					"earlyRequests": true
				}
			}
		},
		"rootView": {
			"viewName": "sap.fe.core.fpmExplorer.editFlow.EditFlow"
		},
		"routing": {
			"config": {
				"controlId": "editFlow"
			},
			"routes": [
				{
					"pattern": ":?query:",
					"name": "TravelList",
					"target": "TravelList"
				},
				{
					"pattern": "/Travel({key}):?query:",
					"name": "TravelDetail",
					"target": "TravelDetail"
				}
			],
			"targets": {
				"TravelList": {
					"type": "Component",
					"id": "Default",
					"name": "sap.fe.templates.ListReport",
					"options": {
						"settings": {
							"contextPath": "/Travel",
							"variantManagement": "Page",
							"initialLoad": "Enabled",
							"navigation": {
								"Travel": {
									"detail": {
										"route": "TravelDetail"
									}
								}
							}
						}
					}
				},
				"TravelDetail": {
					"type": "Component",
					"id": "Default",
					"name": "sap.fe.templates.ObjectPage",
					"viewLevel": 1,
					"options": {
						"settings": {
							"contextPath": "/Travel"
						}
					}
				}
			}
		}
	}
}
```

**STEP**: Implementing a Hook

**DESCRIPTION**: In your controller extension, you can implement the hooks provided by the editFlow controller extensions, such as the onBeforeEdit hook.

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import BaseControllerExtension from "sap/fe/core/controllerextensions/BaseControllerExtension";
import type Dialog from "sap/m/Dialog";
import MessageToast from "sap/m/MessageToast";
import JSONModel from "sap/ui/model/json/JSONModel";
import type { default as Context } from "sap/ui/model/odata/v4/Context";

/**
 * @namespace sap.fe.core.fpmExplorer.editFlow
 */
export default class TravelExtend extends BaseControllerExtension {
	static overrides = BaseControllerExtension.createExtensionOverrides<TravelExtend>({
		onInit: function (): void {
			// Sample data
			const data = {
				text: "",
				formVisible: false
			};
			const dialogModel = new JSONModel(data);
			this.base.getView().setModel(dialogModel, "dialog");
		},
		editFlow: {
			async onBeforeEdit(): Promise<void> {
				this.base.getExtensionAPI();
				return this.openDialog("Do you want to edit this travel request?", true);
			},
			async onAfterEdit(): Promise<void> {
				MessageToast.show("You can now edit this travel request.");
			},
			async onBeforeSave(mParameters?: { context?: Context }): Promise<void> {
				if (mParameters?.context) {
					return mParameters?.context.requestProperty(["BookingFee"]).then((result: number[]) => {
						return result[0] > 50
							? this.openDialog(
									"The booking fee exceed our travel policy - do you really want to save the travel request?",
									true
							  )
							: Promise.resolve();
					});
				}
			},

			async onAfterSave(_mParameters?: { context?: Context }): Promise<void> {
				MessageToast.show("The travel request was successfully saved.");
			},

			onBeforeDiscard: function (): Promise<void> {
				return this.openDialog("Are you sure you want to discard this travel request?");
			}
		}
	});

	/**
	 * Opens a confirmation dialog.
	 * @param text The text to display in the dialog
	 * @param formVisible Whether to show the form in the dialog
	 * @param fnAction Optional action function to execute on confirmation
	 * @returns Promise that resolves on confirmation or rejects on cancellation
	 */
	public async openDialog(text: string, formVisible?: boolean, fnAction?: () => void): Promise<void> {
		const dialogModel = this.base.getView().getModel("dialog") as JSONModel;
		const data = dialogModel.getData();
		data.text = text;
		data.formVisible = formVisible !== undefined ? formVisible : false;
		dialogModel.setData(data);
		// Use building blocks in an XML fragment using the loadFragment method from the SAP Fiori elements ExtensionAPI
		const approveDialog = (await this.base.getExtensionAPI().loadFragment({
			id: "myAcceptDialog",
			name: "sap.fe.core.fpmExplorer.editFlow.Dialog",
			controller: this
		})) as Dialog;

		return new Promise((resolve, reject) => {
			approveDialog.getBeginButton().attachPress(() => {
				approveDialog.close();
				if (fnAction) {
					fnAction();
				}
				resolve();
			});
			approveDialog.getEndButton().attachPress(() => {
				approveDialog.close().destroy();
				reject();
			});
			approveDialog.attachAfterClose(() => {
				approveDialog.destroy();
				reject();
			});
			approveDialog.open();
		});
	}
}
```

**STEP**: Invoking Extension Actions

**DESCRIPTION**: You can use the editFlow controller extension to implement extension actions as described in the following topic:

**STEP**: Draft Handling on a Custom Page

**DESCRIPTION**: You can use the editFlow controller extension to implement draft handling on custom pages as described in the following topic:

**ADDITIONAL RELATED CODE BLOCKS**:

**FILE**: Dialog.fragment.xml

**LANGUAGE**: XML

**CODE**:
```xml
<core:FragmentDefinition
	xmlns="sap.m"
	xmlns:macros="sap.fe.macros"
	xmlns:l="sap.ui.layout"
	xmlns:f="sap.ui.layout.form"
	xmlns:core="sap.ui.core"
>
	<Dialog title="Confirmation" id="confirmationDialog" contentWidth="38rem">
		<content>
			<MessageStrip
				id="message"
				text="{dialog>/text}"
				type="Information"
				showIcon='{DialogProperty}'
				class="sapUiSmallMarginTop sapUiSmallMarginBottom sapUiSmallMarginBeginEnd"
			/>
			<macros:Form metaPath="/Travel/@com.sap.vocabularies.UI.v1.FieldGroup#ApprovalData" title="Travel Data" readOnly="true" />
		</content>
		<beginButton>
			<Button id="continue" text="Continue" press="onConfirm" type="Emphasized" />
		</beginButton>
		<endButton>
			<Button id="cancel" press="onCancel" text="Cancel" />
		</endButton>
	</Dialog>
</core:FragmentDefinition>

```

**FILE**: service.cds

**LANGUAGE**: CDS

**CODE**:
```cds
using {
  sap,
  managed,
  Currency,
  Country
} from '@sap/cds/common';

using from '../service/annotations';

@cds.autoexpose  @readonly
aspect MasterData {}

service TravelService {
  @odata.draft.enabled
  @Common.SemanticKey: [TravelID]

  entity Travel : managed {
    key TravelUUID    : UUID;

        @Common.Label          : 'ID'
        @Common.Text           : Description
        TravelID      : Integer default 0                         @readonly;

        @Common.Label          : 'Begin Date'
        BeginDate     : Date;

        @Common.Label          : 'End Date'
        EndDate       : Date;

        @Common.Label          : 'Booking Fee'
        BookingFee    : Decimal(10, 2) default 0                  @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label          : 'Total Price'
        TotalPrice    : Decimal(10, 2)                            @readonly  @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label          : 'Currency'
        CurrencyCode  : Association to CurrencyCode default 'EUR' @mandatory;

        @Common.Label          : 'Description'
        Description   : String(1024);

        @Common.Label          : 'Travel Details'
        @UI.MultiLineText
        TravelDetails : String(1024);

        @title                 : 'Status'
        @Common.Text           : TravelStatus.name
        @Common.TextArrangement: #TextOnly
        TravelStatus  : Association to TravelStatus default 'O'   @readonly;

        @title                 : 'Agency'
        @Common.Text           : to_Agency.Name
        @Common.TextArrangement: #TextFirst
        to_Agency     : Association to TravelAgency               @mandatory;

        to_Booking    : Composition of many Booking
                          on to_Booking.to_Travel = $self;
  };

  entity Booking : managed {
    key BookingUUID   : UUID;

        @Common.Label: 'Booking ID'
        BookingID     : Integer                                  @Core.Computed;

        @Common.Label: 'Booking Date'
        BookingDate   : Date;

        @Common.Label: 'Connection'
        ConnectionID  : String(4)                                @mandatory;

        @Common.Label: 'Flight Date'
        FlightDate    : Date                                     @mandatory;

        @Common.Label: 'Flight Price'
        FlightPrice   : Decimal(10, 2)                           @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label: 'Currency'
        CurrencyCode  : Currency default 'EUR'                   @mandatory;

        @Common.Label: 'Status'
        BookingStatus : Association to BookingStatus default 'N' @mandatory;
        to_Travel     : Association to Travel                    @mandatory;

        PaymentMethod : String(1) default 'B'                    @Common              : {
          Label                   : 'Payment Method',
          Text                    : _Payment.Method,
          TextArrangement         : #TextOnly,
          ValueListWithFixedValues: true,
          ValueList               : {
            Label         : 'Payment Method',
            CollectionPath: 'Payment',
            Parameters    : [{
              $Type            : 'Common.ValueListParameterInOut',
              LocalDataProperty: PaymentMethod,
              ValueListProperty: 'code'
            }]
          }
        };
        _Payment      : Association to one Payment
                          on _Payment.code = PaymentMethod;
  };

  entity Payment {
    key code   : String(1) @(Common: {
          Label          : 'Payment Code',
          Text           : Method,
          TextArrangement: #TextFirst
        });
        Method : String    @(
          Core.Immutable: true,
          Common.Label  : 'Payment Method'
        );
  }

  entity TravelStatus : sap.common.CodeList {
    key code        : String(1) @Common.Text: descr;
        name        : String(255);
        descr       : String(1000);
        criticality : Int16     @odata.Type : 'Edm.Byte' enum {
          Neutral  = 0;
          Negative = 1;
          Critical = 2;
          Positive = 3;
          NewItem  = 5
        };
  };

  entity CurrencyCode : sap.common.CodeList {
        @Common.Label: 'Currency Code'
    key code  : String(3);

        @Common.Label: 'Currency Description'
        descr : String(255);
  };

  entity BookingStatus : sap.common.CodeList {
    key code : BookingStatusCode
  };

  entity TravelAgency : MasterData {
        @Common.Label: 'Agency ID'
        @Common.Text : Name
    key AgencyID       : String(6);

        @Common.Label: 'Agency Name'
        Name           : String(80);
        Street         : String(60);

        @Common.Label: 'Postal Code'
        PostalCode     : String(10);

        @Common.Label: 'City'
        City           : String(40);

        @Common.Label: 'Country Code'
        CountryCode    : Country;

        @Common.Label: 'Phone Number'
        PhoneNumber    : String(30);

        @Common.Label: 'Email Address'
        EMailAddress   : String(256);

        @Common.Label: 'Web Address'
        WebAddress     : String(256);
        Rating         : Integer;
        Recommendation : Integer;
  };

  type TravelStatusCode  : String(1) enum {
    Open     = 'O';
    Accepted = 'A';
    Canceled = 'X';
  };

  type BookingStatusCode : String(1) enum {
    New      = 'N';
    Booked   = 'B';
    Canceled = 'X';
  };
}

```

**FILE**: annotations.cds

**LANGUAGE**: CDS

**CODE**:
```cds
using TravelService as service from '../service/service';

annotate service.Travel {
  TravelStatus @Common.ValueListWithFixedValues;
  CurrencyCode @Common.ValueList: {
    CollectionPath: 'Currencies',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: CurrencyCode_code,
        ValueListProperty: 'code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'descr'
      }
    ]
  };
}

annotate service.Travel with @UI.LineItem: [
  {Value: TravelID},
  {
    $Type : 'UI.DataFieldForAnnotation',
    Target: '@UI.ConnectedFields#TripDates'
  },
  {Value: to_Agency_AgencyID},
  {
    Value             : TravelStatus_code,
    Criticality       : TravelStatus.criticality,
    @UI.Importance    : #High,
    @HTML5.CssDefaults: {width: '10em'}
  },
  {Value: BookingFee},
  {Value: TotalPrice}
];

annotate service.Travel with @(UI: {SelectionFields: [
  TravelID,
  to_Agency_AgencyID,
  TravelStatus_code,
  BeginDate,
  EndDate
]});

annotate service.Travel with @Capabilities.FilterRestrictions.FilterExpressionRestrictions: [
  {
    Property          : 'BeginDate',
    AllowedExpressions: 'SingleRange'
  },
  {
    Property          : 'EndDate',
    AllowedExpressions: 'SingleRange'
  }
];

annotate service.Travel with @(UI: {
  FieldGroup #TravelData    : {Data: [
    {Value: TravelID},
    {Value: to_Agency_AgencyID},
    {Value: BeginDate},
    {Value: EndDate}
  ]},
  FieldGroup #ApprovalData  : {Data: [
    {
      $Type      : 'UI.DataField',
      Value      : TravelStatus_code,
      Criticality: TravelStatus.criticality,
      Label      : 'Status'
    },
    {Value: BookingFee},
    {Value: TotalPrice}
  ]},
  FieldGroup #HeaderMain    : {Data: [
    {Value: BeginDate},
    {Value: EndDate},
    {Value: TotalPrice}
  ]},
  ConnectedFields #TripDates: {
    Label   : 'Business Trip Dates',
    Template: '{BeginDate} / {EndDate}',
    Data    : {
      BeginDate: {
        $Type: 'UI.DataField',
        Value: BeginDate
      },
      EndDate  : {
        $Type: 'UI.DataField',
        Value: EndDate
      }
    }
  },
  Facets                    : [
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'TravelData',
      Label : 'Travel Information',
      Target: '@UI.FieldGroup#TravelData',
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'ApprovalData',
      Label : 'Approval Data',
      Target: '@UI.FieldGroup#ApprovalData'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'BookingList',
      Label : 'Bookings',
      Target: 'to_Booking/@UI.LineItem'
    }
  ],
  HeaderFacets              : [{
    $Type : 'UI.ReferenceFacet',
    Target: '@UI.FieldGroup#HeaderMain'
  }],
  HeaderInfo                : {
    TypeName      : 'Travel',
    TypeNamePlural: 'Travels',
    Title         : {
      $Type: 'UI.DataField',
      Value: Description
    },
    Description   : {
      $Type: 'UI.DataField',
      Value: TravelID
    },
    TypeImageUrl  : 'sap-icon://header',
  }
});

annotate service.Travel {
  to_Agency @Common.ValueList: {
    CollectionPath: 'TravelAgency',
    Label         : '',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: to_Agency_AgencyID,
        ValueListProperty: 'AgencyID'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'Name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'Street'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'PostalCode'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'City'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'CountryCode_code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'PhoneNumber'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'EMailAddress'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'WebAddress'
      }
    ]
  };
}

annotate service.TravelAgency with @(UI: {
  DataPoint #Recommendation: {
    Value        : Recommendation,
    TargetValue  : 100.0,
    Title        : 'Recommendation',
    Visualization: #Progress
  },
  DataPoint #Rating        : {
    Value        : Rating,
    TargetValue  : 5.0,
    Title        : 'Rating',
    Visualization: #Rating
  },
  FieldGroup #Contact      : {Data: [{
    $Type : 'UI.DataFieldForAnnotation',
    Target: '@Communication.Contact',
    Label : 'Travel Agency'
  }]}
});

annotate service.TravelAgency with @(
  Communication.Contact : {
    email: [{
      type   : #work,
      address: EMailAddress
    }],
    fn   : Name,
    adr  : [{
      type    : #work,
      code    : PostalCode,
      country : CountryCode_code,
      locality: City
    }]
  },
  Common.IsNaturalPerson: false
);

annotate service.Booking {
  CurrencyCode @Common.ValueList: {
    CollectionPath: 'Currencies',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: CurrencyCode_code,
        ValueListProperty: 'code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'descr'
      }
    ]
  };
}

annotate service.Booking with @(UI: {
  HeaderInfo: {
    TypeName      : 'Booking',
    TypeNamePlural: 'Bookings',
    TypeImageUrl  : 'sap-icon://alert',
    Title         : {
      $Type: 'UI.DataField',
      Value: BookingID
    }
  },
  Facets    : [{
    $Type : 'UI.ReferenceFacet',
    ID    : 'FacetIdentifier1',
    Target: '@UI.FieldGroup'
  }],
  LineItem  : [
    {Value: BookingID},
    {Value: BookingDate},
    {Value: ConnectionID},
    {Value: FlightDate},
    {Value: FlightPrice}
  ],
  FieldGroup: {Data: [
    {
      $Type: 'UI.DataField',
      Value: BookingID
    },
    {
      $Type: 'UI.DataField',
      Value: BookingDate
    },
    {
      $Type: 'UI.DataField',
      Value: FlightDate
    },
    {
      $Type: 'UI.DataField',
      Value: FlightPrice
    }
  ]}
});

annotate service.TravelAgency with @(UI: {FieldGroup #Link: {Data: [{
  $Type: 'UI.DataFieldWithUrl',
  Value: Name,
  Url  : WebAddress
}]}});

```

**FILE**: Travel.json

**LANGUAGE**: JSON

**CODE**:
```json
[
	{
		"TravelUUID": "52657221A8E4645C17002DF03754AB66",
		"TravelID": "00000001",
		"BeginDate": "2025-11-04",
		"EndDate": "2025-11-06",
		"BookingFee": "20.00",
		"TotalPrice": "900.00",
		"CurrencyCode_code": "USD",
		"Description": "Business Trip for Christine, Pierre",
		"TravelDetails": "This business trip includes a two-day customer workshop at the airport. The goal is the evaluation of the customer's use cases and the alignment on the road map for the upcoming year.",
		"TravelStatus_code": "A",
		"to_Agency_AgencyID": "070001"
	},
	{
		"TravelUUID": "53657221A8E4645C17002DF03754AB66",
		"TravelID": "00000002",
		"BeginDate": "2025-12-04",
		"EndDate": "2025-12-16",
		"BookingFee": "80.00",
		"TotalPrice": "3164.00",
		"CurrencyCode_code": "USD",
		"Description": "Lars Jensen site visit for system rollout support",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "O",
		"to_Agency_AgencyID": "070002"
	},
	{
		"TravelUUID": "54657221A8E4645C17002DF03754AB66",
		"TravelID": "00000003",
		"BeginDate": "2025-08-04",
		"EndDate": "2025-08-08",
		"BookingFee": "40.00",
		"TotalPrice": "1871.00",
		"CurrencyCode_code": "USD",
		"Description": "John Smith customer workshop at Acme Corp headquarters",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "O",
		"to_Agency_AgencyID": "070003"
	},
	{
		"TravelUUID": "55657221A8E4645C17002DF03754AB66",
		"TravelID": "00000004",
		"BeginDate": "2025-09-04",
		"EndDate": "2025-09-05",
		"BookingFee": "40.00",
		"TotalPrice": "1240.00",
		"CurrencyCode_code": "USD",
		"Description": "Anna Müller onboarding with new project team in Berlin",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "X",
		"to_Agency_AgencyID": "070003"
	}
]

```

Draft Handling

global patterns
controller extensions
draft handling
extensibility
custom logic


**TITLE**: Extending SAP Fiori Elements-Based Apps

**INTRODUCTION**: No introduction

**TAGS**: list report extensions, custom header, custom view, KPI tags, object page extensions, custom section, custom subsection, custom action, custom header facet

**STEP**: Accessing the SAP Fiori Elements ExtensionAPI

**DESCRIPTION**:

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import type ObjectPageExtensionAPI from "sap/fe/templates/ObjectPage/ExtensionAPI";

/**
 * Extension API implementation with custom accept method.
 */
const customImplementation = {
	/**
	 * Accepts the current state and refreshes the data.
	 */
	accept(this: ObjectPageExtensionAPI): void {
		// ... custom code here
		// "this" is the Fiori elements ExtensionAPI
		this.refresh();
	}
};

export default customImplementation;
```

**STEP**: Loading a Custom Controller in XML

**DESCRIPTION**: A simple way to make use of custom coding as event handlers or formatters is to require a controller within your XML and give it a name. You can then use this controller coding with the given name as shown in the following sample code:

**LANGUAGE**: XML

**CODE**:
```xml
<core:FragmentDefinition xmlns:core="sap.ui.core" xmlns="sap.m">
	<HBox core:require="{handler: 'myApp/ext/myCustomSectionHandler'}">
		<Button text="Accept" press="handler.accept" type="Positive" />
	</HBox>
</core:FragmentDefinition>
```

**STEP**: Implementing a Controller Extension

**DESCRIPTION**: You can extend lifecycle hooks or hooks provided by SAP Fiori elements in a controller extension. You can also add your own methods that can be used as formatters and event handlers.
Note: You can't bundle formatter and event handler methods into an object of methods. Instead, define the methods at the top level of the controller extension.
The following code block shows an example controller extension:

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import BaseControllerExtension from "sap/fe/core/controllerextensions/BaseControllerExtension";
import type ObjectPageController from "sap/fe/templates/ObjectPage/ObjectPageController.controller";

/**
 * Controller extension that demonstrates lifecycle hooks and custom methods.
 * @namespace sap.fe.core.fpmExplorer.guidanceControllerExtensions
 */
export default class OPExtend extends BaseControllerExtension<ObjectPageController> {
	static overrides = BaseControllerExtension.createExtensionOverrides<OPExtend>({
		/**
		 * Initialization hook that gets called when the controller is initialized.
		 */
		onInit(): void {
			// you can access the Fiori elements extensionAPI through the base controller
			const extensionAPI = this.base.getExtensionAPI();
			// ... custom code here
		},
		editFlow: {
			/**
			 * Hook that gets called before the save action is executed.
			 */
			onBeforeSave(): Promise<void> {
				// you can access the Fiori elements extensionAPI through the base controller
				const extensionAPI = this.base.getExtensionAPI();
				// ... custom code here
				return Promise.resolve();
			}
		}
	});

	/**
	 * Applies initial state configuration.
	 */
	public applyInitialStateOnly(): boolean {
		// ... custom code here
		return false;
	}

	/**
	 * Custom formatter method for field formatting.
	 * @returns Formatted field value
	 */
	public formatMyField(): string {
		// ... custom code here
		return "";
	}

	/**
	 * Custom accept method for handling acceptance logic.
	 */
	public accept(): void {
		// you can access the Fiori elements extensionAPI through the base controller
		const extensionAPI = this.base.getExtensionAPI();
		// ... custom code here
	}
}
```

**STEP**: Defining a Controller Extension

**DESCRIPTION**: You define the controller extensions in the manifest.json file, as shown in the sample code below. The first entry extends the list report controller with one controller extension. The second entry overrides one specific instance of the object page controller, where the ID is constructed using the pattern YourApplicationID::ComponentID with multiple controller extensions. In case of multiple controller extensions, you must use controllerNames instead of controllerName.

**LANGUAGE**: JSON

**CODE**:
```json
{
	"sap.ui5": {
		"extends": {
			"extensions": {
				"sap.ui.controllerExtensions": {
					"sap.fe.templates.ListReport.ListReportController": {
						"controllerName": "myApp.ext.LRExtend"
					},
					"sap.fe.templates.ObjectPage.ObjectPageController#myAppID::CustomerDetails": {
						"controllerNames": ["myApp.ext.OPExtend", "myApp.ext.CustomerDetailsExtend"]
					}
				}
			}
		}
	}
}
```

**STEP**: Using Event Handlers and Formatters

**DESCRIPTION**: You can use the event handlers and formatters that were added to the controller extension within XML fragments using the following syntax: .extension.yourExtension.method. This is also shown in the following sample code:

**LANGUAGE**: XML

**CODE**:
```xml
<HBox>
	<Button text="Accept" press=".extension.myApp.ext.OPExtend.accept" type="Positive" />
	<Text text="{path: 'TotalNetAmount', formatter:'.extension.myApp.ext.OPExtend.myFormatter' }" />
</HBox>
```

**STEP**: Using Building Blocks in an XML Fragment

**DESCRIPTION**: You can use building blocks in an XML fragment with the loadFragment method from the SAP Fiori elements extension API. You can access the extension API using this.base.getExtensionAPI on the controller extension, as shown in the following sample code:

**LANGUAGE**: TypeScript

**CODE**:
```typescript
import type ObjectPageController from "sap/fe/templates/ObjectPage/ObjectPageController.controller";
import MessageToast from "sap/m/MessageToast";
import ControllerExtension from "sap/ui/core/mvc/ControllerExtension";

/**
 * Controller extension that demonstrates loading fragments.
 */
export default class OPExtend extends ControllerExtension {
	protected base!: ObjectPageController;

	/**
	 * Handler for create press event that loads a custom dialog fragment.
	 */
	async onCreatePress(): Promise<void> {
		try {
			const extensionAPI = this.base.getExtensionAPI();
			const fragmentOptions = {
				id: "myCustomDialog",
				name: "fpmExplorer.fragments.customDialog",
				controller: this
			};

			await extensionAPI.loadFragment(fragmentOptions);
			MessageToast.show("Dialog fragment loaded successfully");
		} catch (error) {
			MessageToast.show("Failed to load dialog fragment");
		}
	}
}
```

Extending SAP Fiori Elements-Based Apps

list report extensions
custom header
custom view
KPI tags
object page extensions
custom section
custom subsection
custom action
custom header facet


**TITLE**: Inline Edit

**INTRODUCTION**: You can render fields or field groups that users can edit inline without switching the entire business entity to draft.

**TAGS**: inlineEdit, inline edit

**STEP**: Implementation

**DESCRIPTION**: To implement inline editing, specify either a list of fields disabled for inline editing or a list of fields enabled for inline editing. If you specify both, the system considers fields that are part of the allow-list and not part of the block-list.

**LANGUAGE**: JSON

**CODE**:
```json
{
	"_version": "1.12.0",
	"sap.app": {
		"id": "sap.fe.core.fpmExplorer",
		"type": "application",
		"i18n": {
			"bundleUrl": "i18n/i18n.properties",
			"supportedLocales": [""],
			"fallbackLocale": ""
		},
		"applicationVersion": {
			"version": "1.0.0"
		},
		"dataSources": {
			"mainService": {
				"uri": "/sap/fe/core/mock/service/",
				"type": "OData",
				"settings": {
					"annotations": [],
					"odataVersion": "4.0"
				}
			}
		}
	},
	"sap.ui5": {
		"dependencies": {
			"minUI5Version": "1.111.1",
			"libs": {
				"sap.ui.core": {},
				"sap.fe.templates": {}
			}
		},
		"contentDensities": {
			"compact": true,
			"cozy": true
		},
		"models": {
			"": {
				"dataSource": "mainService",
				"settings": {
					"operationMode": "Server",
					"autoExpandSelect": true,
					"earlyRequests": true
				}
			}
		},
		"rootView": {
			"viewName": "sap.fe.core.fpmExplorer.inlineEdit.InlineEdit"
		},
		"routing": {
			"config": {
				"controlId": "inlineEdit"
			},
			"routes": [
				{
					"pattern": ":?query:",
					"name": "TravelList",
					"target": "TravelList"
				},
				{
					"pattern": "/Travel({key}):?query:",
					"name": "TravelDetail",
					"target": "TravelDetail"
				}
			],
			"targets": {
				"TravelList": {
					"type": "Component",
					"id": "Default",
					"name": "sap.fe.templates.ListReport",
					"options": {
						"settings": {
							"contextPath": "/Travel",
							"variantManagement": "Page",
							"initialLoad": "Enabled",
							"navigation": {
								"Travel": {
									"detail": {
										"route": "TravelDetail"
									}
								}
							}
						}
					}
				},
				"TravelDetail": {
					"type": "Component",
					"id": "Default",
					"name": "sap.fe.templates.ObjectPage",
					"viewLevel": 1,
					"options": {
						"settings": {
							"contextPath": "/Travel",
							"inlineEdit": {
								"disabledFields": ["BookingFee"],
								"enabledFields": [
									"@com.sap.vocabularies.UI.v1.FieldGroup#TravelData",
									"@com.sap.vocabularies.UI.v1.FieldGroup#ApprovalData",
									"to_Booking/FlightDate"
								],
								"connectedFields": [["BeginDate", "EndDate"]]
							}
						}
					}
				}
			}
		}
	}
}
```

**ADDITIONAL RELATED CODE BLOCKS**:

**FILE**: service.cds

**LANGUAGE**: CDS

**CODE**:
```cds
using {
  sap,
  managed,
  Currency,
  Country
} from '@sap/cds/common';

using from '../service/annotations';

@cds.autoexpose  @readonly
aspect MasterData {}

service TravelService {
  @odata.draft.enabled
  @Common.SemanticKey: [TravelID]

  entity Travel : managed {
    key TravelUUID    : UUID;

        @Common.Label          : 'ID'
        @Common.Text           : Description
        TravelID      : Integer default 0                         @readonly;

        @Common.Label          : 'Begin Date'
        BeginDate     : Date;

        @Common.Label          : 'End Date'
        EndDate       : Date;

        @Common.Label          : 'Booking Fee'
        BookingFee    : Decimal(10, 2) default 0                  @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label          : 'Total Price'
        TotalPrice    : Decimal(10, 2)                            @readonly  @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label          : 'Currency'
        CurrencyCode  : Association to CurrencyCode default 'EUR' @mandatory;

        @Common.Label          : 'Description'
        Description   : String(1024);

        @Common.Label          : 'Travel Details'
        @UI.MultiLineText
        TravelDetails : String(1024);

        @title                 : 'Status'
        @Common.Text           : TravelStatus.name
        @Common.TextArrangement: #TextOnly
        TravelStatus  : Association to TravelStatus default 'O'   @readonly;

        @title                 : 'Agency'
        @Common.Text           : to_Agency.Name
        @Common.TextArrangement: #TextFirst
        to_Agency     : Association to TravelAgency               @mandatory;

        to_Booking    : Composition of many Booking
                          on to_Booking.to_Travel = $self;
  };

  entity Booking : managed {
    key BookingUUID   : UUID;

        @Common.Label: 'Booking ID'
        BookingID     : Integer                                  @Core.Computed;

        @Common.Label: 'Booking Date'
        BookingDate   : Date;

        @Common.Label: 'Connection'
        ConnectionID  : String(4)                                @mandatory;

        @Common.Label: 'Flight Date'
        FlightDate    : Date                                     @mandatory;

        @Common.Label: 'Flight Price'
        FlightPrice   : Decimal(10, 2)                           @Measures.ISOCurrency: CurrencyCode_code;

        @Common.Label: 'Currency'
        CurrencyCode  : Currency default 'EUR'                   @mandatory;

        @Common.Label: 'Status'
        BookingStatus : Association to BookingStatus default 'N' @mandatory;
        to_Travel     : Association to Travel                    @mandatory;

        PaymentMethod : String(1) default 'B'                    @Common              : {
          Label                   : 'Payment Method',
          Text                    : _Payment.Method,
          TextArrangement         : #TextOnly,
          ValueListWithFixedValues: true,
          ValueList               : {
            Label         : 'Payment Method',
            CollectionPath: 'Payment',
            Parameters    : [{
              $Type            : 'Common.ValueListParameterInOut',
              LocalDataProperty: PaymentMethod,
              ValueListProperty: 'code'
            }]
          }
        };
        _Payment      : Association to one Payment
                          on _Payment.code = PaymentMethod;
  };

  entity Payment {
    key code   : String(1) @(Common: {
          Label          : 'Payment Code',
          Text           : Method,
          TextArrangement: #TextFirst
        });
        Method : String    @(
          Core.Immutable: true,
          Common.Label  : 'Payment Method'
        );
  }

  entity TravelStatus : sap.common.CodeList {
    key code        : String(1) @Common.Text: descr;
        name        : String(255);
        descr       : String(1000);
        criticality : Int16     @odata.Type : 'Edm.Byte' enum {
          Neutral  = 0;
          Negative = 1;
          Critical = 2;
          Positive = 3;
          NewItem  = 5
        };
  };

  entity CurrencyCode : sap.common.CodeList {
        @Common.Label: 'Currency Code'
    key code  : String(3);

        @Common.Label: 'Currency Description'
        descr : String(255);
  };

  entity BookingStatus : sap.common.CodeList {
    key code : BookingStatusCode
  };

  entity TravelAgency : MasterData {
        @Common.Label: 'Agency ID'
        @Common.Text : Name
    key AgencyID       : String(6);

        @Common.Label: 'Agency Name'
        Name           : String(80);
        Street         : String(60);

        @Common.Label: 'Postal Code'
        PostalCode     : String(10);

        @Common.Label: 'City'
        City           : String(40);

        @Common.Label: 'Country Code'
        CountryCode    : Country;

        @Common.Label: 'Phone Number'
        PhoneNumber    : String(30);

        @Common.Label: 'Email Address'
        EMailAddress   : String(256);

        @Common.Label: 'Web Address'
        WebAddress     : String(256);
        Rating         : Integer;
        Recommendation : Integer;
  };

  type TravelStatusCode  : String(1) enum {
    Open     = 'O';
    Accepted = 'A';
    Canceled = 'X';
  };

  type BookingStatusCode : String(1) enum {
    New      = 'N';
    Booked   = 'B';
    Canceled = 'X';
  };
}

```

**FILE**: annotations.cds

**LANGUAGE**: CDS

**CODE**:
```cds
using TravelService as service from '../service/service';

annotate service.Travel {
  TravelStatus @Common.ValueListWithFixedValues;
  CurrencyCode @Common.ValueList: {
    CollectionPath: 'Currencies',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: CurrencyCode_code,
        ValueListProperty: 'code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'descr'
      }
    ]
  };
}

annotate service.Travel with @UI.LineItem: [
  {Value: TravelID},
  {
    $Type : 'UI.DataFieldForAnnotation',
    Target: '@UI.ConnectedFields#TripDates'
  },
  {Value: to_Agency_AgencyID},
  {
    Value             : TravelStatus_code,
    Criticality       : TravelStatus.criticality,
    @UI.Importance    : #High,
    @HTML5.CssDefaults: {width: '10em'}
  },
  {Value: BookingFee},
  {Value: TotalPrice}
];

annotate service.Travel with @(UI: {SelectionFields: [
  TravelID,
  to_Agency_AgencyID,
  TravelStatus_code,
  BeginDate,
  EndDate
]});

annotate service.Travel with @Capabilities.FilterRestrictions.FilterExpressionRestrictions: [
  {
    Property          : 'BeginDate',
    AllowedExpressions: 'SingleRange'
  },
  {
    Property          : 'EndDate',
    AllowedExpressions: 'SingleRange'
  }
];

annotate service.Travel with @(UI: {
  FieldGroup #TravelData    : {Data: [
    {Value: TravelID},
    {Value: to_Agency_AgencyID},
    {Value: BeginDate},
    {Value: EndDate}
  ]},
  FieldGroup #ApprovalData  : {Data: [
    {
      $Type      : 'UI.DataField',
      Value      : TravelStatus_code,
      Criticality: TravelStatus.criticality,
      Label      : 'Status'
    },
    {Value: BookingFee},
    {Value: TotalPrice}
  ]},
  FieldGroup #HeaderMain    : {Data: [
    {Value: BeginDate},
    {Value: EndDate},
    {Value: TotalPrice}
  ]},
  ConnectedFields #TripDates: {
    Label   : 'Business Trip Dates',
    Template: '{BeginDate} / {EndDate}',
    Data    : {
      BeginDate: {
        $Type: 'UI.DataField',
        Value: BeginDate
      },
      EndDate  : {
        $Type: 'UI.DataField',
        Value: EndDate
      }
    }
  },
  Facets                    : [
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'TravelData',
      Label : 'Travel Information',
      Target: '@UI.FieldGroup#TravelData',
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'ApprovalData',
      Label : 'Approval Data',
      Target: '@UI.FieldGroup#ApprovalData'
    },
    {
      $Type : 'UI.ReferenceFacet',
      ID    : 'BookingList',
      Label : 'Bookings',
      Target: 'to_Booking/@UI.LineItem'
    }
  ],
  HeaderFacets              : [{
    $Type : 'UI.ReferenceFacet',
    Target: '@UI.FieldGroup#HeaderMain'
  }],
  HeaderInfo                : {
    TypeName      : 'Travel',
    TypeNamePlural: 'Travels',
    Title         : {
      $Type: 'UI.DataField',
      Value: Description
    },
    Description   : {
      $Type: 'UI.DataField',
      Value: TravelID
    },
    TypeImageUrl  : 'sap-icon://header',
  }
});

annotate service.Travel {
  to_Agency @Common.ValueList: {
    CollectionPath: 'TravelAgency',
    Label         : '',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: to_Agency_AgencyID,
        ValueListProperty: 'AgencyID'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'Name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'Street'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'PostalCode'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'City'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'CountryCode_code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'PhoneNumber'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'EMailAddress'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'WebAddress'
      }
    ]
  };
}

annotate service.TravelAgency with @(UI: {
  DataPoint #Recommendation: {
    Value        : Recommendation,
    TargetValue  : 100.0,
    Title        : 'Recommendation',
    Visualization: #Progress
  },
  DataPoint #Rating        : {
    Value        : Rating,
    TargetValue  : 5.0,
    Title        : 'Rating',
    Visualization: #Rating
  },
  FieldGroup #Contact      : {Data: [{
    $Type : 'UI.DataFieldForAnnotation',
    Target: '@Communication.Contact',
    Label : 'Travel Agency'
  }]}
});

annotate service.TravelAgency with @(
  Communication.Contact : {
    email: [{
      type   : #work,
      address: EMailAddress
    }],
    fn   : Name,
    adr  : [{
      type    : #work,
      code    : PostalCode,
      country : CountryCode_code,
      locality: City
    }]
  },
  Common.IsNaturalPerson: false
);

annotate service.Booking {
  CurrencyCode @Common.ValueList: {
    CollectionPath: 'Currencies',
    Parameters    : [
      {
        $Type            : 'Common.ValueListParameterInOut',
        LocalDataProperty: CurrencyCode_code,
        ValueListProperty: 'code'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'name'
      },
      {
        $Type            : 'Common.ValueListParameterDisplayOnly',
        ValueListProperty: 'descr'
      }
    ]
  };
}

annotate service.Booking with @(UI: {
  HeaderInfo: {
    TypeName      : 'Booking',
    TypeNamePlural: 'Bookings',
    TypeImageUrl  : 'sap-icon://alert',
    Title         : {
      $Type: 'UI.DataField',
      Value: BookingID
    }
  },
  Facets    : [{
    $Type : 'UI.ReferenceFacet',
    ID    : 'FacetIdentifier1',
    Target: '@UI.FieldGroup'
  }],
  LineItem  : [
    {Value: BookingID},
    {Value: BookingDate},
    {Value: ConnectionID},
    {Value: FlightDate},
    {Value: FlightPrice}
  ],
  FieldGroup: {Data: [
    {
      $Type: 'UI.DataField',
      Value: BookingID
    },
    {
      $Type: 'UI.DataField',
      Value: BookingDate
    },
    {
      $Type: 'UI.DataField',
      Value: FlightDate
    },
    {
      $Type: 'UI.DataField',
      Value: FlightPrice
    }
  ]}
});

annotate service.TravelAgency with @(UI: {FieldGroup #Link: {Data: [{
  $Type: 'UI.DataFieldWithUrl',
  Value: Name,
  Url  : WebAddress
}]}});

```

**FILE**: Travel.json

**LANGUAGE**: JSON

**CODE**:
```json
[
	{
		"TravelUUID": "52657221A8E4645C17002DF03754AB66",
		"TravelID": "00000001",
		"BeginDate": "2025-11-04",
		"EndDate": "2025-11-06",
		"BookingFee": "20.00",
		"TotalPrice": "900.00",
		"CurrencyCode_code": "USD",
		"Description": "Business Trip for Christine, Pierre",
		"TravelDetails": "This business trip includes a two-day customer workshop at the airport. The goal is the evaluation of the customer's use cases and the alignment on the road map for the upcoming year.",
		"TravelStatus_code": "A",
		"to_Agency_AgencyID": "070001"
	},
	{
		"TravelUUID": "53657221A8E4645C17002DF03754AB66",
		"TravelID": "00000002",
		"BeginDate": "2025-12-04",
		"EndDate": "2025-12-16",
		"BookingFee": "80.00",
		"TotalPrice": "3164.00",
		"CurrencyCode_code": "USD",
		"Description": "Lars Jensen site visit for system rollout support",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "O",
		"to_Agency_AgencyID": "070002"
	},
	{
		"TravelUUID": "54657221A8E4645C17002DF03754AB66",
		"TravelID": "00000003",
		"BeginDate": "2025-08-04",
		"EndDate": "2025-08-08",
		"BookingFee": "40.00",
		"TotalPrice": "1871.00",
		"CurrencyCode_code": "USD",
		"Description": "John Smith customer workshop at Acme Corp headquarters",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "O",
		"to_Agency_AgencyID": "070003"
	},
	{
		"TravelUUID": "55657221A8E4645C17002DF03754AB66",
		"TravelID": "00000004",
		"BeginDate": "2025-09-04",
		"EndDate": "2025-09-05",
		"BookingFee": "40.00",
		"TotalPrice": "1240.00",
		"CurrencyCode_code": "USD",
		"Description": "Anna Müller onboarding with new project team in Berlin",
		"TravelDetails": "Will be provided soon...",
		"TravelStatus_code": "X",
		"to_Agency_AgencyID": "070003"
	}
]

```

Inline Edit