Preliminary Introduction to writing Actions

This document describes how to write your own Actions. Note, that at the time of this writing, much of what is described here should be regarded as preliminary. Even some fundamental design-principles may be subject to change. Therefore, this document should be regarded as an introduction to the current approach and as a basis for discussion. All sorts of comments are welcome.

ActionAuthoring can be roughly divided into three steps: A) Placing a new Action in the menu-hierarchy, B) Describing the looks and behavior of the Action-GUI and C) Defining, how R-code is to be generated from the settings, the user makes in the GUI. Those will be dealt with in turn.

A) Creating a new Action and placing it in the menu-structure

For now, all Actions are kept in a single directory-tree. Each level in the menu-hierarchy has its own sub-directory, and every single action is also kept in it's own sub-directory. Every (sub-)directory, then, contains a file called "description.xml", which is a very short XML-file that keeps just enough information to place the action (or menu-level) in the menu-hierarchy. Here's what a "description.xml"-file looks like:

1) for a directory:

 <document>
 	<content type="dir"/>
 	<entry caption="bogus tests"/>
 </document>

Here, content can either be type="dir" or type="entry". The first means, that this is a menu-level, i.e. the sub-directories found in this directory will be placed as entries in a sub-menu. The "entry"-tag describes the entry that will be put in the menu-structure. Currently only a caption is supported, in the future, icons might be supported additionally.

2) for an Action:

 <document>
 	<content type="entry"/>
 	<entry caption="cool bogus test"/>
 	<action filename="empty.action"/>
 </document>

Here the content-type is "entry", hence this directory is taken to contain a single action. Subdirectories of this directory are ignored. The entry-tag is the same as for directores. For actions, however, one further tag is expected: "action" with the attribute filename.

The filename to specify is that of the more detailed description of the Action in XML-format, which will be described below. That file is parsed every time, the Action is selected from the menu. Note, that this file _should_ reside in the same directory as the description.xml. While you may specify a relative or absolute path to a file in a different directory, that may not be very portable across platforms, and should therefore be avoided.

If you add new Actions at run-time, you will have to get Obversive to regenerate the menu-structure. Currently the only way to do so (besides restarting obversive), is to select a different actions-directory in Preferences->Actions, and then set the correct one, again.


 DanPutler Comment 

