Monthly Archives: April 2009

Model-View-Controller in Stone

We are using a Model-View-Controller design pattern for our web pages. Of course this can mean different things to different people. I was surprised to learn from Wikipedia [a resource that professors tell students not to reference, but that I find ever so helpful] that this design pattern was first described 30 years ago. I was not surprised to learn it originated in the Smalltalk community. For us, it means we are using the implementation of the M-V-C pattern by InterSystems with their Zen AJAX platform.

As we have seen with the rise and massive changes to relational theory, there are a) those who study and adhere to design patterns rigidly, b) those who accept them without question and feel good about using a popular pattern, and c) those who take a practical approach regarding any such theories, constantly applying and reforming them as makes sense.

So as to blend two of the worlds in which I live, I will refer to a) above as the Doctrinalists, b) as the Pietists, and c) as the Kuyperians. I’m going to go out on a limb and guess that some of you might not be familiar with Abraham Kuyper, so if you wish to familiarize yourself, perhaps check out what Princeton Theological Seminary has to say.

Whenever there is a question about how to approach pretty much anything and one of the possible answers is to approach it from a Kuyperian perspective, then in my tradition that would be the right answer. Sure we have our Doctrinalists and our Pietists, but the Kuyperians are the smart ones, as we all know.

While I will try to lay out a simple example of using M-V-C, making this theory more concrete, our approach is not exactly firmed up enough to be cut in stone. So, this could hardly be called my Stone Lecture on Model-View-Controller (assuming you have all read Kuyper’s Stone Lectures at some point in your education, right?).

One very cool feature we have is that we can source our metadata in classes, and project this to the MulitValue DICTionary, the SQL Schema, a Model for use with AJAX (InterSystems Zen platform), and anything else we need in terms of metadata. We can read and write a good old fashioned record by reading it in as an object and executing a save on that object. Very cool, but even better, we can execute a save on a form object in JavaScript, where that form is bound to a Controller tied to a Model.

In the simplest case, this Model could be the persistent class, the same one used to read and write records, aka the File. This is where some of the doctrinalists might get concerned. If our File is the Model and our form were bound to that, then we would have a pretty tight link between the View and the Model. There is a Controller in the midst, however, and the minute it becomes an issue that the Model is also the persistent class, we could write a new, non-persistent Model. I have tried to encourage other SnupNow developers to work with non-persistent Models, but to date I am the only one who has written these. That is about to change, knock on wood, but that is a more complex scenario so the M-V-C example given here will be a simple File-Form.

The form fields are bound to properties in the Controller, which relate directly the properties of the Model. The Model has the logic to read and write records from one or more files, which for a simple persistent class Model, the case we will be showing here, is inherited logic. So, the only reads and writes are written in JavaScript. A record is read when the modelId of the Controller is set and is written when the form is saved.

OK, it’s time for an example. The following example shows a file and a page for maintaining information about a collection of trolls, those little dolls with long hair and not-exactly-attractive facial features. We will write a little web page application that looks like the following (apologies for Template words on the clipboard, but it’s just an example).

troll

This is where those who are not SnupNow developers or really don’t want to know how to make this work can cut out, while those who want to know how to write a little MV (MultiValue) M-V-C (Model-View-Controller) web application using the tools used by the SnupNow project can check this out in more detail.

Notice how the logic for each button is written in JavaScript that is able to interact with the server-side, maintaining an MV file by way of the class used to define that file.  After compiling the following class, we can do a LIST DICT S.Troll to see the dictionary from the MV colon prompt. After compiling the page class after this, we can launch it in a browser and start maintaining the file. This could be done even more simply using a dynaForm, rather than a form, component from InterSystems, but that quickly becomes too simple once any logic needs to be added, so this is a good starting point for the basic Create-Read-Update-Delete (CRUD) functions performed on a single file.

To expand beyond a simple file, we would then introduce a separate Model class from the persistent class so that the following two classes could remain simple but the Model could be as complex as needed.

I opted not to trim the following back to what is essential for showing M-V-C, but to put in fully functioning classes, pretty much the way I would write them right now. Ask questions. There will be a test next Tuesday.

Persistent Class and the M in M-V-C, aka the File:

Class S.Troll Extends (%Persistent, %Populate, %XML.Adaptor, %MV.Adaptor, %ZEN.DataModel.Adaptor) [ Language = mvbasic ]
{

Parameter VERSIONPROPERTY = “Version”;

Property Version As %Integer(MVATTRIBUTE = 1);

Property Name As %String(MVATTRIBUTE = 2, MVWIDTH = 40);

Property HairColor As %String(MVATTRIBUTE = 3, MVWIDTH = 20);

Property TrollType As %String(MVATTRIBUTE = 4, MVWIDTH = 10);

Property GotchaDate As %MV.Date(FORMAT = 3, MVATTRIBUTE = 5, MVTODISPLAY = “D4/”, MVWIDTH = 11);

Property Description As %String(MAXLEN = “”, MVATTRIBUTE = 6, MVWIDTH = 60);

Property Tags As list Of %String(MAXLEN = “”, MVATTRIBUTE = 7, MVWIDTH = 20, SQLPROJECTION = “table”,  SQLTABLENAME = “TrollKeywords”);

XData MVAdditionalDictItems
{
<DictItems>

<DictItem Name=“@ID”>
<Attr>D</Attr>
<Attr>0</Attr>
<Attr>.</Attr>
<Attr>ID</Attr>
<Attr>13L</Attr>
<Attr>S</Attr>
</DictItem>

<DictItem Name=“@”>
<Attr>PH</Attr>
<Attr>Name HairColor TrollType GotchaDate Description Tags</Attr>
</DictItem>

<DictItem Name=“TrollId”>
<Attr>D</Attr>
<Attr>0</Attr>
<Attr>.</Attr>
<Attr>ID</Attr>
<Attr>13L</Attr>
<Attr>S</Attr>
</DictItem>

</DictItems>
}

}

