Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
david_stocker
Advisor
Advisor

This is part of a tutorial series on creating extension components for Design Studio.

Now that we have a handle on how properties are kept synchronized between the client and server, we can go about making it happen.  We're not yet making use of the the additional properties sheet (APS), we don't have any machinisms yet for altering the properties at runtime and changes made in the canvas can't be posted back to the sever copy, property synchronization only goes in one direction; from server copy to canvas.  Therefore, all of our changes will be happening in the component.js file of our gauge component.

We're going to do four things:

  • We need to initialize all properties on the client side.  Recall that only changes from the default state are sent from the server to the client, so we'll need to perform a separate initialization on the client side.

  • We'll adapt the configurable javascript code that we created in Part 3a to use our properties.

  • Whenever the server sends an updated property value, we'll need to update the property on the client side.

  • Whenever a property changes on the client side, we're going to need to redraw the gauge to reflect the property updates.   The best way to do this is to encapsulate the redrawing of the gauge, so that it can be done in initialization, or whenever a property value changes.

Property Initialization

This is the easy part.  For every property, we're going to need a counterpart in the component subclass, within the component.js file.  We're going to use the naming convention _<propertyName> for these properties, so that if we have a property, called foo, we'd have a variable in the component subclass, called _foo.

In the following code, we do two things.  We declare a variable, called me, which is a pointer to this.  This is an alternative to the variable named that, which we've been using up to now.  We're also going to declare all property variables.


var me = this;



//Properties


me._colorCode = 'blue';


me._innerRad = 0.0;


me._outerRad = 0.0;


me._endAngleDeg = 90.0;


me._startAngleDeg = -90.0;


me._paddingTop = 0.0;


me._paddingBottom = 0.0;


me._paddingLeft = 0.0;


me._paddingRight = 0.0;


me._offsetLeft = 0.0;


me._offsetDown = 0.0;






Important!  Make sure that you initialize them to the same values as are in the properties sheet!  Otherwise, you'll have discordant property values on the client and server.  Properties with no defaultValue defined in component.xml default to 0.

Repackaged Redraws

This is rather simple.  Let's start with the last time we worked on the component.js, in the component.  First, we're going to create a new function, called redraw().  We can cut and paste everything from init().  Then we can simply tell init() to call redraw().


me.redraw = function() {


    //Draw the gauge


};






me.init = function() {


    me.redraw();


};





The new redraw() function

Now let's update redraw to incorporate the configurable gauge code from Part 3a.  We're going to do a couple of refactors on that code, while doing this.

  • All of the properties that are actually called me._<propertyName> need to be renamed.

  • Remove the check on inner radius size.  We'll move that check to the setter function.

  • Redraw means that we remove any existing copies of the gauge and draw a new copy.  We'll use D3's remove() method to do this.  So the following will remove any existing svg objects from the component and add a new one for the redrawn gauge.

var myDiv = me.$()[0];



// Clear any existing gauges.  We'll redraw from scratch


d3.select(myDiv).selectAll("*").remove();



var vis = d3.select(myDiv).append("svg:svg").attr("width", "100%").attr("height", "100%");





Our redraw() function now looks like this:


me.redraw = function() {



  var myDiv = me.$()[0];



  // Clear any existing gauges.  We'll redraw from scratch


  d3.select(myDiv).selectAll("*").remove();



  var vis = d3.select(myDiv).append("svg:svg").attr("width", "100%").attr("height", "100%");


  var pi = Math.PI;



  // Find the larger left/right padding


  var lrPadding = me._paddingLeft + me._paddingRight;


  var tbPadding = me._paddingTop + me._paddingBottom;


  var maxPadding = lrPadding;


  if (maxPadding < tbPadding){


  maxPadding = tbPadding


  }



  me._outerRad = (me.$().width() - 2*(maxPadding))/2;



  //The offset will determine where the center of the arc shall be


  me._offsetLeft = me._outerRad + me._paddingLeft;


  me._offsetDown = me._outerRad + me._paddingTop;



  var arcDef = d3.svg.arc()


  .innerRadius(me._innerRad)


  .outerRadius(me._outerRad)


  .startAngle(me._startAngleDeg * (pi/180)) //converting from degs to radians


  .endAngle(me._endAngleDeg * (pi/180)); //converting from degs to radians




  var guageArc = vis.append("path")


     .style("fill", me._colorCode)


     .attr("width", me.$().width()).attr("height", me.$().height()) // Added height and width so arc is visible


     .attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")")


     .attr("d", arcDef);


};





