URM uses a declarative approach for working with data within an application.
URM defines a data language block specification that allows the platform to manage boilerplate functionality The language block also allows developers to manage the granularity of data functionality from a centralized location. The declarative approach also facilitates code reuse.
Data can be defined using many fields such as persistence, groups, network shaping, debugging, etc. Data definitions are referenced using their ID from multiple places within the codebase.
The Data Block API provides an API provides methods to interact with data definitions, the client, and providers.
Data Block can automatically manage many concerns. Here is a comprehensive list.
[todo descriptions]
The current approach used by modern web technologies is to combine three types of language blocks into one or more files. This structure follows the "separation of concerns" principle. For an example, see https://vuejs.org/guide/scaling-up/sfc.html
<template>
<!-- structure and content (typically HTML) -->
</template>
<script>
<!-- logic, executable code and data declarations (typically Javascript) -->
</script>
<style>
<!-- style directives (typically CSS) -->
</style>
URM adds an additional Data Language Block
type to the standard language blocks.
<data>
<!-- data binding, persistence, shaping, flow control, validation, provider, ui hints, etc. -->
</data>
The syntax of a data block resembles CSS and Javascript. Whereas CSS defines selectors
, a data block defines data block definitions
. Each data block definition
is comprised of a dataId
, followed by data block definition fields
.
<dataId> {
<field-name>: <field-value>;
<field-name-n>: <field-value-n>;
}
<dataId-n> {
<field-name>: <field-value>;
<field-name-n>: <field-value-n>;
}
Whitespace is optional.
name{displayName:Name;description:Your full name;}age{type:number;}
This regular expression describes valid id and field names: [a-zA-Z_][a-zA-Z_0-9\-]*
Examples of valid values
a
_a
ab
a-b
a_b
a8
a_
a-
Examples of invalid values
-a
-8
8a
hello.world
A field value is always interpreted as a string, with any surrounding whitespace trimmed.
If the field name is prefixed with a :
, the field value will be evaluated using either eval(...)
or JSON.parse(...)
. If the evaluated value returns a function
, the function will invoked to resolve the actual value.
name_eval {
displayName: () => "Some" + " Person"
}
name_json {
displayName: "Some Person"
}
Field values terminate with a ;
. If a field value uses a ;
or \
character, the character must be escaped with a \
character. This applies to strings, parsed strings, and evaluated strings
name {
description: I have a \; and a \\ in my description;
}
Default field values may be defined using the reserved DataId *
. These field values apply to any definition in the Data Block that omits the field.
* {
provider: some_provider;
namespace: some_namespace;
}
An example Data Block with options.
<data>
* {
provider: LocalStorage;
}
my-string {
scope: workspace;
:debounce: 100;
:validate: myValidateFunction;
bind: #some-element
default: () => {message: 'Hello World'}
}
</data>
<script>
// get the provided value
const myString = await dataBlock.get('my-string')
// 2-way bind
await dataBlock.bind('my-string', '#my-element')
// autobind definitions that use the bind field
await dataBlock.autobind();
</script>
Until the data block becomes a web standard, the data block content can be added via a script. Furthermore, an HTML preprocessor can replace the data block with a script.
<html>
<head>
<script>
window.urm_data_block_string = `
my-text-area {
scope: workspace;
debounce: 100;
validate: myValidateFunction;
default: () => {message: 'Hello World'}
}
...
`
window.urm_data_block = parseDataBlock(wi dow.urm_data_block_string)
</script>
<script
</head>
</html>
The following section outlines a comprehensive list of currently supported data definition fields.
type: string
A unique identifier used to reference the definition. This identifier must be unique within the data block it is defined in.
An id must conform to the regular expression [a-zA-Z_][a-zA-Z0-9_-]*
or the reserved id *
* {
...
}
dataId-1 {
...
}
dataId-n {
...
}
type: string\ default: undefined
An optional namespace to use. Namespaces are used to resolve ids within multiple data block instances and declarations.
type: string\ default: undefined
A human readable name used in reports and user interfaces. A variant of the id will be used if not specified.
type: string\ default: undefined
A brief description of the property. The description is used in tooltips, detail sections, and documentation.
type: string\ default: undefined
A description of the property to be displaying in user interface hint areas.
type: string\ default: undefined
The type of data. This can be inferred from the default value.
type: string\ default: undefined
The subtype of data.
For example
{
type: string;
type2: password;
}
Value fields affect how a value is loaded or saved
type: string\ default: undefined
Specifies the resource provider that manages the data.
type: object\ default: undefined
Provider specific properties
type: string \ default: undefined
Specifies the accounts, applications, sessions, and devices that share the data. Scope is meant to augment provider settings with common parameters. See [[Data Language Block Scopes]]
Scope does not define persistence
Indicates how data is stored and loaded between instantiations
type: any | function\ default: undefined
The default value of the data. This value will be used in several circumstances:
type: function\ default: undefined
A function to validate data integrity when initializing it from an external source. This function can modify the incoming value or properties on the incoming value.
The signature the function is:
(value:T, context:Context) => T | undefined
A validate function can return a new or replaced value. It can also throw an exception.
If the validate function returns undefined, the value is considered valid.
type: selector-string | element | (selector-string | element)[] | function\ default: undefined
Targets to use when autobinding data to elements. Multiple targets may be set for the same dataId.
type: object\ default: undefined
The following fields may be set in the shaping object. They are all optional.
type: boolean
Only persist the value when commit
is called. This event may be attached to an element blur (losing focus), to an enter
keypress on a text field, or by pressing a commit
button, etc. When commit is enabled, no other shaping parameters are considered.
type: number
Delays sending updates until time based conditions are met.
type: number
Limits the frequency of updates sent. Throttling does not alter content.
type: boolean
Indicates if the value is sent immediately, before and timeouts.
type: number
Indicates the change between 2 set values required to trigger an immediate
send.
Indicates if, when, and how data is compressed before transfering or storing. Also indicates compression parameters and protocol.
Indicates if and how cached updates are combined or replaced before sending them.
type: true | false\ default: undefined
Indicates if the data should be debugged. This includes tracing operations such as lifecycle, initialization, validation, reads, writes, and errors.
type: true | false | (read | write | permission|error)[]\ default: undefined
Indicates if data access (reads, writes, permissions, errors) of the data should be logged.
?
?
?
The Data Block API provides functionality to manage data, data definition fields, and interactive services.
Retrieve the data associated with a data definition
Set the data associated with a data definition
Retrieve a data definition
Retrieve all data definitions
Retrive a data definition fields (not the realized field value)
Retrive a data definition field (not the realized field value)
To allow for terse code, several shortcut macros have been added to the other three language block sections. As with the data language block, a preprocessor can replace the shortcut macros with valid code.
<script>
const myTextArea = ^description // getDataRef('description')
const myTextArea = ^^description // getDataValue('description')
^description = 'Some description' // setDataValue('description', 'Some description')
const debounce = ^description.debounce // getDataProperty('description', 'debounce')
const debounce = ^description.* // getDataProperties('description')
const defaultValue = ^description?.default() // getDataProperty('description', 'default')?.()
</script>
<template>
<div ^description /> <!-- v-model="getDataRef('description')" -->
<div ^^description /> <!-- v-model="getDataValue('description')" -->
</template>
<style>
.some-class::before {
content: ^description
}
.another-class::before {
url(^some-data-source)
}
</style>