Essentials

TIBET Essentials

Welcome to the second tutorial in our "Hello World!" project series.

In this second tutorial we'll add a new custom tag to display the current date and time, then add an event handler to update that time whenever we click the tag. Finally we'll add a tag that displays data via a simple data binding.

There's a lot more to TIBET but this tutorial hits the high points of TIBET's markup-first focus.

This tutorial builds on the TIBET Quickstart Guide so if you haven't worked through that tutorial do that first. It's ok...we'll be here when you get back ;).

The concrete steps we'll go through in this tutorial are summarized in the tl;dr section below. You can do the tl;dr version, or follow along with full explanations by skipping ahead to the 'Step By Step' section.

Here we go…

tl;dr

Start the development server for your project:

$ tibet start
...

Load http://127.0.0.1:1407#?boot.profile=development in Chrome.

Create a tag for date/time data using tibet type with --dna computedtag:

$ tibet type hello:now --dna computedtag

Edit ~app_tags/APP.hello.now/APP.hello.now.js so it returns a new element containing the current date/time value when asked to transform itself:

APP.hello.now.Type.defineMethod('tagCompile',
function(aRequest) {
    var elem;

    //  Get the initiating element.
    if (!TP.isElement(elem = aRequest.at('node'))) {
        return;
    }

    //  Set its content to the date.
    TP.nodeSetContent(elem, (new Date()).toString());

    //  Return the element.
    return elem;
});

Also ensure APP.hello.now.js includes an event handler for TIBET's UIActivate signal. Add the handler using defineHandler as shown here:

APP.hello.now.Inst.defineHandler('UIActivate', function(aSignal) {
    var target,
        tag;

    target = aSignal.getTarget();
    tag = TP.wrap(target);

    tag.set('value', new Date().toString());
});

Edit the ~app_tags/APP.hello.app/APP.hello.app.xhtml template to hold an instance of both our hello:world and new hello:now tags:

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

Once you've made the previous changes your screen should show both Hello World! and the current date/time as it existed when the page rendered:

hello:now implementation
<hello:now/>

Because of the event handler created for UIActivate, clicking the date/time should automatically refresh the displayed data.

With the first phase in place let's wrap things up with a data binding example…

Create a new hello:data tag to provide a sandbox for data binding:

$ tibet type hello:data --dna templatedtag

Update the tag's template file (APP.hello.data.xhtml) to contain the following:

<div tibet:tag="hello:data"
    bind:io="http://127.0.0.1:1407/tibet.json#jpath($.project.name)">
</div>

Finally, edit APP.hello.app.xhtml to contain a hello:data tag:

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

Once you've saved these changes you should see all three tags rendering:

Simple JSON Bind
simple JSON bind

That's a lightning-fast intro to TIBET computed tags, event handling, and data binding. Obviously there's a lot more to each of those subjects but hopefully even this quick introduction shows you how powerful TIBET is and how TIBET lets you focus on tags and types with minimal coding.

For a full discussion of each of the previous steps read on…

Step By Step…

Before we dive deeply into our next steps make sure your hello project is running.

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

cd ${project_home}
tibet start

                                  ,`
                            __,~//`
   ,///,_            .~////////'`
  '///////,       //////''`
         '//,   ///'`
            '/_/'
              `
    ////////////////////     ///////////////////  ////
    `//'````````````///      `//'```````````````  '''
     /`              //       /'
    /                //      '/
   ,/____             /'    ,/_____
  /////////;;,,_      //   ,//////////;,_
              `'/,_   '/              `'///,_
                 `'/,_ /                   '//,
                    '/,/,                    '/_
                      `/,                     `/,
                        '                      `/
                                               '/
                                                /

1504056886785 [7] TDS TIBET Data Server v5.0.0-pre.11 (development)
...
...
...
1504056888242 [7] TDS hello 0.1.0 @ http://127.0.0.1:1407 (production build) (build)
1504056888242 [7] TDS hello 0.1.0 @ http://127.0.0.1:1407#?boot.profile=development (dev)


Next, load http://127.0.0.1:1407#?boot.profile=development in Chrome.

If you haven't changed anything from the Quickstart Guide your should see something like:

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

When you need more precision in rendering your tags than you can get with templating, TIBET offers another approach to tag authoring using what we call 'computed tags'.

A 'computed' tag in TIBET is a tag whose rendered markup is produced by JavaScript rather than an external template file. There's no "compiler" per se.

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/>.

We could do this task using handlebars-style 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 and associated test file, hot patching them into our application. No template file will be created since this is a computed tag.

To keep things simple 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 once 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 tagCompile method.

Click that <hello:now/> link:

hello:now link alert
Hello Now alert()

Newly created computed tags running outside of the Sherpa™ will alert() you to update the tag's tagCompile method, a simple way of helping you to work iteratively from definition through refinement. (In the Sherpa you'd be taken directly to that method for editing).

As the link alert suggests, let's refine the hello:now tagCompile method next.

tagCompile

When TIBET processes tags it invokes a number of methods on the types which support those tags. The tagCompile method is perhaps the most commonly used of those since it's the method responsible for converting the authored form of the tag into a renderable form.

