Cookie Consent by FreePrivacyPolicy.com TIBET
TIBET Logo

TIBET

Essentials

TIBET Essentials - Part 2


Welcome to Part 2 of our TIBET Essentials series.

This tutorial builds on the TIBET Quickstart and TIBET Essentials - Part 1 guides.

Start with those. We'll be here when you get back :).


In this guide we'll focus on:

  • 'computed' tags, tags which render via JavaScript logic
  • adding behavior to tags using TIBET's defineHandler method


Here we go…

Preliminaries

Before we dive into things, make sure your hello project is running.

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

cd ${project_home}
tibet start
...


If you haven't changed anything since completing the previous guide you should see something like this:

Project Home Page
A Whole Lotta Hello

With our project back up and running it's time to tackle the next step.

Precision

Computed Tags

A computed tag is a tag whose rendered markup is produced by JavaScript rather than an external template file.

Computed tags provide for more powerful logic-driven rendering that a pure template can provide, yet you can still consume them using 100% markup.

hello:now

To create a simple example of a computed tag let's build another custom tag, this time one that outputs the current date/time when it renders. We'll call this tag <hello:now/>.

NOTE: We could do this task using substitution syntax in a templated tag but for purposes of this tutorial we'll create a computed tag instead.

Create a new computed tag by using --dna computedtag with the tibet type command:

$ tibet type hello:now --dna computedtag

In response to this command TIBET will generate a tag type, stylesheet, and test file, live patching them into our package and application.

Note that no XHTML template will be created when using computedtag dna.

Let's add our new tag to the <hello:app/> template, adjusting it so we get both <hello:world/> and <hello:now/> output in our home page.

Edit the APP.hello.app.xhtml template to say hello and tell the time.

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

~app_tags/APP.hello.app/APP.hello.app.xhtml

Save your template with the new hello:now tag and you should see:

hello:now first try
<hello:now/> version 0

What just happened?

We created a new computed tag, added it to our app tag template, and we get a link identifying the tag location in our UI.

That link is an indication our new type is working as expected. What we're seeing is the default implementation of a computed tag's rendering logic, inherited from TIBET's TP.tag.CustomTag supertype.

Click that <hello:now/> link and you'll see an alert similar to:

hello:now link alert
Hello Now alert (Electron)

Computed tags running outside of the Lama™ will alert() you to update the tag's getExpandedSource or getExpandedNode method.

When the Lama is active you're taken directly to an in-situ code editor.

As the link alert suggests, let's refine the getExpandedSource method next.

getExpandedSource

TIBET's tag processor provides a number of entry points for generating your tag UI.

The tagExpand is the method used by TIBET's tag processor and it defers to getExpandedNode to get a renderable DOM Node. That method defers to getExpandedSource to get a parsable DOM String.

You can theoretically override any one of these methods but it's best to stick to getExpandedSource for the majority of operations.

The tibet type command's DNA for a computed tag includes a stub for getExpandedSource in the tag's source file to make editing it easier.

Open ~app_tags/APP.hello.now/APP.hello.now.js, our hello:now type's source:

//  ========================================================================
/**
 * @copyright Copyright (C) 2022, the AUTHORS. All Rights Reserved.
 */
//  ========================================================================

/**
 * @type {APP.hello.now}
 * @summary TP.ux.ComputedTag subtype which...
 */

//  ------------------------------------------------------------------------

TP.ux.ComputedTag.defineSubtype('APP.hello:now');

//  ------------------------------------------------------------------------
//  Type Methods
//  ------------------------------------------------------------------------

APP.hello.now.Type.defineMethod('getExpandedSource',
function(tpElement, aRequest) {

    /**
     * @method getExpandedSource
     * @summary Creates and returns a string or pair representing the string
     *     expansion of the tpElement parameter provided. This is the core
     *     method most subtypes will override to do their expansion work.
     * @param {TP.dom.Element} tpElement A wrapped instance to be expanded.
     * @param {TP.sig.Request} aRequest A request containing processing
     *     parameters and other data.
     * @returns {String|Array} The string or ordered pair result from expansion.
     *     When using an ordered pair return the new string and a processing
     *     hint (TP.UPDATE, TP.REPLACE).
     */

    return this.callNextMethod();
});

//  ------------------------------------------------------------------------
//  Instance Methods
//  ------------------------------------------------------------------------

//  ------------------------------------------------------------------------
//  end
//  ========================================================================

APP.hello.now.js

Since we're just getting warmed up with TIBET code let's take it a line at a time. It's a heavily-commented file with only 5 lines of executable code.

Line 1

TP.ux.ComputedTag.defineSubtype('APP.hello:now');

Much like our earlier look at the hello:app source file, the first line in many TIBET files will be a line that invokes defineSubtype to create a new type.

As mentioned elsewhere, TP is the global root for all TIBET types and variables. APP is the global root for all your application types and code.

