XoTCL

 
While working on the extensions for the dynfields package and contacts, I have been using XoTCL to write most of the code as the object oriented approach really helps a lot in coding fast. 
Contained in here are notes and information which I collected during that process.
 
 

How to create a CR Class  
Please familiarize yourself with the concept of classes and objects and instances. Otherwise the following text will most likely be gibberish to you.
To work with content items in XoTCL, based on xotcl-core package which we assume you have installed, you first need to create a class for your object.
 

::Generic::CrClass create CWBlock -superclass ::xowiki::Page -pretty_name "CW Block" -pretty_plural "CW Blocks" -table_name "cw_blocks" -id_column "cw_block_id" -cr_att\
ributes {
    ::Generic::Attribute new -attribute_name pic -datatype integer -pretty_name "Picture ID"
}
This will create a new class CWBlock with the content_type "::CWBlock" that has the superclass "::xowiki::Page", meaning it will inherit all attributes from the ::xowiki::Page class (and accordingly the content_type). It is using it's own table "cw_blocks" to store the additional attributes defined and we do have one additional attribute named "pic" there.
You put this call in your package "class-procs.tcl" file (you can name it anyway you want, just make sure it ends with "-procs.tcl"). This will make sure that on server restart the class information is handed over to xotcl so it knows what to do with this class.  
In case you object type "::CWBlock" does not yet exist, xotcl will generate it for you. Sadly, if you change the attributes, e.g. because you wanted to add a new attribute to it, you are can't because xotcl won't do it for you on the fly. Additionally it won't work in the future, because the views on the database assume that the new attribute is actually in the database table. Nifty.
BUT, do not dispair, until this is changed, here is what you can do:
 

::xo::db::content_type create_attribute  -content_type ::CWPage  -attribute_name pic2  -datatype integer  -pretty_name Pic2 -column_spec integer
This will allow you to create a second attribute called "pic2" in the database and work from there. Make sure to only call this ONCE, so e.g. in your apm_callback upon upgrade of your package.
After that you can edit your Class definition to look like this:
 

::Generic::CrClass create CWBlock -superclass ::xowiki::Page -pretty_name "CW Block" -pretty_plural "CW Blocks" -table_name "cw_blocks" -id_column "cw_block_id" -cr_att\
ributes {
    ::Generic::Attribute new -attribute_name pic -datatype integer -pretty_name "Picture ID"
    ::Generic::Attribute new -attribute_name pic2 -datatype integer -pretty_name "Pic2"
}

 As you might have guessed you can easily create classes for existing object types and work with them through the XoTCL commands afterwards.

Creating a new content item of a class
To create a new item of a class you have to follow a two step process. First you need to create a new object of that class and then you need to save it.
Taking the example from above you would create a new ::CWBlock by calling

set page [::CWBlock new -name "my_new_name" -text "My content"] 
This will create a new object of type ::CWBlock with the (unique) name "my_new_name". After you have created it you need to save it.

$page save_new
This will actually create the new cr_item in the database (as ::CWBlock ultimately inherict from the CrItem class). 
 

Instantiate Objects
 
XoTCL allows you to create select statements to be passed to db_string via the instance_select_query type.
One example of this is e.g. the function to count the number of items in 
Additionally you can use the |instantiate_objects  function to automatically instantiate the objects returned by the SQL you created above in a object.

    set s [$base_type instantiate_objects -sql [eval  $base_type instance_select_query $sql]] 
You can then access each contained object (read: returned row) by e.g. saying 

    foreach c [$s children] {
      $c instvar  .....
       ....
    }
As you can see the foreach works nicely with the container object. 

 

 
XoWIKI
 

Where to find things
XoWIKI is quite complex for the beginner to understand. Let alone for the developer. Here are some insights where you can find code that nudges you along:

