Forms & Fields
Wins
- MVC data validation using XMLSchema, JSONSchema, or TIBET types.
- Advanced group-level validation including nested group validations.
- Automatic data generation from empty forms using TIBET data binding.
- Fast, flexible, group and field navigation via keyboard shortcuts.
- Drag-and-drop form generation from raw JSON files in the Lama IDE.
Contents
Concepts
Cookbook
reference
Code
Concepts
Introduction
TIBET has powerful support for complex business forms thanks to markup-based data binding, powerful display/storage formatters, schema-driven data validation, advanced keyboard navigation, field group features, and unique support for relevant, readonly, and required states.
TIBET's development has focused on business form requirements since our early days building out support for the full XForms specification. You can still see that influence in our support for XMLSchema, XMLEvents, XPath, XPointer, and numerous other XML standards that let you bind, query, and validate XML directly.
With TIBET there's no need for converting existing XML services or data feeds to JSON, you can use these sources directly and efficiently. (You can also use them directly in your UI).
Of course, TIBET also provides full support for JSON data via TIBET URIs, TIBET Content Types, and TIBET Data Binding. With data binding in particular you can leverage JSON Schema and JSON Path so you can bind, query, and validate JSON data without custom code.
If you're using JSON data you can also drag-and-drop a raw JSON file into your UI in the TIBET Lama and TIBET will generate a baseline form automatically. This form-generation capability is still early-stage but is a focal point for future enhancements in the Lama.
Schema-based XML and JSON validation lets you use a unified approach to data validation on both sides of the wire. It also decouples your client and server teams by providing a "contract" you can use to validate your REST APIs and your client HTTP calls independently.
From an end-user perspective, TIBET's keyboard mapping support, particularly support for keyboard shortcuts and navigation keys, was built so users whose work relies heavily on the keyboard can literally run without a mouse, maximizing data throughput.
Ultimately TIBET's form-related functionality is focused on throughput in a business context, helping you build the back-office and front-office applications that get the most work done with the least amount of lost effort or custom code.
Data Formatting
One of the things that comes up pretty quickly when building MVC-based forms is that there are, in reality, at least three different formats that you need to consider for any piece of data.
Imagine a REST-backed system where a Date needs to be supplied in a very
particular format, what TIBET calls the "storage format". At the same time the
client-side "model" will want it as ms
or ISO8601
for unambiguous parsing by
the Date type. Then there's the user-visible format in your UI, what TIBET calls
the "display format". One piece of data; three formats.
Display Formatters
A display formatter is a formatter which takes model data and reformats it for user-visible display. A common example might be taking model data and displaying it in all uppercase, or ensuring dates or phone numbers are always formatted a particular way.
In TIBET you define a display formatter using the ui:display
attribute:
<span id="span" ui:display="@{@@@-@@@@}"/>
<div id="div" ui:display="#{##.00}"/>
<input type="text" id="input_text" id="input_text"
ui:display="startUpper"/>
<textarea id="textarea" id="textarea" ui:display="YYYY"/>
<select id="select_multiple" id="select_multiple" multiple="multiple" ui:display="TP.test.ColorDisplayConverter">
Display formatters, like other formatters, ultimately rely on the as
method in
TIBET. In the prior examples each element's value
is acquired and formatted
with the results of invoking the as
method on that value, providing the
content of ui:display
to that method:
displayValue = value.as('@{@@@-@@@@}');
displayValue = value.as('#{##.00}');
displayValue = value.as('startUpper');
displayValue = value.as('YYYY');
displayValue = value.as('TP.test.ColorDisplayConverter');
The as
method is one of TIBET's "best method" methods, along with from
.
If you're not familiar with TIBET's getBestMethod
feature it's a way of using
type information and reflection to determine the best method, e.g. the most
specific method to use for handing a particular request. One key advantage of
the getBestMethod
approach is its ability to adapt to new types without having
to alter library code, an important consideration.
When you invoke as
on an object TIBET takes the parameter and looks for the
best variant of as
it can invoke. If no match is found the parameter object's
from
method is invoked instead.
Here's a simple example of how the type name variant works:
stringVal.as('TP.test.ColorDisplayConverter');
The code above will check String for an asTP_test_ColorDisplayConverter
instance method. If that method isn't found on String then the
TP.test.ColorDisplayConverter
type's from
method will be invoked, ultimately
invoking fromString(stringVal)
on that type.
Storage Formatters
A storage formatter is a data parser/formatter that takes input and formats it before it is typically passed to a "setter" on a model. This approach lets you reuse models with a variety of input controls without concern for the model's internal data format.
In TIBET you define a storage formatter using the ui:storage
attribute:
<input type="text" id="input_text" id="input_text" ui:storage="startUpper"/>
<textarea id="textarea" id="textarea" ui:storage="YYYY" ui:type="Date"/>
<select id="select_multiple" id="select_multiple" multiple="multiple"
ui:storage="TP.test.ColorStorageConverter">
<option id="select_multiple_1" value="red">Red</option>
<option id="select_multiple_2" value="green">Green</option>
<option id="select_multiple_3" value="blue">Blue</option>
</select>
As mentioned earlier, the ui:display
and ui:storage
values are ultimately
used in creation of calls to TIBET's as
routine. When as
doesn't find a good
match it flips the call around and invokes from
on the target object.
To add new formatters, validators, or other extensions you can implement the
proper from{type}
methods and TIBET will automatically invoke them when it
processes as/from
method chains.
For example, our ui:storage="TP.test.ColorStorageConverter"
reference above
will take the string input of 'red', 'green', or 'blue' and convert it, in this
case to an instance of TP.gui.Color
matching the correct string thanks to the
fromString
method we implement below:
TP.test.ColorStorageConverter.Type.defineMethod('fromString',
function(aValue, params) {
switch (aValue) {
case 'red':
return TP.cc('red');
case 'green':
return TP.cc('green');
case 'blue':
return TP.cc('blue');
default:
break;
}
});
To enhance reuse you can also provide a ui:type
attribute. When this attribute
is present the value of the control is first converted to that type, then run
through the ui:storage
formatter.
Data Validation
TIBET takes a very object-oriented and MVC-centric view of how to manage data.
In TIBET your validation requirements are defined as type names related to attributes at the object level. You don't assign data types to UI controls, you assign them to attributes. This approach creates a reusable set of validations on the models, a much more MVC-style approach.
Of special note is that you can create types whose underlying validation checks are based on any combination of types you like. In other words, you can leverage XML Schema, JSON Schema, and native TIBET types in the same type. There's no requirement that your validation types originate from a single source.
As part of its validation processing TIBET will automatically signal
TP.sig.UIValid
and/or TP.sig.UIInvalid
to notify any observers that a
field's validation state has changed.
Type Validation
You can use any object in TIBET as a model in your MVC design pattern. There's no specific "model supertype" in TIBET. For example, if we want to create a new type that focuses on validation of U.S. Social Security Numbers we might do the following:
TP.lang.Object.defineSubtype('test.SSN');
TP.test.SSN.Type.defineMethod('validate',
function(anObject) {
var testRegExp,
str;
testRegExp = /^\d{3}[- ]?\d{2}[- ]?\d{4}$/;
str = TP.str(anObject);
return testRegExp.test(str);
});
With a type in place which implements a validate
method we can now tell any
other object in TIBET that we want to use that data type for one or more
attribute values:
TP.lang.Object.defineSubtype('test.SimpleTestType');
TP.test.SimpleTestType.Inst.defineAttribute('ssn', null,
{valid: {dataType: 'TP.test.SSN'}}
);
In the code above we define an ssn
attribute with a default value of null
and an attribute descriptor that includes a valid
slot. The value for the
valid
slot of a descriptor is always an object whose keys can include things
like dataType
, minLength
, maxLength
, minValue
, maxValue
, etc. See the
list of attribute constraints for more.
Schema Validation
Data validation using schemas is essentially identical to using any other TIBET
type. You can use the loadSchemaFrom
method of both the TP.xs.schema
type
and the TP.json.JSONSchema
type to load one or more schema documents. TIBET
will read these schemas and create matching types within TIBET to manage the
validations.
For XML Schema you load and process schema documents into types using:
TP.xs.schema.loadSchemaFrom(TP.uc('~lib_schema/tibet_common_types.xsd'));
Here's a sample portion of that XSD file:
<xs:simpleType id="tibet:password">
<xs:restriction base="xs:string">
<xs:pattern value="^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%\^*])(?=.{8,})"/>
</xs:restriction>
</xs:simpleType>
The resulting type can then be applied to an attribute via the name
TP.tibet.password
:
TP.lang.Object.defineSubtype('test.PasswordValidator');
TP.test.PasswordValidator.Inst.defineAttribute('password', null,
{valid: {dataType: 'TP.tibet.password'}}
);
You can load JSON schema in a similar fashion:
TP.json.JSONSchema.loadSchemaFrom(TP.uc('~lib_schema/tibet_common_types.json'));
Assuming the following schema (ignoring the … for snipped content):
{
"$schema": "http://json-schema.org/draft-04/schema#",
"definitions": {
...
"gender": {
"name": "TP.tibet.gender",
"type": "string",
"enum": ["male", "female", "fluid", "withheld"]
}
}
}
TIBET will create the TP.tibet.gender
type with the associated enumeration
from this schema. As with other types, you can use this type in validations by
defining an attribute such as:
TP.lang.Object.defineSubtype('test.Person');
TP.test.Person.Inst.defineAttribute('gender', null,
{valid: {dataType: 'TP.tibet.gender'}}
);
Markup-Defined Validation
One of the more interesting features of TIBET is the concept of 'action' and 'info' tags, tags without a particular UI, but whose presence in a UI can offer a lot of power.
Specific examples of 'info tags' in TIBET that have applicability to forms are
the tibet:data
, tibet:type
and tibet:aspect
tags.
For example, ~lib/test/tibet/forms/Validation1.xhtml
includes a tibet:data
tag:
<tibet:data id="Validation1Data"
id="urn:tibet:Validation1_person"
contentType="TP.test.BaseMarkupEmployee">
<person xmlns="">
...snipped...
</person>
</tibet:data>
The above tag provides data to the UI while also defining the type to use
(via contentType
) to validate any data sent to that tag for storage.
In addition to defining data you can define new types using the tibet:type
tag. The ~lib/test/src/tibet/forms/Validation2.xhtml
file contains the
following:
<tibet:type id="TP.test.Validation2Data" baseType="TP.core.JSONContent">
...
<tibet:aspect id="Date" value="$.dateval" dataType="Date"/>
<tibet:aspect id="Email" value="$.emailval" dataType="TP.xforms.email"/>
<tibet:aspect id="Identifier" value="$.identifierval"
dataType="TP.tibet.identifier"/>
<tibet:aspect id="IPV4Addr" value="$.ipaddr4val"
dataType="TP.tibet.ipv4_address"/>
...
</tibet:type>
In the sample above we define a new subtype of TP.core.JSONContent
named
TP.test.Validation2Data
with 4 aspects (attributes) and their associated JSON
paths and data types. We can assign this type to be used to validate other data
in the page.
Note that it's not necessary to provide individual fields in a tibet:data
tag.
You can also use makestructures="true"
to tell TIBET you want to use what
XForms refers to as a "lazy author" approach. Any XPaths run to fetch or set
data in the tag will automatically force their implied structure to be built
within the XML content area of the tibet:data
tag:
<tibet:data id="Validation2Data" id="urn:tibet:Validation2_data" makestructures="true" contentType="TP.test.Validation2Data"/>
Also note that we use contentType="TP.test.Validation2Data"
above, telling
this empty tibet:data
tag to use our previously markup-defined data type to do
any validation.
Field States
In typical forms-based applications there are at least three field states other than validity which need to be addressed, namely: relevant, readonly, and required. These states are managed by application logic but need to also be reflected in the user interface such that a user can quickly determine what they need to do to properly complete a form.
relevant
The concept of relevant
has to do with whether a particular field needs to be
addressed based on other information. Irrelevant fields can be disabled so the
user skips them.
A simple example would be the combination of a U.S. Citizen
checkbox and an
SSN
field. If the U.S. Citizen
value is true
then SSN
becomes relevant,
otherwise it's not.
Here's some sample markup from the TIBET test suite:
<label for="uscitizenField">U.S. Citizen?: </label>
<input type="checkbox" id="uscitizenCheckbox"
bind:io="{checked: urn:tibet:Validation3_person#tibet(uscitizen)}"/><br/>
<label for="SSNField">SSN: </label>
<input type="text" id="SSNField"
bind:io="{value: urn:tibet:Validation3_person#tibet(SSN)}"/><br/>
The underlying logic at the model level, which is actually wrapping an XML representation of an employee record in our test suite, is as follows:
TP.core.XMLContent.defineSubtype('test.BaseMarkupEmployee');
TP.test.BaseMarkupEmployee.Inst.defineAttribute('uscitizen',
TP.xpc('boolean(./person/uscitizen/text())'),
{valid: {dataType: Boolean}}
);
TP.test.BaseMarkupEmployee.Inst.defineAttribute('ssn',
TP.xpc('string(./person/SSN/text())'),
{
relevant: TP.xpc('boolean(./person/uscitizen/text())'),
valid: {dataType: 'TP.test.SSN'}
}
);
Note that we define the uscitizen
field as defaulting to the result of running
the XPath boolean(./person/uscitizen/text())
and the value for ssn
as the
result of the XPath string(./person/SSN/text())
. We default the value to
data in the XML.
Also note we set whether ssn
is relevant
based on the result of running
another XPath, this time boolean(./person/uscitizen/text())
. It's important to
note that this path queries the same XML document, just a different aspect. The
XML itself is ultimately the model.
Whenever the relevant
state of an attribute changes TIBET will automatically
signal TP.sig.UIEnabled
or TP.sig.UIDisabled
to notify any observers that
the field should be enabled (it's now relevant) or disabled (it's now
irrelevant).
TIBET will also adjust the markup in response to relevancy changes, adding or
removing the special attribute pclass:disabled
on related controls.
readonly
You can manage readonly through binding by using bind:in
rather than
bind:io
. The bind:in
attribute sets up bindings from the model to the view
but not in the other direction. This ensures the model can't be altered by
changes to the control value…however you should only use this attribute with
things that don't really allow for alteration of their value in the UI.
You can also control readonly state through TIBET's readonlywhen
attribute.
Setting this to any path or query which resolves to a boolean will automatically
control whether TIBET adds or removes the pclass:readonly
attribute.
Whether a field or control is readonly can directly affect how it renders.
TIBET manages a pclass:readonly
attribute on all DOM elements. (Note we don't
use readonly
to avoid action by the browser which is often difficult or
impossible to alter).
The pseudo-class (pclass) prefix tells you it's a TIBET-managed property you shouldn't manipulate yourself; however, you can create style that reacts to its presence or absence.
In addition to managing the pclass:readonly
attribute TIBET will also signal
when an underlying object's readonly state changes. In particular the TIBET
signals TP.sig.UIReadonly
and TP.sig.UIReadwrite
are signaled whenever the
readonly state changes.
required
The required
state, like relevant
is managed at the attribute descriptor
level. You can define the required
property of an attribute descriptor as a
fixed boolean or query for the value using an XPath, JSONPath, TIBET path, or by
evaluating a function.
TP.test.SimpleTestType.Inst.defineAttribute('ssn',
null,
{valid: {dataType: 'TP.test.SSN'}, required: true}
);
As with other field states, TIBET manages a pseudo-class pclass:required
which
it adds or removes based on changes to a control's underlying required state.
Also, when required
state changes TIBET signals TP.sig.UIRequired
or
TP.sig.UIOptional
depending on the status.
See the tests and samples listed in the code section for more.
Field Grouping
Any form of even basic complexity can benefit from the use of grouping. In TIBET
we have a special element, tibet:group
, which provides advanced functionality
related to groups of controls, particularly for forms applications.
There are two particularly useful features of groups in TIBET: group-level validation and group-level navigation.
Group Validation
The file ~lib/test/src/tibet/forms/Validation3.xhtml
includes the following:
<tibet:group id="EmployeeGroup" validwhen="all">
<label for="GenderField">Gender: </label>
<input type="text" id="GenderField" bind:io="{value: urn:tibet:Validation3_person#tibet(gender)}" tabindex="0"/><br/>
<label for="uscitizenField">U.S. Citizen?: </label>
<input type="checkbox" id="uscitizenCheckbox" bind:io="{checked: urn:tibet:Validation3_person#tibet(uscitizen)}" tabindex="0"/><br/>
<label for="SSNField">SSN: </label>
<input type="text" id="SSNField" bind:io="{value: urn:tibet:Validation3_person#tibet(SSN)}" tabindex="0"/><br/>
</tibet:group>
Of particular interest in the above sample is the validwhen
attribute which
can take several different values including all
and any
. When all
is set
the group will be considered valid only when all contained controls are valid.
Likewise any
means any valid control in the group makes the group valid.
The tibet:group
element is like any other TIBET tag so all aspects of
relevant, readonly, and required behavior apply and are treated in the same way
by TIBET with respect to pclass and signaling behavior.
Also note that tibet:group
elements can be nested and that validation checks
and other aspects work for nested groups as well. See
~lib/test/src/tibet/forms/Validation4.xhtml
for samples.
Group Navigation
Group navigation takes advantage of TIBET's support for keyboard mapping and keyboard shortcuts as outlined in the TIBET Devices documentation.
There are a lot of specific key bindings that are part of TIBET's default
behavior with respect to forms. See the files in
~lib/test/src/tibet/focusing/
directory for specifics.
Cookbook
See the documentation on Data Binding and TIBET Devices for specifics on how to manage binding and keyboard processing in your forms-based applications.
We'll be adding samples augmenting those in the concepts section in the near future. In the meantime, see the tests and samples listed in the code section.
reference
Attribute Constraints
dataType JS/TIBET type object, String resolved to JS/TIBET type object
enumeration Array, comma-separated String, AccessPath
equal Object that can be compared, AccessPath
fractionDigits Number or object that can be 'asNumber'ed, AccessPath
length Number or object that can be 'asNumber'ed, AccessPath
maxExclusive Number or object that can be 'asNumber'ed, AccessPath
maxInclusive Number or object that can be 'asNumber'ed, AccessPath
maxLength Number or object that can be 'asNumber'ed, AccessPath
maxValue Number or object that can be 'asNumber'ed, AccessPath
minExclusive Number or object that can be 'asNumber'ed, AccessPath
minInclusive Number or object that can be 'asNumber'ed, AccessPath
minLength Number or object that can be 'asNumber'ed, AccessPath
minValue Number or object that can be 'asNumber'ed, AccessPath
notEqual Object that can be compared, AccessPath
pattern RegExp, AccessPath
totalDigits Number or object that can be 'asNumber'ed, AccessPath
XML Schema Built-Ins
TP.xs.ENTITIES
TP.xs.ENTITY
TP.xs.IDREF
TP.xs.IDREFS
TP.xs.NCName
TP.xs.NMTOKEN
TP.xs.NMTOKENS
TP.xs.NOTATION
TP.xs.QName
TP.xs.anyURI
TP.xs.base64Binary
TP.xs.boolean
TP.xs.byte
TP.xs.date
TP.xs.dateTime
TP.xs.decimal
TP.xs.double
TP.xs.duration
TP.xs.float
TP.xs.gDay
TP.xs.gMonth
TP.xs.gMonthDay
TP.xs.gYear
TP.xs.gYearMonth
TP.xs.hexBinary
TP.xs.int
TP.xs.integer
TP.xs.language
TP.xs.long
TP.xs.negativeInteger
TP.xs.nonNegativeInteger
TP.xs.nonPositiveInteger
TP.xs.normalizedString
TP.xs.positiveInteger
TP.xs.short
TP.xs.string
TP.xs.time
TP.xs.token
TP.xs.unsignedByte
TP.xs.unsignedInt
TP.xs.unsignedLong
TP.xs.unsignedShort
TP.xs.whiteSpace
Code
The "forms code" in TIBET is really a combination of functionality from a wide variety of sources including TIBET's node types, data binding code, XML and JSON schema support code, TIBET's keyboard mapping functionality, etc.
Data Binding
See TIBET Data Binding.
XML Schema
See ~lib/src/xs
:
JSON Schema
See ~lib/src/tibet/json
for the base JSONSchema type and content type.
Validation Tests / Samples
See ~lib/test/src/tibet/forms
. The TP.lang.Object_Tests.js
contains the
test code which loads the various sample pages and runs the tests.
Sample markup authoring for the validation tests is found in:
Validation1.xhtml
Validation2.xhtml
Validation3.xhtml
Validation4.xhtml
Sample schema files used in the markup and test files is found in:
Validation_Test_Types.json
Validation_Test_Types.xsd
Field State Tests / Samples
See ~lib/test/src/tibet/forms
. The TP.lang.Object_Tests.js
contains the
test code which loads the various sample pages and runs the tests.
Sample markup authoring for the field state tests is found in:
Computed1.xhtml
Computed2.xhtml
Relevant1.xhtml
Required1.xhtml
Required2.xhtml
Required3.xhtml
Required4.xhtml
Required5.xhtml
Required6.xhtml
Grouping Tests / Samples
See ~lib/test/src/tibet/grouping
. The TP.tibet.group_Tests.js
contains the
test code which loads the various sample pages and runs the tests.
Sample markup authoring for the grouping tests is found in:
Grouping1.xhtml
Grouping2.xhtml
Grouping3.xhtml
Grouping4.xhtml
Grouping5.xhtml
Grouping6.xhtml
Grouping7.xhtml
Grouping8.xhtml
Grouping9.xhtml
Grouping10.xhtml
Grouping11.xhtml
Navigation Tests / Samples
See ~lib/test/src/tibet/focusing
. The TP.dom.UIElementNode_Tests.js
and
TP.html.Element_Tests.js
files contain the test code itself.
Sample markup authoring for the focusing tests is found in:
Focusing1.xhtml
Focusing2.xhtml
Focusing3.xhtml
Focusing4.xhtml
Focusing5.xhtml
Focusing6.xhtml
Focusing7.xhtml
Focusing8.xhtml
Focusing9.xhtml
Focusing10.xhtml
Focusing11.xhtml
Focusing12.xhtml
Focusing13.xhtml
Focusing14.xhtml
Focusing15.xhtml
Focusing16.xhtml
Focusing17.xhtml
Focusing18.xhtml
FocusingStack1.xhtml
FocusingStack2.xhtml
Data Formatters
See ~lib/test/src/tibet/formatting
for a variety of tests and samples.
String_Tests.js
focuses on the String format
and transform
calls and shows
a number of the substitutions which are possible to format your data.
TP.dom.ElementNode_Tests.js
contains tests for the ui:display
and
ui:storage
attributes, which let you define input and output formatters for
your fields.
TP.lang.Object_Tests.js
contains tests for the as
method which also can be
used to product formatted output (when you give it a string format or template).
TP.templateParser_Tests.js
provides tests of TIBET's template parser, in
particular its parse
call which handles templated input.
Sample markup authoring for the formatting tests is found in:
Formatting1.xhtml
Formatting2.xhtml
Formatting3.xhtml
Formatting4.xhtml
Formatting5.xhtml
Formatting6.xhtml
Formatting7.xhtml