Technical Articles
Simple issue of passing the context to the Formatter
Background
I was working on enhancing an existing Fiori application. This application has some search fields and then a table displaying the results. The requirement was simple, to add a button to download the table data. The standard table control provides us an example here (using 1.38.11)which I referenced and work was 99% done. This last one percent led to this blog post, read on!
The 1% issue
Before explaining the issue please check my project structure below along with sample formatter code. The formatter is added as dependency in controller as we normally does.
getStatus: function (value) {
var resourceBundle = this.getView().getModel("i18n").getResourceBundle();
switch (value) {
case "A_1":
return resourceBundle.getText("A_1");
case "A_2":
return resourceBundle.getText("A_2");
case "A_3":
return resourceBundle.getText("A_3");
case "A_4":
return resourceBundle.getText("A_4");
default:
return value;
}
}
As can be seen above my view table is using a custom formatter defined in a separate file. The data being read is from model and same formatting needs to be applied while downloading of data also. The current formatter when called via view can read my i18n resource model via below mentioned statement as accordingly replace or update the value.
this.getView().getModel("i18n").getResourceBundle();
The same formatter when called via controller method during export it failed. The this in this case refers to the control via which it is being called which is Column control inside ExportToCSV. So somehow in the sample code below for COL3 we need to pass the context of the controller so that it works.
var oExport = new Export({
exportType: new ExportTypeCSV({
separatorChar: "\t" ,
mimeType: "application/vnd.ms-excel" ,
charset: "utf-8" ,
fileExtension: "xls"
}),
// Pass in the model created above
models : this.getView().getModel("SGTIN"),
// binding information for the rows aggregation
rows : {
path : "/"
},
// column definitions with column name and binding info for the content
columns : [{
name : "COL1",
template : {
content: "{COL1}"
}
},{
name : "COL2",
template : {
content: {
parts: [{path:'COL2'}],
formatter : Formatter.getCol2
}
}
},{
name : "COL3",
template : {
content : {
parts : [{path:'COL3'}],
formatter :Formatter.getStatus ----> PROBLEM HERE
}
}
}]
});
// download exported file
oExport.saveFile().catch(function(oError) {
MessageBox.error("Error when downloading data. Browser might not be supported!\n\n" + oError);
}).then(function() {
oExport.destroy();
});
So how do we basically pass the current contexts to this inner function so that i18n is available, read on!
Resolution
One thing is clear we need to pass the current context while calling it from my Main controller, question that remains is how?
As always we googled and tried the solution mentioned at the link here where we add the current context to the method call as any array but it did not work
formatter :[Formatter.getStatus,this]
We tried using the parts also to pass the context but it expects a path to the current model?
parts : [{path:'COL3'},this],
So the worst case scenario for me was to copy paste the same code which is in the formatter to the controller but that will be off course stupid and breaking the DRY ( Do not repeat yourself) principle. I was kind of stuck and thought of discussing this with a very close friend Ashish Ahire about the situation I am in. As always he was back with a super quick solution as mentioned below
formatter :Formatter.getStatus.bind(this)
This worked like a charm, just using bind and passing the current context worked! I actually ended up reading in detail about Call(), Apply() and bind(). Apart from bind other two functions will also work. To summarize these methods basically execute the function with the passed on context as arguments, super powerful. Thanks Ashish!
What is next?
I documented this solution because I feel we might have many other developers facing the same issue. If we have any other alternative apart from this one feel free to pitch in, open to all ears. I think call, apply etc. needs a separate blog post to understand them in detail, hopefully will write it soon.
Nice one Nabheet Madan !
If you want to do it in the xml view (latest ui5)
https://ui5.sap.com/#/topic/b0fb4de7364f4bcbb053a99aa645affe
and there is one more way also using proxy, which is not really needed as we have the latest .bind()
BR,
Mahesh
Thanks Mahesh.. cool now we are covered from view as well as controller side:)
Hello,
It is very interesting, thanks a lot.
Just a question, why not simply put the view instance during the instantiation of the formatter object ? Like this, you store it in the whole formatter class...
Regards,
Joseph