@curvenote/runtime
The runtime
package allows you to create variables and components that react to changes in state through
user-defined functions. The runtime is a small component that can be used in other packages to keep
the state of a document reactive. The package is based on Redux which is
compatible with many popular javascript frameworks (e.g. React, Vue, etc.).
Getting Started
This package is not setup directly for use in a browser, please see the @curvenote/components package to see it in use. For use in other packages, node, etc. you can download the latest release from npm:
You should then be able to extend/integrate the runtime
as you see fit:
For more information on Redux or Redux Thunk, please see their docs and tutorials.
State Structure
The basic state structure is:
Each of the sub-states, {…}
, is a dictionary with uuid
keys, to an object that represents a
variable
or a component
.
-
specs: the definition of components, including properties and events. The variable spec is the only component spec included by default.
-
variables: holds the state of named values (e.g. numbers, strings, etc.), they cannot have events (other than changing the value of the variable)
-
components: an object that holds the state of a component (e.g. a slider, equation, etc. or more complicated widget). Components have properties that can be defined as functions, as well as named events (e.g. click, change, etc.) that are defined within the spec.
The state must be composed inside of an runtime
dictionary. This allows you to compose
the runtime state inside of your larger application, if required.
Variables
Variables have a name
and a value
and they can also be defined by a function
(func
). Depending on if a function is provided the variable will be derived
, meaning that
the function is used to evaluate the variable and provide the current
value.
All components and variables also have a scope
which is used to provide the variables by name when they
are evaluated.
To create a variable, create a store and dispatch the createVariable action:
The name must be a simple variable name, with an optional scope
prepending the name, the default scope
is "global". The value in this case of x
is null and a function is provided as ('1 + 1'
)
which will be evaluated by the middleware in the store
.
Get and Set Variable Properties
The dispatched action returns a shortcut that can be used to decrease the verbosity of further changes to the
variable properties. Note that the current state and the value
of the variable are often different. The
variable is guaranteed to have the value
only initialization, as other events may change its
current
value.
To get the current
state of the variable:
All of the properties of the variable are contained within the variable
object that is up to date with
the state provided by the store
.
To change the value
of the variable, or provide a func
for evaluation, this can be done
through setting the variable:
In the second line, a function is provided referencing y
, which will be evaluated as these variables
live in the same scope
.
Components & Specs
To define a new component you must first define a component spec. This lays out all of the properties that a component has as well as any events it may create.
Define a Spec
For example, a slider has the following spec:
The slider has a min
, max
, step
and a value
, when a user drags
the slider, it creates a change event function and handler that has a single input to a function called "value" (which
is not necessarily related to the value
property 😕, more on that later.)
The name
of the spec will need to be referenced when creating components of this type. As such that
needs to be registered with the store, shown in the last line of the example above.
Create a Component
To create a range component, there must be a spec
defined, and the properties and event handlers of this
instance of the component can be defined. Note also that this component must live in a scope
,
which allows you to reference variables in that scope by name.
In this case the current sliders state can be accessed in a few ways:
Here we have created a component that is set up with two-way-data-binding to the variable x
:
-
when
x
changes thevalue
property of the slider will also change; and -
when the slider is interacted with and dispatches a
change
event, that event evaluates thefunc
:
This dictionary is used to update the variables in the state, and changes the value of x
.
Responding to Component Events
As was mentioned before, you do not have to necessarily update the value of the slider (in this case it won’t move) or you may want to update multiple variables at the same time:
This changes the slider component to declare that when a change event happens, update:
-
x = value
-
y = value + 1
-
z = value * 2
Here the function has a single argument called “value” because that is what we defined in the spec:
We could change this to any other string or add other required entries for the event. These variable names will overwrite any variables named that in the scope (or globally).
Remember these are arbitrary evaluated strings, so you can do anything that Javascript can do. This includes executing user defined functions:
You also have access to other variables in the scope from the evaluated function:
Made with love by Curvenote
Last updated August 24th, 2021