Practical example

To get you an idea how scripting a plugin looks like, compared to the direct approach you have seen in the previous chapters, we will create the full t-test plugin once again -- this time only with the R functions of the rkwarddev package.

Tip

The package will add a new GUI dialog to RKWard under FileExportCreate RKWard plugin script. Like the name suggests, you can create plugin skeletons for further editing with it. This dialog itself was in turn generated by an rkwarddev script which you can find in the demo directory of the installed package and package sources, as an additional example. You can also run it by calling demo("skeleton_dialog")

GUI description

You will immediately notice that the workflow is considerably different: Contrary to writing the XML code directly, you do not begin with the <document> definition, but directly with the plugin elements you would like to have in the dialog. You can assign each interface element -- be it check boxes, dropdown menus, variable slots or anything else -- to individual R objects, and then combine these objects to the actual GUI. The package has functions for each XML tag that can be used to define the plugin GUI, and most of them even have the same name, only with the prefix rk.XML.*. For example, defining a <varselector> and two <varslot> elements for the "x" and "y" variable of the t-test example can be done by:

variables <- rk.XML.varselector(id.name="vars")
var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE, id.name="x")
var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE, id.name="y")
		

The most interesting detail is probably source=variables: A prominent feature of the package is that all functions can generate automatic IDs, so you do not have to bother with either thinking of id values or remembering them to refer to a specific plugin element. You can simply give the R objects as reference, as all functions who need an ID from some other element can also read it from these objects. rk.XML.varselector() is a little special, as it usually has no specific content to make an ID from (it can, but only if you specify a label), so we have to set an ID name. But rk.XML.varslot() would not need the id.name arguments here, so this would suffice:

variables <- rk.XML.varselector(id.name="vars")
var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE)
var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE)
		

In order to recreate the example code to the point, you would have to set all ID values manually. But since the package shall make our lives easier, from now on we will no longer care about that.

Tip

rkwarddev is capable of a lot of automation to help you build your plugins. However, it might be preferable to not use it to its full extend. If your goal is to produce code that is not only working but can also be easily read and compared to your generator script by a human being, you should consider to always set useful IDs with id.name. Naming your R objects identical to these IDs will also help in getting script code that is easy to understand.

If you want to see how the XML code of the defined element looks like if you exported it to a file, you can just call the object by its name. So, if you now called var.x in your R session, you should see something like this:

<varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" />
		

Some tags are only useful in the context of others. Therefore, for instance, you will not find a function for the <option> tag. Instead, both radio buttons and dropdown lists are defined including their options as a named list, where the names represent the labels to be shown in the dialog, and their value is a named vector which can have two entries, val for the value of an option and the boolean chk to specify if this option is checked by default.

test.hypothesis <- rk.XML.radio("using test hypothesis",
	options=list(
		"Two-sided"=c(val="two.sided"),
		"First is greater"=c(val="greater"),
		"Second is greater"=c(val="less")
	)
)
		

The result looks like this:

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

All that is missing from the elements of the Basic settings tab is the check box for paired samples, and the structuring of all of these elements in rows and columns:

check.paired <- rk.XML.cbox("Paired sample", value="1", un.value="0")
basic.settings <- rk.XML.row(variables, rk.XML.col(var.x, var.y, test.hypothesis, check.paired))
		

rk.XML.cbox() is a rare exception where the function name does not contain the full tag name, to save some typing for this often used element. This is what basic.settings now contains:

<row id="row_vTFSPP10TF">
	<varselector id="vars" />
	<column id="clm_vrsTFSPP10">
		<varslot id="vrsl_compare" label="compare" source="vars" types="number" required="true" />
		<varslot id="vrsl_against" label="against" i18n_context="compare against" source="vars" types="number" required="true" />
		<radio id="rad_usngtsth" label="using test hypothesis">
			<option label="Two-sided" value="two.sided" />
			<option label="First is greater" value="greater" />
			<option label="Second is greater" value="less" />
		</radio>
		<checkbox id="chc_Pardsmpl" label="Paired sample" value="1" value_unchecked="0" />
	</column>
</row>
		

In a similar manner, the next lines will create R objects for the elements of the Options tab, introducing functions for spinboxes, frames and stretch:

check.eqvar <- rk.XML.cbox("assume equal variances", value="1", un.value="0")
conf.level <- rk.XML.spinbox("confidence level", min=0, max=1, initial=0.95)
check.conf <- rk.XML.cbox("print confidence interval", val="1", chk=TRUE)
conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Confidence Interval")
		

Now all we need to do is to put the objects together in a tabbook, and place that in a dialog section:

full.dialog <- rk.XML.dialog(
	label="Two Variable t-Test",
	rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame)))
)
		

We can also create the wizard section with its two pages using the same objects, so their IDs will be extracted for the <copy> tags:

