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.
Contents
Concepts
Cookbook
- Get A TIBET Node Wrapper
- Get A Native Node From A TIBET Node
- Get A TIBET Content Wrapper
- Get Raw Content From TIBET Content
- Create A New TIBET Node Type
- Create A New TIBET Content Type
Code
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
contentType
s (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
.