xowiki-form-procs.tcl
In this document you will be able to find all the Form classes which allow the generation of a form for the specific class. The superclass of all xowiki form classes should be ::xowiki::WikiForm, which is again a subclass of "::Generic::Form". It provides you with the parameters which you can have in any xowiki class:

  • field_list: List of elements to show in the form and their order.
  • name: name of the cr_item which is generated for this page. Also the identifier in the URL 
  • title: Displayed title 
  • creator: Who created the page (defaults to current user)
  • text: The actual text. Is usually overwritten by other Classes in this file
  • description: Some short description on this revision
  • nls_language: Language being used. Note that the nls_language should match the language prefix in the name (so do not say "en:index" and set the language to "de_DE").
  • with_categories: Should categories be displayed (boolean)
  • submit_link: Good question. Probably the "mode" to go after submit. Defaults to "view"
  • folderspec: Yeah....
  • autoname: Should the name be automatically given. Have to play with that one a little bit. 

 xowiki-www-procs
In here the methods are defined which are used on the class when you choose a certain mode. Not entirely sure though, but it does contain e.g. the "view" method for the xowiki Page

xowiki-procs
These contain the main procedures dealing with xowiki. For the developer the important part is the definition of the "get_content" procedures which allow you to override the default method of displaying the content from xowiki::Page. This is done for nearly every class, so look around there to get some ideas how you can display the content based on the class.
Some random insights:

  • It is possible to define in the folder object how a certain object should be rendered. This is stated somewhere in the documentation. You can then name your variable in the template like the one you stated in the folder object and thereby create templates which allow you to do more than just have plain text fields. This also allows you to change the default variables.
  • If this solution is not powerful enough you can write an extension class of ::xowiki::WikiForm, which can be used by your object type class (which again is a subclass of xowiki::Page). There you can manipulate things further.

Examples are to follow, as usual :-). Until then check out the official documentation at http://media.wu-wien.ac.at/download/xowiki-doc/

Modelling relationships
 
The issue of modelling and implementing association types (in UML association classes) or n-ary associations is an on-going research topic and there is not a single best way to do it. When dealing with relationships the first would be to figure out what you want to achieve.
If your goal is a two-way (navigability) composition between two object_types then an association class is the best option (which in pure OpenACS is modelled using acs-rels).
If your goal on the other hand is a tenary association where you have more than two concepts that is modelled as (UML) class and instances of the three form a tenary composition then you are out of luck with acs-rels. 

Two-Way composition
If you are used to use ACS Rels to model relationships, modelling them in XoTCL is different and can be solved in different ways, dependent on what your goal is you want to achieve.
Let's first review the szenario. You have a list of users and you want to map them to an organization (or many of them). In pure OpenACS you would create a relationship type for users and organizations (e.g. employement_rel) that links the object_type "user" and "organization". Then you would call "relation_add" and a relationship is created between the two. This relationship can be found in the table acs_rels and you can easily figure out the employees of an organization by querying acs_rels like this:
 

select object_id_one from acs_rels where object_id_two = :organization_id and rel_type = 'employment_rel'
 
There is currently no object-relational association mapping for application classes in xotcl-core db abstraction, comparable to Hibernatoe or similar persitency framworks. Furthermore there does not exist (yet) a layer to deal with ACS Rels directly. 
To come around this the easiest way would be to create the two classes and use advances features of slots to keep navigability etc.

Class User -slots {
    Attribute employed_at -multivalued true
}
Class Organization -slots {
    Attribute employees -multivalued true
}

This would store the multivalues within the users and organizations table, allowing for quick navigation in XoTCL, but not necessarily directly in the DB layer (which you might have to do).
An alternative is to use a special Reference class to have a single association table:

Class Reference -slots {
    Attribute references
}
Class User -slots {
    Reference employed_at -references Organization -multivalued true
}
Class Organization -slots {
    Reference employees -references Users -multivalued true
}

 
 
 
Documentation 
Documentation is a very important part of the development process and the more you document the higher the likelyhood that someone else is actually going to reuse your code. This is especially important with object oriented approaches like XoTCL where reusability is one of the main strengths.
You can view a list of all currently defined Classes at /xotcl in your site, or go to http://www.openacs.org/xotcl for a decent subset of things. 
 

