Implementing Model/View Communication

Introduction

When you perform the object-oriented analysis for your application, you can separate the problem domain from the design of the application's user interface. The problem domain provides a perspective on what the application should do, including all business rules. Objects that represent the problem domain serve as models and are typically non-visual components. Viewers are components that display data to the user or provide access to the information in the model. To enable communication between the model components and viewer components, you can implement model/view communication.
Although models and viewers are typically used together, each is nevertheless an independent component. Their independence allows for customization, design flexibility, and efficient programming. The use of a model/view architecture also
  • enables you to develop stable models that do not change much over time
  • enables you to customize viewers based on the preferences of different users who might need to work with the same problem domains presented in the models
  • simplifies application maintenance because the user interface is separated from the problem domain
As a component developer, implementing model/view communication can possibly require you to add a significant amount of SCL code. However, users of the models and viewers that you create do not have to do any programming to perform component communication.
Interfaces are designed to help implement model/view communication by providing a kind of relationship between the model and the viewer. An interface essentially defines the rules by which two components can communicate with each other. The interface that is used between a model and a viewer ensures that the viewer knows how and when to communicate with the model to access the data it needs.

What Happens during Model/View Communication

The following items describe the flow of control for model/view communication:
  1. When the model attribute of a viewer is set, an event handler is established on the viewer to listen for the “contents updated” event to be sent from the model. The event handler is the _onContentsUpdated method. In addition, the _setcamModel method executes when the viewer's model attribute is set, both at build time and at run time. The _setcamModel method includes a call to a method that is both implemented on the model and defined in the interface.
  2. The “contents updated” event is sent by the model when one of the attributes in its contentsUpdatedAttributes attribute changes or when the model specifically sends the event.
    The model's contentsUpdatedAttributes attribute contains the name of one or more other attributes. These attributes have been identified as critical components on the component. They affect the contents of the model, and the viewer must be notified when their values change. The “contents Updated” event passes the name of the changed attribute as an input argument to the _onContentsUpdated method.
  3. The viewer's event handler, which is the _onContentsUpdated method, calls back to the model to retrieve updated information. The viewer is able to communicate with the model due to the methods defined in the interface.
For example, consider a frame that has a list box control that you want to use to present a list of standard color names (such as Red, Green, and Blue). The Color List Model component provides this list of names based on values that are supported by SAS software. You can establish a model/view relationship between a list box and an instance of the Color List Model component by dragging the model from the Component window and dropping it onto the list box in the frame.
At the point where the drop occurs on the list box, the SAS/AF classes have been designed to verify whether the two objects know how to communicate with each other via model/view communication using a common interface. If the model has a supported interface that matches a required interface on the viewer, the model is “attached” to the viewer. In this case, the color list model supports the sashelp.classes.staticStringList interface, and the list box requires the same interface, so a model/view relationship exists. Two types of processing occur once the model/view relationship is established:
  1. The model attribute on the viewer is set to the name of the model, which executes the setCAM for the attribute (_setcamModel). In the list box/color list model example, the implementation of the _setcamModel method for the list box contains code that queries the model and retrieves a list of items using a _getItems call, which is a supported method in the interface. The CAM then sets the value of the viewer's items attribute to the list that is returned by _getItems.
  2. When the “contents Updated” event is sent by the model, the viewer's _onContentsUpdated method executes. This method's implementation is similar to the _setcamModel in that it queries the model using methods supplied in the interface, and it retrieves the model's information to update the viewer. In the list box example, a _getItems call is used to retrieve the list of colors each time the model is updated.

Creating Your Own Models and Viewers

Introduction

To implement model/view communication, you need to examine the design of the model, the viewer, and the interface to set the appropriate actions. Consider the following steps when designing components for model/view:
  1. Decide whether an existing interface provides the design you need.
    If it does not, you can create a new interface.
  2. Add the interface as a supported interface for your model.
  3. Create or identify the key attributes on the model. Modify the model's contentsUpdatedAttributes attribute to include the key attributes.
  4. Implement the methods for the model specified by the interface. Ensure that the method or methods query or update the key attributes that you identified.
  5. Add the interface as required for the viewer.
  6. Override the viewer's _onContentsChanged and _setcamModel methods. Typically, the implementation invokes methods that have been defined in the interface and implemented in the model to retrieve new information from the model. This new information is used to update attributes or to perform an action on the viewer. The _onContentsChanged method handles the “contents Updated” event, and the _setcamModel method retrieves the initial values from the model when the viewer is instantiated.

