UnitConverter Example
A basic example, sharing code on many platforms. See the 'domain model' or model layer, the coreui or 'view model' as well as platform specific layers.

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

file: example/unitConverter/model/UnitConverter.sc
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.
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 (which by default consists of all of the directories in your projects 'bundles' directory). Here the layer name is example.unitConverter.model. It is defined by the file model.sc in the bundles/example/example/unitConverter/model directory:
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.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.

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.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 {
   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.

Adding Shared UI Components
Now we'll add a layer called example.unitConverter.coreui to be the view model of the UnitConverter application. It contains all of the UI code shared by more than one platform. It will define properties which can we bind to and use directly from the platform specific view layers.
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.
   @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.

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/html/core/UnitConverter.schtml
<%@ @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>
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;
      }
   }
}