Skip to Content

Persistent table personalization in SAP UI5/FIORI apps using Variant Management: Step-by-Step guide

Often, we come to a situation where users request to add personalization on UI table so that they can store the view according to their needs and can easily view the required information at just a click of a button.

We are very familiar with the use of sap.ui.table to show data in tabular form as it is very easy to manipulate this form of UI control, plus it offers a lot of flexibility to cater our needs as well. The only thing that it lacks is the personalization. Of course, the table personalization can be added using sap.ui.table.TablePersoController which can control the visibility of the columns, but they won’t be persistent.

So, in order to make it persistent, we can add VariantManagement control to our FIORI application table along with the TablePersoController and store this information in persistent DB offered by backend ABAP system.

The variant management could look like as below screenshot:

The first thing we need to do here is to add this Variant Management control (sap.ui.comp.variants.VariantManagement) to our UI. The below code snippet can be added in the extension or toolbar (deprecated in the new versions) area of the UI table.

<Table id="DemoTableUI" visibleRowCountMode="Fixed" selectionBehavior="Row" selectionMode="None">
	<extension>
		<m:Toolbar>
			<m:content>
				<!--add variant management-->
				<variant:VariantManagement variantItems="{/VariantList}" select="onSelect" save="onSaveAs" enabled="true" manage="onManage" showExecuteOnSelection="false" showShare="false" id="Variants">
					<variant:variantItems>
						<variant:VariantItem text="{Name}" key="{Key}"/>
					</variant:variantItems>
				</variant:VariantManagement>
			<m:ToolbarSpacer/>
				<m:Button icon="sap-icon://action-settings" press="onPersoButtonPressed" tooltip="Columns Settings"/>
			</m:content>
		</m:Toolbar>
	</extension>

The above code will add the variant management control on the toolbar of the UI Table. The button is added to add the table persco controller which will open up the list of the columns to be set as visible or invisible according to the need.

To setup the variant management and to connect the variant management with the backend system, the ushell services (Unified Shell services) will be used.

the code snapshot is shown below that will invoke the service:

// Peronalisation from ushell service to persist the settings
if (sap.ushell && sap.ushell.Container && sap.ushell.Container.getService) {

	var oComponent = sap.ui.core.Component.getOwnerComponentFor(this.getView());
	this.oPersonalizationService = sap.ushell.Container.getService("Personalization");
	var oPersId = {
			container: "TablePersonalisation", //any
			item: "DemoTableUI"                //any- I have used the table name 
			};
	// define scope 
	var oScope = {
			keyCategory: this.oPersonalizationService.constants.keyCategory.FIXED_KEY,
			writeFrequency: this.oPersonalizationService.constants.writeFrequency.LOW,
			clientStorageAllowed: true
				};
	// Get a Personalizer
	var oPersonalizer = this.oPersonalizationService.getPersonalizer(oPersId, oScope, oComponent);
	this.oPersonalizationService.getContainer("TablePersonalisation", oScope, oComponent)
		.fail(function() {})
		.done(function(oContainer) {
			this.oContainer = oContainer;
			this.oVariantSetAdapter = new sap.ushell.services.Personalization.VariantSetAdapter(this.oContainer);
			// get variant set which is stored in backend
                        this.oVariantSet = this.oVariantSetAdapter.getVariantSet("DemoTableUISet");
			if (!this.oVariantSet) { //if not in backend, then create one
				this.oVariantSet = this.oVariantSetAdapter.addVariantSet("DemoTableUISet");
						}
                        // array to store the existing variants
                        Variants = [];
                        // now get the existing variants from the backend to show as list
                        for (var key in this.oVariantSet.getVariantNamesAndKeys()) {
				if (this.oVariantSet.getVariantNamesAndKeys().hasOwnProperty(key)) {
					var oVariantItemObject = {};
					oVariantItemObject.Key = this.oVariantSet.getVariantNamesAndKeys()[key];
					oVariantItemObject.Name = key;
					Variants.push(oVariantItemObject);
							}
						}
                        // create JSON model and attach to the variant management UI control
                     	this.oVariantModel = new sap.ui.model.json.JSONModel();
			this.oVariantModel.oData.Variants = Variants;
			this.getView().byId("Variants").setModel(this.oVariantModel);
					}.bind(this));
// create table persco controller
			this.oTablepersoService = new TablePersoController({
				table: this.getView().byId("DemoTableUI"),
				persoService: oPersonalizer
				});

			}

 

