Forms

TIBET Forms

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 Sherpa IDE.

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

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

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