XoTCL Classes 
The xotcl-core procs allow you to easily document the classes using parameters so they show up in the OpenACS api browser, making them really useful.
If you create a Class, you can specify the "-ad_doc" parameter to document the class itself. Then you can search for the class in the API browser and get the documentation. A good example for this is ::Generic::Form which works like this (taken from /packages/xotcl-core/tcl/generic-proc.tcl):
 

  Class Form -parameter {
    fields
...
  } -ad_doc {
    Class for the simplified generation of forms. This class was designed
    together with the content repository class
    <a href='http://cognovis.de/xotcl/show-object?object=::Generic::CrClass'>::Generi....
    <ul>
    <li><b>fields:</b> form elements as described in
       <a href='http://cognovis.de/api-doc/proc-view?proc=ad_form'>ad_form</a>.
...
    </ul>
  }

XoTCL methods 
We have to differentiate between "instproc" and "proc":
proc is something you use if you want a procedure that exists 
only in that instance of an object. So if you want a procedure for 
class Foo which doesn't appear in instances of Foo then you use "proc". (http://alice.wu-wien.ac.at:8000/xotcl-mailing-list/0031.html
My definition would be: If you use "proc" you can call the procedure like any normal TCL procedure, e.g. ::xowiki::Page::import, which will import multiple objects into XoWIKI.
If you use instproc, you can use the procedure on every single object instance, like "Page instproc get_content", which allows you to call "[$Page get_content]" if $Page references an instance of an XoWIKI Page, returning the content of this particular instance.
From the documentation:
 

 procs are object specific methods:
 Object o
 o proc test {} {puts hello}
is quite similar to
  namespace eval ::o {
     proc test {} {puts hello}
  }
Instprocs are methods defined in classes, provided to the instances
Class C
C instproc foo {} {puts hi}
C create c1
c1 foo
the last command will print "hi".
a proc has a higher precedence (proiority) than a class defined method.
If we define a proc foo for the object, it will shadow the class definition.
This allows to overwrite a method for an object.
 c1 proc foo {} {puts "ho"}
 c1 foo
the last command will print "ho". Of course, other instances
of C will not be affected.
methods can be chained together. every method can call the shadowed methods
via next. This allows to refine methods
 c1 proc foo {} {puts "ho"; next; puts "ha"}
 c1 foo
the last command will print three lines with "ho", "hi", "ha".

 
xotcl-core extends those two procedure types with their ad partners "ad_instproc" and "ad_proc". In contrast to the two others, ad_instproc and ad_proc allow for the documentation between the parameter section and the code section just as ad_proc in "normal" TCL does.
An example is the documentation e.g. for CrClass::instance_select_query (again from generic-procs.tcl)

CrClass ad_instproc instance_select_query {
    {-select_attributes ""}
...
  } {
    returns the SQL-query to select the CrItems of the specified object_type
    @param select_attributes attributes for the sql query to be retrieved, in addion
      to ci.item_id acs_objects.object_type, which are always returned
    @return sql query
} {
    if {![info exists folder_id]} {my instvar folder_id}
.... 
}

As you can see from the above you can use both the @param flag for explaining your parameters as well as the @return flag for defining the return value. Documenting both is a very good idea in each procedure as it allows other developers to treat the procedure as a black box and reuse it to their own delight.

XoTCL Variables
To access variables in xotcl, you use [my set foo]. To be able to use $foo directly you need to run "my instvar foo" first, which will set the variable in the current context. 

Forms
 
To create a form for your class you should specify when you add the CrClass which form class it is using. The naming convention goes for "<yourclassname>Form", so if your class is e.g. ::cognovis::User then ::cognovis::UserForm would be the class name for your form.
Within the form class, especially if the superclass is ::xowiki::WikiForm you can specify a field_list of attributes from the class that you are able to fill out. Additionally you can overwrite the defaults for each of the fields how they should be displayed by using {f.<attribute_name> "= hidden,optional"}, giving the usual parameters that you already give in normal ad_form declarations.