Application Development and Automation Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
gowthamc
Explorer
1,519

Hi all
In this blog i am going to explain the topic called Comparison Pattern.

Comparison Pattern In SAPUI5, a Comparison Pattern is a design pattern used to compare different sets of data or to present changes between data states. This pattern is particularly useful in scenarios where you need to display the differences between two versions of data or compare two datasets visually. It helps users to understand changes and make decisions based on the comparison.

Key Features of the Comparison Pattern

Side-by-Side Comparison: Allows users to view two datasets or versions of data side by side. This is useful for comparing different states of data or comparing data from different sources.

Highlight Differences: Visualizes the differences between datasets. Differences can be highlighted with color coding or markers to make them easily identifiable.

Version Comparison: Often used to compare different versions of a document or dataset, showing what has changed between versions.

Interactive Elements: Provides interactive elements such as filters or toggles to refine the comparison view. Users can interact with these elements to focus on specific differences or aspects of the data.

Implementation in SAPUI5

To implement the Comparison Pattern in SAPUI5, you can use various controls and techniques depending on your specific requirements:

Tables and Lists: Use sap.ui.table.Table or sap.m.List to display data in a tabular or list format. You can render two tables or lists side by side to compare data.

Highlighting Differences: Use custom formatting or styling to highlight differences between datasets. For example, you can use sap.ui.core.Text with conditional formatting or color coding to show changes.

Panels and Containers: Utilize sap.ui.layout.Panel or sap.ui.layout.VerticalLayout to organize and display comparison results. Place two panels side by side to show different versions of data.

Data Binding: Bind the data to UI elements using SAPUI5’s data binding capabilities. Use Binding and Model to manage and display the comparison data dynamically.

Custom Controls: Create custom controls if you need specialized comparison functionality. For instance, a custom control could be developed to show differences between two datasets with advanced visualizations.

EXAMPLE

in this below example, I have used Json model for maintaining data and in main view i have used
card and standardlistitem as a pattern for displaying data in main view (View1.view.xml)

below is the application webapp folder structure

gowthamc_0-1725360076419.png

in main view (View1.view.xml) file i have used list to display list products and i have given multiselecting property to select items, below is the code of that as well as output of respected code

 

 

 

 

<mvc:View controllerName="project2.controller.View1"
    xmlns:mvc="sap.ui.core.mvc"
    xmlns="sap.m"
    xmlns:layout="sap.ui.layout"
    xmlns:core="sap.ui.core">
    <Page id="page" title="{i18n>title}">
      <content>
        <List id="idProductsList" selectionChange="onSelection" mode="MultiSelect"
              items="{
                  path: '/ProductCollection',
                  sorter: {
                      path: 'Namse'
                  }
              }">
          <headerToolbar>
            <Toolbar>
              <Title text="Laptops" level="H2" />
              <ToolbarSpacer />
              <Button id="compareBtn" visible="false" press="onToNextPage" />
            </Toolbar>
          </headerToolbar>
          <items>
            <StandardListItem title="{Name}" description="{ProductId}" info="{Quantity} {UoM}"
              additionalText="{CurrencyCode} {Price}"
              type="Inactive">
              <customData>
                <core:CustomData key="Weight" value="{WeightMeasure} {WeightUnit}" />
              </customData>
            </StandardListItem>
          </items>
        </List>
      </content>
    </Page>
</mvc:View>

 

 

 

 

below is the out put 

gowthamc_1-1725360711887.png

by default compare button will be invisible to make that visible we need select more then one items from the list

gowthamc_2-1725360760980.png

whenever we select any item from the list in that time it will trigger the function called onSelection

 

 

 

 

onSelection: function(oEvent) {
                debugger
                var iSelectedItemsCount,
                    bShowCompareButton;
    
                this.getOwnerComponent().aSelectedItems = oEvent.getSource().getSelectedContextPaths();
                iSelectedItemsCount = this.getOwnerComponent().aSelectedItems.length;
                bShowCompareButton = iSelectedItemsCount > 1;
    
                if (bShowCompareButton) {
                    this._oCompareButton.setText("Compare (" + this.getOwnerComponent().aSelectedItems.length + ")");
                }
    
                this._oCompareButton.setVisible(bShowCompareButton);
            }

 

 

 

 

in the above code this._oCompareButton is the comparison button ID which i have taken in onAfterRendering function below is the snippet of that

 

 

 

 

 

 onAfterRendering : function () {
                debugger
                this._oCompareButton = this.getView().byId("compareBtn");
            }

 

 

 

 

