UX Base Tags
NOTE: we're migrating the
xctrls
namespace to a newux
namespace.The
xctrls
namespace is DEPRECATED and UNSUPPORTED.Stay tuned for significant updates as we complete our
ux
migration.
Wins
- Powerful tag composition model based on markup-first authoring.
- Easy low-code integration of data binding, keyboard handling, and more.
- Custom controls are easy to create using inheritance and OO concepts.
- Fully-integrated GUI Driver supporting component integration testing.
Contents
Concepts
Cookbook
Code
Concepts
One of the things we've seen consistently during our 25-year tenure in the web is a tendency to focus on a platform's widget kit as a key measure of project fit.
Ironically, off-the-shelf widgets rarely determine project success.
All too often development teams discover the widgets they planned on using don't support the keyboard correctly, can't be localized, can't be integrated with their data without conversion, or don't include some key feature the application requires.
While we like to think widgets are "universal" the reality is due to branding, locale, UX, and other business requirements, widgets often aren't as reusable as we like to believe.
TIBET's implementation took all of this into consideration.
Instead of building widgets in the traditional fashion where data sources, keyboard handling, event handling, etc. were tightly wired into the widget, we turned that model inside out and made it possible to define much of a widget's operation through external attributes.
TIBET "deconstructs" the typical concept of a widget and lets you create new tags out of markup, attributes, and simple OO extensions to powerful UI base classes.
Orthagonal namespace'd attributes provide much of TIBET's UX power.
TIBET's bind
namespace provides data binding, the ev
and on
namespaces
integrate event-handling, the tibet
namespace lets you direct events to common
types or controllers, and more.
Using ux:
Tags
To use a ux
tag just include it in your application pages. Most tags are
authored such that they render correctly without the need for code.
Note that you don't need to fuss with namespaces. TIBET automatically ensures
the ux
namespace is available within your application and all of TIBET's
orthagonal functionality for bind
, on
, etc. works exactly as you'd expect.
Most app functionality can be encapsulated in reusable custom tags you augment
using tibet
, bind
, on
, ev
, and other attributes to provide data and
adjust how signals flow to and from the tag.
Coding is limited to event handlers you define on either reusable controllers or other tags/types in TIBET's signal responder chain. What code is written stays organized.
Tag Construction
Let's take a quick look at how a simple tag, app:button
, might be built.
We'll take a look at a few of the common things all tags have to consider: their base type, what signals they manage internally, and how they interact with their child elements.
Base Type
The first thing in creating any tag is deciding on a proper supertype.
A number of TIBET's tags use an xhtml
template to provide UI framing rather
than relying on JavaScript. This allows the rendered aspects of the tag to be
checked for well-formedness and validated against XML Schema if desired.
Our app:button
is simple to handle via markup, so we inherit from TIBET's
TP.ux.TemplatedTag
, an ux
-specific subtype of TIBET's common
TP.tag.TemplatedTag
type that mixes in ux
behavior as a set of traits.
We can create our new tag with a simple command line:
tibet tag app:button --supertype=TP.ux.TemplatedTag
(As an aside, the complexity and cross-cutting behavior of UI hierarchies makes them particularly well-suited to leveraging traits as a form of multiple inheritance. TIBET has exceptional support for this approach.)
If you look through the ux
namespace you'll see most tags inherit from a
common supertype you'll come to recognize pretty quickly (a computed or
templated supertype of some kind) which mixes in ux
traits.
For example, TP.ux.TemplatedTag
is constructed as follows:
TP.tag.TemplatedTag.defineSubtype('TP.ux.TemplatedTag');
TP.ux.TemplatedTag.addTraitTypes(TP.ux.Element);
This pattern shows up in most extension namespaces, inheritance from a typical base type and then mixing in of traits specific to the target namespace.
Tag Templating
As mentioned earlier, our app:button
will render based on an XHTML template.
In our case we want the button to bring over any child nodes it might wrap when authored so our template resembles:
<app:button id="{{$SOURCE.(@name)}}" class="{{$SOURCE.(@class)}}">
{{$SOURCE.(./node())}}
</app:button>
As we can see, the button's template is fairly direct.
Our button retains its XML form as app:button
(it doesn't become button
), it
pulls in any id
or class
attribute from the originally authored DOM
($SOURCE
), and it pulls in any child elements.
Some of the root properties available to a tag template include:
'APP', APP,
'TP', TP,
'$FOCUS', focusFunc,
'$REQUEST', aRequest,
'$SELECTION', selectionFunc,
'$SOURCE', TP.wrap(source),
'$TAG', TP.wrap(parentNode),
'$TARGET', aRequest.at('target'),
'$*', selectionFunc, // Alias for $SELECTION
'$@', focusFunc // Alias for $FOCUS
When dealing with repeated content a few additional properties are defined:
'$_', wrappedVal,
'$INPUT', repeatSource,
'$INDEX', index,
'$FIRST', index === 1,
'$MIDDLE', index > 1 && index < last,
'$LAST', index !== last,
'$EVEN', index % 2 === 0,
'$ODD', index % 2 !== 0,
'$#', index);
Opaque Signals
With our base type defined we next want to tell TIBET which events, signals actually, should be captured and/or bubbled at the widget level.
One of the most important characteristics of a widget is how it creates an opaque view of what's below it in terms of specific DOM.
When you work with a table widget, for example, you want the events to come from
the table, not from a random text node in a random td
.
For our app:button
we want signals related to being active and enabled
to be managed by the button so we list those as opaqueBubblingSignalNames
.
APP.app.button.Type.defineAttribute('opaqueBubblingSignalNames',
TP.ac('TP.sig.UIActivate', 'TP.sig.UIDeactivate',
'TP.sig.UIDisabled', 'TP.sig.UIEnabled'));
As you might expect, there's also a opaqueCapturingSignalNames
property.
Access Paths
When you are dealing with markup it can be helpful to have an easy way to access elements using named queries (what we call access paths) rather than hard-coded DOM lookups that might cause maintenance issues.
TIBET provides an easy way to define an attribute name, like body
in the
example below, and assign it to a query path.
Below we map body
to a CSS query path and tell it we want to collapse the
query result into a single node:
TP.ux.dialog.Inst.defineAttribute('body',
TP.cpc('> *[tibet|pelem="body"]',
TP.hc('shouldCollapse', true)));
With the above definition in place the tag's code can invoke get('body')
and
the result will be the desired element.
If we need to change the markup implementation we change only the path and
template, none of the code that uses the body
element has to change.
See TIBET Paths for more on access paths and queries.
Keyboard Sequences
TIBET UX tags can also take advantage of another TIBET feature that lets you quickly define what keyboard shortcuts apply to a tag and what signals those keyboard actions should trigger.
The file TP.xctrls.dialog.keybindings.js
contains the following line:
TP.xctrls.dialog.registerKeybinding('DOM_Esc_Up', 'TP.sig.DialogCancel');
The line above tells TIBET to register DOM_Esc_Up
such that any time that key
is pressed while the dialog is active it should signal TP.sig.DialogCancel
.
The xctrls:dialog
responds to signals of that type by hiding the dialog. So
with a simple line of code we get keyboard-independent event registration for
key handing.
All you need to do to register keyboard sequences is to create a keybindings
file using the same naming convention and place your registrations in it, then
make sure the tag's manifest loads that file after it loads the tag.
Tag "Bundles"
When you create a custom tag in TIBET the result is a directory that typically contains 4-5 files: the tag source code, the tag test code, the tag template, the tag style sheet, and a tag "manifest".
The manifest for a TIBET tag is an XML file with TIBET-specific tags that define all the components that make up that tag, when they should load, what order should be used, whether there are specific configuration changes between things like development and production, etc.
For our dialog/keybinding example here's the scripts
section:
<config id="scripts">
<!-- tag-related source file references here -->
<script src="TP.xctrls.dialog.js"/>
<script src="TP.xctrls.dialog.keybindings.js"/>
</config>
As you can imagine, this approach lets you arrange your source code, bring in separate helper scripts, etc. in any way that makes sense. TIBET will automatically roll up and package the content of this block when you build.
Cookbook
Look for "manpages" for TIBET's built-in UX tags to appear shortly. In the meantime see the various references in the 'code' section for sample usage in TIBET's test and Lama codebases.
Code
The xctrls
codebase resides largely in ~lib/src/xctrls
. Top-level types are
kept in that directory. Individual xctrls
widgets are kept in TIBET
'bundle' subdirectories.
There are dozens of Lama widgets in ~lib/src/tibet/tools/lama
. The Lama
codebase also provides you with example code on how to create your own
namespaced widget/tag set.
The early TIBET Lama prototype makes extensive use of
xctrls
components.
The "deconstructed" Lama is being assembled from our growing ux
tag set. Look
for a number of examples of how to leverage ux
widgets as that work
progresses.