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:

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

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:

  1. JASP loads inst/qml/MyTTest.qml and displays the form
  2. User configures options
  3. JASP calls myTTest(jaspResults, dataset, options) in R
  4. The R function reads options, computes, and populates jaspResults

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"]] <- container

This 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:

  1. Add type information to options: if options$variables contains the variables, add options$variables.types to specify the types
  2. Use jaspTools::addTypedDataSet(): pass a named list like list(var1 = "scale", var2 = "nominal") — useful when reusing one dataset across many tests
  3. Convert manually: if passing a dataset directly (not a filename), call as.factor() or as.ordered() yourself before passing it

6.7 Summary: adding a new analysis

  1. Add an Analysis entry in inst/Description.qml with func and qml
  2. Create inst/qml/MyAnalysis.qml with the options form
  3. Create R/myAnalysis.R with the analysis function
  4. Export myAnalysis in NAMESPACE
  5. Recompile and refresh in JASP