We'll start by taking a look at the tagCompile stub generated by tibet tag in the source file ~app_tags/APP.hello.now/APP.hello.now.js.

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

/**
 * @type {APP.hello.now}
 * @summary A computed tag which...
 */

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

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

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

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

    /**
     * @method tagCompile
     * @synopsis Convert instances of the tag into their XHTML form.
     * @param {TP.sig.Request} aRequest A request containing the tag element
     *     to convert along with other optional processing parameters.
     * @returns {Element|Array<Element>} The element(s) to replace the inbound
     *     element with in the final DOM.
     */

    return this.callNextMethod();
});

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

APP.hello.now.js

Since this is our first deep look at TIBET code let's take it a line at a time:

Line 1

TP.core.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 on a supertype, providing the name of a new subtype. In this case we create a new TP.core.ComputedTag subtype.

Line 2

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

We begin the actual implementation by defining a method on our new hello:now type to handle tagCompile processing. Note the first parameter is the method name we're defining.

TIBET methods are always defined using the defineMethod method, one of a set of meta-methods TIBET uses to help manage Type definition.

Using method methods is a best practice mentioned in JavaScript: The Good Parts

In this case we want our tagCompile method to be a type method so we add a .Type qualifier to our tag name to target the type. If we wanted an instance method we'd add .Inst. 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 instance.

Line 3

function(aRequest) {

This one's simple enough. With defineMethod's second parameter we're providing the method body, the function that will do the work of transforming the tag. Virtually all tag processing methods take a single parameter, a TIBET Request object, shown here.

Line 4

return this.callNextMethod();

Now things get a little more interesting.

TIBET is unabashedly Object-Oriented. As a result we often need 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 inheritance/lookup chain without requiring hard-coded references or duplicating the argument list (TIBET handles that automatically).

Since we subtyped TP.core.ComputedTag that type will be checked for tagCompile 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.

Many popular OO languages don't have true type inheritance, they have 'static methods' which are local to a type and which don't support interitance. That's true for TypeScript, ECMA6/7, and most JavaScript OO implementations.

TIBET's OO infrastructure is modeled on Smalltalk, with a dash of Traits thrown in to support composition in a predictable, controllable fashion. TIBET lets types inherit methods and attributes, just like instances, a feature which enables TIBET's tag system to be more efficient.

But we digress….

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.

tagCompile v2

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

Note that there's no constraint on what the `tagCompile` method does, only that it return whatever element(s) form the replacement content.

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

APP.hello.now.Type.defineMethod('tagCompile',
function(aRequest) {
    var elem;

    //  Get the initiating element.
    if (!TP.isElement(elem = aRequest.at('node'))) {
        return;
    }

    //  Set its content to the date.
    TP.nodeSetContent(elem, (new Date()).toString());

    //  Return the element.
    return elem;
});

APP.hello.now.js

Let's look at this chunk by chunk.

Get the initiating element

The first step is to access the node in the document representing our authored tag. The request parameter's at method can give us that by requesting the node property. We verify we get a valid element from the request using the TP.isElement() primitive:

// Get the initiating element.
if (!TP.isElement(elem = aRequest.at('node'))) {
    return;
}

Set the content of the element

Next we set the content of the element to be the String representation of a new Date:

// Set its content to the date.
TP.nodeSetContent(elem, (new Date()).toString());

Return the element to be rendered.

Finally, we return the node (typically an Element or DocumentFragment) to be rendered in place of the original node. In this case we return the original element since we don't need a replacement node, we just want to update the current element's content in place.

//  Return the element.
return elem;

Save these changes and our screen should now display:

hello:now implementation
<hello:now/>

Success!

Let's test! This time however, 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.core.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. In TIBET all tests are associated with an object. The tests TIBET generates when we create a new type are associated with that type. When we run tibet test {typename} TIBET loads and uses 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.

NOTE that for this tutorial we're using command-line testing. You can also trigger tests directly from the Sherpa or run your tests in `karma` via the karma-tibet package, a fully-supported part of the TIBET ecosystem. TIBET tests can span everything from units to UI.

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, the tagCompile method.

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, the next step is to add behavior ("feel") to our tag.

Tag Behavior

Tag Behavior

When we think of behavior in JavaScript terms we're typically thinking about how an object responds to events. Event-driven development is central to web development, particularly client-side web development.

A key feature of TIBET is its signaling subsystem, a set of components which unify how events work across browsers while also supporting non-native events. In TIBET you work with Signal instances for DOM events, Exceptions, state changes, etc.

For this tutorial our goal is to add reusable behavior to our hello:now tag so it will update its date/time display any time we activate (click) it. We do that via signal handling.

defineHandler

To define a signal handler we use the defineHandler method, a variant of TIBET's defineMethod method that's specific to signal handlers. As with defineMethod the first parameter is the handler name and the second parameter is the handler body (function).

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 that 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 on 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 and is one of a number of 'UI Signals' TIBET provides to enhance accessibility and testability across browsers and input devices.

Note that we don't need to set up or tear down observations or do any other boilerplate coding. 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…and notice that we didn't reload…TIBET hot-patched our tag implementation with the new functionality and all rendered instances of the tag automatically updated.

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) {
    var target,
        tag;

    target = aSignal.getTarget();
    tag = TP.wrap(target);

    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 node that received the initial event, much as we'd expect from a normal DOM event handler.

The next line 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 may cause different behavior. For an input field, this would set its .value, property but because our target here is an inline, non-form element, setValue instead sets its content (innerHTML in this case).

Save the new handler definition and 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 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 and 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.

Data Binding

Data Binding

The final topic we want to cover in this Essentials guide is data binding.

Getting the right data in the right place is one of the most important tasks for most enterprise applications. To help make that job easier TIBET offers a powerful data binding system you can use directly from markup with simple, standardized URI and query path syntax.

hello:data

Before we get into the specifics we need a nice place to put our data. Let's create a new custom tag so we keep things localized in terms of our edits.

Create a hello:data tag by using the tibet type --dna templated command:

$ tibet type hello:data --dna templatedtag

With a placeholder tag ready for our data we want to make sure we include one of our hello:data tags in the overall hello:app tag so it renders with our application's main page.

Edit ~app_tags/APP.hello.app/APP.hello.app.xhtml to contain:

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

Our new hello:data tag should render as a placeholder link but we can fix that right up :).

