cancel
Showing results for 
Search instead for 
Did you mean: 

Design Studio 1.3 SDK - Are script contributions for SAPUI5 method functions supported?

MustafaBensan
Active Contributor
0 Kudos

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.

Accepted Solutions (1)

Accepted Solutions (1)

mike_howles4
Active Contributor
0 Kudos

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!

MustafaBensan
Active Contributor
0 Kudos

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.

mike_howles4
Active Contributor
0 Kudos

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!

MustafaBensan
Active Contributor
0 Kudos

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

mike_howles4
Active Contributor
0 Kudos

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!

MustafaBensan
Active Contributor
0 Kudos

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.

XaviPolo
Active Contributor
0 Kudos

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:

  • on "createControl":
    • all getters/setters (explained above)
  • on Fire Event: Another DS Component call a method from our component / or a event fired by fireDesignStudioEvent() method in our component:
    • all getters to get Old Values, and only setters that has new values.

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.

XaviPolo
Active Contributor
0 Kudos

Hi again,

Another problem with SAP UI5 components is that getters/setters are called in alphabetic order, and don't use the order established in "metadata: properties:" 😞

MustafaBensan
Active Contributor
0 Kudos

Hi Xavier,

In the comments above Mike Howles had observed that the getters/setters execute in the order listed in the contribution.xml file.  I tried this approach and it worked for me.  Have you considered this method?

Mustafa.

XaviPolo
Active Contributor
0 Kudos

yes, definitely not in alphabetical order :-), but not all the properties are called in the order defined in contribution.xml

Databound objects are called in last position after all the other properties. This and the name of my properties has confused me

MustafaBensan
Active Contributor
0 Kudos

Okay, I see.  I haven't tried databound properties yet. 

What do you mean by the names of your properties being confusing?

XaviPolo
Active Contributor
0 Kudos

that my properties are:

  • allOption
  • autoLoadOptions
  • measure
  • metadata

As you can see calling them seems to be in alphabetic order 🙂

reiner_hille-doering
Active Contributor
0 Kudos

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:

  1. Properties are updated in bulk per round trip.
  2. If a property was updated multiple times within a round trip (multiple assignments in ZTL), only the last survives.
  3. The order of property setter calls is not defined. Based on our frameworks that we use internally it might be alphabetical with some exceptions, e.g. data-bound properties at the end, followed by "metadata". But this might all change in future and differ between platforms.
  4. To avoid that setters are called too often, currently the corresponding getter is called before and new value is compared with old value. However, this implementation might change in the future - letting the backend decide which properties need to be send. So you shouldn't rely on the fact that new values are always really new.
  5. If you need to be a consistent state of your properties, you can use afterUpdate. In UI5 SDK this function is introduced with DS 1.3 SP 1 and called "afterDesignStudioUpdate".

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.

MustafaBensan
Active Contributor
0 Kudos

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.

reiner_hille-doering
Active Contributor
0 Kudos

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.

MustafaBensan
Active Contributor
0 Kudos

Thanks for the clarification, Reiner.  I'll see how it works out.

Regards,

Mustafa.

jens_we
Explorer
0 Kudos

Hi Mustafa,

have you found any solution for your issue? I am in trouble with the same kind of behaviour...

Regards,

Jens

MustafaBensan
Active Contributor
0 Kudos

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.

Former Member
0 Kudos

very interesting angle!

however:  someMethod : function(value){ 

gives me a syntax error in component.js.

I have to use "function someMethod()" - but that results in a script error when executed

Former Member
0 Kudos

This message was moderated.

Answers (1)

Answers (1)

Former Member
0 Kudos

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)

MustafaBensan
Active Contributor
0 Kudos

Thanks for sharing this Dmitry.