I need an example that is a bit more concrete to get this. Let me indicate what I do (or don't) understand up to this point through the use of an example. Assume we wanted to create access to a set of regression tools through the use of a menu bar pull-down called "Regression", one of the options within this menu would be "Logistic Regression", which would then have sub-options (using a cascading menu) of "Logit" and "Probit". My understanding that to implement what I've indicated thus far would result in the following XML code (placed in a file called "description.xml", but I'm not sure where this file would live):

 <document>
 	<content type="dir"/>
 	<entry caption="Regression"/>
        <document>
 	        <content type="dir"/>
 	        <entry caption="Logistic"/>
                <document>
 	                <content type="entry"/>
 	                <entry caption="Logit"/>
 	                <action filename="logit.action"/>
                </document>
                <document>
 	                <content type="entry"/>
 	                <entry caption="Probit"/>
 	                <action filename="probit.action"/>
                </document>
        </document>
 </document>

The only thing that seems potentially wrong to me in the code above is the nested "dir" tags related to the "Logistic Regression" menu entry. It seems to me that it would more naturally lend itself to a <content type="entry"/> tag, but it doesn't really have an action script to go with it.



 ThomasFriedrichsmeier Comment 

Actually, there's not one big description.xml defining the entire menu-structure, but rather lots of small description.xml-files, each in their own subdirectory (see below), and each defining only a single menu-entry. So actually, the examples I gave are full examples of description.xml-files. They just don't get any larger than that. I'm not quite sure, how to explain this concept. Maybe you could have a look at the actions.zip-file in CVS. But maybe things get clearer below. Note of course, that all this is up for discussion.


The other thing I'm really unclear about is what is the nature of the file directory structure that goes with this. Let's assume that obveRsive lives in /usr/local/obversive (or on a windows box in C:\Program Files\obversive). Would the implied directory structure then be:

/usr/local/obversive (C:\Program Files\obversive)

    /usr/local/obversive/Regression (C:\Program Files\obversive\Regression)
        /usr/local/obversive/Regression/Logistic (C:\Program Files\obversive\Regression\Logistic)
            logit.action probit.action logit.tpt probit.tpt

or would it be:

/usr/local/obversive (C:\Program Files\obversive)

    /usr/local/obversive/Regression (C:\Program Files\obversive\Regression)
        /usr/local/obversive/Regression/Logistic (C:\Program Files\obversive\Regression\Logistic)
            /usr/local/obversive/Regression/Logistic/Logit (C:\Program Files\obversive\Regression\Logistic\Logit
                logit.action logit.tpt
            /usr/local/obversive/Regression/Logistic/Probit (C:\Program Files\obversive\Regression\Logistic\Probit
                probit.action probit.tpt

or am I completely clueless about the directory structure? I could imagine that obversive has no explicit directory structure, so that the action author is free to do what s/he likes in this regard. For example the author would just provide a path to the file using an action tag that looked like:

<action filename="/home/username/Regression/Logistic/logit.action"/>

I understand that this results in complete chaos, so we may require all actions to live in a single directory (say /usr/local/obversive/actions) with the possibility of action authors creating sub-directories under this main actions directory.

If the structure is more along the lines of my first two examples, then the question I have is how is the needed directory structure created? Does an action author need to explicitly create the needed directory structure, or is it created automatically by obveRsive?}


 ThomasFriedrichsmeier Comment 

The second example is correct. An each of these subdirectories would have a description.xml-file. For

 /usr/local/obversive/Regression/Logistic

that would contain

 <content type="dir"/>

while for

 /usr/local/obversive/Regression/Logistic/Probit

it would contain

 <content type="entry"/>

The directory-structure would be explicitely created by the ActionAuthor (that is until some time in the future, we have a super-cool graphical action-editor...). For now all actions have to be placed in the same directory-structure (but not in the same directory). That directory structure may be rooted anywhere you like, however (that is already configurable in current CVS). Note, that my idea would be prospectively, that you may duplicate the branches of interest of this directory structure in your home-directory and place additional actions there. Those would then be integrated with the "default"/distributed action-hierarchy. The advantage would be, that you don't need write access to the common directory-tree.


B) Defining the Action

So now you have a new Action placed in the obversive menu-structure. Next you'll have to define the action in the file you specified in description.xml. Below is the code of the "Test Action" with explanations in between:

 <!DOCTYPE obversive_action>

DOCTYPE is not interpreted, yet, but set it to "obversive_action" for clarity.

 <!-- This is a simple example, of how an "action" with both a dialog- and a wizard-interface might be configured. -->

Comments as usual.

 <document>
 	<title>Some Test</title>

Caption of the GUI-window.

 	<code filename="some.file"/>

 DanPutler Comment 

So what is the relationship between "some.file", and the file given in the <action filename=""/> tag? Is "some.file" the *.action file or the *.tpt file?



 ThomasFriedrichsmeier Comment 

some.file is the *.tpt-file. Note that the <code>-tag is optional. By default code.tpt will be used. Actually, my reason for introducting the <code>-tag is, that I plan to make it ""possible"" to inline the code in the .action-file. (Something like <code><!---

 print ("This is pure R-code")
 -->

</code> ). Further, the reason the code-filename is not given in description.xml, but rather in the .action-file, is that I'm trying to reduce memory-usage of actions to a minimum. While not activated (i.e. shown), an action is basically just a single string storing the filename of the .action-file. Only once the action is chosen from the menu, all the further information is parsed and the GUI is created. As soon as you close the dialog, everything except for the string storing the filename is freed again.


By default, the code-template (see below) used will be "code.tpt" in the directory, the action is located. You override this by specifying a different file, here.

 	<dialog>

The document-element requires at least one of the child-elements "dialog" and/or "wizard". Their meaning should be obvious. If only either of "dialog" or "wizard" is given, only that sort of interface will be available. If both are given, the user can switch between the interfaces. All direct children of the "dialog" element will be placed horizontally from left to right. Better control of widget-placement can be achieved using a number of tags described further below.

 		<tabbook>

The "tabbook"-element opens a tabbook. The only direct children allowed are "tab"-elements, which each represent a page in the tabbook. The tabs will be placed in the book, in the order they appear in the XML.

 			<tab label="general">

tab-elements take a (mandatory) attribute "label", which is the string written on the tab-item.

 				<varselector id="vars" label="select variable(s)"/>

The "varselector"-tag defines a list from which you can select available objects. Besides a label, this element requires an attribute "id", which should be a unique string, by which the varselector can be referenced at other places. Currently no further attributes are supported.


 DanPutler Comment 

an earlier data.frame selector will need to be created. I've written code that allows up to query the .R file associated with a particular project for objects of a give type (e.g., it provides a list of only data.frames).



 ThomasFriedrichsmeier Comment 

You're right of course. My idea on how to do that, would be to continue using the <varselector>-tag, but to introduce an additional "filter"-tag like e.g.:

 <varselector id="frames" label="select table(s)" filter="frames"/>

That varselector would then only show data.frames. It should also be possible to say, excluded "string"-variables from the varselector, or even to exclude variables, that are not marked as at least ordinal scale level. I'm not entirely sure, how the syntax should be for that, yet.


 				<column>

This is another layouting-element. All direct children of a "column"-tag will be place vertically from top to bottom. Similarily a "row"-tag would arrange its children from left to right (which is the default outside of columns). Using nested "row"- and "column"-tags, you have a good deal of control over the looks of the GUI.

 					<varslot type="numeric" id="x" source="vars" required="true" label="compare"/>
 					<varslot type="numeric" id="y" source="vars" required="true" label="against"/>

"varslot"s are the counterpart to the "varselector". This is, where selected variables may be held. The "source"-attribute references the "varselector", from which variables may be selected. The "type"-attribute (currently ignored) specifies, which sort of variables are allowed. Finally, the "required"-attribute specifies, whether this varslot has to be filled with a valid entry before the user can proceed. Of course, this widget also requires an "id"-attribute so that it can be referenced from outside.

 					<radio id="hypothesis" label="using test hypothesis">
 						<option value="two.sided" label="Two-sided"/>
 						<option value="greater" label="First is greater" checked="true"/>
 						<option value="less" label="Second is greater"/>
 					</radio>

This defines a group of radio-buttons with three options, the second of which is checked by default. The "value"-attribute is the string-value that is assigned to this widget, when the corresponding option is checked. This string-value can be used to generate R-code (see below).

 					<checkbutton label="assume equal variances" id="option_a" value="some option"/>
 					<checkbutton label="use some option" checked="true" id="option_b" value="use this option"/>

Checkbuttons. The value assigned to the widget is the string given, if checked, an empty string if not checked.

 				</column>
 			</tab>
 			<tab label="stuff">
 				<column>
 					<text>These are a few useless test items (including a lengthy
 					text, which is so long, that it will have to be wrapped, which
 					is quite a long text, don't you think?)</text>
 					<text>Below is a tabbook in a tabbook:</text>

Text. Long lines will be wrapped, spaces will be reduced to single spaces like in HTML. This is mostly equivalent to a <p>-tag in HTML.

 					<tabbook>
 						<tab label="there's"><text>nothing</text></tab>
 						<tab label="in this"><text>tabbook</text></tab>
 					</tabbook>

You can place tabbooks inside tabbooks, of course.

 					<frame label="textframe"><text>This is text in a frame</text></frame>

If a "frame"-tag contains child-elements, it will be shown as a rectangular frame around them.

 					<column>
 						<text>There's a line below this</text>
 						<frame/>

Otherwise (if it does not contain any child-elements), it is rendered as a line (horizontal in columns, vertical in rows).

 						<text>There's a line above this</text>
 					</column>
 				</column>
 			</tab>
 			<tab label="info">
 				<column>
 					<text>This dialog was parsed and created on the fly, at the time
 					you selected it from the menu.</text>
 				</column>
 			</tab>
 		</tabbook>
 	</dialog>

So this is the end of the dialog-interface.

 	<wizard recommended="true">

And this is the wizard-interface. You can use recommended="true" to recommend the use of the wizard for this action. Users may chose (actually it's the default setting) to be presented a wizard-interface by default, whenever that is recommended.

 		<pane>
 			<text>first pane</text>
 		</pane>

The "wizard"-element takes as only direct children "pane"-elements, which represent the pages of the wizard. Everthing inside panes will once again be layed out left to right (unless you use columns).

 		<pane>
 			<column>
 				<text>second pane</text>
 				<row>
 					<column>
 						<varselector id="vars" label="select variable(s)"/>
 					</column>
 					<column>
 						<varslot type="numeric" id="x" source="vars" required="true" label="compare"/>
 						<varslot type="numeric" id="y" source="vars" required="true" label="against"/>
 					</column>
 				</row>
 			</column>
 		</pane>
 		<pane>
 			<column>
 				<text>third pane</text>
 				<radio id="hypothesis" label="using test hypothesis">
 					<option value="two.sided" label="Two-sided"/>
 					<option value="greater" label="First is greater" checked="true"/>
 					<option value="less" label="Second is greater"/>
 				</radio>
 				<checkbutton label="assume equal variances" id="option_a" value="some option"/>
 				<checkbutton label="use some option" checked="true" id="option_b" value="use this option"/>
 			</column>
 		</pane>
 	</wizard>

As you see, all of this is mostly a duplication of what has been defined for the dialog-interface. You may of course make the wizard-interface look very different to the plain dialog. Be sure, however, to assign corresponding widgets the same "id" in both interfaces. This is not only used to transfer settings from the dialog-interface to the wizard-interface and back, when the user switches interfaces, but also simplifies writing your code-template (see below).

 </document>

So now we have the GUI defined (note, that you do not need to mention standard-elements like the "Submit"-button). Here's one more widget you can use (yes, this document is a patchwork):

 	<column>
 		<input type="string" id="name" label="Whom do you want to greet?" required="true"/>
 		<input type="numeric" type_hint="slider" id="liking" label="How much to you like them?" required="true" min="0" max="1"/>
 		<input type="numeric;int" id="count" label="How often to say hello?" required="true" min="1" max="10"/>
 	</column>

"input" is a widget for all sorts of discrete values (as opposed to vectors). The first line will be rendered as a textfield. All non-empty strings are accepted.

The second line requests a widget to be used to enter a number in the range "min"-"max", and suggests to use a slider for that. The idea is, that this sort of "type_hint" might be overridden by user settings (and will also be ignored, if no lower and upper bounds are given).

Finally the third line requests a widget to enter an integer number in the range 1..10. The default representation for this is a spinbox (type_hint="spinner" to request a spinner explicitely).

So acutally, an "input"-widget may look quite different depending on the given attributes and configuration-settings. The idea is however, that all "input"-widgets will not necessarily look the same, but serve the same function: reading a discrete value.

C) Generating code

Now we have a GUI defined, but we still need to generate some R-code from that. For that, we need another text-file, by default "code.tpt" (you can override this in the GUI-description using the <code filename="..."/>-tag). This file is mostly plain R-code, but may contain references to the defined widgets:

 for (i in 1:@getValue ("count")) \{
 	print ("Hello @getValue ("name", "true")! Somebody likes you this much: @getValue ("liking")")
 \}

So the interesting part about this are the calls to @getValue (widget_id, quote_escaping?). For instance,

 @getValue ("count")

will be replaced with whatever value the widget "count" currently holds.

 @getValue ("name", "true")

This will be replaced with the value held in name, additionally escaping quotation-marks, so quotes can be printed safely.

Other than that, you write more or less plain R-code. You will have to escape {}s, though, as shown in the example. Actually, you can do a lot more complicated stuff than just filling in strings. The language used here is TPT ( httphttp://tazthecat.net/~isaac/libtpt/ ).

For instance the code below may still not be very complicated, but you can see, how you get a lot of additional flexibility using constructs like @if (and even @for, @while and the like are available).

 bogus.test (@getValue ("x"), @getValue ("y"), @getValue ("hypothesis")\
 @if (!@compare (@getValue ("option_a"), "some option")) {
 , use.option.a\
 }
 )

This is all for now. Happy experimenting.


Last edited on Monday, April 14, 2003 8:12:55 pm.


Edit PageHistory Diff Info