Data persistence

As described in the Data tree chapter, data in the dizmoViewer is stored and exchanged in the data tree.

In order to access data in this tree, dizmoViewer provides a few objects as well as a set of access methods to read, write and remove data nodes. dizmoViewer also provides methods to get a callback whenever a particular data item changes.

The three objects that allow you to interact with dizmoViewer are pre-instantiated objects (singletons) provided by the system for every dizmo instance.

  1. viewer - global viewer class. This is the same for all dizmos.
  2. dizmo - the current instance of the bundle. Storage here is tied to the current instance of the dizmo.
  3. bundle - the bundle that this instance belongs to. Storage here is shared with all instances of the bundle.

Accessing properties

To select with which main branch of the data tree you want to interact, you have to use the correct basic object ("/viewer", "/dizmos" or "/bundles").

Within each of these subtrees, there are three subtrees ("attributes", "public", "private"). You will have to decide which subtree that you want to access.

When reading and writing properties you may use any valid JavaScript data item. For example, a number, a string, a boolean (true/false), an array, or even an object.

To access nodes in the "public" subtree of an object use the publicStorage field in combination with the access method. Or, use the privateStorage field to access nodes in "private" data tree of the object.

To give you an idea of how this looks, here are few examples on how you would access properties (data items) in the private or public part of the data subtree of the three main objects:

var pw = viewer.privateStorage.getProperty("login/password");

dizmo.publicStorage.setProperty("stdout/topNumbers", [25, 3.14, 9, 7.222]);

bundle.privateStorage.deleteProperty("oldItem");

Options

All property access functions except deleteProperty accept an optional options item as their last parameter. If specified, this can be a JavaScript object providing flags to control the detailed behavior and result of the function called.

Here are the five options that are currently available:

Option Effect
value:true Access the value(s) of the specified node
nodes:true Access the child nodes of the specified node
recursive:true Access recursively the entire subtree under the specified node
json:true (De)serialize the data passed using JSON.stringify and JSON.parse
string:true Store / retrieve the data passed as a string without touching it
fallback Retrieve something else in case of a non-existing property
file:true Store files in the data tree
timeout:5000 Time limit in ms for the retrieval of files stored in the data tree

Note: Options

The two options value:true and nodes:true are currently considered to be mutually exclusive. You can only set one or the other to true.

Not all options make sense for every access method. Have a look at the detailed description of each function.

Please also refer to the API documentation of Storage for more details.

Data Format

There are two options, json:true and string:true, that allow you to specify what kind of data you want to store in a property node.

Using json:true as an option to setProperty means that you are passing a valid JSON object to the method and that you will want to retrieve it as a JSON object later using json:true. With getProperty, dizmoViewer will assume the data stored in the node has been stored there using the json:true option.

Although JavaScript and JSON are often considered to be fully compatible (i.e. bijective) please note that JSON does not support all native JavaScript datatypes:

«JavaScript syntax defines several native data types that are not included in the JSON standard: Date, Error, Regular Expression, Function, and undefined. These JavaScript data types must be represented by some other data format, with the programs on both ends agreeing on how to convert between the types.»

For more details on this please have a look at the Wikipedia article on JSON.

Note: Data Formats

If you want to store a string that should not be touched, you may use the option string:true with getProperty and setProperty to store it in the data tree.

However, always use the json:true in the public sections of the data tree to ensure compatability between all dizmos, and compatability with external systems. string:true should only be used in the private parts of the data tree.

If you do not specify any option, dizmoViewer by default assumes { value:true, json:true } and to be used for the options parameter.

setAttribute and getAttribute always take and deliver JSON objects.

json:true and string:true are mutually exclusive. You can only set one or the other to true. These options only make sense when accessing values (value:true).

Reading properties

The function getProperty loads property data from the location in the data tree as specified by the path.

getProperty(path [, options]);

The nodes:true flag may be used in the getProperty function to get an array of the names of the children nodes of the specified node.

The value:true flag returns the value that is written into the specified node. If you omit value:true and node:true, value:true will be used.

If the json:true flag is set, the data returned from the node is deserialized using JSON.parse. On the other hand, if the string:true flag is set, the data returned from the node is passed back as a string without any conversion. If you omit json:true and string:true, json:true is used instead.

Examples:

var user = dizmo.privateStorage.getProperty("login/username");

var nodeArr = dizmo.privateStorage.getProperty("login", {nodes:true});

