sequenceDiagram
participant User
participant QML as QML Form
participant Desktop as JASP Desktop
participant Engine as R Engine
User->>QML: Changes an option
QML->>Desktop: Updated options object
Desktop->>Engine: Calls R function with (jaspResults, dataset, options)
Engine->>Desktop: Updated jaspResults (tables, plots, state)
Desktop->>User: Renders output
6 Connecting QML to R
Understanding how QML, Description.qml, and R functions are wired together is essential for module development.
6.1 The reactive loop
JASP implements a reactive loop between the interface and the analysis engine:
Every time a user changes any option, JASP re-invokes the R analysis function with the full, updated options list. The jaspResults caching mechanism prevents redundant computation — only outputs whose dependencies changed are recomputed.
6.2 How components map to R options
Each QML component with a name property becomes a key in the options list passed to R:
| QML | R |
|---|---|
CheckBox { name: "descriptives" } |
options[["descriptives"]] → TRUE/FALSE |
RadioButtonGroup { name: "alternative" } with RadioButton { value: "greater" } |
options[["alternative"]] → "greater" |
DoubleField { name: "ciLevel"; defaultValue: 0.95 } |
options[["ciLevel"]] → 0.95 |
AssignedVariablesList { name: "variables" } |
options[["variables"]] → character vector |
AssignedVariablesList { name: "dependent"; singleVariable: true } |
options[["dependent"]] → single string |
TextField { name: "title" } |
options[["title"]] → string |
DropDown { name: "method" } |
options[["method"]] → selected value string |
6.3 The wiring: Description.qml → QML → R
Description.qml ties everything together:
// inst/Description.qml
Analysis
{
title: "My T-Test"
func: "myTTest" // ← must match R function name (exported in NAMESPACE)
qml: "MyTTest.qml" // ← must match file in inst/qml/
}inst/Description.qml ──→ inst/qml/MyTTest.qml ──→ R/myTTest.R
(menu entry) (options form) (analysis function)
When a user clicks the analysis in the menu:
- JASP loads
inst/qml/MyTTest.qmland displays the form - User configures options
- JASP calls
myTTest(jaspResults, dataset, options)in R - The R function reads
options, computes, and populatesjaspResults
6.4 Option name consistency
The name in QML must exactly match what you access in R. This is the single source of truth:
// QML
CheckBox { name: "meanDifference"; label: qsTr("Mean difference") }
CIField { name: "meanDifferenceCiLevel" }# R
if (options[["meanDifference"]]) {
ciLevel <- options[["meanDifferenceCiLevel"]]
# ...
}See Chapter 8 for naming conventions and Appendix C for the full reference of standardised option names.
6.5 Dependencies and caching
The $dependOn() calls in R reference option names from QML. When a listed option changes, the associated output element is invalidated and recomputed:
table$dependOn(c("variables", "ciLevel", "alternative"))This means: rebuild this table whenever variables, ciLevel, or alternative changes in the QML form. If any of these options change, JASP sets the table to NULL — so the next time your code runs, the if (!is.null(...)) return() check fails and the table is recreated.
Dependencies can also be set on a container. When a container’s dependency changes, the entire container and everything inside it (tables, plots, state) is discarded:
container <- createJaspContainer(title = gettext("Results"))
container$dependOn(c("dependent", "variables"))
jaspResults[["resultsContainer"]] <- containerThis is different from setting $dependOn() on a single table inside a container — in that case only that table is removed, while the container and its other contents survive. Setting the dependency on the container itself means all contents are thrown out at once, which is what you want when the outputs share a common computation that must be redone.
6.6 The dataset
By default (preloadData: true in Description.qml), JASP preloads the dataset and passes it to your R function via the dataset argument. This is the preferred way to handle data. Columns are automatically converted to the types requested by QML components — if an AssignedVariablesList has allowedColumns: ["scale"], those columns arrive in R as numeric.
The old function readDataSetToEnd() is deprecated — do not use it in new code. With preloaded data the dataset argument already contains all requested columns with the correct types. If you need listwise deletion:
dataset <- jaspBase::excludeNaListwise(dataset, exclude)To disable preloading for analyses where JASP cannot infer column names from the options (e.g., SEM, JAGS): set preloadData: false in Description.qml for that specific analysis. See Section 5.4 for full details.
6.6.1 Unit tests with preloaded data
Because column types are now set by QML rather than by an R read function, you may need to specify types explicitly in unit tests. There are three ways:
- Add type information to options: if
options$variablescontains the variables, addoptions$variables.typesto specify the types - Use
jaspTools::addTypedDataSet(): pass a named list likelist(var1 = "scale", var2 = "nominal")— useful when reusing one dataset across many tests - Convert manually: if passing a dataset directly (not a filename), call
as.factor()oras.ordered()yourself before passing it
6.7 Summary: adding a new analysis
- Add an
Analysisentry ininst/Description.qmlwithfuncandqml - Create
inst/qml/MyAnalysis.qmlwith the options form - Create
R/myAnalysis.Rwith the analysis function - Export
myAnalysisinNAMESPACE - Recompile and refresh in JASP