after clicking on Compare button it will navigate to the comparison view (Comparison.controller.js)

the output of comparison view is

gowthamc_5-1725361319859.png

gowthamc_6-1725361349417.png

in this Comparision view i have tacken two controlles to compare the items one is CustomListItem and card

below is the view code for comparision view

 

 

 

 

<mvc:View
	xmlns="sap.m"
	xmlns:cards="sap.f.cards"
	
	xmlns:f="sap.f"
	xmlns:l="sap.ui.layout"
	xmlns:mvc="sap.ui.core.mvc"
	controllerName="project2.controller.Comparison"
	height="100%">

	<f:DynamicPage class="sapUiComparisonContainer">
		<f:title>
			<f:DynamicPageTitle
				id="dynamic-page"
				stateChange="onStateChange"
				backgroundDesign="Transparent">
				<f:heading>
					<Title text="comparing the selected items" />
				</f:heading>
				<f:snappedContent>
					<Carousel
						height="auto"
						class="sapUiSmallMarginBottom"
						id="carousel-snapped"
						pageChanged="onPageChanged"
						pageIndicatorPlacement="Top"
						showPageIndicator="{= !${settings>/isDesktop} }"
						pages="{
							path: 'products>/Products'
						}">
						<customLayout>
							<CarouselLayout visiblePagesCount="{settings>/pagesCount}" />
						</customLayout>

						<f:Card class="sapUiTinyMarginTop">
							<f:header>
								<cards:Header
									title="{products>Name}"
									subtitle="{products>Status}"
									iconsrc="{
												path: 'products>ProductPicUrl'
												
											}"
									iconDisplayShape="Square" />
							</f:header>
						</f:Card>
					</Carousel>
				</f:snappedContent>
			</f:DynamicPageTitle>
		</f:title>
		<f:header>
			
			<f:DynamicPageHeader backgroundDesign="Transparent">
				<Carousel height="auto" showPageIndicator="true">
					<List id="listItems" backgroundDesign="Solid" class=""
						items="{ path: 'products>/Props'}" >
						<items>
							<CustomListItem class="sapUiComparisonContent sapUiSmallMarginTop sapUiSmallMarginBottom">
								<Panel expandable="false" expanded="false" headerText="{products>key}" height="2.75rem">
								</Panel>
								<HBox class="" alignItems="Start" backgroundDesign="Solid"
										items="{ path: 'products>values', templateShareable : true}">
									<items>
										<VBox class="sapUiComparisonItem">
											<layoutData>
												<FlexItemData growFactor="1" baseSize="0" />
											</layoutData>
											<HBox>
												<FormattedText htmlText="{products>text}" />
											</HBox>
										</VBox>
									</items>
								</HBox>
							</CustomListItem>
						</items>
					</List>
				</Carousel>
			</f:DynamicPageHeader>
		</f:header>

        
		<f:content>
			<Carousel
					height="auto"
					class="sapUiSmallMarginBottom"
					id="carousel-expanded"
					pageChanged="onPageChanged"
					pageIndicatorPlacement="Top"
					showPageIndicator="{= !${settings>/isDesktop} }"
					pages="{
						path: 'products>/Products'
					}">
					<customLayout>
						<CarouselLayout visiblePagesCount="{settings>/pagesCount}" />
					</customLayout>

					<f:Card class="sapUiTinyMarginTop" >
						<f:header>
							<cards:Header title="{products>Name}" subtitle="{products>Status}"
								iconsrc="{ path: 'products>ProductPicUrl'}" iconDisplayShape="Square" />
						</f:header>
						<f:content>
							<l:VerticalLayout width="100%">
								<l:BlockLayout>
									<l:BlockLayoutRow>
										<l:BlockLayoutCell>
											<HBox>
												<Label text="Supplier:"/>
											</HBox>
											<HBox class="sapUiSmallMarginBottom">
												<Text text="{products>SupplierName}" />
											</HBox>
											<HBox>
												<Label text="Main Category:"/>
											</HBox>
											<HBox class="sapUiSmallMarginBottom">
												<Text text="{products>MainCategory}" />
											</HBox>
											<HBox>
												<Label text="Category:"/>
											</HBox>
											<HBox class="sapUiSmallMarginBottom">
												<Text text="{products>Category}" />
											</HBox>
										</l:BlockLayoutCell>
										<l:BlockLayoutCell>
											<HBox>
												<Label text="Width (cm)" />
											</HBox>
											<HBox class="sapUiSmallMarginBottom">
												<Text text="{products>Width}" />
											</HBox>
											<HBox>
												<Label text="Height (cm)" />
											</HBox>
											<HBox class="sapUiSmallMarginBottom">
												<Text text="{products>Height}" />
											</HBox>
											<HBox>
												<Label text="Weight (kg)" />
											</HBox>
											<HBox class="sapUiSmallMarginBottom">
												<Text text="{products>WeightMeasure}"/>
											</HBox>
										</l:BlockLayoutCell>
									</l:BlockLayoutRow>
								</l:BlockLayout>
							</l:VerticalLayout>
						</f:content>
					</f:Card>
				</Carousel>
		</f:content>
	</f:DynamicPage>
