Markup

TIBET Content Types

wins

  • Automatic wrapping of raw data in intelligent content containers.
  • Minimize getter/setter code with attribute mapping of data queries.
  • Create specific types to manage complex source data with simple OO.
  • Configuration-driven mapping of new content types to URI patterns.

concepts

Content types in TIBET represent intelligent types which give you a way to easily encapsulate your application data and DOM elements and supply them with real functionality.

For example, any element in a visual or non-visual DOM can be managed by writing an intelligent object that's aware of the tree structure of the element but hides it behind a set of smart getters and setters. This encapsulation approach is particularly well-suited to building widgets whose internal markup structures should be hidden, but whose "features" should be exposed in an easy-to-invoke fashion.

Similarly, imagine a block of application JSON with its own internal nesting and data types. You can access that JSON directly but that opens your application up to potential data corruption as well as long-term maintenance issues. If you need to change the structure or data types in the JSON but have paths hardcoded throughout your code that's a problem. Encapsulating those paths via an object avoids those problems.

TIBET's content types fall into two primary camps, those that handle XML/DOM nodes and those that handle non-Node data, rooted at TP.dom.Node and TP.core.Content respectively.

TP.dom.Node and friends

TP.dom.Node is the root of all custom tags in TIBET and has subtypes for each of the 9 node types found in the DOM specification. Subtypes of TP.dom.ElementNode ultimately provide the core functionality of TIBET's tag system. All custom tag types in the TIBET library and your applications descend from these root types.

You don't have to know about the overwhelming majority of node types since TIBET automatically returns the best-fit type if you're using TIBET URIs or APIs. You simply rely on get() and set() to manage your data along with TIBET Paths and TIBET does the rest.

We'll reference the UICANVAS concept below. Because TIBET runs in a multi-window (i.e. multiple frame, really) environment, we never (never, never) access the standard window and document global variables directly (really! never!). Instead, we have a notion of the 'current user interface canvas' (a window). This is the window that your GUI is being drawn into while the code doing the actual manipulation is running inside of the main code frame in TIBET. UICANVAS is an internal variable tracked by TIBET to use in user interface DOM queries.

The URI access below will return an instance of APP.hello.app if you run it within the hello project used by TIBET Quickstart et. al.:

//  Get the '#app' element in the UICANVAS:
tag = TP.uc('#app').getContent();
tag.getTypeName(); // <-- APP.hello.app in a 'hello' project

TIBET URIs are a very powerful mechanism to retrieve a variety of data in TIBET. Here, we see how they can retrieve DOM nodes. Read more about them here in the TIBET URIs documentation.

The getContent() call of a TIBET URI, when retrieving a DOM node, will 'auto wrap' that node in another object of a specific TIBET type. Alternatively, the TP.wrap and TP.unwrap methods can be used to manually wrap and unwrap raw DOM content. They are typically invoked on your behalf but can be invoked directly. If you have a raw DOM node you can get the best-fit type for it via TP.wrap:

//  Returns the '#document' node of the UICANVAS
rawDOMDoc = TP.sys.uidoc(true);

rawDOMNode = rawDOMDoc.getElementById('app');
tibetNode = TP.wrap(rawDOMnode);

Although it's a significant feature in its own right, the real value of TIBET's node types goes beyond simply wrapping a DOM node to encapsulate data access.

TIBET's node types, particularly the element types, allow you to treat markup like a form of macro source code, telling TIBET how you want to render that markup, how you want to process it as a set of "instructions" for the TIBET shell, how it responds to common events, etc.

You can create new tags using the TIBET CLI or the TIBET Lama.

In the Lama you'd use either the dispenser or halo to create your tag (see the Lama documentation).

In the CLI you'd use the tibet type command.