Defining a Model Based on the StaticStringList Interface

The most simple example of implementing model/view involves providing support for an existing interface. Using the steps outlined above, you can:
  • Provide support for the methods defined in sashelp.classes.staticStringList.intrface so that you do not have to create a new interface.
  • Add the _getItems method and implement code that populates an items attribute on the model.
  • Work with an existing viewer that requires the staticStringList interface, such as a list box.
To create the model:
  1. Using the Class Editor, create a new class whose parent is sashelp.fsp.Object.class and whose description is My Model.
  2. Save the class as sasuser.myclasses.myModel.class.
  3. On the Interfaces node in the tree, right-click and select New Interface from the pop-up menu. Specify sashelp.classes.staticStringList.intrface for the interface and set Status to Supports.
    The Class Editor prompts you to add the methods that are defined in the interface. Click Yes.
    Note: Although this example may not provide implementation for each method that is defined in the interface, it is a recommended practice to implement in your class all methods that are specified in the interface. You do not necessarily know which methods the viewer might invoke.
  4. Right-click on the Attributes node and select New Attribute. Add an attribute named items and assign it as a List type. Save the class.
  5. Select the Methods node. Select the _init method with the signature of ()V, and then right-click and select Override from the pop-up menu. Select the _getItems method, and then right-click and select Source from the pop-up menu. Add the following SCL code to implement both methods in the entry named sasuser.myclasses.myModel.scl that is created for you:
    useclass sasuser.myclasses.myModel.class;
    /* Override of _init method */
     init: public method (state="0");
     dcl num rc;
     dcl list temp=makelist();
     _super();
    
     rc=clearlist(items);
    
     temp=items;
     rc=insertc(temp, 'One', -1);
     rc=insertc(temp, 'Two', -1);
     rc=insertc(temp, 'Three', -1);
     items=temp;
    endmethod;
    
    getItems: public method return=list;
     return(items);
    endmethod;
    enduseclass;
    Compile the code, and then close the Source window and return to the Class Editor.
  6. Close the Class Editor.
Once the model is complete, you can create a frame and test the model with a control that requires the same interface. For example, you could use a list box control since it requires the staticStringList interface:
  1. Create a new frame.
  2. Add your new model to the Components window by selecting Add Classes from its pop-up menu. Select or enter sasuser.myclasses.myModel.class.
    The class named My Model appears in the Components window.
  3. Drag a list box control onto the frame, and then drag and drop a My Model component onto the list box to establish a model/view relationship. Regardless of the version of SAS that you are using, test the frame using the TESTAF command to see that the list box populate correctly.
    The list box should immediately be populated with the values that you specified in the model's _getItems method.

Creating a Model/View Relationship Based on a New Interface

If existing interfaces do not provide the necessary relationship for a model and a viewer to communicate, you can create a new interface. This example demonstrates how you can:
  • create a new interface.
  • implement the methods defined in that interface for a new model class that supports the interface.
  • provide support in a viewer for the required interface by overriding the viewer's _onContentsUpdated and _setcamModel methods.
To create the interface:
  1. Use the Interface Editor to create a new interface whose description is My Interface.
  2. Right-click and select New Method from the pop-up menu, and then add a method named getColumnData with a signature ()L.
  3. Close the Interface Editor and save the new interface as sasuser.myclasses.MyInterface.intrface.
Alternatively, you can use SCL to create an INTRFACE entry:
interface sasuser.myclasses.MyInterface;
   getColumnData: public method return=list;
