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:
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.
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();
};
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.
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);
};
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;
}
}
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).
var propertyValue = this.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, 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();
};
});
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.
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.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
13 | |
10 | |
10 | |
7 | |
6 | |
5 | |
5 | |
5 | |
4 | |
4 |