sap.ushell.Container.getService(“Personalization”) will get the instance of the Personalization service from the unified shell which will be used to store the information to the backend system.

This method uses the container mode to persist the variants. Ultimately, we will add values that need to be stored and save them in the container to persist them.

Since, this gives us an oData service to read and write data, we need to get the instance of the adapter to do so. Thus we create an instance of the variant set adapter.

The other thing to notice is that the we need to create our own variant set name. Here I have used the table ID as the variant set name for simplicity but this is up to you.

this.oVariantSetAdapter.addVariantSet will add the new variant set for the application to the service and persist it in the backend. once, we have this in place, we can CRUD the variants in this variant set.

The Table Perso Controller used here is with the accordance to the table i.e. from the control sap.ui.table.TablePersoController.

The code above will not only invoke the variant management service but also attach the perso controller to the table. The perso controller also uses the unified shell service and renders according to the table instance specified in it.

To use the perso controller, we will add code to open the column list on the press event of the button on the toolbar labelled as settings

onPersoButtonPressed: function(oEvent) {
	this.oTablepersoService .openDialog({
		ok: "this.onPerscoDonePressed.bind(this)"
	});
	this.oTablepersoService ._oDialog.attachConfirm(this, this.onPerscoDonePressed.bind(this));
	},
onPerscoDonePressed: function(oEvent) {
	this.oTablepersoService .savePersonalizations();
	},

 

Thus, on pressing the button, the screen containing the column list will popup from which the user can select or un-select to un-hide or hide the column respectively. All the columns details are coming from the table instance provided to the perso controller.

Now comes the interesting part, saving and managing the variant. Once the user chooses the columns that are to visible including their position, he/she can press the variant management button to save the personalization.

on pressing OK button on the popup, the event handler method “”onSaveAs” will be fired. The below code snapshot will store the variant with the columns settings including the order to the backend

		onSaveAs: function(oEvent) {
			// get variant parameters:
			var VariantParam = oEvent.getParameters();
			// get columns data: 
			var aColumnsData = [];
			this.getView().byId("DemoTableUI").getColumns().forEach(function(oColumn, index) {
				var aColumn = {};
				aColumn.fieldName = oColumn.getProperty("name");
				aColumn.Id = oColumn.getId();
				aColumn.index = index;
				aColumn.Visible = oColumn.getVisible();
				aColumnsData.push(aColumn);
			});

			this.oVariant = this.oVariantSet.addVariant(VariantParam.name);
			if (this.oVariant) {
				this.oVariant.setItemValue("ColumnsVal", JSON.stringify(aColumnsData));
				if (VariantParam.def === true) {
					this.oVariantSet.setCurrentVariantKey(this.oVariant.getVariantKey());
				}
				this.oContainer.save().done(function() {
					// Tell the user that the personalization data was saved
				});
			}
		},

