This is part of a tutorial series on creating extension components for Design Studio.
In the last instalment, we noted that the APS can't directly determine the component dimensions, height and width. Therefore, we added a requirement that the user maintain those values. This is problematic , from a usability perspective. The user may end up with different values in the APS than in the canvas. In this installment, we'll fix that; at least partially.
You may have noticed something in Part 3d, when we wired up our gauge for properties. The height and width of the component are editable properties in the property sheet. In fact, they are always there and are inherited from the SDK. You might also have noticed that:
var myWidth = me.$().width();
Height and width are not like conventional, developer defined properties. They are not synchronized via the normal SDK mechanisms and are handled via deeper Design Studio plumbing, but they are available directly from the component's container. Hence the JQuery call fetch their values. Since the APS is not inheriting these "hidden" properties, we can't simply make the same call in the APS. Since they don't participate in the standard getter/setter based property synchronization, we can simply create getters and setters for them. We can ask the canvas, however.
One of the functions that you inheret, when you subclass sap.designstudio.sdk.PropertyPage is callRuntimeHandler(). With it, your APS JavaScript class can make calls directly into the canvas JavaScript class. It is quite simple actually. The canvas based function may return a value, or it may be void. So you may use it to trigger an action in the canvas; from the APS. Today, we're going to use it to extract some information.
We are going to do three things:
In component.js from last time, we'll insert the two getter functions.
//Getters for the height and width of the component
me.getWidth = function(){
return me.$().width();
};
me.getHeight = function(){
return me.$().height();
};
In additional_properties_sheet.js, at the very start of the me.redraw() function, we'll insert the callRuntimeHandler() calls to get the height and width values and store them in our local copies.
//Update the height and width by getting them from the canvas
me._widthProxy = me.callRuntimeHandler("getWidth");
me._heightProxy = me.callRuntimeHandler("getHeight");
The width and height rows in additional_properties_sheet.html
<tr>
<td>Width</td>
<td>
<p id="aps_width"></p>
</td>
</tr>
<tr>
<td>Height</td>
<td>
<p id="aps_height"></p>
</td>
</tr>
That's it! Now, whenever we refresh the padding visualizer, we'll be using the current value of the height and width from the properties sheet. There is one caveat and we still have a minor usability problem, with no workaround. If the user updated ONLY the height or width, then no property changed event is triggered and no refresh happens (because there is no place for us to trigger me.redraw()). It is also not possible to message out from the canvas, so we can't trigger anything in the APS from there. This means that if the user modified these two settings and does nothing afterwards in the properties pane, she'll have to manually click on the refresh button in the APS. If she edits a padding property afterwards, however, the visualizer will automatically be refreshed.
We'll show the full code of the three files that we changed in this installment.
Our component.js now looks like this:
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._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();
};
//Getters for the height and width of the component
me.getWidth = function(){
return me.$().width();
};
me.getHeight = function(){
return me.$().height();
};
});
Our additional_properties_sheet.js now looks like this:
sap.designstudio.sdk.PropertyPage.subclass("com.sap.sample.scngauge.SCNGaugePropertyPage", function() {
var me = this;
//Viz definitiions
me.lineThickness = 2;
//Outer Dimensions & Positioning
me._paddingTop = 0;
me._paddingBottom = 0;
me._paddingLeft = 0;
me._paddingRight = 0;
//Height and Width Proxies
me._widthProxy = 200;
me._heightProxy = 200;
me.init = function() {
$("#form").submit(function() {
me._paddingTop = parseInt($("#aps_padding_top").val());
me._paddingBottom = parseInt($("#aps_padding_bottom").val());
me._paddingLeft = parseInt($("#aps_padding_left").val());
me._paddingRight = parseInt($("#aps_padding_right").val());
me.firePropertiesChanged(["paddingTop", "paddingBottom", "paddingLeft", "paddingRight"]);
me.redraw();
return false;
});
me.redraw();
};
me.paddingTop = function(value) {
if (value === undefined) {
return me._paddingTop
}
else {
me._paddingTop = value;
me.redraw();
return me;
}
};
me.paddingBottom = function(value) {
if (value === undefined) {
return me._paddingBottom
}
else {
me._paddingBottom = value;
me.redraw();
return me;
}
};
me.paddingLeft = function(value) {
if (value === undefined) {
return me._paddingLeft ;
}
else {
me._paddingLeft = value;
me.redraw();
return me;
}
};
me.paddingRight = function(value) {
if (value === undefined) {
return me._paddingRight;
}
else {
me._paddingRight = value;
me.redraw();
return me;
}
};
me.redraw = function() {
//Update the height and width by getting them from the canvas
me._widthProxy = me.callRuntimeHandler("getWidth");
me._heightProxy = me.callRuntimeHandler("getHeight");
$("#aps_padding_top").val(me._paddingTop);
$("#aps_padding_bottom").val(me._paddingBottom);
$("#aps_padding_left").val(me._paddingLeft);
$("#aps_padding_right").val(me._paddingRight);
$("#aps_width").text(me._widthProxy);
$("#aps_height").text(me._heightProxy);
// Clear any existing content. We'll redraw from scratch
d3.select("#content").selectAll("*").remove();
var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var pi = Math.PI;
//Line Accessor Function
var lineAccessor = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
///////////////////////////////////////////
//Gauge Dummy
///////////////////////////////////////////
//Determing the position of the gauge dummy (black circle)
// 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
}
//Do the same with the overall height and width
var smallerAxis = me._heightProxy;
if (me._widthProxy < smallerAxis){
smallerAxis = me._widthProxy
}
var outerRad = (smallerAxis - 2*(maxPadding))/2;
$("#aps_radius").text(outerRad);
//The offset will determine where the center of the arc shall be
var offsetLeft = outerRad + me._paddingLeft;
var offsetDown = outerRad + me._paddingTop;
//The black Circle
var arcDef = d3.svg.arc()
.innerRadius(0)
.outerRadius(outerRad)
.startAngle(-180 * (pi/180)) //converting from degs to radians
.endAngle(180 * (pi/180)); //converting from degs to radians
var guageDummy = vis.append("path")
.style("fill", "black")
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so arc is visible
.attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")
.attr("d", arcDef);
///////////////////////////////////////////
//Line Data
///////////////////////////////////////////
var lineDataOuter = [{"x":0, "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":0}];
var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":me._paddingLeft, "y":0}, {"x":me._paddingLeft, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":0}];
var lineDataPaddingRight = [{"x":( me._widthProxy - me._paddingRight), "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":( me._widthProxy - me._paddingRight), "y":me._heightProxy}, {"x":( me._widthProxy - me._paddingRight), "y":0}];
var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": me._widthProxy, "y":0}, {"x": me._widthProxy, "y":me._paddingTop}, {"x":0, "y":me._paddingTop}, {"x":0, "y":0}];
var lineDataPaddingLower = [{"x":0, "y":(me._heightProxy - me._paddingBottom)}, {"x": me._widthProxy, "y":(me._heightProxy - me._paddingBottom)}, {"x": me._widthProxy, "y":me._heightProxy}, {"x":0, "y":me._heightProxy}, {"x":0, "y":(me._heightProxy - me._paddingBottom)}];
var lineDataCrosshairsHorizontal = [{"x":me._paddingLeft, "y":(me._paddingTop + outerRad) }, {"x":(me._paddingLeft + 2*outerRad), "y":(me._paddingTop + outerRad) }];
var lineDataCrosshairsVertical = [{"x":(me._paddingLeft + outerRad), "y":me._paddingTop }, {"x":(me._paddingLeft + outerRad), "y":(me._paddingTop + 2*outerRad) }];
var borderLinesPaddingLeft = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingLeft))
.attr("stroke", "blue")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
var borderLinesPaddingRight = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingRight))
.attr("stroke", "blue")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
var borderLinesPaddingUpper = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingUpper))
.attr("stroke", "blue")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
var borderLinesPaddingLower = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingLower))
.attr("stroke", "blue")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
var borderLinesOuter = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataOuter))
.attr("stroke", "black")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
var borderLinesCrosshairHorizontal = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataCrosshairsHorizontal))
.attr("stroke", "white")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
var borderLinesCrosshairVertical = vis
.attr("width", me._widthProxy).attr("height", me._heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataCrosshairsVertical))
.attr("stroke", "white")
.attr("stroke-width", me.lineThickness)
.attr("fill", "none");
}
});
Our additional_properties_sheet.html now looks like this:
<html>
<head>
<title>Gauge Padding Visualizert</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script src="/aad/zen.rt.components.sdk/resources/js/sdk_propertysheets_handler.js"></script>
<script src="additional_properties_sheet.js"></script>
</head>
<script>
new com.sap.sample.scngauge.SCNGaugePropertyPage();
</script>
<body>
<form id="form">
<fieldset>
<legend>Gauge Padding Visualizer</legend>
<table>
<tr>
<td>Padding Top</td>
<td>
<input id="aps_padding_top" type="number" name="paddingTop" size="40" maxlength="40"></input>
</td>
</tr>
<tr>
<td>Padding Bottom</td>
<td>
<input id="aps_padding_bottom" type="number" name="paddingBottom" size="40" maxlength="40"></input>
</td>
</tr>
<tr>
<td>Padding Left</td>
<td>
<input id="aps_padding_left" type="number" name="paddingLeft" size="40" maxlength="40"></input>
</td>
</tr>
<tr>
<td>Padding Right</td>
<td>
<input id="aps_padding_right" type="number" name="paddingRight" size="40" maxlength="40"></input>
</td>
</tr>
<tr>
<td>Width</td>
<td>
<p id="aps_width"></p>
</td>
</tr>
<tr>
<td>Height</td>
<td>
<p id="aps_height"></p>
</td>
</tr>
<tr>
<td>Outer Radius</td>
<td>
<p id="aps_radius"></p>
</td>
</tr>
<tr>
<td>
<input name="submit" type="submit" value="Refresh"/>
</td>
</tr>
</table>
</fieldset>
</form>
<div id='content'></div>
<div id='componentproxy'></div>
</body>
</html>
As always, the completed extension (as of part 4) is available as a Github repository.
Next time, we'll add a scripting API to our component.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
26 | |
22 | |
19 | |
13 | |
10 | |
9 | |
9 | |
8 | |
7 | |
7 |