Being Defensive

When setting the inner radius, we're going to want to sanity check the value and warn the designer if it is nonsensical.  So we'll add function for checking this; which we can call from the setters that need it.  If the new value of inner radius is greater than the calculated outer radius, then post an alert.  We won't be able to modify the property sheet value from component.js, so instruct the designer to change the last value with another alert.


//Validate the Inner and Outer Radii


me.validateRadii = function(inner, outer) {


     if (inner <= outer) {


          return true;


     } else {


          alert("Warning!  The gauge arc can't have a negative radius!  Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


          return false;


     }


};





Whenever the designer attempts to change one of the bounding box properties (height, width, padding), we also need to recalculate the new outer radius and perform a sanity check.  If the newly calculated radius is valid, then we can apply it.  If not, then we'll have to reject the change.


//Recalculate Outer Radius.  Also, double check that the new value fits with me._innerRad


me.recalculateOuterRadius = function(paddingLeft, paddingRight, paddingTop, paddingBottom){


  // Find the larger left/right padding


  var lrPadding = paddingLeft + paddingRight;


  var tbPadding = paddingTop + paddingBottom;


  var maxPadding = lrPadding;


  if (maxPadding < tbPadding){


  maxPadding = tbPadding


  }


  var newOuterRad = (me.$().width() - 2*(maxPadding))/2;


  var isValid = me.validateRadii(me._innerRad, newOuterRad);


  if (isValid === true){


  me._outerRad = newOuterRad;


  return true;


  }


  else {


  return false;


  }


}





Property Updates

Whenever a property change is initiated on the server side, the corresponding setter is fired on the client side.  The Design Studio SDK framework uses a standard pattern for property getters and setters.  There should be a single function called <property>(), with a single parameter, value.  It returns either the value of the property, or the parent object (this).


  • If the getter/setter is called without a value, it is being used as a getter and should return the value of the property.  E.g. this returns the value of foo.

var propertyValue = this.foo();





  • If the getter/setter is called with a value, it is being used as a setter.  It should update the property value and return the parent object.  E.g. this changes the value of foo.

this.foo(updatedValue);





If the property is relevant to the inner or outer radius of the gauge, we need to validate the change before actually setting it.  For these five properties, innerRadius, paddingLeft, paddingRight, paddingTop and paddingBottom (hieght and width are also relvant, but are standard properties and we can't control them), we need to perform a sanity check before actually setting the value.  If it is valid, we set the value, as on the other setters.  If it is invalid, we won't change the value and we'll need to alert the designer to this fact, so that they can modify the value.

If the setter is being called (and sanity checks are valid), be sure to call me.redraw(), before returning the object!


//Getters and Setters


me.colorCode = function(value) {


  if (value === undefined) {


  return me._colorCode;


  } else {


  me._colorCode = value;


  me.redraw();


  return me;


  }


};




me.innerRad = function(value) {


  if (value === undefined) {


  return me._innerRad;


  } else {



  var isValid = me.validateRadii(value, me._outerRad);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Inner Radius must be equal to or less than " + me._outerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._innerRad = value;


  me.redraw();


  }


  return this;


  }


};




me.endAngleDeg = function(value) {


  if (value === undefined) {


  return me._endAngleDeg;


  } else {


  me._endAngleDeg = value;


  me.redraw();


  return this;


  }


};




me.startAngleDeg = function(value) {


  if (value === undefined) {


  return me._startAngleDeg;


  } else {


  me._startAngleDeg = value;


  me.redraw();


  return this;


  }


};




me.paddingTop = function(value) {


  if (value === undefined) {


  return me._paddingTop;


  } else {


  var isValid =me.recalculateOuterRadius(me._paddingLeft, me._paddingRight, value, me._paddingBottom);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._paddingTop = value;


  me.redraw();


  }


  return this;


  }


};