The variable user contains the value that was saved into the node "login/username". If the node was not written to previously, it contains the value undefined.

The second variable, nodeArr, contains an array with the names of the subnodes of "login". These subnodes should have been created before. If no subnodes have been created yet, it contains an empty array.

Note: New fallback option

Up to dizmoJS API 1.2, if you requested a non-existing property from storage, e.g. dizmo.publicStorage.getProperty("foo"), it would return null. Starting with dizmoJS API 1.3, this has changed to undefined. The best way to support both APIs in your dizmo is to check a non-value with the following call:

if (!(dizmo.publicStorage.getProperty("foo")) {
    // initialize property }

If you want to return something else for a non-existing property, you can change it by providing a fallback parameter to the getProperty call:

dizmo.privateStorage.getProperty('foo', {fallback: 'bar'});

now if "foo" is currently not defined, it returns "bar".

One can still not write undefined, but instead has to use the provided calldizmo.privateStorage.deleteProperty('foo'); to get rid of a property.

Writing properties

The function setProperty saves the data item that is provided to the location in the data tree as specified by the path.

setProperty(path, value[, options]);

The default flag in the setProperty method is value:true. It is also the only flag that may be used in the method. Therefore, it may as well be omitted. To create a new node, use setProperty to set any value (e.g. null).

If the json:true flag is set, the data written to the node is serialized, using JSON.stringify. On the other hand, if the string:true flag is set, you have to pass a string that is written any conversion unchanged. If you omit json:true and string:true, json:true will be used.

Examples:

dizmo.privateStorage.setProperty("login/keep", false);

dizmo.privateStorage.setProperty("login/keep", false, {value:true});

The result of these two calls is identical as the options parameter's default value is {value:true}.

dizmo.privateStorage.setProperty("login/keep", "no");

dizmo.privateStorage.setProperty("login/keep", "no", {string:true});

The result of these two calls is not identical. In the first case, the string passed is serialized using JSON.stringify (resulting in ""no"") while in the second example the string is stored as "no".

Note: Case insensitivity

Paths / Node names in the data tree are case insensitive. Therefore, filenames are translated into lowercase when storing files in the data tree. Thus, filenames are treated as case insensitive. This is important when files are transferred between OS: and some OS:s have case-insensitive filenames. To avoid related problems, filenames have to be handled as case insensitive everywhere.

Note: Ordering of child nodes

The order of child nodes in the data tree is undefined. That is, loading the data tree of a dizmo when booting , via dizmoLive, or when loading a setup does not guarantee a specific order of children nodes in the data tree. If the order is essential, it needs to be implemented using an index into the list of children.

Deleting a property

The function deleteProperty allows removing a node from the data tree.

Note: It is important to note that removing a node from the data tree will not only remove the node and its value but also the entire subtree attached beneath it.

deleteProperty(path);

Unlike other access functions, deleteProperty does not have an options parameter as it always removes entire nodes including values and all descendants, including their values.

Example:

dizmo.privateStorage.deleteProperty("login/password");

dizmo.privateStorage.deleteProperty("login");

While the first example removes the "login/password" item in the private data tree of the dizmo object, the second removes the "login" node as well as all its children including all the values.

Subscribing to property changes

A key feature of the dizmo data tree is its capability to call back some of your code, whenever a particular property is about to be changed from any part of the code within your own dizmo. The origin of change can be from any other dizmo, by the user or by dizmoViewer. This feature allows you to structure your code in unique ways and to easily communicate between dizmos without the need to poll for changes of data.

To get a notification by means of a callback whenever a property is changed, use the subscribeToProperty function. To stop a notification, use the unsubscribe function with the subscriptionId you got when subscribing.

subscribeToProperty(path, callback[, options][, SubscriptionRegisteredCallback]);

The value:true flag may be used as an option of the subscribeToProperty function to get a callback whenever the value stored in this particular node is about to change. Please note that in this case, you can also leave away this parameter as value:true is the default value of the options parameter.

Alternatively, the nodes:true flag may be used as an option of the subscribeToProperty function to get a callback whenever there is a change in the children nodes of the specified node, that is, if a child node is added or removed from the list of subnodes. The call returns the current list of subnodes and the prior list of subnodes. From the difference between the two, you can work out the added or removed nodes.

When listening to changes with the value:true option, the recursive:true flag may be used as an additional option. Then, your callback function will be called if the value of the node, or the value of any of its subnodes, changes at any level below the node specified by path.

If the json:true flag is set, the data returned from the node is deserialized using JSON.parse before passing it to the callback function. On the other hand, if the string:true flag is set, the data returned from the node is passed back to the callback function as a string without any conversion. If you omit json:true and string:true, then json:true will be used.

Examples:

    function myCallbackVal (path, newVal, oldVal) {
        console.log("The value of node " + path + " is changing from " +
        oldVal + " to " + newVal);
    }

    var subscrId = dizmo.publicStorage.subscribeToProperty(
        "login",
        myCallbackVal
    );

    function myCallbackNodes (path, newNodes, oldNodes) {
        console.log("Node " + path);

        for (var i = 0; i < newNodes.length; i++) {
            console.log(newNodes[i]);
        }
    }

    var subscrId = dizmo.publicStorage.subscribeToProperty(
        "login",
        myCallbackNodes,
        {nodes:true}
    );

    function myCallbackRecursiveVal (chArr) {
        for (var i = 0; i < chArr.length; i++) {
            console.log("The value of node "+chArr[i].path+" is changing from "+chArr[i].oldVal+" to "+chArr[i].val);
        }
    }

    var subscrId = dizmo.publicStorage.subscribeToProperty(
        "login",
        myCallbackRecursiveVal,
        {recursive:true,value:true}
    );

Note: Subscription

Please make sure you unsubscribe from any subscriptions your dizmo does not need anymore. dizmoViewer continues to call the specified callback function every time a node changes its value, which can produce unnecessary load on the overall system.

It is also possible to unsubscribe from a node in a callback function that was triggered based on a previously created subscription.

All subscriptions are asynchronous. Thus, when a subscription is called and immediately after the value is changed, it a subscription is not triggered. An additional (optional) callback exists to bridge that gap. It is called the moment the subscription is registered and ready to be fired. Place your code to change the value inside this callback to ensure that the subscription is triggered.

dizmo.privateStorage.subscribeToProperty('foo', function (p, v, of) { ... }, function () { // it is assured that the subscription is triggered
dizmo.privateStorage.setProperty('foo','bar');
});

DizmoViewer is smart enough to not run your callback function if the property did not change because the new value is identical to the old one.

If a node to which you subscribed is deleted, the subscription is removed as well. There is still a callback, with the value first set to null, indicating it is about to be removed.

Unsubscribing from property changes

To cancel a subscription installed before, use the unsubscribeProperty function:

    unsubscribeProperty(subscriptionId);

Above, subscriptionId is the identifier that returned when you subscribed to the specific node.

Example:

To cancel the subscription made above, you would use

dizmo.publicStorage.unsubscribeProperty(subscrId);

Grouping property changes

If you don't want to receive one callback for each property change, you can group them using beginUpdate and endUpdate calls.

dizmo.publicStorage.beginUpdate("login");

After this call, no subscriptions to the tree login are called again until you call

dizmo.publicStorage.endUpdate("login");

At that point, you'll get one callback, containing an array with all the changes that happened between beginUpdate and endUpdate.

Organizing data hierarchically

The example below shows how you might want to organize some properties and get a notification if one of them is changed. In order to do this, organize your data in a groups, i.e. as subnodes of a common parent node:

dizmo.privateStorage.setProperty("login/username", "jdev");
dizmo.privateStorage.setProperty("login/password", "dorwssap");
dizmo.privateStorage.setProperty("login/keep", true);

Now use the function subscribeToProperty with the recursive:true and value:true options. The function notifies your callback function whenever one of the items in the group changes.

var subscriptId = dizmo.privateStorage.subscribeToProperty(
    "login",
    function(chArr) {
         console.log(chArr[0].path, ' has changed from ', chArr[0].oldVal, " to ", chArr[0].val);
    },
    {recursive:true,value:true}
);

The code above creates a console output whenever the value of one of the nodes under the "login" node changes. It even works if you dynamically add more subnodes to the "login" tree node later.

Note: Data trees can have as many levels and nodes as you need. You may also have multiple subscriptions to cover various parts of a data tree.

Storing files

To store files in dizmoViewer (e.g., an image in the Slideshow dizmo) use the setProperty function and set options to file:true. You may also specify an optional timeout value for the call in options.

dizmo.privateStorage.setProperty("images/t0hNX4gTK2", "http://www.mydomain.com/image.png", {file:true, timeout:1000});

Make sure the property name is unique for each file. One way to do that might be to create a unique random identifier, as in the example above.

The value parameter of the setProperty call when storing files is a URI pointing to the source location of the file. The protocols currently supported to specify the source of the data are "http://", "https://" "file://", "data://" and "blob://". The last two allow you to save binary data that might have been produced locally instead of using data coming from a file that was stored somewhere before.

When using file:true option, dizmoViewer tries to load the file from its source location and store it to the data tree. When using the getProperty function on such a tree node, the return value is an URI pointing to the persistent storage location of the file.

The timeout option is the time in milliseconds until an exception is thrown if the file could not be saved until then. Its default value is 5000 ms.

setProperty is a synchronous call when used with the default timeout. For example, if you want to store a video file that is located on a web-server, you want this call to be executed asynchronously. In this case, set the timeout value to 0 to request an asynchronous execution. Use the subscribeToProperty function to get a notification callback when saving the file in the data tree has been finished.

Accessing attributes

The attribute subtrees in the three main objects ("viewer", "dizmo" and "bundle") are an efficient and simple way to communicate with dizmoViewer as well as get and set various values and parameters with just a few access methods.

Data items in the "attributes" subtrees of the three main objects are somewhat different from general properties covered above. Their names and content are pre-defined, as they are provided and maintained by dizmoViewer.

Also, attributes are frequently used data items when writing a dizmo. TThus, they have gotten their own access methods, which are even more compact than those for properties in the "public" and "private" subtrees. Here are a few examples of how to access data items in the "attributes" subtrees of the three main objects:

var dizmoVersion = bundle.getAttribute("version");

dizmo.setAttribute("settings/title", "My dizmo Title");

var subId = viewer.subscribeToAttribute("geometry/angle", dSpaceRotated(changedValues))

Reading attributes

The function getAttribute loads attribute data from a location in the attributes subtree as specified by the path.

getAttribute(path);

Please note that getAttribute does not have an options parameter, because the "attributes" subtree is static by nature. Furthermore, unlike nodes in the "private" and "public" subtrees, attribute nodes may contain a value or have subnodes instead but never both at the same time. Thus, there is no need for an explicit distinction between accessing values and nodes. If you read from a node that has subnodes (for example "geometry") you always get the subnodes. If you read from a node that contains a value on the other hand (for example "geometry/x") you always get the value stored in this node.

Examples:

 var x = viewer.getAttribute("geometry/x");

 var nodes = dizmo.getAttribute("settings");

In the second example, nodes contain an object with the names and values of the subnodes.

Writing attributes

The function setAttribute writes the data provided to the location in the attribute specified by the path.

setAttribute(path);

For the same reason as given above, the setAttribute method does not have an options parameter.

For a complete list of the available viewer attributes, checkout the API documentation of the Viewer class.

Subscribing to attribute changes

As described above in "Subscribing to property changes", you can also subscribe to attribute changes.

To get a notification with a callback whenever an attribute is changed, use the subscribeToAttribute function. To stop a notification, use the unsubscribeAttribute function with the subscriptionId you got when subscribing.

subscribeToAttribute(path, callback[, SubscriptionRegisteredCallback]);

Similar to the getAttributes method subscribeToAttribute does not have an options parameter. So if you subscribe to a node that contains a value, you are notified if this single value changes. If, on the other hand, you subscribe to changes of an node that has subnodes, your callback is executed whenever one of the values of one of these subnodes changes. This is equal to the recursive option when subscribing to property values.

Example:

function myCallbackVal (path, newVal, oldVal) {
    console.log("The value of node " + path + " is changing from " +
    oldVal + " to " + newVal);
}

var subscrId = dizmo.subscribeToAttribute(
    "geometry/x",
    myCallbackVal
);

Note: Subscriptions and callbacks

Again, we are separating the callback function in the example above for the sake of readability. Of course, you may use an anonymous function in the subscribeToAttribute call directly.

All subscriptions are asynchronous. Therefore, when a subscription is called and immediately after the value is changed, it does not trigger the subscription. An additional (optional) callback bridges that gap. It is called the moment the subscription is registered and ready to be fired. Place your code to change the value inside this callback to ensure that the subscription is triggered.

dizmo.subscribeToAttribute('foo', function (p, v, of) { ... }, function () { // it is assured that the subscription is triggered
dizmo.setAttribute('foo','bar');
});

Here too, dizmoViewer does not run your callback function if the attribute did not change because the new value is identical to the old one.

Subscribing to attribute changes with a condition

To get a notification with a callback only when a condition is met, use the function `subscribeToAttributeConditional.

subscribeToAttributeConditional(attributeName, condition, callback[, SubscriptionRegisteredCallback]);

Example:

function myCallbackVal (path, newVal, oldVal) {
    console.log("The value of node " + path + " is changing from " +
    oldVal + " to " + newVal);
}

var subscrId = dizmo.subscribeToAttributeConditional(
    'state/front',
    true,
    myCallbackVal
);

In this example, too, we separate the callback function for the sake of readability.

As with subscribeToProperty and subscribeToAttribute, dizmoViewer does not run your callback function if the attribute did not change.

Unsubscribing from attribute changes

To cancel a subscription installed before, call:

unsubscribeAttribute(subscriptionId);

Where subscriptionId is the identifier that returned when you subscribed to a specific node.

Example:

To cancel the subscription made above, you would use

dizmo.unsubscribeAttribute(subscrId);

Note: Some of the attributes are not subscribable, but have a boolean property of that name. If it is set to true, subscriptions on it are allowed. If it is set to false, an error will be thrown when a user tries to subscribe to it.

The list below is based the list on mutability during a dizmo's run time (and the dizmoViewer runtime). For example, the attribute product/compiledate on the viewer never changes during a dizmo's runtime. Neither will the attribute created on a dizmo or the attribute dizmojsversion on a bundle.

Using the list of dizmos

As mentioned before, there is a "/dizmos" node that holds the dizmoIds of all the dizmos currently instantiated in dizmoViewer. As these Ids are only partially helpful and as this node cannot be accessed directly using getProperty or getAttribute, there are three additional calls in the API that help you to deal with the list of dizmos that are around in dizmoViewer.

Use viewer.getDizmos to get an array of the dizmo objects that are currently available in dizmoViewer. Other than the dizmoIds stored in the data tree, these objects give you full access (including all the methods of a dizmo) to the dizmo objects returned.

Example:

var dizmoObjs = viewer.getDizmos();
dizmoObjs[0].setAttribute("settings/framecolor", "#aa0000ff");

In order to get notified whenever a new dizmo is created in dizmoViewer, use viewer.onDizmoAdded to install a callback function that should be executed when this happens. This function returns an subscription id that you will need later to unsubscribe.

Example:

var subscrId = viewer.onDizmoAdded(myCallback[, SubscriptionRegisteredCallback]);

To be notified whenever a new dizmo is removed from dizmoViewer, use viewer.onDizmoRemoved to install a callback function that is executed when this happens. This function returns an subscription id that need later to unsubscribe.

Example:

var subscrId = viewer.onDizmoRemoved(myCallback[, SubscriptionRegisteredCallback]);

Use viewer.unsubscribeDizmoChanges to unsubscribe your callback when you do no longer want to receive notifications about changes in the list of dizmos.

Example:

viewer.unsubscribeDizmoChanges(subscrId);

Using the list of bundles

As mentioned before there is a "/bundles" node that holds the bundleIds of all the bundles currently installed in dizmoViewer. As these Ids are only partially helpful and as this node cannot be accessed directly using getProperty or getAttribute, there are three additional calls in the API that help you to deal with the list of bundles that are available in dizmoViewer.

Use viewer.getBundles to get an array of the bundle objects that are currently available in dizmoViewer. Other than the bundleIds stored in the data tree, these objects give you full access (including all the methods of a bundle) to the bundle objects returned.

Example:

var bundleObjs = viewer.getBundles();
var category = bundleObjs[0].getAttribute("category");

To get notified whenever a new bundle is installed in dizmoViewer, use viewer.onBundleAdded to install a callback function that should be executed when this happens. This function returns an subscription id that you will need later to unsubscribe.

Example:

var subscrId = viewer.onBundleAdded(myCallback[, SubscriptionRegisteredCallback]);

To be notified whenever a new dizmo is removed from dizmoViewer, use viewer.onBundleRemoved to install a callback function that is executed when installation occurs. This function returns a subscription id that you need later to unsubscribe.

Example:

var subscrId =viewer.onBundleRemoved(myCallback[, SubscriptionRegisteredCallback]);

Use viewer.unsubscribeBundleChangesto unsubscribe your callback when you do no longer want to be notified about changes in the list of dizmos.

Example:

viewer.unsubscribeBundleChanges(subscrId);