Cookie Consent by FreePrivacyPolicy.com TIBET
TIBET Logo

TIBET

Essentials

TIBET Essentials - Part 3


Welcome to Part 3 of our TIBET Essentials series.


In this installment we'll focus on:

  • sharing state using TIBET URNs as value holders
  • using the initialize method to perform one-time setup
  • signaling, observing, and responding to notifications


Our Hello World! app can display a date/time in response to UIActivate events but that date/time display doesn't update automatically.

We could just add a timer to update it every second, however to make things more interesting we're going to add a tag that will reflect how long it's been since we last updated the hello:now tag's date/time display. We'll then update our new tag's delta display in response to a regular timer signal.


Let's get started…

Preliminaries

Make sure your hello project is running.

Open a terminal, cd to your project home, and execute tibet start:

cd ${project_home}
tibet start
...


Having completed TIBET Essentials - Part 2 you should see:

Project Home Page
hello:world + hello:now

With our project back up and running we're ready for new code.

hello:again - Part 1

We'll start off by creating a new tag to display the timestamp delta.

By now you should be familiar with the tibet type command.

Create a new hello:again tag using computedtag dna:

tibet type hello:again --dna computedtag


Next we'll add our new hello:again tag to the hello:app template file (~app_tags/APP.hello.app/APP.hello.app.xhtml):

<div tibet:tag="hello:app">
    <hello:world/>
    <hello:now/>
    <hello:again/>
</div>


As always, TIBET will automatically update our current view:

Project Home Page
hello:again - default link

We can style the new tag so it lays out a little more cleanly.

Edit ~app_tags/APP.hello.again/APP.hello.again.css to contain:

@namespace hello url("urn:app:hello");

hello|again {
    display: block;
    margin: 2em;
}


TIBET auto-refreshes and we now should see:

Project Home Page
hello:again - styled link

So far so good.

Design Overview

Let's take a moment to outline our concrete plan.

In response to hello:now timestamp updates we want our hello:again tag to reset the delta and refresh.

To achieve that goal we'll need to keep the time of the last timestamp update somewhere both the hello:now and hello:again tags can access it.

When the hello:now tag is rendered/refreshed we'll need to update that data location with the new timestamp.

To keep the delta value "current" we'll also need the hello:again tag to respond to a regular timer signal (perhaps via setInterval) to keep the delta updating between activations.

Let's start by tracking hello:now's rendering timestamp in a shareable location.

The best place to do that is in a TIBET URN.

hello:now - Data Sharing

A TIBET URN is essentially a named data container. Once created you can access the container by name from anywhere in your code (or templates).

URNs must use urn: as their initial string prefix. That value is then followed by what the URN standard refers to as the Namespace ID (NID) and Namespace-specific string (NSS).

urn:{NID}:{NSS}

TIBET doesn't enforce global NID uniqueness or persistence; you can use whatever works best for your application.

By convention we tend to use tibet for lib URNs, app for generic app URNs, and your application's namespace (hello in this case) for custom app URNs.

We'll use a NID of hello for this example so our URNs will be prefixed with urn:hello: and then a specific name for our data item.

Initial Render

We want to store the initial rendering timestamp so update the hello:now tag's getExpandedSource type method to contain the following:

APP.hello.now.Type.defineMethod('getExpandedSource',
function(tpElement, aRequest) {
    const timestamp = new Date().toString();
    TP.uc('urn:hello:now-timestamp', timestamp);

    return timestamp;
});

The TP.uc call, a shortcut to TP.uri.URI.construct, gives us a handle to the URN instance identified by urn:hello:now-timeout.

If a URN doesn't exist yet it's created. If it does exist we get a handle to the unique instance, allowing us to share it across the entire application.

The second parameter to the TP.uc call can optionally provide an initial value. For the initial creation we use that to initialize the URN's value.

Activations

In addition to the initial render, we want to track click/keyup refreshes.

We can do that by updating the hello:now tag's UIActivate instance signal handler to update the URN value as well.

Note that in the following code we use the URN's setResource method to ensure our update triggers TIBET's built-in change notification machinery.

APP.hello.now.Inst.defineHandler('UIActivate',
function(aSignal) {
    const timestamp = new Date().toString();

    const tag = TP.wrap(aSignal.getTarget());
    tag.set('value', timestamp);

    const urn = TP.uc('urn:hello:now-timestamp');
    urn.setResource(timestamp);
});


URNs are automatically managed and uniqued by the TIBET runtime so we don't need to store the URN in a variable, constant, or other location.

With those two updates we've completed the work of saving the last update timestamp and ensuring any observers will be notified when it changes.

Our next task is to update the hello:again tag to render and respond to changes to our URN's value.

hello:again - Part 2

Tag Rendering

Our hello:again tag is currently displaying a default link when rendered.

Let's update it to render the value of our new URN, making it reflect the value it finds when it initially renders.

Edit ~app_tags/APP.hello.again/APP.hello.again.js to include:

APP.hello.again.Type.defineMethod('getExpandedSource',
async function(tpElement, aRequest) {
    const result = await TP.uc('urn:hello:now-timestamp').getResource();
    return result.getValue();
});