endinterface;
You can use the SAVECLASS command to save the SCL code as an interface. For more information, see SAS Component Language Reference.
To create the model:
  1. Using the Class Editor, create a new class whose parent is sashelp.fsp.Object.class and whose description is Column Data Model.
  2. Save the class as sasuser.myclasses.ColumnDataModel.class.
  3. In the Interfaces node of the Class Editor, right-click and select New Interface from the pop-up menu. Specify the model that you created above (sasuser.myclasses.myInterface.intrface) for the interface and set Status to Supports.
    The Class Editor prompts you to add the method that you defined in the interface. Click Yes.
  4. In the Attributes node of the Class Editor, add an attribute named columnData and assign it as a List type. Add an attribute named table and assign it as a Character type. Add a third attribute named columnName and assign it as a Character type.
  5. Select the attribute named contentsUpdatedAttributes, and then select Override from the pop-up menu. Select the Initial Value cell, and then click the ellipsis button (...) to edit the values. In the dialog box, select columnName and table, and then click OK.
  6. Save the class.
  7. In the Methods node of the Class Editor, select the getColumnData method, and then right-click and select Source from the pop-up menu. Add the following SCL code to the entry named sasuser.myclasses.ColumnDataModel.scl that is created for you:
    useclass sasuser.myclasses.ColumnDataModel.class;
    getColumnData: public method
          return=list;
       dcl num rc dsid levels;
    
       /* reset the existing items attribute */
       rc=clearlist(columnData);
    
       /* open the SAS table specified in the table attribute */
    
       dsid = open (table);
       if dsid ne 0 then do;  /* process if table exists */
         if varnum (dsid, columnName) > 0 then do;
           levels=0;
           rc=lvarlevel(dsid,columnName,levels,columnData);
           rc=revlist(columnDataModel);
         end;
       end;
    
       rc=close(dsid);
       return(columnData);
    
    endmethod;
    enduseclass;
    Compile and save the SCL code, and then close the Source window and return to the Class Editor.
  8. Close the Class Editor.
Next, you can create a subclass of the list box control that requires the MyInterface interface that you created:
  1. Use the Class Editor to create a subclass of the List Box control (sashelp.classes.listbox_c.class). Name your class My List Box.
  2. Select the Interfaces node in the Class Editor tree and add a new interface. Specify the sasuser.myclasses.myInterface.intrface interface.
  3. Save the class as sasuser.myclasses.myListBox.class.
  4. In the Methods node of the Class Editor, select the _onContentsUpdated method, and then right-click and select Override from the pop-up menu. Select the _setcamModel method, and then right-click and select Override from the pop-up menu. Right-click and select New Method from the pop-up menu, and then add a method named getModelData. Right-click and select Source from the pop-up menu to add the following SCL code, which implements all three methods:
    useclass sasuser.myclasses.myListBox.class;
    getModelData: public method;
       /* This method retrieves the data from the model. */
       /* modelID is an attribute inherited from Object  */
       /* that contains the identifier of the model when */
       /* the viewer's model attribute is set.           */
       items=modelID.getColumnData();
    endmethod;
    
    onContentsUpdated: public method
       colItems:char;
       getModelData();
    endmethod;
    
    setcamModel: protected method
       attributeValue:update:char
       return=num;
       _super(attributeValue);
       getModelData();
    endmethod;
    enduseclass;
    Compile the SCL and save the entry.
  5. Close the Class Editor.
Once the model is complete, you can create a frame and test the model with a control that requires the same interface:
  1. Create a new frame.
  2. Add your new model and the list box subclass to the Components window by selecting Add Classes from its pop-up menu. Select or enter sasuser.myclasses.ColumnDataModel.class and sasuser.myclasses.myListBox.class.
    The Column Data Model and My List Box classes appear in the Components window.
  3. Drag a My List Box onto the frame, and then drag and drop a Column Data Model component onto the list box to establish a model/view relationship.
    Note that the list box is not populated until you set the table and columnName attributes on the model. Use the Properties window to set columnDataModel1's table attribute to a valid SAS table such as sashelp.prdsale. Set columnDataModel1's columnName attribute to a valid column name in the table. For example, Region is a column in the sashelp.prdsale table.