Zen, AJAX, Page Class with the <form> as the View, using a page template not shown here:

Class Playground.dawnw.TrollCRUD Extends Util.Template.Page [ Language = mvbasic ]

{

Parameter PAGENAME = “Troll Create-Read-Update-Delete MVC Example”;

Property ClipboardTitle As %ZEN.Datatype.string [ InitialExpression = “My Trolls” ];

Property ClipboardInstructionsName As %ZEN.Datatype.string [ InitialExpression = “Template” ];

XData vcontentPane
{
<pane xmlns=http://www.intersystems.com/zen&#8221;>
<tabGroup id=“tabGroup” showTabBar=“true” >
<tab id=“tab1” title=“tab1” caption=“My Trolls”>
<pane paneName=“Tab1Pane”/>
</tab>
<tab id=“tab2” title=“tab2” caption=“List of All Trolls”>
<pane paneName=“Tab2Pane”/>
</tab>
</tabGroup>
</pane>
}

XData Tab1Pane
{
<pane xmlns=http://www.intersystems.com/zen” xmlns:s=http://www.snupnow.com/component” >
<dataController id=“source” modelClass=“S.Troll”  />

<s:form controllerId=“source” id=“signupForm” 
    groupClass=“snupform” enclosingClass=“snupencform” hintClass=“formhint” 
    width=“570px” labelClass=“formlabel”
    invalidMessage=“There seems to be a little problem here”>
<label controlClass=“componentTitle” enclosingClass=“componentHeader” 
    value=“Troll Information” />
<s:fieldSet id=“firstFieldset”
groupClass=“snupfs” enclosingClass=“snupencfl”>

<text label=“Troll ID” id=“ID” name=“ID” 
    dataBinding=“%id” size=“60”
    onchange=“zenPage.reloadForm();”
    hint=“Leave blank when entering a new troll” 
    />

<text label=“Name” id=“Name” name=“Name” 
    dataBinding=“Name” size=“60”
    />

<text label=“HairColor” id=“HairColor” name=“HairColor”
    dataBinding=“HairColor” size=“60”
    />

<radioSet label=“TrollType” id=“TrollType” name=“TrollType”  
    dataBinding=“TrollType” 
    valueList=“Standard,Tiny,Off-brand”
    />

<dateSelect label=“GotchaDate” id=“GotchaDate” name=“GotchaDate” 
    dataBinding=“GotchaDate”
     />

<textarea label=“Description” id=“Description” name=“Description”
    dataBinding=“Description”
    rows=“6” cols=“56”
    />

<textarea label=“Tags” id=“Tags” name=“Tags”
    dataBinding=“Tags”
    hint=“Place one tag on each line”
    rows=“4” cols=“56”
    />

</s:fieldSet>

<hgroup>
<button caption=“New” onclick=“zenPage.newItem();” />
<spacer width=“10”/>
<button caption=“Save” onclick=“zenPage.saveItem();” />
<spacer width=“10”/>
<button caption=“Cancel” onclick=“zenPage.cancel();” />
<spacer width=“10”/>
<button caption=“Delete” onclick=“zenPage.deleteItem();” />
<spacer width=“10”/>
<button caption=“Lookup” onclick=“zenPage.reloadForm();” />

</hgroup>

</s:form>

</pane>
}

Method saveItem() [ Language = javascript ]
{
    var form zen(‘signupForm’);
    var controller zen(‘source’);
    form.save();
    saveError controller.getError();
    // We will assume the error is a Version mismatch to handle concurrency for this simple example
    if (saveError != )
    {
        controller.setModelId(controller.modelId);
        zenPage.reloadForm();
        alert(“Rats! That didn’t work because someone else changed this information first. Their changes are here now, so you can try again at this time. \n\n”saveError);
    }
    zenPage.newItem();
    zen(‘ID’).setProperty(‘readOnly’,false);
    zen(‘ID’).focus();
    return
}

Method reloadForm() [ Language = javascript ]
{
    var id zen(‘ID’).getValue();
    var form zen(‘signupForm’);
    form.reload(id);
    return
}

Method newItem() [ Language = javascript ]
{
    var controller zen(‘source’);
    controller.createNewObject();
    var form zen(‘signupForm’);
    zen(‘ID’).setProperty(‘readOnly’,true);
    form.reset();
    return
}

Method cancel() [ Language = javascript ]
{
    var form zen(‘signupForm’);
    form.reset();
    var controller zen(‘source’);
    controller.setProperty(‘modelId’,);
    return
}

Method deleteItem() [ Language = javascript ]
{
    var controller zen(‘source’);
    var id=controller.getModelId();
    controller.deleteId(id);
    controller.createNewObject();
    return
}

}