General Interface is an open source project hosted by the Dojo Foundation

Exercise 1 on Basic Spinner

This example describes how to create a basic spinner control.

I. Create a Functional Specification

It is important to create a solid functional specification. With a good specification, it is not necessary to waste time modifying and re-factoring code. Requirements for the basic spinner include an HTML text input control with enhanced features and functionality to allow for key input and listening for keydown and mousewheel events. When the up arrow key is pressed, the control will increment its value. The mouse wheel will have a similar effect when wheeled up by the user. By default, the control will have a single-pixel gray border. The next table shows the complete list of properties, and the following table shows the associated events and behaviors.

Example 1 Controls
Property Name Value
position relative
left  
top  
width 60 (pixels)
height 20 (pixels)
border solid 1px gray
vertical-align middle
font-name  
font-size 20
value 1
Example 1 Events and Behaviors
Property Name Value
Key Up Increments the text field
Key Down Decrements the text field
Mousewheel Up Increments the text field
Mousewheel Down Decrements the text field
Change Persists the value after focus is lost
value 1

The default control will appear as follows:

II. Define the class

Authoring a GUI class requires a working knowledge of HTML, XML, and JavaScript. However, given the functional separation that MVC provides, it is possible to divide the tasks among team members who are appropriately skilled for each portion.