me.paddingBottom = function(value) {


  if (value === undefined) {


  return me._paddingBottom;


  } else {


  var isValid = me.recalculateOuterRadius(me._paddingLeft, me._paddingRight, me._paddingTop, value);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me.me._paddingBottom = value;


  me.redraw();


  }


  return this;


  }


};




me.paddingLeft = function(value) {


  if (value === undefined) {


  paddingLeft = me._paddingLeft;


  return paddingLeft;


  } else {


  var isValid = me.recalculateOuterRadius(value, me._paddingRight, me._paddingTop, me._paddingBottom);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._paddingLeft = value;


  me.redraw();


  }


  return this;




  }


};




me.paddingRight = function(value) {


  if (value === undefined) {


  paddingRight = me._paddingRight;


  } else {


  var isValid = me.recalculateOuterRadius(me._paddingLeft, value, me._paddingTop, me._paddingBottom);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._paddingRight = value;


  me.redraw();


  }


  return this;


  }


};




The new component.js

The new component.js, in its entirety:


sap.designstudio.sdk.Component.subclass("com.sap.sample.scngauge.SCNGauge", function() {



  var me = this;



//Properties


  me._colorCode = 'blue';


  me._innerRad = 0.0;


  me._outerRad = 0.0;


  me._endAngleDeg = 90.0;


  me._startAngleDeg = -90.0;


  me._paddingTop = 0;


  me._paddingBottom = 0;


  me._paddingLeft = 0;


  me._paddingRight = 0;


  me._offsetLeft = 0;


  me._offsetDown = 0;




//Validate the Inner and Outer Radii


  me.validateRadii = function(inner, outer) {


  if (inner <= outer) {


  return true;


  } else {


  return false;


  }


  };




//Recalculate Outer Radius.  Also, double check that the new value fits with me._innerRad


  me.recalculateOuterRadius = function(paddingLeft, paddingRight, paddingTop, paddingBottom){


  // Find the larger left/right padding


  var lrPadding = paddingLeft + paddingRight;


  var tbPadding = paddingTop + paddingBottom;


  var maxPadding = lrPadding;


  if (maxPadding < tbPadding){


  maxPadding = tbPadding


  }


  var newOuterRad = (me.$().width() - 2*(maxPadding))/2;


  var isValid = me.validateRadii(me._innerRad, newOuterRad);


  if (isValid === true){


  me._outerRad = newOuterRad;


  return true;


  }


  else {


  return false;


  }


  }



//Getters and Setters


  me.colorCode = function(value) {


  if (value === undefined) {


  return me._colorCode;


  } else {


  me._colorCode = value;


  me.redraw();


  return me;


  }


  };



  me.innerRad = function(value) {


  if (value === undefined) {


  return me._innerRad;


  } else {



  var isValid = me.validateRadii(value, me._outerRad);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Inner Radius must be equal to or less than " + me._outerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._innerRad = value;


  me.redraw();


  }


  return this;


  }


  };



  me.endAngleDeg = function(value) {


  if (value === undefined) {


  return me._endAngleDeg;


  } else {


  me._endAngleDeg = value;


  me.redraw();


  return this;


  }


  };



  me.startAngleDeg = function(value) {


  if (value === undefined) {


  return me._startAngleDeg;


  } else {


  me._startAngleDeg = value;


  me.redraw();


  return this;


  }


  };



  me.paddingTop = function(value) {


  if (value === undefined) {


  return me._paddingTop;


  } else {


  var isValid =me.recalculateOuterRadius(me._paddingLeft, me._paddingRight, value, me._paddingBottom);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._paddingTop = value;


  me.redraw();


  }


  return this;


  }


  };



  me.paddingBottom = function(value) {


  if (value === undefined) {


  return me._paddingBottom;


  } else {


  var isValid = me.recalculateOuterRadius(me._paddingLeft, me._paddingRight, me._paddingTop, value);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me.me._paddingBottom = value;


  me.redraw();


  }


  return this;


  }


  };



  me.paddingLeft = function(value) {


  if (value === undefined) {


  paddingLeft = me._paddingLeft;


  return paddingLeft;


  } else {


  var isValid = me.recalculateOuterRadius(value, me._paddingRight, me._paddingTop, me._paddingBottom);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._paddingLeft = value;


  me.redraw();


  }


  return this;



  }


  };



  me.paddingRight = function(value) {


  if (value === undefined) {


  paddingRight = me._paddingRight;


  } else {


  var isValid = me.recalculateOuterRadius(me._paddingLeft, value, me._paddingTop, me._paddingBottom);


  if (isValid === false){


  alert("Warning!  The gauge arc can't have a small inner radius than outer!  Outer Radius must be equal to or greater than " + me._innerRad);


  alert("Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  } else {


  me._paddingRight = value;


  me.redraw();


  }


  return this;


  }


  };





  me.redraw = function() {



  var myDiv = me.$()[0];



  // Clear any existing gauges.  We'll redraw from scratch


  d3.select(myDiv).selectAll("*").remove();



  var vis = d3.select(myDiv).append("svg:svg").attr("width", "100%").attr("height", "100%");


  var pi = Math.PI;



  // Find the larger left/right padding


  var lrPadding = me._paddingLeft + me._paddingRight;


  var tbPadding = me._paddingTop + me._paddingBottom;


  var maxPadding = lrPadding;


  if (maxPadding < tbPadding){


  maxPadding = tbPadding


  }



  me._outerRad = (me.$().width() - 2*(maxPadding))/2;



  //Don't let the innerRad be greater than outer rad


  if (me._outerRad <= me._innerRad){


  alert("Warning!  The gauge arc can't have a negative radius!  Please decrease the inner radius, or increase the size of the control.  Height & width (including subtraction for padding) must me at least twice as large as Internal Radius!");


  }



  //The offset will determine where the center of the arc shall be


  me._offsetLeft = me._outerRad + me._paddingLeft;


  me._offsetDown = me._outerRad + me._paddingTop;



  var arcDef = d3.svg.arc()


  .innerRadius(me._innerRad)


  .outerRadius(me._outerRad)


  .startAngle(me._startAngleDeg * (pi/180)) //converting from degs to radians


  .endAngle(me._endAngleDeg * (pi/180)); //converting from degs to radians



  var guageArc = vis.append("path")


      .style("fill", me._colorCode)


      .attr("width", me.$().width()).attr("height", me.$().height()) // Added height and width so arc is visible


      .attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")")


      .attr("d", arcDef);



  };




  me.init = function() {


  me.redraw();


  };




});




Testing the Component

So when the component comes up, it should look like this in the designer, with a height and width of 200em, a starting angle of -90 degrees (relative to 12 O'clock) and an end angle of 90 degrees.  It should also be blue.

Altering the start and end angles to -45 and +45 respectively and choosing red from the color picker should result in a component that looks like this in the canvas.

Epilogue

Testing the Padding properties makes me realize that the gauge is left justified by default.  Either we need more sophisticated placement options, or we need to drop the padding rules entirely.  If this were a normal project, I'd opt for the latter as controlling the gauge size via height and width is simpler.  Instead, we'll use it as an opportunity to introduce the Additional Properties Sheet (APS), by building a small "Padding Visualizer".

As always, the completed extension (as of part 3) is available as a Github repository.

3 Comments