full.wizard <- rk.XML.wizard(
		label="Two Variable t-Test",
		rk.XML.page(
			rk.XML.text("As a first step, select the two variables you want to compare against
				each other. And specify, which one you theorize to be greater. Select two-sided,
				if your theory does not tell you, which variable is greater."),
			rk.XML.copy(basic.settings)),
		rk.XML.page(
			rk.XML.text("Below are some advanced options. It is generally safe not to assume the
				variables have equal variances. An appropriate correction will be applied then.
				Choosing \"assume equal variances\" may increase test-strength, however."),
			rk.XML.copy(check.eqvar),
			rk.XML.text("Sometimes it is helpful to get an estimate of the confidence interval of
				the difference in means. Below you can specify whether one should be shown, and
				which confidence-level should be applied (95% corresponds to a 5% level of
				significance)."),
			rk.XML.copy(conf.frame)))
		

That is it for the GUI. The global document will be combined in the end by rk.plugin.skeleton().

JavaScript code

Until now, using the rkwarddev package might not seem to have helped so much. This is going to change right now.

First of all, just like we did not have to care about IDs for elements when defining the GUI layout, we do not have to care about JavaScript variable names in the next step. If you want more control, you can write plain JavaScript code and have it pasted to the generated file. But it is probably much more efficient to do it the rkwarddev way.

Most notably you do not have to define any variable yourself, as rk.plugin.skeleton() can scan your XML code and automatically define all variables you will probably need -- for instance, you would not bother to include a check box if you do not use its value or state afterwards. So we can start writing the actual R code generating JS immediately.

Tip

The function rk.JS.scan() can also scan existing XML files for variables.

The package has some functions for JS code constructs that are commonly used in RKWard plugins, like the echo() function or if() {...} else {...} conditions. There are some differences between JS and R, e.g., for paste() in R you use the comma to concatenate character strings, whereas for echo() in JS you use +, and lines must end with a semicolon. By using the R functions, you can almost forget about these differences and keep writing R code.

These functions can take different classes of input objects: Either plain text, R objects with XML code like above, or in turn results of some other JS functions of the package. In the end, you will always call rk.paste.JS(), which behaves similar to paste(), but depending on the input objects it will replace them with their XML ID, JavaScript variable name or even complete JavaScript code blocks.

For the t-test example, we need two JS objects: One to calculate the results, and one to print them in the printout() function:

JS.calc <- rk.paste.JS(
	echo("res <- t.test (x=", var.x, ", y=", var.y, ", hypothesis=\"", test.hypothesis, "\""),
	js(
		if(check.paired){
			echo(", paired=TRUE")
		},
		if(!check.paired && check.eqvar){
			echo(", var.equal=TRUE")
		},
		if(conf.level != "0.95"){
			echo(", conf.level=", conf.level)
		},
		linebreaks=TRUE
	),
	echo(")\n"),
	level=2
)

JS.print <- rk.paste.JS(echo("rk.print (res)\n"), level=2)
		

As you can see, rkwarddev also provides an R implementation of the echo() function. It returns exactly one character string with a valid JS version of itself. You might also notice that all of the R objects here are the ones we created earlier. They will automatically be replaced with their variable names, so this should be quite intuitive. Whenever you need just this replacement, the function id() can be used, which also will return exactly one character string from all the objects it was given (you could say it behaves like paste() with a very specific object substitution).

The js() function is a wrapper that allows you to use R's if(){...} else {...} conditions like you are used to. They will be translated directly into JS code. It also preserves some operators like <, >= or ||, so you can logically compare your R objects without the need for quoting most of the time. Let's have a look at the resulting JS.calc object, which now contains a character string with this content:

	echo("res <- t.test (x=" + vrslCompare + ", y=" + vrslAgainst + ", hypothesis=\"" + radUsngtsth + "\"");
	if(chcPardsmpl) {
		echo(", paired=TRUE");
	} else {}
	if(!chcPardsmpl && chcAssmqlvr) {
		echo(", var.equal=TRUE");
	} else {}
	if(spnCnfdnclv != "0.95") {
		echo(", conf.level=" + spnCnfdnclv);
	} else {}
	echo(")\n");
		

Note

Alternatively for if() conditions nested in js(), you can use the ite() function, which behaves similar to R's ifelse(). However, conditional statements constructed using ite() are usually harder to read and should be replaced by js() whenever possible.

Plugin map

This section is very short: We do not need to write a .pluginmap at all, as it can be generated automatically by rk.plugin.skeleton(). The menu hierarchy can be specified via the pluginmap option:

	[...]
	pluginmap=list(
		name="Two Variable t-Test",
		hierarchy=list("analysis", "means", "t-Test"))
	[...]
			

Help page

This section is very short as well: rk.plugin.skeleton() cannot write a whole help page from the information it has. But it can scan the XML document also for elements which probably deserve a help page entry, and automatically create a help page template for our plugin. All we have to do afterwards is to write some lines for each listed section.

Tip

The function rk.rkh.scan() can also scan existing XML files to create a help file skeleton.

Generate the plugin files

Now comes the final step, in which we will hand over all generated objects to rk.plugin.skeleton():

plugin.dir <- rk.plugin.skeleton("t-Test",
	xml=list(
		dialog=full.dialog,
		wizard=full.wizard),
	js=list(
		results.header="Two Variable t-Test",
		calculate=JS.calc,
		printout=JS.print),
	pluginmap=list(
		name="Two Variable t-Test",
		hierarchy=list("analysis", "means", "t-Test")),
	load=TRUE,
	edit=TRUE,
	show=TRUE)
			

The files will be created in a temporal directory by default. The last three options are not necessary, but very handy: load=TRUE will automatically add the new plugin to RKWards configuration (since it is in a temp dir and hence will cease to exist when RKWard is closed, it will automatically be removed again by RKWard during its next start), edit=TRUE will open all created files for editing in RKWard editor tabs, and show=TRUE will attempt to directly launch the plugin, so you can examine what it looks like without a click. You might consider adding overwrite=TRUE if you are about to run your script repeatedly (e.g. after changes to the code), as by default no files will be overwritten.

The result object plugin.dir contains the path to the directory in which the plugin was created. This can be useful in combination with the function rk.build.package(), to build an actual R package to share your plugin with others -- e.g. by sending it to the RKWard development team to be added to our plugin repository.

The full script

To recapitulate all of the above, here is the full script to create the working t-test example. Adding to the already explained code, it also loads the package if needed, and it uses the local() environment, so all the created objects will not end up in your current workspace (except for plugin.dir):

require(rkwarddev)

local({
	variables <- rk.XML.varselector(id.name="vars")
	var.x <- rk.XML.varslot("compare", source=variables, types="number", required=TRUE)
	var.y <- rk.XML.varslot("against", source=variables, types="number", required=TRUE)
	test.hypothesis <- rk.XML.radio("using test hypothesis",
		options=list(
			"Two-sided"=c(val="two.sided"),
			"First is greater"=c(val="greater"),
			"Second is greater"=c(val="less")
		)
	)
	check.paired <- rk.XML.cbox("Paired sample", value="1", un.value="0")
	basic.settings <- rk.XML.row(variables, rk.XML.col(var.x, var.y, test.hypothesis, check.paired))

	check.eqvar <- rk.XML.cbox("assume equal variances", value="1", un.value="0")
	conf.level <- rk.XML.spinbox("confidence level", min=0, max=1, initial=0.95)
	check.conf <- rk.XML.cbox("print confidence interval", val="1", chk=TRUE)
	conf.frame <- rk.XML.frame(conf.level, check.conf, rk.XML.stretch(), label="Confidence Interval")

	full.dialog <- rk.XML.dialog(
		label="Two Variable t-Test",
		rk.XML.tabbook(tabs=list("Basic settings"=basic.settings, "Options"=list(check.eqvar, conf.frame)))
	)

	full.wizard <- rk.XML.wizard(
			label="Two Variable t-Test",
			rk.XML.page(
				rk.XML.text("As a first step, select the two variables you want to compare against
					each other. And specify, which one you theorize to be greater. Select two-sided,
					if your theory does not tell you, which variable is greater."),
				rk.XML.copy(basic.settings)),
			rk.XML.page(
				rk.XML.text("Below are some advanced options. It is generally safe not to assume the
					variables have equal variances. An appropriate correction will be applied then.
					Choosing \"assume equal variances\" may increase test-strength, however."),
				rk.XML.copy(check.eqvar),
				rk.XML.text("Sometimes it is helpful to get an estimate of the confidence interval of
					the difference in means. Below you can specify whether one should be shown, and
					which confidence-level should be applied (95% corresponds to a 5% level of
					significance)."),
				rk.XML.copy(conf.frame)))

	JS.calc <- rk.paste.JS(
		echo("res <- t.test (x=", var.x, ", y=", var.y, ", hypothesis=\"", test.hypothesis, "\""),
		js(
			if(check.paired){
			echo(", paired=TRUE")
			},
			if(!check.paired && check.eqvar){
			echo(", var.equal=TRUE")
			},
			if(conf.level != "0.95"){
			echo(", conf.level=", conf.level)
			},
			linebreaks=TRUE
		),
		echo(")\n"), level=2)

	JS.print <- rk.paste.JS(echo("rk.print (res)\n"), level=2)

	plugin.dir <<- rk.plugin.skeleton("t-Test",
		xml=list(
			dialog=full.dialog,
			wizard=full.wizard),
		js=list(
			results.header="Two Variable t-Test",
			calculate=JS.calc,
			printout=JS.print),
		pluginmap=list(
			name="Two Variable t-Test",
			hierarchy=list("analysis", "means", "t-Test")),
		load=TRUE,
		edit=TRUE,
		show=TRUE,
		overwrite=TRUE)
})