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
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,
Good blog and well explanation with each and everything.
Thanks,
Syam
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.
Interesting blog, you might also find the following documentation useful, explains use of UI2 and Flexibility services for storing variants. The benefit of the flexibility services are you can maintain global global variants, where as UI2 allows users to maintain the own.
https://help.sap.com/saphelp_ewm94/helpdata/de/2a/e520a67c44495ab5dbc69668c47a7f/frameset.htm
Many thanks,
Jason
Hi Rahul,
Thanks for writing this informative blog it helped a lot.
I have few queries related to your blog.
Thanks in advance!
Regards
Kriti
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
Have you done it?
There is a way to share variants among users. I didn't explore that option but you might.
Hi Kriti,
Even after deploying it on ABAP server, the variants don't show up. Any idea why? Thanks in advance.
Seyed Ismail.
Hi Kriti.
Could you pls share controller code to understand how need to implement variant functionality.
Waiting for your reply.
Thanks
Suge
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
Hi, can you tell me in what part of the controller do you put this part?
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:
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 methodhttps://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!
Hi Abhishek,
I also faced this save issue and it seems in your else condition when you are trying to fetch key by that time it has already created new and passing you new and that is the reason it fails.
PFB working code snippet.
Regards,
Kriti
Hi Kriti,
Please share view and controller code to itsugendirantce@gmail.com to understand the flow.
thanks
Suge
Hi Kriti,
Can you please let me know what is 'defVarKey' in the above code snippet?
Thanks,
Sagar Bansal
Hi Mallik,
Please share view and controller to itsugendirantce@gmail.com to understand the flow.
thanks
Suge
Hi All,
I followed this blog. I can able to do personalization for sap.m.table using variant managment. But when i open the VariantManagement dialog the "SAVE" button is now even Visible.
Can anyone please help..!
Hi
Could you please share view and controller code to itsugendirantce@gmail.com to understand this.
thanks
Suge
Hi,
I am trying to implement a Variant management in SAPUI5 using Personalization service of sap.ushell.Container. I have written functions to Save, Manage(delete, rename) and select Variants from the drop down. However i see strange behavior when i select a variant in the method mentioned below.
Assume i have existing variants 'A1', 'A2' and 'A3'. When i SaveAs a new variant with new values (lets call it 'X1'), the new variant is created. Then i select another already existing variant from dropdown( A1 or A2 or A3), i see the corresponding values. Now i again select the newly created variant X1 but i don't see the new values.
When i debug the above mentioned method, i see that for all the existing variants, the
oEvent.getParameter('key')
returns the variant indexs like 0,1,2,3 etc. but for the newly created variant X1, it returns the value 'sv1579082806311' and hence it doens't find it in variantsetand then it doesn't show the new values.
If i run the program again, i see that previously created variant X1 now shows correct values as the method
oEvent.getParameter('key')
returns the index and not 'sv....'. but if i now create a new variant X2, the same issue happens with X2.I am running the App on cloud WebIDE and not on the FIORI launchpad.
Can someone help me what may be going wrong while saving the variant ?
Thanks
Hi,
The first time, the dialog is opened but the code “this.oTablepersoService ._oDialog” is undefined.
So the first time save is not working. But when you click the settings icon again, Dialog is instantiated. Any idea how to overcome this issue?
Thank you.
Hi Seyed Ismail ,
i think you are using the m.table and with it the sap.m.TablePersoController. The above example is using the sap.ui.table and with it the sap.ui.table.TablePersoController.
You must activate the sap.m.TabelPersoController at this point with .activate():
Then it should work.
Hi Sebastian,
I got the same problem as described by Seyed but with using the sap/ui/table/TablePersoController.
Any hints will be much appreciated.
EDIT: Nasty workaround with a setTimeout:
rgds
Jens
Hi Rahul,
Great Blog !!! I need to develop similar requirement so, from your blog i need to know below points.
Thanks,
Rahul
Hi Rahul,
Thanks for the nice blog.
I had a question on the following snippet:
Am using, sap.m.Table and sap.m.TablePersoController, when I do this.oTablesoService._oDialog.attachConfirm I get an error. Someone has posted in the blog that the service needs to be activated. I tried that too.
How do you attach an event to the OK button of the TablePersoController dialog, for follow on actions? Please suggest.
Thanks,
Meeta
Hi Rahul,
Thanks for the nice blog.
I have a question about field "Author", it is always empty. How do I fill this field?
Hi Rahul,
thanks a lot for the detailled description.
One question arises for me: How do I configure the personalization base URL and relative URL?
The backend Service is running, I tested that, but my app tries to load data from the base data Service wtih relative URL /VariantSet instead of the personalization service.
Best regards,
Stefan
Hi,
Where to place //Peronalisation from ushell service to persist the settings// code in controller. Do we need to put it in onInit or which function?
Others can identify separate function place in respective controller.
Please help on this quickly
regards
Suge
Hi @Rahul Kumar Sankla
Please share entire view and controller code to itsugendirantce@gmail.com. It will help us to understand how functionality written.
am not able to implement it.
waiting for your response.
thanks
Hi Rahul/Other Community Members,
Is there a way to prevent modifying the Standard variant? The TablePersoController triggers the PersoService and saves the data on the click of OK irrespective of the variant selected.
Thanks,
Sagar Bansal
Hi,
After implementing all the code, on the initial load of the application the Variant list show 'Default' as the first value instead of standard. Any idea how to change that?
Thanks,
Sagar Bansal
Hello, thanks for this great article !
Question : the "standard" variant is set as favorite and cannot be removed. Is it possible to do the same with 1 custom variant ? (I mean to prevent end users from removing "favorite" on a custom variant created by the central team) ?
Regards, Quentin