The following steps show how to create the GUI class.

  1. Create a new JavaScript file.
    1. Require the template engine and any interfaces. The new spinner control will eventually be used as a form field in a General Interface application, so the interface, jsx3.gui.Form, will also be included in the require statement. Refer to the documentation for jsx3.gui.Form for a complete list of all methods provided by the interface.
    2. Define the new class using defineClass. Refer to the super class (jsx3.gui.Template.Block), and any interfaces (jsx3.gui.Form). While SPINNER is a pointer to the class, spinner is a pointer to the class prototype. These names are arbitrary and merely serve as a useful alias while defining the class. Also note the name for the new class: my.Spinner1.
    3. Include an init method. This is the constructor that allows the class to be instantiated. It is object oriented and should therefore make a call to jsxsuper. Users can instance the class by calling: new my.Spinner1("somename");
    4. Subclass the getTemplateXML method to convert the template's HTML markup (to be created) into the corresponding JavaScript functions needed by the class. The XML is defined inline within the JavaScript and uses an apostrophe escape.
      jsx3.require("jsx3.gui.Template","jsx3.gui.Form");  //a)
      jsx3.lang.Class.defineClass("my.Spinner1"           //b)
      ,jsx3.gui.Template.Block
      ,[jsx3.gui.Form]
      ,function(SPINNER,spinner) {
        spinner.init = function(strName) {                //c
          this.jsxsuper(strName);
        };
      spinner.getTemplateXML = function() {               //d
               return ['<transform ',
             '    xmlns="http://gi.tibco.com/transform/" ',
             '    xmlns:u="http://gi.tibco.com/transform/user" version="1.0">',
              '  <model/>' ,
             '  <template/>' ,
             '</transform>'].join(""); };*
          });
  2. Define any model defaults for the GUI control. The spinner will implement a default value of 1 if no value is specified. Because the control implements the jsx3.gui.Form interface, its value is stored using the jsxvalue field. This means that any call to the getValue method (a mix-in method provided by jsx3.gui.Form) will return 1 if the given instance does not define a value.
    jsx3.require("jsx3.gui.Template","jsx3.gui.Form");
    jsx3.lang.Class.defineClass("my.Spinner1"
    ,jsx3.gui.Template.Block
    ,[jsx3.gui.Form]
    ,function(SPINNER,spinner) {
      spinner.init = function(strName) {
        this.jsxsuper(strName);
      };
      spinner.jsxvalue = 1;
          spinner.getTemplateXML = function() {
            return ['<transform ',
           '    xmlns="http://gi.tibco.com/transform/" ',
           '    xmlns:u="http://gi.tibco.com/transform/user" version="1.0">',
            '  <model/>' ,
           '  <template/>' ,
            '</transform>'].join("");
          };
        });

    The system serializes all simple model properties. This means that serializing an instance by calling its toXML method will result in the property value, 1, being serialized, regardless of whether or not it was explicitly declared on the instance.

  3. Author the HTML prototype to reflect the requirements in the functional specification:
    <input type="text" value="1"
                onchange=";" onkeydown=";" onkeyup=";" onmousewheel=";"
    style="position:relative;left:;top:;width:60px;height:20px;
    margin:0px;border:solid 1px gray;font-name:;
    font-size:10px;vertical-align:middle;" />

    Next, replace every HTML attribute, event, or style property value that should be made dynamic with a replacement variable (denoted by curly braces). For example, to allow the font-size property to be dynamically replaced with the model property, jsxfontsize, change the explicit style declaration from this:

    font-size:10px;

    to this:

    font-size:{jsxfontsize};

    The HTML now appears as follows.

    <input type="text" value="{jsxvalue}"
                onchange="{onchange}" onkeydown="{onkeydown}"
                onkeyup="{onkeyup}" onmousewheel="{onmousewheel}"
    style="position:{$position};left:{jsxleft};top:{jsxtop};
                width:{jsxwidth|60};height:{jsxheight|20};margin:{jsxmargin};
                border:{border|solid 1px gray};font-name:{jsxfontname};
    font-size:{jsxfontsize};vertical-align:middle;" />


    Note the following items:

    • The position property uses the variable {$position}. The $ prefix is reserved by the system to define a pre-built set of system variable resolvers that are provided by the template engine. System Resolvers contains a full list of pre-built resolvers. By using these you can greatly simplify creating your template. For example, the {$position} resolver will return either relative or absolute, depending upon the value of the jsxrelativeposition property.
    • The width, height, and border declarations include a default value. This ensures that the control is at least 60 pixels wide by 20 pixels tall with a single-pixel gray border. Default values are denoted using a pipe character. Refer to Control Statements for implementation details on @defaultvalue.
    • Event declarations such as onkeydown="{onkeydown}" tell the system that you have a controller method named onkeydown that should be notified of the keydown event. Step 10 explains this further.
    • The value field for the text input uses the {jsxvalue} variable. This means that when the control is painted on-screen its value will be equal to this.jsxvalue. This is how all template variables work and is the preferred method for matching properties on the model to properties in the view template. In other words, {jsxfontsize} points to this.jsxfontsize and {x.y.z} points to this.x.y.z. This simple pattern ensures proper synchronization between model and view.
      The class appears as follows:
      jsx3.require("jsx3.gui.Template","jsx3.gui.Form");
      jsx3.lang.Class.defineClass("my.Spinner1"
      ,jsx3.gui.Template.Block
      ,[jsx3.gui.Form]
      ,function(SPINNER,spinner) {
        spinner.init = function(strName) {
          this.jsxsuper(strName);
        };
        spinner.jsxvalue = 1;
            spinner.getTemplateXML = function() {
               return ['<transform xmlns="http://gi.tibco.com/transform/" ' ,
              '     xmlns:u="http://gi.tibco.com/transform/user" version="1.0">' ,
               '  <model/>' ,
              '  <template>' ,
               '    <input type="text" value="{jsxvalue}" ' ,
              '        onchange="{onchange}" onkeydown="{onkeydown}" ' ,
              '        onkeyup="{onkeyup}" onmousewheel="{onmousewheel}" ' ,
                '          style="position:{position};left:{jsxleft};top:{jsxtop};' ,
              '        width:{jsxwidth|60};height:{jsxheight|20};margin:{jsxmargin};' ,
              '        border:{border|solid 1px gray};font-name:{jsxfontname};',
                '        font-size:{jsxfontsize};vertical-align:middle;" />' ,
              '  </template>' ,
              '</transform>'].join("");
             };
      });
  4. The last step is to define the controller functions. As previously explained, any event declared in the template using the standard variable syntax (such as onkeydown="{onkeydown}") will automatically call the prototype class function of the same name. This means if you declare an onkeydown event in your template, you should also declare the JavaScript controller onkeydown. By naming the controllers appropriately, the on-screen view will be able to locate the corresponding in-memory controller. When found, an instance of jsx3.gui.Event and a reference to the target GUI object will be passed to the controller. The controller can then update the model and the view by calling the setProperty method. This single call updates the named property on the model and projects the update to the on-screen view. Note how the setValue method utilizes this function call. This is a final critical step that all controller methods should use to ensure a complete feedback loop from model to view. The final class is as follows (the init and compile methods have been removed for readability):
    jsx3.require("jsx3.gui.Template","jsx3.gui.Form");
    jsx3.lang.Class.defineClass("my.Spinner1"
    ,jsx3.gui.Template.Block
    ,[jsx3.gui.Form]
    ,function(SPINNER,spinner) {
      // ** init and compile methods removed for readability **
          //add the controller logic here...
      spinner.onkeydown = function(objEvent,objGUI) {
        if(objEvent.upArrow())  this.up();
        else if(objEvent.downArrow()) this.down();
      };
      spinner.onkeyup = function(objEvent,objGUI) {
        this.jsxvalue = objGUI.value;
      };
      spinner.onchange = function(objEvent,objGUI) {
        this.jsxvalue = objGUI.value;
      };
      spinner.onmousewheel = function(objEvent,objGUI) {
        var wd = objEvent.getWheelDelta();
    	    if (wd > 0) this.up();
    	    else if(wd < 0) this.down();
      };
      spinner.up = function() {
        this.setValue(parseInt(this.getValue())+1);
      };
      spinner.down = function() {
        this.setValue(parseInt(this.getValue())-1);
      };
       spinner.setValue = function(strValue) {
        //tell the engine to update the model and synchronize the view
        this.setProperty("jsxvalue",strValue);
      };
    });

Contents

Searching General Interface Docs

Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.