For example, we could create a new templated tag using tibet type as follows (provided we're a project named hello):

tibet type hello:world --dna templatedtag

Then, when the <hello:world id="hi"/> tag is rendered in the application, this code will retrieve it. This code uses an alternate way of accessing the DOM, via an explicit reference to the UICANVAS:

//  Note that by passing true here, we can make this call 'auto collapse' the
//  Array it would normally return to a single value.
helloTag = TP.byCSSPath('#hi', TP.sys.getUICanvas(), true);
helloTag.getTypeName(); //  <-- APP.hello.world in a 'hello:world' project

See documentation on tibet type for more information on how to create new types (and tag types) using the TIBET CLI.

The TP.dom.Node type and its subtypes provide a rich hierarchy of functionality to manipulate one of the core objects in authoring applications. One core capability of node type is inherited from TIBET's object system: the ability to define a 'mapped aspect' to internal data within the node.

Assume we edited the APP.hello.world.xhtml template to contain:

<hello:world type="worldly">
    <header>This is an XHTML5 header</header>
    <div class="body">This is body content</div>
    <footer>This is an XHTML5 footer</footer>
</hello:world>

Now we can add a simple aspect mapping for the header content in the APP.hello.world.js file:

//  Add one or more attribute path definitions...
//  TP.cpc() is shorthand for 'CSS Path Construct'
APP.hello.world.Inst.defineAttribute('header', TP.cpc('> header'));
APP.hello.world.Inst.defineAttribute('body', TP.cpc('> div.body'));
APP.hello.world.Inst.defineAttribute('footer', TP.cpc('> footer'));

Then, we can access the tag's body content like this:

//  Get the '#app' element in the UICANVAS:
tag = TP.uc('#app').getContent();
tag.get('body');

This allows you to abstract DOM structure for custom elements behind simple get()/set() semantics and is very useful to manage maintenance around markup structure as an application evolves during its lifetime.

TP.core.Content et. al.

TP.core.Content has a number of custom subtypes to support adding intelligence to what would otherwise typically be simple String content.

Perhaps the most commonly used content subtype is TP.core.JSONContent which is used to automatically wrap JSON data when no specific type has been given.

A simple example is fetching the tibet.json file for the project:

//  Fetch tibet.json. The "content" will be a TP.core.JSONContent object.
content = TP.uc('~/tibet.json').getContent();

Using TIBET's powerful OO system you can create custom JSON content types to manage your specific application data formats and requirements. See Create A New TIBET Content Type below for more information.

Like with TIBET dom types, the primary value in custom JSON types is that you can assign specific access paths via defineAttribute so that your get() and set() calls work as expected. Thanks to get() and set() the underlying JSON data structure can change without having to alter anything but your defineAttribute path definitions (you can also add regular methods as needed).

See the TIBET Paths documentation for more on TIBET paths.

Let's say we want to create a special subtype of TP.core.JSONContent that knows how to handle the JSON content from the tibet.json project file that comes with every TIBET project:

{
    ...,
    "project": {
        "name": "helloworld"
    }
    ...,
}

As an example, we can create a subtype of TP.core.JSONContent for the tibet.json file using the TIBET CLI's tibet type command as follows:

$ tibet type ProjectJSON --supertype TP.core.JSONContent
working in: /Users/ss/temporary/hello/public/_ProjectJSON_
processing directories...
processing templates...
templating complete...
positioning files...
positioning complete...
adjusting package entries...
<script src="~app_src/types/APP.hello.ProjectJSON.js"/> (added)
<script src="~app_src/types/APP.hello.ProjectJSON_test.js"/> (added)
New configuration entries created. Review/Rebuild as needed.
Cleaning up working directory.
Type DNA 'default' cloned to ~app_src/types as 'ProjectJSON'.

When the tibet type command is finished our application has a new type, a new test file, and is ready to load the type automatically as part of our application's standard build.

We can also immediately test our new type via tibet test:

$ tibet test APP.hello.ProjectJSON
# Loading TIBET platform at 2019-07-20T19:54:00.823Z
# TIBET reflection suite loaded and active in 6895ms
# Running Type tests for APP.hello.ProjectJSON
# TIBET starting test run
# 1 suite(s) found.
1..1
#
# tibet test APP.hello.ProjectJSON.Type --suite='APP.hello.ProjectJSON suite'
ok - Is a TP.core.JSONContent type.
# 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.ProjectJSON
# TIBET starting test run
0..0
# PASS: 0 pass, 0 fail, 0 error, 0 skip, 0 todo.
# Running Local tests for APP.hello.ProjectJSON
# TIBET starting test run
0..0
# PASS: 0 pass, 0 fail, 0 error, 0 skip, 0 todo.

Just like DOM node types, we can add an attribute mapping for the project name by editing the source file ~app/public/src/types/APP.hello.ProjectJSON.js:

//  Below this line defining the type itself...
TP.core.JSONContent.defineSubtype('APP.hello.ProjectJSON');

//  Add one or more attribute path definitions...
//  TP.jpc() is shorthand for 'JSON Path Construct'
APP.hello.ProjectJSON.Inst.defineAttribute('projectName', TP.jpc('$.project.name'));

By associating the TP.jpc('$.project.name) path with the projectName attribute, any time get() or set() is used with projectName, that path is executed and the data is set or get into the JSON structure:

request = TP.hc('contenttype', APP.hello.ProjectJSON);  //  specify content type
content = TP.uc('~/tibet.json').getContent(request);    //  fetch content
content.get('projectName');                             //  query by attribute name

hello

With the definition above we can now access tibet.json and get a more intelligent type back, one we can leverage in other code but one which fully encapsulates the actual structure of the data we're accessing.

You can also define a default content type for a URI instance so you don't need to rely on the request-based approach for each retrieval invocation. By using this mechanism, any time the URI is accessed after the defaultContentType attribute is set, an object of that type will be constructed:

url = TP.uc('~/tibet.json');
url.set('defaultContentType', APP.hello.ProjectJSON);
...
content = url.getContent();     //  fetch content
content.get('projectName');
...
moreContent = url.getContent(); //  fetch content
moreContent.get('projectName');

Or… you can use TIBET's configuration system to define your desired URI contentTypes (along with other URI values) in a URI map:

"uri": {
    "map": {
        "tibet_json": {
            "pattern": "~/tibet.json",
            "contenttype": "APP.hello.ProjectJSON"
        }
    }
}

Note that the "file name" level (tibet_json) isn't used for matching, just for your own lookups, but it shouldn't include periods to avoid confusing the logic that does configuration lookups. We substitute underscore (_) here instead. Again, this is just to provide a key into the map, but is not used for lookups.

See the TIBET URIs documentation for more on URI maps.

cookbook

Get A TIBET Node Wrapper

To get a TIBET wrapper for a DOM element use a TIBET URI or TP.wrap.

Any access to a DOM node via TIBET URI will return you a TIBET node instance rather than a raw DOM node:

//  Access a DOM element using a TIBET URI...it will return a TIBET node:
tag = TP.uc('#app').getContent();

If you have a raw DOM node use TP.wrap to get the best wrapper instance:

tibetNode = TP.wrap(rawDOMnode);

Get A Native Node From A TIBET Node

To unwrap a TIBET node (get it's underlying node) you can use TP.unwrap or getNativeNode():

rawNode = TP.unwrap(tibetNode);

//  or

rawNode = tibetNode.getNativeNode();

Get A TIBET Content Wrapper

To get a TIBET content wrapper use a TIBET URI or TP.core.Content.construct.

If you access content via a TIBET URI you will automatically be provided with a TP.core.Content subtype instance of some form. If the content is a JSON string you'll receive a TP.core.JSONContent instance:

url = TP.uc('~/tibet.json');
content = url.getContent();

If you want a specific subtype to wrap your content you can define that a number of ways includign putting it in the request object you pass to getContent, defining it as the defaultContentType for the URI instance, or using a URI map entry:

request = TP.hc('contenttype', APP.hello.ProjectJSON);  //  define content type
content = TP.uc('~/tibet.json').getContent(request);    //  fetch content

OR:

url = TP.uc('~/tibet.json');
url.set('defaultContentType', APP.hello.ProjectJSON);
...
content = url.getContent();    //  fetch content
content.get('projectName');

OR

"uri": {
    "map": {
        "tibet_json": {
            "pattern": "~/tibet.json",
            "contenttype": "APP.hello.ProjectJSON"
        }
    }
}

Get Raw Content From TIBET Content

TIBET Content objects manage their content in the form of a data attribute you can access using get('data'):

dat = contentObj.get('data');

In the case of a JSONContent object the data is the parsed form of the JSON. If you want the string form use asString:

str = contentObj.asString();

Create A New TIBET Node Type

There are four common tag types you can quickly and easily subtype by using the tibet type command line tool:

tibet type hello:world --dna templatedtag
tibet type hello:world --dna computedtag
tibet type hello:world --dna actiontag
tibet type hello:world --dna infotag

If you need a pure element wrapper you can also choose to use tibet type with a simple supertype specification:

tibet type hello:world --supertype TP.dom.UIElementNode

Note that in this latter case your type will not be placed in a "tag bundle". It will be considered a standard type and placed in ~app_src/types.

Create A New TIBET Content Type

To create a new content type you can use tibet type with a simple supertype specification pointing to the content supertype you require:

tibet type ProjectJSON --supertype TP.core.JSONContent

The above syntax will create a standard type and place it in ~app_src/types.

code

Node-related functionality is found in ~lib/src/tibet/kernel/TIBETDOMtypes.js. Additional types are found in ~lib/src/tibet/kernel/TIBETUIDOMTypes.js.

There are also dozens of DOM-related primitive functions found in:

  • ~lib/src/tibet/kernel/TIBETDOMPrimitivesBase.js
  • ~lib/src/tibet/kernel/TIBETDOMPrimitivesPlatform.js
  • ~lib/src/tibet/kernel/TIBETDOMPrimitivesPost.js
  • ~lib/src/tibet/kernel/TIBETDOMPrimitivesPre.js

Non-node content types are defined in ~lib/src/tibet/kernel/TIBETContentTypes.js. There are numerous subtypes of TP.core.Content in other locations in the library.

Non-node content primitives are found in TIBETContentPrimitives.js.