Sometimes you want to repeat a set of options for an arbitrary number of items. E.g. suppose you want to implement a plugin for sorting a data.frame. You may want to allow for sorting by an arbitrary number of columns (in case of ties among the first column(s)). This could simply be realized by allowing the user to select multiple variables in a <varslot> with
multi="true". But if you want to extend this, e.g. allowing the user to specify for each variable whether it should be converted to character / numeric, or whether sorting should be ascending or descending, you need more flexibility. Other examples would be plotting multiple lines in one plot (allowing to select object, line style, line color, etc. for each line), or specifying a mapping for recoding from a set of old values to new values.
Enter the <optionset>. Let's look at a simple example, first:
<dialog [...]> [...] <optionset id="set" min_rows="1"> <content> <row> <input id="firstname" label="Given name(s)" size="small"> <input id="lastname" label="Family name" size="small"> <radio id="gender" label="Gender"> <optioncolumn label="Male" value="m"/> <optioncolumn label="Female" value="f"/> </radio> </row> </content> <optioncolumn id="firstnames" label="Given name(s)" connect="firstname.text"> <optioncolumn id="lastnames" label="Family name" connect="lastname.text"> <optioncolumn id="gender" connect="gender.string"> </optionset> [...] </dialog>
Here, we created a UI for specifying a number of persons (e.g. authors). The UI requires at least one entry (
min_rows="1"). Inside the <optionset>-element, we begin by specifying the
<content>, i.e. those elements that belong to the option set. You will be familiar with most elements
inside the <content>.
Next we specify the variables of interest that we will want to read from the option set in our JS file. As we will be dealing with
an arbitrary number of items, we cannot just read
getString ("firstname") in JS. Rather, for each value of
interest, we specify an <optioncolumn>. For the first optioncolumn in the example, <connect="firstname.text"> means that the content of the <input>
element "firstname" is read for each item. <optioncolumn>s for which a
label is given,
will be shown in the display, in a column by that label. In JS, we can now fetch the first names for all authors using
getList("set.lastnames") for the family
getList("set.gender") for an array of "m"/"f" strings.
Note that there are no restrictions on what you can place inside an <optionset>. You can even use embedded components. Just as with any other element, all you have to do is to collect the output variables of interest in an <optioncolumn>-specification. In the case of embedded plugins, this is often a section of the "code" property. E.g.:
<dialog [...]> [...] <optionset id="set" min_rows="1"> <content> [...] <embed id="color" component="rkward::color_chooser" label="Color"/> </content> [...] <optioncolumn id="color_params" connect="color.code.printout"> </optionset> [...] </dialog>
Of course you can also use UI logic inside an optionset. There are two options for doing this: You can do so by making connection (or scripting) in the main <logic> section of your plugin, as usual. However, you will access the UI elements in the contents region as (e.g.) "set.contents.firstname.XYZ". Note the prefix "set" (the
id you have assigned to the set and "contents"). Alternatively, you can add a separate <logic> section as a child element of your <optionset>. In this case,
ids will be addressed relative to the contents region, e.g. "firstname.XYZ". Only the <script>-element is not allowed in the logic section of an optionset. If you want to use scripting,
you will have to utilize the plugin's main <logic> section.
When scripting logic in an optionset, all you can do is access the current content region. Thus, typically, it is only meaningful to connect elements inside the contents region to each other. Connecting a property outside the <optionset> to a property inside the content region, may be useful for initialization. However, modifying the contents region after initialization will not apply to items that the user has already defined. Only to the currently selected item in the set.
So far we have considered an <optionset> that provides buttons for adding / removing items. However, in some cases, it is much more natural to select items outside the <optionset>, and only provide options for customizing some aspects of each item in an <optionset>. E.g. suppose you want to allow the user to plot several objects inside one plot. For each object, the user should be able to specify line color. You could solve this by placing a <varselector> and <varslot> inside the <content> area, allowing the user to select one item at a time. However, it will mean much less clicks for the user, if you use a <varslot multi="true"> outside the <optionset>, instead. Then you will connect this selection of objects to a so-called "driven" optionset. Here's how:
<dialog [...]> <logic> <connect client="set.vars" governor="vars.available"/> <connect client="set.varnames" governor="vars.available.shortname"/> </logic> [...] <varselector id="varsel"/> <varslot id="vars" label="Objects to plot"/> <optionset id="set" keycolumn="var"> <content> [...] <embed id="color" component="rkward::color_chooser" label="Line color"/> </content> [...] <optioncolumn id="vars" external="true"> <optioncolumn id="varnames" external="true" label="Variable"> <optioncolumn id="color_params" connect="color.code.printout"> </optionset> [...] </dialog>
We'll start looking at the example at the bottom. You'll note that two <optioncolumn> specifications have
external="true". This tells RKWard that these are controlled from outside the <optionset>. Here, the sole purpose of the "varnames"-optioncolumn is to provide easy-to-read labels in the
optionset display (it is connected to the "shortname" modifier of the property holding the selected
objects). The purpose of the "vars"-optioncolumn is to serve as the "key" column, as specified by
<optionset keycolumn="vars"...>. This means that for each entry in this list, the set will offer
one set of options, and options are logically tied to these entries. This column is connected to the property holding the
selected objects in the <varslot>. That is for each object that is selected there, the <optionset> will allow to specify line color.
External column can also be
connected to properties inside the
<content> region. However, it is important to note that optioncolumns declared
external="true" should never be modified from inside the <optionset>, and
external="false" (the default) should never be modified from outside the
Optionsets are a powerful tool, but they can sometimes do more harm than good, as they add considerable complexity, both from the perspective of a plugin developer, and from the perspective of a user. Thus, think twice, when using them. Here's some advice:
For some simple cases, the <matrix> element may provide a useful lightweight alternative.
Don't make your plugin do too much. We gave the example of using an optionset for a plugin to draw several lines within one plot. But in general it is not a good idea to create a plugin that will produce individual plots for each item in an optionset. Rather make the plugin produce one plot, and the user can call it multiple times.
If you don't expect more than two or three items in a set, consider repeating the options, manually, instead.