The ux namespace is TIBET's default namespace for UX tags such as our TemplatedTag and ComputedTag supertypes.

In this case we create a new TP.ux.ComputedTag subtype named 'APP.hello.now'.

Line 2

APP.hello.now.Type.defineMethod('getExpandedSource',

We begin the actual implementation by defining a method on our new hello:now type.

The first parameter to defineMethod is always the method name.

TIBET methods are always defined using the defineMethod method, one of a set of meta-methods TIBET uses to help manage Type definition. Using so-called method methods is a best practice as described in JavaScript: The Good Parts

In this case we want our getExpandedSource method to be a type method so we add a .Type qualifier to target the type.

If we wanted an instance method we'd add .Inst to target the instance prototype.

If we wanted a 'local' method, a method unique to a single object, we leave off the 'Type' or 'Inst' qualifier and invoke defineMethod directly on the targeted object.

Line 3

function(tpElement, aRequest) {

With defineMethod's second parameter we're providing the method body, the function that will do the work of transforming the tag.

Most common tag processing methods take the current tag instance (tpElement) and current processing request (aRequest), as shown above.

NOTE: Do not use arrow functions for method bodies. Arrow functions have unique binding behavior that is incompatible with object-oriented binding of TIBET methods. Arrow functions are best reserved for iterators in TIBET.

Line 4

return this.callNextMethod();

Now things get a little more interesting.

A common requirement in OO is the ability to invoke the supertype version of a method. In this case we're asking our stub to do just that, to call 'the next method' up the chain without requiring hard-coded references or duplicating the argument list.

Since we subtyped TP.ux.ComputedTag and this is a .Type method, that type will be checked for getExpandedSource and the search will continue up through the type hierarchy until an implementation is found.

If you're used to C++, Java, or other JS libraries that statement should give you pause.

JavaScript, like many of today's popular OO languages, doesn't have true type-level inheritance, it has 'static' methods and attributes.

Static methods and attributes are local to a type and are not inherited by subtypes, limiting the patterns of logic flow and code reuse.

TIBET's OO infrastructure is modeled on Smalltalk, with a dash of Traits thrown in to support composition in a predictable, controllable fashion.

TIBET's callNextMethod logic first looks to the immediate type or instance, then works up the inheritance/trait chain to find the proper method regardless of the nature of the receiver.

Line 5

});

Close method body, close defineMethod parameter list, end statement.

Done.


With our review of the stub implementation complete our next task is to create a real one.

getExpandedSource v2

In the version of our getExpandedSource method below we've replaced our stub's callNextMethod() logic with a sample implementation.

NOTE: there's no strict constraint on what the getExpandedSource method does, only that it return a string which can produce a valid XML Node (node, not element…you can create text nodes, comment nodes, whatever you like).

Edit your version of APP.hello.now.js to include this implementation:

APP.hello.now.Type.defineMethod('getExpandedSource',
function(tpElement, aRequest) {
    return new Date().toString();
});

APP.hello.now.js

Save these changes and our screen should now display:

hello:now implementation
<hello:now/>

Success!…

…but our date string is a little too close to that left edge.

Let's refine our look a little more with some CSS.

Styling hello:now

In our previous guide we showed how to style a templated tag whose authored XML had been transformed into XHTML (an h1 in particular).

Our hello:now tag doesn't transform however, it stays in its original namespace-qualified XML format as <hello:now>...</hello:now with a child text node.

So how do we adjust its style?

By using the standard CSS syntax for XML.

Edit the APP.hello.now.css file so it contains:

/**
 * @overview 'APP.hello.now' styles.
 */

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

hello|now {
    margin-left: 2em;
}

Note the vertical bar (|) where the colon (:) would be in a tag (or attribute) name.

CSS manages namespaced content by replacing : with | for element and attribute names.

It's critical you include an @namespace entry for any namespace you refer to… otherwise you'll be wondering why your style isn't being applied :).

With our style applied we now see the following:

hello:now implementation
<hello:now/>

Testing hello:now

Let's run our tests again. This time let's just test our new tag…

Change to an available terminal and enter tibet test APP.hello.now:

$ tibet test APP.hello.now
# Loading TIBET platform at 2019-11-09T17:43:17.668Z
# TIBET reflection suite loaded and active in 5219ms
# Running Type tests for APP.hello.now
# TIBET starting test run
# 1 suite(s) found
1..1
#
# tibet test APP.hello.now.Type --suite='APP.hello:now suite'
ok - Is a TP.ux.ComputedTag tag.
# pass: 1 total, 1 pass, 0 fail, 0 error, 0 skip, 0 todo, 0 only.
#
# PASS: 1 total, 1 pass, 0 fail, 0 error, 0 skip, 0 todo, 0 only.
# Running Inst tests for APP.hello.now
# TIBET starting test run
0..0
# PASS: 0 pass, 0 fail, 0 error, 0 skip, 0 todo.
# Running Local tests for APP.hello.now
# TIBET starting test run
0..0
# PASS: 0 pass, 0 fail, 0 error, 0 skip, 0 todo.