hello:data placeholder
<hello:data/> placeholder

bind attributes

To add a simple data binding let's update our hello:data tag's template to render a div with a single bind:io attribute to describe the data we want to render.

Edit ~app_tags/APP.hello.data/APP.hello.data.xhtml template to contain:

<div tibet:tag="hello:data"
    bind:io="http://127.0.0.1:1407/tibet.json#jpath($.project.name)">
</div>

Once you save this change you should see a screen similar to the following:

Simple JSON Bind
<hello:data/>

We added a single attribute and we've got data displaying from a JSON file. But how?

TIBET's bind namespace has a number of specific attributes: scope, repeat, in, out, and io being the prominent ones. The bind:io attribute we used in our sample above lets us define a two-way binding (io vs. in vs. out define different bind directions).

In our example we defined a source URI pointing to our project's tibet.json file qualified with an XPointer of jpath($.project.name). Basically we gave it the same URI we would give a browser to access the data and told it to run the JSONPath once it had the data.

All tags in TIBET default to invoking set('value', data) once they have the result data from a bind. Our data tag's div, which is ultimatly backed by the html:div type, ends up setting its content to the query result. Each tag type responds polymorphically so binds do the right thing.

bind syntax

All TIBET binds ultimately use a uri[#query] format where the query portion is optional.

When it processes a bind TIBET will always use the URI portion to find the root data, then run any optional query you provide to slice into that data source for the final bind result.

In TIBET the TP.uri.URI type can be used to access any remote data. You can also name any data in your application using a standard URN of the form urn:tibet:some_name. This latter feature allows you to bind to anything you desire while using standard URI syntax.

For queries TIBET lets you use a number of standard and de-facto standard query formats. You can use XPath 1.0 for any XML, JSONPath for any JSON, and for object data you can use a query format we refer to as "TIBET paths" (similar to Python paths).

As implied by our mention of XPath, TIBET can bind to XML as easily as it binds to JSON or your client-side object data. There's no need to convert XML data just so you can bind to it.

bind scopes

While all binds ultimately resolve to a uri[#query] format they can be authored in "chunks" to provide scoping to different parts of your application.

In the simplest case we can split the bind data source and query apart. For example, we could also have written our data tag using bind:scope and bind:io as in:

<div tibet:tag="hello:data" bind:scope="http://127.0.0.1:1407/tibet.json">
    <span bind:io="jpath($.project.name)"/>
</div>

The bind:scope attribute can also be nested using segments of paths. For example:

<div tibet:tag="hello:data" bind:scope="http://127.0.0.1:1407/tibet.json">
    <div bind:scope="jpath($.tibet)">
        <span bind:io="dna"/>
    </div>
</div>

TIBET works hard to optimize scoped data access so binds are fast and efficient.

There's a whole lot more to binds but hopefully this has given you a quick look at just how easy TIBET makes it to manage data in your UI using industry-standard URI and query syntax.

Summary

Summary

This "essentials" guide built upon the foundation created in the TIBET Quickstart Guide.

To kick things off we added a new custom hello:now tag which leverages a JavaScript method to render the current date and time.

Next we expanded our hello:now tag's functionality by adding behavior to all hello:now tags so they update their current date/time display whenever they receive a UIActivate signal. In particular, we did this without any requirement that they add/remove listeners, helping reduce boilerplate and the potential for memory leaks, duplicate, or dangling listener registrations.

We moved on to create a simple hello:data tag that displays information from the project's package.json file using TIBET data binding syntax.

TIBET has a lot more to offer obviously but hopefully this further exploration of a simple hello application has helped reinforce one of the core things that makes TIBET special, namely a development process focused on tags, not code.

We invite you to continue to explore TIBET by checking out the entire TIBET platform, particularly the TIBET Sherpa, our revolutionary web development IDE.