Note that we await the result of invoking getResource since URIs can acquire their value from a variety of potentially async sources.

Once we have the result we ask for its value via getValue() and return.


Saving our changes above should result in the following display:

Hello Again - With Timestamp
hello:again - timestamped

With that change we've got our hello:again tag rendering the URN value. But it will only do it once since it's not yet instrumented to respond to changes.

If you click on hello:now you'll see that the timestamp for hello:now changes but our hello:again tag doesn't update.

Our next step is to help it stay current with any changes to hello:now.

Change Notification

The majority of TIBET objects include the machinery necessary to signal that an attribute (aspect) has changed via their set()-related methods.

That includes TIBET URIs, URLs, and URNs which is why we used setResource to set the resource (aka value) of our URN.

The thing is, while our URN is signaling changes each time we activate hello:now, there's currently nobody listening.

Let's fix that by updating our hello:again implementation to observe our URN.

We'll do this once, on initial app startup, in our type's initialize method.

Edit ~app_tags/APP.hello.again/APP.hello.again.js and add the following:

APP.hello.again.defineMethod('initialize',
function() {
    this.observe(TP.uc('urn:hello:now-timestamp'), 'ValueChange');
});


TIBET's signaling system lets any object receive signals from any other object by invoking the observe() method.

Using the type's initialize method means we only invoke observe() once, avoiding the possibility of duplicate observations.

Note that we authored initialize above as a type local method, meaning that there's no .Type or .Inst qualifier after the type name. The method is placed on the individual type and won't be inherited by subtypes.

Signal Handling

You might have noticed our observe call didn't define a handler function.

TIBET doesn't restrict handlers to functions, they can also be objects.

The observe call defaults to making the invoking object the handler.

When a signal is dispatched any non-function object registered as a handler is checked for the best possible handler method to use via reflection.

Since we invoked observe from the APP.hello.again type the type itself is the handler and it will be checked for methods targeting ValueChange.

As it turns out, all TP.tag.CustomTag subtypes implement a Type-level handler for ValueChange that automatically updates any rendered instances.

Running The Initializer

Since we added/altered an initialize method and those are only invoked once on application startup we have two options for getting our observation to go live:

  • reload the application (but reloads lose context)
  • invoke it directly via the DevTools console or Lama

To do the latter enter the following snippet in the DevTools console:

APP.hello.again.initialize();

Once the initializer has run and the observe has executed clicking the hello:now tag should automatically update the display of our hello:again tag.

Hello Again - Mirrored
hello:again - sync'd timestamps

Delta Time

Our final task(s) relate to the display of our hello:again delta time.

Display

We really haven't been producing a delta time so far, we've been just mirroring the timestamp value. Let's fix that.

Edit ~app_tags/APP.hello.again/APP.hello.again.js to contain:

APP.hello.again.Type.defineMethod('getExpandedSource',
async function(tpElement, aRequest) {
    const result = await TP.uc('urn:hello:now-timestamp').getResource();
    const last = new Date(result.getValue()).getTime();

    return `${Date.now() - last}`;
});


Now our display should resemble the following:

Hello Again - Delta
hello:again - delta time

Clicking hello:now repeatedly will update the delta display depending on varying latencies, but it still doesn't update automatically.

Update

Our final step is to trigger a refresh of the delta time on a regular basis.

We can do the first half by triggering an update signal on a timer.

Since we only need a single, consistent heartbeat signal, we can add that portion of the logic to our previous initialize method:

APP.hello.again.defineMethod('initialize',
function() {
    const urn = 'urn:hello:now-timestamp';

    this.observe(TP.uc(urn), 'ValueChange');
    this.observe(urn, 'Heartbeat');

    setInterval(() => { TP.signal(urn, 'Heartbeat') }, 1000);
});

TIBET signaling is extremely flexible, letting you signal using any string or object as an origin and letting you handle those signals from any object or function.

Here we've updated our initialize to observe the string (not URN) urn:hello:now-timestamp for Heartbeat signals in addition to the previous ValueChange observation.

We've also configured an interval to signal Heartbeat from any origin matching the URN string roughly every second.

As before, we need to manually invoke any initialize method we add or alter from the DevTools console (or Lama):

APP.hello.again.initialize();

Our Heartbeat signal is now running but there's no real handler for it yet.

Let's add a simple event handler that will refresh all instances of our tag.

Since we invoked observe from the type add this type method:

APP.hello.again.Type.defineHandler('Heartbeat',
function(aSignal) {
    this.refreshInstances();
});

With this final change the delta display should automatically begin updating every second or so.

Clicking the hello:now tag will reset the display to something under a second and it'll go back to counting up from there.

Summary

Summary

In this third installment of TIBET Essentials we:

  • worked with a TIBET URN to share state across components
  • implemented a type initialize method for one-time setup
  • created signal handler logic for responding to value changes
  • created a signal handler for a generic heartbeat signal



We hope you've enjoyed the TIBET Essentials series so far.

If there are additional topics you'd like to see let us know!

Enjoy your TIBET journey!

The Pup