</mvc:View>

 

 

 

 

below  is the controller code for comparision view

 

 

 

 

sap.ui.define( [
	
	"sap/m/MessageBox",
	"sap/ui/core/mvc/Controller",
	"sap/ui/core/ResizeHandler",
	"sap/ui/model/json/JSONModel"
], function ( MessageBox, Controller, ResizeHandler, JSONModel) {
	"use strict";

	var SCREEN_MAX_SIZES = {
		PHONE: 600,
		TABLET: 1024
	};

	var ITEMS_COUNT_PER_SCREEN_SIZE = {
		PHONE: 1,
		TABLET: 2,
		DESKTOP: 4
	};

	return Controller.extend("project2.controller.Comparison", {
		

		onInit: function () {
			this._oRootControl = this.getOwnerComponent().getRootControl();
			this.oRouter = this.getOwnerComponent().getRouter();
			this.oRouter.attachRouteMatched(this.onRouteMatched, this);
			this._aCachedItems = {};
			this._aAllProducts = this.getOwnerComponent().getModel().getData().ProductCollection;
			this._aSelectedItems = [];
			this._iPagesCount = this._getPagesCount(this._oRootControl.$().innerWidth());
			this._bIsDesktop = this._checkIsDesktop();
			this._iFirstItem = 0;
			this._iLastItem = this._iPagesCount;
			this._setModels();
			// Resize handler is needed in order us to determine how many items
			// will be shown next to each other, depending on the screen size.
			this._iResizeHandlerId = ResizeHandler.register(
				this.getOwnerComponent().getRootControl(),
				this._onResize.bind(this));
		},

		onRouteMatched: function(oEvent) {
			this._aSelectedItems = this.getOwnerComponent().aSelectedItems;
			this._aSelectedItemsIds = this._getSelectedItemsIds();
			this._iPagesCount = this._getPagesCount(this._oRootControl.$().innerWidth());
			this._bIsDesktop = this._checkIsDesktop();
			this._updateFirstPage();

			this._aProductsToShow = this._getProductsToShow(this._aAllProducts);

			this.getView().getModel("settings").setData({
				pagesCount: this._iPagesCount,
				isDesktop: this._bIsDesktop
			});
			this.getView().getModel("products").setData(this._aProductsToShow);
		},

		onAfterRendering: function () {
			this._oCarouselSnapped = this.getView().byId("carousel-snapped");
			this._oCarouselExpanded = this.getView().byId("carousel-expanded");
			this._oDynamicPage = this.getView().byId("dynamic-page");
		},

		onPageChanged: function (oEvent) {
			var aActivePages = oEvent.getParameter("activePages"),
				oProductsData;

			// The data about the selected products (in the table-like view) needs to be
			// updated, according to the new active (visible) pages of the Carousel control.
			// This event is triggered upon sliding through Carousel's pages.
			this._iFirstItem = aActivePages[0];
			this._iLastItem = aActivePages[aActivePages.length - 1] + 1;
			this._updateCarouselsActivePage();

			oProductsData = this._getModelData(this._aProductsToShow.Products);
			this.getView().getModel("products").setData(oProductsData);
		},
		onStateChange: function (oEvent) {
			var bIsExpanded = oEvent.getParameter("isExpanded");

			// This is needed because of animation issues with Carousel control
			// when it is placed in the Title area of the DynamicPage
			bIsExpanded && this._oDynamicPage.removeSnappedContent(this._oCarouselSnapped);
			!bIsExpanded && this._oDynamicPage.addSnappedContent(this._oCarouselSnapped);
		},
		_onResize: function (oEvent) {
			var iWidth = oEvent.size.width,
				iNewPagesCount = this._getPagesCount(iWidth);

			if (iNewPagesCount !== this._iPagesCount) {
				this._iPagesCount = iNewPagesCount;
				this.getView().getModel("settings").setProperty("/pagesCount", this._iPagesCount);

				this._bIsDesktop = this._checkIsDesktop();
				this.getView().getModel("settings").setProperty("/isDesktop", this._bIsDesktop);

				this._updateFirstPage();
				this._updateProductsData();
			}
		},

		_updateFirstPage: function () {
			var iAllProductsCount = this._aSelectedItems.length;

		
			if (this._iFirstItem + this._iPagesCount > iAllProductsCount) {
				this._iFirstItem = iAllProductsCount - this._iPagesCount;
				this._updateCarouselsActivePage();
			}
		},

		_updateCarouselsActivePage: function () {
			// Synchronization of the two Carousels
			this._oCarouselSnapped.setActivePage(this._oCarouselSnapped.getPages()[this._iFirstItem]);
			this._oCarouselExpanded.setActivePage(this._oCarouselExpanded.getPages()[this._iFirstItem]);
		},

		_setModels: function () {
			this.getView().setModel(new JSONModel(), "settings");
			this.getView().setModel(new JSONModel(), "products");
		},

		_updateProductsData: function () {
			var oProductsData = this._getModelData(this._aProductsToShow.Products);

			this.getView().getModel("products").setData(oProductsData);
		},

		_getPagesCount: function (iWidth) {
			var iAllProducts = this._aSelectedItems.length,
				iPagesCount;

			if (iWidth <= SCREEN_MAX_SIZES.PHONE) {
				iPagesCount = ITEMS_COUNT_PER_SCREEN_SIZE.PHONE;
			} else if (iWidth <= SCREEN_MAX_SIZES.TABLET) {
				iPagesCount = ITEMS_COUNT_PER_SCREEN_SIZE.TABLET;
			} else {
				iPagesCount = ITEMS_COUNT_PER_SCREEN_SIZE.DESKTOP;
			}

			if (iAllProducts && iPagesCount > iAllProducts) {
				iPagesCount = iAllProducts;
			}

			return iPagesCount;
		},

		_checkIsDesktop: function () {
			return this._iPagesCount === ITEMS_COUNT_PER_SCREEN_SIZE.DESKTOP;
		},

		_getSelectedItemsIds: function () {
			return this._aSelectedItems.map(function (item) {
				return parseInt(item.split("/").pop());
			});
		},

		_getProductsToShow: function (aAllProducts) {
			var aSelectedProducts = [];

			this._aSelectedItemsIds.forEach(function (id) {
				aSelectedProducts.push(aAllProducts[id]);
			});

			return this._getModelData(aSelectedProducts);
		},

		_getModelData: function (aSelectedProducts) {
			var allProps = [],
				ilastPage = this._iFirstItem + this._iPagesCount,
				oProp,
				oCurrentProduct,
				oCurrentProductInformation,
				oPropertyValue;

			this._iLastItem = ilastPage > aSelectedProducts.length ? aSelectedProducts.length : ilastPage;

			// Manipulates data in a way that allows us to be able to display the name
			// of the property as a Panel title and the values of this property on each
			// of the products to be displayed in different columns in a table-like view.
			for (var key in aSelectedProducts[0]) {
				if (aSelectedProducts[0].hasOwnProperty(key) && key !== "ProductPicUrl") {
					oProp = {};
					oProp.key = key;
					oProp.values = [];

					for (var i = this._iFirstItem; i < this._iLastItem; i++) {
						oCurrentProduct = aSelectedProducts[i];
						oCurrentProductInformation = this._aCachedItems[oCurrentProduct.ProductId]
													&& this._aCachedItems[oCurrentProduct.ProductId][key];

						// Performance optimization logic: reusing already created information for products,
						// instead of creating a new one, each time a same product is selected.
						if (oCurrentProductInformation) {
							oProp.values.push(oCurrentProductInformation);

						} else {
							oPropertyValue = {
								text: "<strong>" + aSelectedProducts[i][key] + "</strong>",
								description: "Some description of the property here"
							};

							oProp.values.push(oPropertyValue);
							this._cacheProductInformation(oCurrentProduct, key, oPropertyValue);
						}
					}

					allProps.push(oProp);
				}
			}

			return {
				Products: aSelectedProducts,
				Props: allProps
			};
		},

		_cacheProductInformation: function (oProduct, sProp, oPropertyValue) {
			var sProductId = oProduct.ProductId;

			if (!this._aCachedItems[sProductId]) {
				this._aCachedItems[sProductId] = {};
			}

			this._aCachedItems[sProductId][sProp] = oPropertyValue;
		}

	});

});