# Finished in 4270 ms w/TSH exec time of 204 ms.


All good. We just tested a specific type. But how?

Like virtually all of TIBET, TIBET's test harness isn't page-based, it's object-based.

The tests TIBET generates when we create a new type are associated with that type.

Running tibet test {typename} tells TIBET to load our application and use reflection to run any tests it finds.

By associating tests with objects you can keep your testing focused, improve cycle times, and support smarter forms of code coverage analysis.

Recap

Using the tibet type command (with a --dna computedtag option) we've created a new tag that renders based on a JavaScript method on the tag type.

Our current implementation simply outputs the current Date.now() value but complex logic is easy to support using this approach.

With the look in place, it's time to add behavior/feel.

Tag Behavior

Tag Behavior

When we think of behavior we're typically thinking about how an object responds to events.

A key feature of TIBET is its signaling subsystem, infrastructure which unifies how events work across browsers (and across the main and renderer processes in Electron).

In TIBET you work with Signal instances for DOM events, exceptions, state changes, and any other event you wish to create.

For this tutorial we want our tag to update its date/time display any time we activate (click or keyup) it.

We do that by defining a signal handler.

defineHandler

To define a signal handler we use another of TIBET's meta-methods, the defineHandler method.

As with defineMethod, the first parameter to defineHandler is a name (the signal name to be handled) and the second parameter is a function (the handler body).

Because we want to add behavior to hello:now tags we will define our event handler in the JavaScript source file specific to that tag. This helps keep functionality organized.

NOTE: TIBET does not require all functionality for a type to be defined in a single file. You are free to alter or extend features of a tag in separate files.

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

APP.hello.now.Inst.defineHandler('UIActivate', function(aSignal) {
    alert('UIActivate');
});

APP.hello.now.js

In the code above we define a signal handler for UIActivate signals received by instances of hello:now tags (thanks to the .Inst qualifier).

Recall that type methods use .Type, instance methods use .Inst, and local methods use no qualifier. In all cases the targeted object receives the method.

If you're wondering what UIActivate is, it's a TIBET signal which generalizes click and keyup.

UIActivate is one of a number of 'UI Signals' TIBET provides to enhance accessibility and testability across browsers and input devices.

All hello:now tags will automatically respond to UIActivate simply by virtue of our defining a handler for that signal in our tag implementation.

Save your changes, then click on the text of the hello:now tag in your UI:

UIActivate
hello:now UIActivate stub

With this simple action we've confirmed our event handler is operational.

Notice that we didn't reload, redraw, or refresh.

TIBET hot-patched our tag implementation with the new functionality and all previously-rendered instances of the tag automatically get the new behavior.

Signal Data

We still need to make our tag update its content in response to activation. We do that by leveraging data in the Signal instance provided to the signal handler.

Edit ~app_tags/APP.hello.now/APP.hello.now.js again, updating the handler to match:

APP.hello.now.Inst.defineHandler('UIActivate', function(aSignal) {
    const tag = TP.wrap(aSignal.getTarget());
    tag.set('value', new Date().toString());
});

APP.hello.now.js

In the code above we access the signal's target via getTarget. This is the low-level element node that received the initial event, much as we'd expect from a normal DOM event handler.

The TP.wrap call wraps that low-level element in the best-fit TIBET type, in this case an instance of our APP.hello.now type, granting access to the tag's instance methods.

The final line leverages TIBET's 'getter/setter' syntax via set(), ultimately triggering a call to setValue on our tag.

Note that this method relies on polymorphism in that setting the value of different tag types has consistent semantics but unique results.

For an input field, set('value') would set its .value, property but because our target here is an inline, non-form element, setValue instead sets its child content.

Save the new handler definition, then click on the date in the hello:now tag.

The hello:now tag should display the current time each time we click.

It's that easy.

Recap

In this section we used the defineHandler method to add a signal handler to instances of our hello:now tag. Our new signal handler updates the tag's time display whenever it receives a UIActivate signal, expanding the value of our reusable component.

As with our previous efforts we didn't have to reload the page to activate this behavior, we simply edited the source file and saved our changes. TIBET did the rest.

Of special interest is that clicking on a previously rendered tag "just worked". There was no need to set up/tear down listeners or do other boilerplate coding for events.

Summary

Summary

This guide built upon the TIBET Quickstart and TIBET Essentials - Part 1 guides.

First we added a new custom hello:now tag which leverages JavaScript to render the current date and time.

Next we expanded hello:now functionality by adding a custom signal handler for refreshing the date/time display in response to UIActivate signals.

We accomplished all that without add/remove listener overhead, reducing boilerplate and the potential for memory leaks, duplicate, or dangling listener registrations.

Continue on to TIBET Essentials - Part 3 to explore sharing state across components.