UnitConverter Example
This example shows a few of StrataCode's basic concepts and demonstrates a simple application running on lots of different platforms.

Here is the model layer for the UnitConverter component. It defines all we need for the conversion functionality:

file: example/unitConverter/model/UnitConverter.sc
/* A simple unit converter using StrataCode objects and data binding. */
class UnitConverter {
   static class Converter {
      double value1;
      double value2;
      String unit1, unit2; // Unit labels
      String title;
      String toString() {
         return title == null ? "<unset>" : title; 
      }
   }
   
   object converters extends ComponentList<Converter> {
      object temperature extends Converter {
         value1 = 0;
         value2 :=: value1 * 9.0 / 5.0 + 32;
         unit1 = "Celsius";
         unit2 = "Fahrenheit";
         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 = "Kilometers/hour";
         unit2 = "Miles/hour";
      }
      object power extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts";
         unit2 = "Horsepower";
         title = "Power";
      }
   }
}
There are four differences from Java demonstrated here:
  • Instead of inner classes, this example uses inner objects.
  • A customized object template - the ComponentList class uses the @CompilerSettings annotation to register an object template. That template inserts the "add" call so the inner object is added to the list. This same mechanism is used for a parent user interface widget to treat its inner objects as child widgets.
  • There is a new :=: opeator used to relate the two values in a formula. This is a bi-directional data binding expression. 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 are assignable without braces {}.
Show the generated code.

Defining a Layer
The UnitConverter.sc file is stored in the layer directory called example/unitConverter/model in your layer path. That layer name's is example.unitConverter.model and is defined by the file model.sc in the example/unitConverter/model directory that looks like:
file: example/unitConverter/model/model.sc
package sc.example.unitConverter;

@sc.obj.Sync(syncMode=sc.obj.SyncMode.Automatic)
public example.unitConverter.model extends util {
   //defaultSyncMode = SyncMode.Automatic;
   codeType = CodeType.Declarative;
   codeFunction = CodeFunction.Model;
}
The layer name must always be the first thing and the file name always matches the layer directory name.

It sets a package to use as the root for all files in this layer. That means UnitConverter.sc can live in the layer directory, not in sc/example/unitConverter and need not specify a package. Layer packages help organize code more easily and narrow the focus for a layer that pays off for making things easier to navigate.

The codeTypes and codeFunctions properties don't affect the runtime. They help the tools organize layers in the program editor. This 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 Java.

Modifying a Layer
Now to introduce the modify operator, we'll add a very simple layer based on the model layer called example.unitConverter.extendedModel. First the layer definition file:
file: example/unitConverter/extendedModel/extendedModel.sc
@Sync(syncMode=SyncMode.Automatic)
public example.unitConverter.extendedModel extends example.unitConverter.model {
   //defaultSyncMode = SyncMode.Automatic;
   codeType = CodeType.Declarative;
   codeFunction = CodeFunction.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:

file: example/unitConverter/extendedModel/UnitConverter.sc
UnitConverter {
 
   // Adds three more converters - we could replace them by just redefining
   // the type of the array, as long as the new array was a of a compatible type like this:
   // object converters extends sc.util.ComponentList<Converter> ...
   // The annotations AddBefore and AddAfter allow you to add elements before or after an 
   // element in the previous layer
   converters {
      object volume extends Converter {
         value1 = 1.0;
         value2 :=: value1 / 0.94635295;
         unit1 = "Liters";
         unit2 = "Quarts";
         title = "Volume";
      }
      object weight extends Converter {
         value1 :=: value2 / 0.035273962;
         value2 = 0;
         unit1 = "Grams";
         unit2 = "Ounces (US)";
         title = "Weight";
      }
      object area extends Converter {
         value1 = 0;
         value2 :=: value1 / 2.5899881;
         unit1 = "Sq Kilometers";
         unit2 = "Sq Miles";
         title = "Area";
      }
      /*
      object power extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts";
         unit2 = "Horsepower";
         title = "Power";
      }
      object power2 extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts2";
         unit2 = "Horsepower2";
         title = "Power2";
      }
      object power3 extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts3";
         unit2 = "Horsepower3";
         title = "Power3";
      }
      object power4 extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts4";
         unit2 = "Horsepower4";
         title = "Power4";
      }
      object power5 extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts5";
         unit2 = "Horsepower5";
         title = "Power5";
      }
      object power6 extends Converter {
         value1 = 0;
         value2 :=: value1 * 0.00134;
         unit1 = "Watts6";
         unit2 = "Horsepower6";
         title = "Power6";
      }
      */
   }
}
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 example/unitConverter/extendedModel/build/java/sc/example/unitConverter/UnitConverter.java. At any given time, there's only one stack of layers, implementing a single set of types for each runtime, just like in Java.

Adding Shared UI Components
Now we'll add a layer called example.unitConverter.coreui, to add some UI code shared by more than one platform:
file: example/unitConverter/coreui/UnitConverter.sc
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 {
   }

   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.

Adding the JS/HTML UI
The first user interface we'll look at is implemented using StrataCode's HTML Template library, schtml:
file: example/unitConverter/jsui/UnitConverter.schtml
<%@ @URL(pattern="/uc") %>
<html>
<head>
	<title>UnitConverter on StrataCode</title>
	<link rel="stylesheet" type="text/css" href="style.css"/>
</head>
<body>

   <div 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>
Show the quick intro to schtml for reading the code here.

Here are the UnitConverter UIs for four more frameworks. Though they look similar, they each leverage the underlying frameworks and so build native UIs. All share the same model code.

For Android:

file: example/unitConverter/androidui/UnitConverter.sc
// 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:
file: example/unitConverter/swingui/UnitConverter.sc
// 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:
file: example/unitConverter/wicketui/UnitConverter.sc
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:
file: example/unitConverter/wicketui/UnitConverter.html
<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:
file: example/unitConverter/gwtui/UnitConverter.sc
@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;
      }
   }
}