on 07-21-2014 2:49 PM
All of the SAPUI5 SDK component examples provided with the Design Studio Samples demonstrate how to expose property getter/setter functions as script contributions. Indeed, the SDK Developer Guide also just focuses on this. However, UI5 controls may also include method functions that allow certain actions to be performed on the control. In this context, I have the following questions:
1) Is it possible to expose method functions via script contributions in the contribution.ztl file? If yes, what is the correct syntax for doing so? Based on my experimentation so far, it seems like method functions cannot be exposed directly via script contributions;
2) If method functions are not supported for script contributions, are there any recommended approaches as a workaround? One possible approach that comes to mind is as follows:
i) Define an invisible "dummy" property of type boolean to correspond with each method function that we want to expose as a script contribution;
ii) Define invisible properties to correspond to the parameters required by the method functions;
iii) In the contributon.ztl code, perform the following tasks:
(a) Set the invisible parameter property values that correspond to the desired method function;
(b) Invoke the dummy property getter or setter function by getting or setting the boolean value of the dummy property in the contribution.ztl code;
iii). Override the corresponding dummy property getter or setter function in the component.js code with additional logic to read the invisible parameter property values and then call the method function.
Any feedback would be appreciated.
Thanks,
Mustafa.
Unless there's a much more official graceful way, here's one approach:
One dummy property in the contribution.xml:
<property type="String" title="Execute" id="execute" visible="false"/>
Two example .ztl "methods":
/**
* Execute some method
*/
void someMethod( String someParameter){*
this.execute = "someMethod|" + someParameter + "~" + Math.random();
*}
/**
* Execute another method
*/
void anotherMethod( String someParameter){*
this.execute = "anotherMethod|" + someParameter + "~" + Math.random();
*}
And finally, the component.js that makes it work (2 regular example functions for the above 2 in the .ztl, followed by my bizarre 'setExecute' function that makes it all work.
someMethod : function(value){
alert("I am 'someMethod'. You passed me value: " + value);
},
anotherMethod : function(value){
alert("I am 'anotherMethod'. You passed me value: " + value);
},
setExecute : function(s){
var cmd;
if(s) cmd = s.split("~");
if(cmd!==undefined && cmd.length > 1){
var methodNameParam = cmd[0].split("|");
var methodName = methodNameParam[0];
var methodParam;
if(methodNameParam.length>1) methodParam = methodNameParam[1];
if(this[methodName]) {
this[methodName](methodParam);
}else{
alert("You passed me a bad method (" + methodName + ")");
}
}
},
getExecute : function(){
return String(Math.random());
}
Now, aside from this being a hack, you may notice Math.random() -- This is to defeat the state saving that happens in the server-side JavaScript command engine, so you can ensure you can invoke the same method and parameter over and over.
Would love to see a better approach!
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Mike,
Thanks very much for your suggestion. Your mind-bogglingly creative solutions never cease to amaze me .
Reviewing the code, yes I did wonder about the use of the Math.random() function. Then when I subsequently read your explanation about the state saving, the penny dropped as to the issue I was experiencing with the approach I had previously tried (as described in point form in my question), whereby I could not invoke the setter function multiple times from the BIAL script. So I added a call to the fireDesignStudioPropertiesChanged() function, which has partially solved my issue but the parameter properties are out of sync.
I'll describe what I've implemented and hopefully you can shed some more light on how to resolve the issue:
In the contribution.xml file I've defined invisible properties for the method function and it's parameters as follows:
<component id="ActionLabel"
title="Action Label"
icon="res/com.infovizi.prototypes.actionlabel/icon.png"
handlerType="sapui5">
<jsInclude>res/com.infovizi.prototypes.actionlabel/js/component.js</jsInclude>
<cssInclude>res/com.infovizi.prototypes.actionlabel/css/component.css</cssInclude>
<property id="text" type="String" title="Text" visible="true" />
<property id="showAlert" type="boolean" title="Show Alert" visible="false" />
<property id="alertText1" type="String" title="Alert Text 1" visible="false" />
<property id="alertText2" type="String" title="Alert Text 2" visible="false" />
<initialization>
<defaultValue property="SHOWALERT">false</defaultValue>
<defaultValue property="TEXT">"Hello"</defaultValue>
</initialization>
</component>
In the contribution.ztl file I've defined a script method to invoke a method function for displaying an alert as follows:
class com.infovizi.prototypes.ActionLabel extends Component {
/* Displays an alertbox */
void showAlertBox(/* Text 1 */ String alertMsg1, /* Text 2 */ String alertMsg2) {*
this.alertText1 = alertMsg1;
this.alertText2 = alertMsg2;
this.showAlert = true;
*}
}
In the component.js file, to keep things simple I've chosen the UI5 Label control to extend with a custom method function that I want to invoke via the script contribution as follows:
sap.ui.commons.Label.extend("com.infovizi.prototypes.ActionLabel",{
metadata : {
properties : {
"showAlert" : "boolean",
"alertText1" : "string",
"alertText2" : "string"
}
},
initDesignStudio: function(){
},
renderer:{},
// Override ShowAlert setter to perform action
setShowAlert: function(alertState){
if(alertState == true) {
this.displayAlert(this.getAlertText1(),this.getAlertText2());
// Reset showAlert property value to "false" and call fireDesignStudioPropertiesChanged()
// to allow setShowAlert function to be invoked multiple times via BIAL script
this.showAlert = false;
this.fireDesignStudioPropertiesChanged(["showAlert"]);
}
return this;
},
// Alert method
displayAlert: function(alertMsg1, alertMsg2){
alert("Message: " + alertMsg1 + " " + alertMsg2);
},
});
I then created a DS app with the following script in the click event of a button to invoke the method function in the custom Label control:
ACTIONLABEL_1.showAlertBox("Hello", "World!");
After launching the application and clicking the button for the first time, the alert display function is displayed as expected but the parameters have not been updated, even though they are updated in the script contribution function, so the parameters are not displayed, as follows:
After the alert is dismissed and the DS button is clicked again, this time the parameters are passed correctly as follows:
So the parameter updates for alertText1 and alertText2 seem to be out of sync for some reason. I'm sure I'm missing a nuance here. Any ideas?
Conceptually, I'm just trying to achieve what Leandro Cardoso has done with the script contribution action function calls in his Notify component. The only difference is that I'm implementing HandlerType "sapui5" instead of "div".
Any thoughts about the parameter syncing issue would be appreciated.
Mustafa,
I used your example code and was able to reproduce the error. I am able to observe that the properties are being set out of order. The showAlert property is set before the alertText1 and alertText2 properties. This makes no sense, as you'd think that the server-side JS would honor order of execution, but my suspicion is that it does not dispatch a property update until the ZTL function has completed.
If I am correct, I suspect that the bulk property updates use a first in, first out (FIFO) sequence. I was able to change the order of your properties in contribution.xml to this, which will illustrate a proper working alertText1 and a broken alertText2:
<property id="alertText1" type="String" title="Alert Text 1" visible="false" />
<property id="showAlert" type="boolean" title="Show Alert" visible="false" />
<property id="alertText2" type="String" title="Alert Text 2" visible="false" />
If you were to move alertText2 before showAlert, then both will work in your example.
My guess is because this is server-side ZTL code, the updates to the properties occur in bulk once per "round-trip" aka execution, and the order is FIFO, which I personally thing is counter-intuitive, but if this suspicion is correct and by-design, at least we now know the reason for the behavior.
So you could either use my approach that gets around that, or use individual BIAL statements to set the alertText1, then alertText2, and then call the showAlert, but that seems unnecessarily overly verbose.
Of course this is all conjecture and assumptions on my part, so maybe can weigh in and set us (or me) straight on what the best approach would be?
Hope this helps, all the same!
Hi Mike,
Thanks so much for the detailed analysis (and confirming my sanity ). The observed behaviour is quite bizarre indeed. Interestingly, I had previously tried the verbose approach of individual BIAL statements to set the alertText1, then alertText2, and then call the showAlert. However, this had no effect either.
For now, I successfully tried the lazy approach of putting the properties in the desired order in the contribution.xml file as follows:
<property id="alertText1" type="String" title="Alert Text 1" visible="false" />
<property id="alertText2" type="String" title="Alert Text 2" visible="false" />
<property id="showAlert" type="boolean" title="Show Alert" visible="false" />
Thanks for that useful observation. I will also try your random number approach adapted for multiple parameters.
Like you, I'd be curious to receive any suggestions from Reiner Hille-Doering
Mustafa,
Thanks for adding that you tried the verbose method, I'd made the assumption that each BIAL statement would trigger an individual "round-trip" and apply property update(s), however this would seem to prove otherwise, perhaps it's one bulk update per entire BIAL code block of execution? In either case, yes it would seem it's a peculiarity but I'm glad to hear you have a path in the interim of SAP giving us a better way!
Good luck!
Hi Mike,
Yes, I agree that it seems like a bulk execution of a BIAL code block. It's almost as if all the property getters are called when the this.fireDesignStudioPropertiesChanged(["showAlert"]) function is executed, regardless of the properties explicitly listed in the property array. Very unusual.
Hi Michael, Mustafa,
I have some problem with getters/setters, and debugging the code, I can see that getters/setters are called in a "for each" statement, that iterates over all properties of our component.
oControl.oComponentProperties=oComponentProperties;
for(sPropName in oControlProperties){this.updateProperty(oControl,sPropName,oControlProperties);
The logic to know if a setter call is needed:
var getter=oControl["get"+uppercased];
var setter=oControl["set"+uppercased];
if(getter&&setter){var oldVal=null;
try{oldVal=getter.call(oControl);
}catch(e){}var newVal=aProperties[sPropName];
if(oldVal!=newVal){setter.call(oControl,newVal);
}
as we can see in code if the property has get and set methods, there is a check to know if the value has changed
in this check the getter method is called to get "old value",
so, in this iteration all getters are called to know the old values.
Setters are called only if data was changed, but for first time the properties are undefined => oldVal == undefined and new values that come from defaults are "", null, 0, etc... so all setters are called.
I can see that getters/setters are called:
But I find some bugs in the precess to get Old Values
If our SAP UI5 extended component is bounded to data source, we need to create get/setMetadata to pass query data to our component.
BUT there is a problem in -> getter=oControl['getMetada'], because doesn't return our getMetadata method.
In consequence old value and new value are always different, and set method is called every time.
The other problem is that if one of our properties has "set" method overwritten by us, but not the "get", the oldValue is always null making the result of comparision true and set method is called.
Hi Michael, hi Mustafa, hi Xavier,
it it is quite fascinating what you guys try and achieve with the SDK. What you are doing is effectively a simple Remote Procedure Call framework on top of Design Studio SDK's property synchronization feature. And indeed what you find out is all correct:
To implement a reliable RFC, you would need a call queue, e.g. implemented by a String property containing a JSON array. As call ID I would use another int-Property that is increased by every call (instead the random number). Function args need to be serialized as JSON as well.
Maybe once one of use post a reference implementation...
BTW I'm currently implementing for 1.4 also ways to do the same trick in opposite direction: effectively calling ZTL functions from the front end. This gives a whole bunch of new possibilities for SDK components...
Reiner.
Hi Reiner,
Thanks very much for your comprehensive feedback. It's good to try to push the boundaries with the SDK .
Yes, what I was trying to achieve was indeed an RFC framework. It seems like a variation of Mike's approach taking into account your specific suggestions may do the trick.
So for clarification, some of follow-up questions:
1) You have suggested the use of two JSON arrays, one for the "call queue" and one for the function arguments. Can you elaborate just a little about your reference to the call queue?
2) If there is also a requirement for the function to return several argument values then presumably this should be achieved by setting corresponding "invisible" argument properties from within the function?
3) Regarding the implementation of front-end ZTL function calls in DS 1.4, does this open up the possibility of interacting with the DOM from a ZTL function? What other possibilities do you have in mind as use cases as well?
Regards,
Mustafa.
Hi Mustafa,
1)
Assume there is a ZLT function like
void remoteCall(sMethod, aArguments) {*
var queue = JSON.parse(this.queue);
var callEntry = {
"id": this.callid++,
"method": sMethod,
"args": aArguments;
};
queue.push(callEntry);
this.queue = JSON.parse(queue);
*}
The corresponding setter and getter could look like
setQueue = function(val) {
var queue = JSON.parse(val);
// May needed to sort by ID?
queue.forEach(function(entry) {
that[entry.method].apply(that, etry.args);
});
this.firePropertiesChanged(["queue"]); // This will clean the queue
}
getQueue = function() {
return "[]";
}
This is all untested and should only demonstrate the ID. I'm not even sure if the callid property is needed.
2) The way back (result from invoked function) would again require an invisible property (e.g. a JSON indexed by callID containing the result of the call. This could be transported back after the call using "firePropertiesChanged".
3)
The new 1.4 feature goes more in the direction that an SDK component.JS could indirectly call BIAL scripting functions. E.g. a listbox that should show all available dimensions need to call ds.getDimension() before it renders. And a sortable table might want to call ds.sortByMember() in response to a UI action.
Hi Jens,
Reiner's suggested solution above was implemented in this SDK Notification Bar Component by Karol Kalisz. Reviewing the source code to understand the technique may help with your scenario. The code is quite readable.
Regards,
Mustafa.
As for me, i use something like this to keep parameters valid and call events on client side.
1) Create property in contribution.xml
<property type="String" title="actions" id="actions" visible="false">
2) Create method u call in contribution.ztl
void performAction(/* Action */ String action_name,/* Parameter */ String parameter)/*or JSON parameter*/
{*
var obj;
if (this.actions.length==0)
{
obj={"actions":[{"action_name":action_name,"parameter":parameter}]};
}
else
{
obj = JSON.parse(this.actions);
obj['actions'].push({"action_name":action_name,"parameter":parameter});
}
this.actions=JSON.stringify(obj);
*}
3) Create corresponding part in component.js
this.actions = function(value) {
if (value === undefined) {
return actions;
}
else {
actions=value;
parse_command(actions);
return this;
}
};
function parse_command(commands)
{
/*parse JSON, perform actions*/
}
this.afterUpdate = function(){
/*I do not use +1/+random/+char because i had problems with this wich i don`t remember(maby fake events),so i fire PropertiesChanged*/
var properties_to_update = new Array();// array for all parameters i wish to reset
if (actions.length>0)
{
actions="";
properties_to_update.push("actions");
}
/*add other properties i want to reset to array and then fire PropertiesChanged to reset it on the server side*/
that.firePropertiesChanged(properties_to_update);
};
as simple as possible)
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
84 | |
23 | |
11 | |
9 | |
8 | |
5 | |
5 | |
5 | |
5 | |
4 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.