The arguments of this method will provide the Variant information entered by the user. to read, we need to use the method oEvent.getParameters(to get those values.

The above code will store the table’s column information required to be stored in an local array. The important information required are column name, its ID, the index of the column and its visibility. Of course, the other information like sorting etc can also be stored but it is entirely based on the requirement.

The variant created do not store array but store the content as a string, so it is absolutely important to convert the array/object to a string value using JSON.stringify() method.

Once, all the information is written to the variant set, the container can be saved.

One more thing here to notice is that to store values the method setItemValue() is used. to store multiple information to the same variant, this method can be called multiple times with different arguments. for e.g.

this.oVariant.setItemValue(“ColumnsVal1”, JSON.stringify(aColumnsData));

this.oVariant.setItemValue(“ColumnsVal2”, JSON.stringify(aColumnsData));

This is helpful in case to something like a filter value to be stored.

the below snapshot shows the stored variant

When user chooses the variant from the above list, we need to read the service and change the table columns accordingly.. Below is the code to do so:

		onSelect: function(oEvent) {
			var selectedKey = oEvent.getParameters().key;
			for (var i = 0; i < oEvent.getSource().getVariantItems().length; i++) {
				if (oEvent.getSource().getVariantItems()[i].getProperty("key") === selectedKey) {
					var selectedVariant = oEvent.getSource().getVariantItems()[i].getProperty("text");
					break;
				}
			}
			this._setSelectedVariantToTable(selectedVariant);
		},

		_setSelectedVariantToTable: function(oSelectedVariant) {
			if (oSelectedVariant) {
				var sVariant = this.oVariantSet.getVariant(this.oVariantSet.getVariantKeyByName(oSelectedVariant));
				var aColumns = JSON.parse(sVariant.getItemValue("ColumnsVal"));

				// Hide all columns first
				this.getView().byId("DemoTableUI").getColumns().forEach(function(oColumn) {
					oColumn.setVisible(false);
				});
				// re-arrange columns according to the saved variant

				aColumns.forEach(function(aColumn) {
					var aTableColumn = $.grep(this.getView().byId("DemoTableUI").getColumns(), function(el, id) {
						return el.getProperty("name") === aColumn.fieldName;
					});
					if (aTableColumn.length > 0) {
						aTableColumn[0].setVisible(aColumn.Visible);
						this.getView().byId("DemoTableUI").removeColumn(aTableColumn[0]);
						this.getView().byId("DemoTableUI").insertColumn(aTableColumn[0], aColumn.index);
					}
				}.bind(this));
			}
			// null means the standard variant is selected or the variant which is not available, then show all columns
			else {
				this.getView().byId("DemoTableUI").getColumns().forEach(function(oColumn) {
					oColumn.setVisible(true);
				});
			}
		},

To read the variant’s data, this.oVariantSet.getVariant() is called and to convert the stored values back into the array JSON.parse() is used. Once we have this information, we can modify the table and refresh the table control to show that data on the screen.

The variants can be managed as well. on pressing the manage button the user will get a screen like below:

he/she can change the name or delete the variant or can make anyone of them as default. to manage this, the below code snippet will do so:

onManage: function(oEvent) {
	var aParameters = oEvent.getParameters();
	// rename variants
	if (aParameters.renamed.length > 0) {
		aParameters.renamed.forEach(function(aRenamed) {
			var sVariant = this.oVariantSet.getVariant(aRenamed.key),
				sItemValue = sVariant.getItemValue("ColumnsVal");
			// delete the variant 
			this.oVariantSet.delVariant(aRenamed.key);
			// after delete, add a new variant
			var oNewVariant = this.oVariantSet.addVariant(aRenamed.name);
			oNewVariant.setItemValue("ColumnsVal", sItemValue);
		}.bind(this));
	}
	// default variant change
	if (aParameters.def !== "*standard*") {
		this.oVariantSet.setCurrentVariantKey(aParameters.def);
	} else {
		this.oVariantSet.setCurrentVariantKey(null);
	}
	// Delete variants
	if (aParameters.deleted.length > 0) {
		aParameters.deleted.forEach(function(aDelete) {
			this.oVariantSet.delVariant(aDelete);
		}.bind(this));
	}
	//  Save the Variant Container
	this.oContainer.save().done(function() {
		// Tell the user that the personalization data was saved
	});
},

Th event parameter will provide the values of all the data changed on the variant manage screen. The methods called are pretty straightforward to change, delete and make default variant.

The above code is the final piece of the puzzle to be in place for this variant management control to work with the table.

One last important information, the backend service used by ushell personalisation service  is INTEROP with name space /UI2/

I hope the above information helps you and if there are any challenges, please comment .

References:

https://sapui5.hana.ondemand.com/#/api/sap.ui.comp.variants

https://sapui5.hana.ondemand.com/#/api/sap.ushell.services.Personalization

https://help.sap.com/viewer/a7b390faab1140c087b8926571e942b7/1709.001/en-US/755536526c384096b5f37d15a693b98d.html

Many thanks

Rahul

18 Comments
You must be Logged on to comment or reply to a post.
  •  

    Hi Rahul,

    excellent blog, manifests ur understanding of the framework and how to play around it..

    One quick question though, you mentioned that the personalisation is not persisted but my understanding was that when  you personalize the table column configurations it is stored as a CDS metadata extensions on the backend for this user and hence should be picked automatically for that user.

    Could you please confirm if that is not the case…

    Also, when preparing the table columns the else block restores all the column to visible, would it overwrite the CDS annotations like hidden if any..

    Regards,

    Sitakant.

    • Hi Sitakant,

      What you are saying is about smart table control wherein you can bind the CDS oData directly with the table. The CDS holds the metadata for columns and annotation modeler is used to display them on the view. also, this control has built-in variant management. So, if you use smart table control, you can just activate the Variant management in the smart table control itself. even if you do that, it will still store the variants in the above mention service (/UI2/INTEROP) but the smart table control will manage it automatically and you need not to write any code to do so.

      Whereas the above content is for sap.ui.table where in there is no built-in variant management functionality and thus we can add the same as mention in the content of the blog.

      Anyhow, it will not going to impact the CDS annotation at all as the annotation is a part of the oData metadata which can’t be changed from UI5 app directly. all these manipulation happen on the app only and is stored separately in the BE.

      I hope I answered your question. write me back if you you have any further queries.

      regards

      Rahul

       

       

       

  • Hi Rahul,

       Thanks for writing this good blog and i have few queries related to your blog.

    First, where do we store Variant Items to persist the table personalized data using Variant Management?

    Second, can we use custom or standard OData service(non CDS OData) to store the table personalized data in variants. Moreover, if there is already custom OData service available for custom Fiori Apps, then shall we go ahead to enhance the existing custom OData service or create a new OData service to implement the Variant Management control to persist the table personalized data in back end DB provided by backend ABAP system.

    Lastly, could you please explain the methodology in detail which you had used to bind the variant items so that data is coming from database table.

    Regards,

    Sandip

    • For the first question, the data is stored automatically by the system in the service /UI2/INTEROP as soon as the personalisation service is instantiated. The same service is not just limited to store the variant data, but is used for many different purposes as well.

      Of course, the same data can be stored in a custom non-CDS based oData service but that could  consume a lot of time to design. We should use standard SAP services as much as possible.

      I used a JSON data model to bind variant items. but it is up to you the way you want to bind. You can directly bind the entityset to the control if at all and wherever it is possible.

      I hope I answered your concerns. Write me back if need be.

  • Nice blog, I am trying to save filter values in Variant. But oEvent.getSource().getVariantItems().length in onSelect() method return zero. Even after adding Variant using onSave() method. Could you please help me.

     

    Thanks,

    Lakshman Balanagu.

  • Hi Rahul,

    Thanks for writing this informative blog it helped a lot.

    I have few queries related to your blog.

     

    1. On deploying this code I am not getting back the saved variant when I refresh and open the app second time. Any idea?
    2. Instead of TablePersoController can we use P13Dialog?
    3. How to implement it for Filter and sort.

    Thanks in advance!

     

    Regards

    Kriti

      1. On deploying this code I am not getting back the saved variant when I refresh and open the app second time. Any idea? —- where did you deploy your application, on HCP or SAP UI5 ABAP repository. This will only work when app is deployed to SAP UI5 ABAP repository. for HCP, you might have to use the smartvariant control.
      2. Instead of TablePersoController can we use P13Dialog?— yes you can use this. As long as you are able to get the table column’s configuration.
      3. How to implement it for Filter and sort.—- in the same manner as the values for the columns are stored. Just add the filter values to the array that stores the column’s config. Same with the sort (store flag for sorted, sort type — something like that) and on initialization, add them to your table.
  • Hi Rahul,

    Thanks for your reply.

    I deployed on ABAP sever only.

    Its working fine now.

    One more query how to make saved variant available to other users like public variant which all can use.

    Thanks in advance!

     

    Regards,

    Kriti

     

  • Hi Rahul,

    I implemented the variant based on your blog. thank you for the blog .  I have a doubt on

    setasDefault part while create a new variant. in my code i added it like below.

    oPersonalizationVariantSet.setCurrentVariantKey(oVariant.getVariantKey());

    But , when i closed and re open the app, it doesn’t show as default. it picks the standard variant

    as a default one, i checked in the manage screen.

    How do i manage this?

     

    Thank you,

    Regards,

    JK.

     

    • try one the below ones and reply me which one worked. most likely it would be

      this.getView().byId(“variantManagement”).setDefaultVariantKey(aDefaultVariantKey);

    • This worked for me to select the default variant key

      //Set and fire the default variant for the user
      							this.getView().byId("tableVariant").setInitialSelectionKey(this.oVariantSet.getCurrentVariantKey());
      this.getView().byId("tableVariant").fireSelect({"key": this.oVariantSet.getCurrentVariantKey()});
  • Hi Rahul great blog! This post is hands down the best reference to implement Variant Management.
    I have implemented this on a sap.ui.table.Table and everything is working properly. Just had 2 questions on:

    1. How do you make the “Save” button enabled on the Variant Dialog, “SaveAs” is only enabled for me. So if a user intends to modify the personalization he has to delete and create a new variant one right now.
    2. On selecting *standard* variant, the code simply sets visible all the fields. (Re-ordering is not reset).
      But how can I make it behave like reset option on the TablePersoDialog, resetting from the TablePersoDialog even brings back the original ordering and configuration like initially hidden columns.

      Kudos!

    • For me the save variant option is always available. not sure why it is disabled for you. Debugging it might just help.

      For the second one, I usually make a javascript file for default order of the columns including its hidden attribute. So that if the user chooses the standard variant from the list, I take the default order into account and display my columns accordingly. You can make a JSON object saved for your own column and its attributes. What I mean is that you need to store the original order and attribute  values somewhere in the app so that you can reset to them as per your need.

       

      Something like below. Note that the _aJsonColumns.data contains the original column configuration.

      Column config could possibly be something like as below (I stored it in a different file in my application):

       

      • Yes having a json for the initial state is a neat option.

        I did debug for the save button not being visible and found it is linked to the variant management current item being in a dirty state.

        I am using 1.48.10 and from a bit of searching I found  an issue in the lib version itself. Would be good to have a workaround to this. Edit: found a solution it was so simple, just replaced the addVariant() part with the below snippet to the onSave method

        // Check if variant param not overwrite and add variant else save existing variant
        if (!VariantParam.overwrite) {
         this.oVariant = this.oVariantSet.addVariant(VariantParam.name);
        } else {
         this.oVariant = this.oVariantSet.getVariant(this.getView().byId("tableVariant").getSelectionKey());
        }

         

        https://answers.sap.com/questions/12086627/variant-management-save-option.html

        Using the workaround mentioned in the post or by just setting the button control state to enabled by code I can have the save button enabled, but this lands me into another issue.

        I get an error stating:
        Variant name ‘testSave’ already exists in variant set ‘dev_checkinout_uiTable_vSet’ (Old key: ‘0’ New key: ‘2’) ‘: sap.ushell.services.Personalization

        Do you happen to have any solution to this?

        Kudos!