Here is the model layer for the UnitConverter component. It defines all we need for the conversion functionality:
object UnitConverter { static class Converter { double value1, value2; String unit1, unit2; String title; public String toString() { return title; } } object converters extends ComponentList<converter> { object temperature extends Converter { value1 = 0; value2 :=: value1 * 9.0 / 5.0 + 32; unit1 = "Celcius"; unit2 = "Farenheit"; title = "Temperature"; } object distance extends Converter { value1 = 0; value2 :=: value1 * 0.62137119; unit1 = "Kilometers"; unit2 = "Miles"; title = "Distance"; } object speed extends Converter { title = "Speed"; value1 = 0; value2 :=: value1 * 1.609344; unit1 = "Miles per hour"; unit2 = "Kilometers per hour"; } } }There are four differences from Java demonstrated here:
- Instead of inner classes, this example uses inner objects.
- A customized object template - Remember that StrataCode uses code-generation, and object templates are the mechanism to tell the code-generator how to handle an inner object of an outer class. In this case, the ComponentList class uses the @CompilerSettings annotation to register an object template to customize the code-generation of its inner objects. That template inserts the "add" call so the inner object which is added to the list when the instance is initialized. This same mechanism is used in various user-interface toolkits for a parent user interface widget to automatically add it's inner objects as child widgets.
- Notice the use of the :=: operator. It is used to relate two values in a bi-directional expression using data binding. When value1 changes, the expression is evaluated and value2 is set. Similarly when value2 is changed, the inverse of that expression is evaluated and value1 is set. Bi-directional bindings are usable with expressions that have a way to invert them, e.g. arithmetic expressions that have a single variable or simple assignments, or methods which register an explicit reverse method through an annotation.
- Properties of an object can be assigned with a class-level construct. Normally in java, you would need to wrap a property assignment in open/close braces (i.e. {}) but in StrataCode, it's a class-level operation to make your code more declarative.
package sc.example.unitConverter; @sc.obj.Sync(syncMode=sc.obj.SyncMode.Automatic) public example.unitConverter.model extends util { //defaultSyncMode = SyncMode.Automatic; codeType = CodeType.Model; }
The package operator here sets a package to use as the root for all files in this layer. That means UnitConverter.sc can live in the layer directory itself, not in the subdirectory sc/example/unitConverter. The source files themselves do not have to specify a package. Layer packages help organize code with fewer directories and narrows the focus for a layer making code bases easier to navigate.
The codeTypes setting does not affect the runtime. It helps tools organize layers (e.g. in a management UI or IDE). The layer definition file is written in interpreted Java and can include code to customize your application when this layer is included. It handles the same tasks that build.xml does but using declarative StrataCode rather than XML.
@Sync(syncMode=SyncMode.Automatic) public example.unitConverter.extendedModel extends example.unitConverter.model { //defaultSyncMode = SyncMode.Automatic; codeType = CodeType.Model; }
The extendedModel extends the model layer using the Java extends operator. You can include multiple base layers, separated by commas. When you extend a base layer, you also by default inherit the package of the first layer in the list which has exportsPackage=true. Application layers frequently all use the same package and so this is a nice shortcut.
Here's a purely declarative layer which defines three more converter types:
UnitConverter { converters { object volume extends Converter { value1 = 0; value2 :=: value1 * 0.94635295; unit1 = "Quarts"; unit2 = "Liters"; title = "Volume"; } object weight extends Converter { value1 :=: value2 * 0.035273962; value2 = 0; unit1 = "Ounces (US)"; unit2 = "Grams"; title = "Weight"; } object area extends Converter { value1 = 0; value2 :=: value1 * 2.5899881; unit1 = "Square Miles"; unit2 = "Square Kilometers"; title = "Area"; } } }Notice we omit the "class" operator in front of the type name. This is how you modify a type in a previous layer. This name has to match the file name and there has to be a type with this type name in the previous layer. These are the same sanity checks Java uses to be sure code is well-formed before compiling it.
If you run: "sc example.unitConverter.extendedModel", it generates one UnitConverter.java file that has 6 converters in the generated UnitConverter.java file. At any given time, there's only one stack of layers, implementing a single set of types for each runtime, just like in Java.
UnitConverter { // Converts the value strings to/from numbers. numberToString to defines // a reverse method so it can be used in 2-way bindings. @Component // used only so we can manage the properties of the instance in the management UI object numberConverter extends NumberConverter { } int currentConverterIndex = 0; // currentConverter will always point to the selected item in the combo box Converter currentConverter := converters.get(currentConverterIndex); }There's a String to Number converter we'll use from the UI controls. This layer introduces two properties. The property currentConverter is set when currentConverterIndex changes by calling the get method. This binding also fires if the list contents changes.
<%@ @URL(pattern="/uc") %> <html extends="EditablePage"> <head> <title>UnitConverter on StrataCode</title> <link rel="stylesheet" type="text/css" href="style.css"/> </head> <body> <div id="appFrame" class="appFrame"> <form id="unitConverterForm"> <select id="converterChoice" optionDataSource=":= converters" selectedIndex=":=: currentConverterIndex"/> <br> <span id="unit1Label" class="unitLabel"> <%= currentConverter.unit1 %> </span> <input id="unit1Field" class="unitField" autoComplete="off" value=":=: numberConverter.numberToString(currentConverter.value1)"/> <br> <span id="unit2Label" class="unitLabel"> <%= currentConverter.unit2 %> </span> <input id="unit2Field" class="unitField" autoComplete="off" value=":=: numberConverter.numberToString(currentConverter.value2)" /> </form> <div id="feedback"><%= numberConverter.error %></div> </div> </body> </html>
For Android:
// Since model's UnitConverter does not extend anything, we can just redefine this // type to avoid one level of object containment UnitConverter extends Activity { // Converts the value strings to/from numbers. numberToString to defines // a reverse method so it can be used in 2-way bindings. object numberConverter extends NumberConverter { } object mainView extends LinearLayout { orientation = VERTICAL; // currentConverter will always point to the selected item in the combo box Converter currentConverter := converters.get(converterChoice.selectedItemPosition); // Combo box to choose the current conversion algorithm object converterChoice extends CSpinner { adapter = new ArrayAdapter<Converter>(UnitConverter.this, android.R.layout.simple_spinner_item, converters); } class UnitTextField extends CEditText { singleLine = true; layoutParams = new LinearLayout.LayoutParams(FILL_PARENT, WRAP_CONTENT, 0); inputType = InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED; } class UnitLabel extends CTextView { singleLine = true; layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT, 0); { setPadding(5,0,5,0); } } object unit1 extends LinearLayout { object unit1Label extends UnitLabel { textString := currentConverter.unit1; // Display's converter's "unit 1" } object unit1Field extends UnitTextField { textString :=: numberConverter.numberToString(currentConverter.value1); } } object unit2 extends LinearLayout { object unit2Label extends UnitLabel { textString := currentConverter.unit2; } object unit2Field extends UnitTextField { textString :=: numberConverter.numberToString(currentConverter.value2); } } object errorLabel extends CTextView { // The number converter provides an error when an invalid number is supplied textString := numberConverter.error; textColor := Color.RED; } } }For Swing:
// Since model's UnitConverter does not extend anything, we can just redefine this // type to avoid one level of object containment. UnitConverter extends AppFrame { location = new Point(1040, 10); size = new Dimension(230, 230); // Combo box to choose the current conversion algorithm object converterChoice extends JComboBox { int alignx = 20; items := converters; location := SwingUtil.point(xpad+alignx, ypad); size := SwingUtil.dimension(windowWidth - 2*(xpad+alignx), preferredSize.height); } currentConverterIndex :=: converterChoice.selectedIndex; // first row - label + field follows current converter int row1y := (int)(ypad + converterChoice.location.y + converterChoice.size.height); object unit1Label extends JLabel { text := currentConverter.unit1; // Display's converter's "unit 1" labelFor = unit1Field; location := SwingUtil.point(xpad, row1y + baseline); size := preferredSize; } object unit1Field extends JTextField { location := SwingUtil.point(xpad + unit1Label.preferredSize.width + gap, row1y); size := SwingUtil.dimension(windowWidth - unit1Label.size.width - 2*xpad - gap, preferredSize.height); // Bind's text property to current converter's value1 after converter to/from string text :=: numberConverter.numberToString(currentConverter.value1); } int row2y := (int)(unit1Field.location.y + unit1Field.size.height + ypad); object unit2Label extends JLabel { text := currentConverter.unit2; labelFor = unit2Field; size := preferredSize; location := SwingUtil.point(unit1Label.location.x, row2y + baseline); } object unit2Field extends JTextField { location := SwingUtil.point(xpad + unit2Label.preferredSize.width + gap, row2y); size := SwingUtil.dimension(windowWidth - unit2Label.size.width - 2*xpad - gap, preferredSize.height); text :=: numberConverter.numberToString(currentConverter.value2); } object errorLabel extends JLabel { // The number converter provides an error when an invalid number is supplied text := numberConverter.error; location := SwingUtil.point(xpad, unit2Field.location.y + unit2Field.size.height + ypad + baseline); size := preferredSize; } }For Wicket there are two files:
UnitConverter extends CWebPage { // Converts the value strings to/from numbers. numberToString to defines // a reverse method so it can be used in 2-way bindings. transient object numberConverter extends sc.util.NumberConverter { } // Wicket requires this of model objects Converter implements java.io.Serializable { } int currentConverterIndex = 0; Converter currentConverter = converters.get(currentConverterIndex); object feedback extends FeedbackPanel { outputMarkupId = true; } object unitConverterForm extends CForm<UnitConverter> { object converterChoice extends CDropDownChoice { choiceValues = converters; currentValue :=: currentConverter; selectionChangedNotifications = true; } object unit1Label extends CLabel { textValue := currentConverter.unit1; } object unit1Field extends CTextField<Double> { required = true; type = Double.class; fieldValue :=: currentConverter.value1; } object unit2Label extends CLabel { textValue := currentConverter.unit2; } object unit2Field extends CTextField<Double> { required = true; type = Double.class; fieldValue :=: currentConverter.value2; } } public UnitConverter() { } }and an HTML file:
<html xmlns:wicket="http://wicket.apache.org/">
<head>
<title>UnitConverter on StrataCode</title>
<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>
<form wicket:id="unitConverterForm" id="unitConverterForm">
<select wicket:id="converterChoice">
<option>no converters</option>
</select>
<p>
<span wicket:id="unit1Label">unit1Label</span>
<input wicket:id="unit1Field" autocomplete="off"/>
<br>
<span wicket:id="unit2Label">unit2Label</span>
<input wicket:id="unit2Field" autocomplete="off"/>
<p>
<input type="submit" value="Submit" id="formsubmit"/>
</form>
<div wicket:id="feedback">[[ errorPanel ]]</div>
</body>
</html>
For GWT:
@GWTModule UnitConverter { // Converts the value strings to/from numbers. numberToString to defines // a reverse method so it can be used in 2-way bindings. object numberConverter extends sc.util.NumberConverter { } object mainView extends VerticalPanel { // Combo box to choose the current conversion algorithm object converterChoice extends CListBox { items := converters; visibleItemCount = 1; // Turns a list into a drop-down box selectedIndex = 0; } // currentConverter will always point to the selected item in the combo box Converter currentConverter := converters.get(converterChoice.selectedIndex); object unit1Panel extends FlowPanel { object unit1Label extends InlineLabel { text := currentConverter.unit1; // Display's converter's "unit 1" } object unit1Field extends CTextBox { // Bind's text property to current converter's value1 after converter to/from string text :=: numberConverter.numberToString(currentConverter.value1); } } object unit2Panel extends FlowPanel { object unit2Label extends InlineLabel { text := currentConverter.unit2; } object unit2Field extends CTextBox { text :=: numberConverter.numberToString(currentConverter.value2); } } object errorLabel extends Label { // The number converter provides an error when an invalid number is supplied text := numberConverter.error; } } }