If you want to use Jaffa using just the features shown so far you could have a fully functional form already. However one of the goals we originally had for the project was to make handling large forms much easier. In this light the features we've shown so far can really just be considered as building blocks... widgets is where it's really at.
Several widgets ship with Jaffa to make building a feature rich form very easy (a number are demo'd on this page), but you can also build your own widgets to add to the toolkit. This setup is intended for use in environments where parts of the dev team can do the work on widget design, and others can focus on metadata and form layout.
Widgets are generally designed to be called like existing jQuery UI Widgets, in that a basic DOM element is targeted. We do require an ID attribute on this DOM element. eg:
<div id="dropDownWidgetSample"></div>
With regards to styling widgets, the base widget (which all widgets extend)
will take care of injecting additional classes you request, as long as you
provide them as part of your config
("class-list": widgetBranding). For the sake
of brevity the examples below don't show this, but most inject a class
structure something like this:
var widgetBranding = {
"": ["control-group"],
"label.widgetLabel": ["control-label"],
"label.radioLabel": ["radio", "inline"],
".jaffaValidationError": ["alert", "alert-error"]
};
All this is doing is looking at the selector value on the left and injecting the extra classes on the right (in this case Boostrap classes). All the selectors are constrained to being children of the top-level container element, and the empty selector used for the first value targets the container itself. Given that many widgets implement jQuery UI widgets for controls this usually leaves you with a few options for branding:
Some examples of the basic widgets that ship in Jaffa. With varying configuration there are more options then displayed here, but this covers the high notes. Each widget has its configuration in the contextual help elements attached to it, so just click the question marks if you want to see how things are done.
The humble text input is (at face value) fairly basic, but Jaffa offers a few features that let you do some interesting stuff with a text widget.
The simplest of examples first though.
As you can see, the config is fairly simple. In fact we've put more effort into attaching this help text then the widget itself takes to get up and running.
<div id="textWidgetSimple"></div>
<div id="textWidgetSimpleHelp">... all this stuff...</div>
$("#textWidgetSimple").jaffaText({
"label": "A text field:",
"field": "simpleTextField",
"help-content": "#textWidgetSimpleHelp",
"mandatory": true
});
Or we can change it into a <textarea/> with very little effort. We also added a default value... cutting edge stuff.
All we had to do was provide the textarea
config entry and the widget will adjust. The rows
and cols are optional,
since your styles may be a better way of setting these
dimensions. The widget will still create a
<textarea/> without
them. There is no error checking on what you provide
here by the way, so if you provide nonsense values, the
browser will decide what it wants to do with them.
<div id="textWidgetTextarea"></div>
<div id="textWidgetTextareaHelp">... all this stuff...</div>
$("#textWidgetTextarea").jaffaText({
"label": "A textarea:",
"field": "simpleTextarea",
"default-value": "I have a default value...",
"help-content": "#textWidgetTextareaHelp",
"textarea": {
"rows": "5",
"cols": "40"
}
});
Next, the 'Validating Text' tab shows how the widgets can integrate with Validatious.
Back on the 'Validation' tab we got our first look Validatious,
which is especially useful when it comes to validating text fields.
Most other controls/widgets tend to deal with fixed vocabularies
and/or formats (like dates), so we generally just allow simple
mandatory flags in their config
(you can target the underlying field with your own validation
though if you want to get fancy).
A text field is pretty wide open though, and this widget has no
such luxury. With so many validators
built in to Validatious for handling text however, the focus of
the widget is mainly on letting you pick and choose from validation
rules and provide custom responses in the UI. Providing this sort
of validation config (v2Rules)
does take preference over a basic
mandatory flag, so remember to
add a Validatious required rule
if you want the field to be mandatory.
Here's an example of an mandatory field, and it only accepts URLs.
So the presence of the v2Rules
property is the first thing the widget is looking for,
then each rule needs to have a name. The widget uses this
for its own index, as well as notifying Jaffa of a new
rule using this name (as per the core validation process).
If you don't provide a message for the rule, this name
will also be the only feedback you receive on screen
(ie. it failed).
<div id="textWidgetUrl"></div>
<div id="textWidgetUrlHelp">... all this stuff...</div>
$("#textWidgetUrl").jaffaText({
"label": "Enter a URL:",
"field": "urlTextField",
"help-content": "#textWidgetUrlHelp",
"v2Rules": {
"mandatory": {
"validator": "required"
},
"needsUrl": {
"validator": "url",
"message": "I asked for a URL dumb-ass!"
}
}
});
And another example, this time of a slightly more complicated set of rules that also require parameters. We are going to do a trivial password complexity test (REALLY trivial, this isn't an example of good practice in password complexity) using only the Validatious built-in validators.
Config is starting to get a bit longer now, but it's still
fairly simple though. We've introduced the syntax for
providing parameters to validators that required them,
and we also snuck in a new type
option to turn our input into a password field.
<div id="textWidgetPassword"></div>
<div id="textWidgetPasswordHelp">... all this stuff...</div>
$("#textWidgetPassword").jaffaText({
"label": "Enter a password:",
"field": "passwordComplexityTextField",
"type": "password",
"help-content": "#textWidgetPasswordHelp",
"v2Rules": {
"mandatory": {
"validator": "required",
"message": "A password is required!"
},
"noSpecialCharacters": {
"validator": "alphanum",
"message": "Only alpha-numeric characters are allowed. eg: a-z, A-Z, 0-9"
},
"min4": {
"validator": "min-length",
"params": [4],
"message": "Only passwords of betweeen 4 and 8 characters are allowed."
},
"max8": {
"validator": "max-length",
"params": [9], // <= Bug in Validatious. It uses '<' rather then '<='
// http://cjohansen.lighthouseapp.com/projects/16714/tickets/7-max-length#ticket-7-1
"message": "Only passwords of betweeen 4 and 8 characters are allowed."
}
}
});
Text widgets support a few different types of lookups; basically
anything that jQuery UI's Autocomplete
can do. For the more basic types of Autocomplete, there is little
more then wrapping going on, and you can provide both local and
remote data for identical results. This first example shows the simple
static String approach (["value1", "value2"])
for both a local data source.
There's hardly anything to this config at all, since the
widget just passes the value of lookup-data
straight to jQuery UI.
<div id="textWidgetLookupBasic"></div>
<div id="textWidgetLookupBasicHelp">... all this stuff...</div>
var data = ["value1", "value2"];
$("#textWidgetLookupBasic").jaffaText({
"label": "Local: ",
"field": "autoTextLookupStatic1",
"help-content": "#textWidgetLookupBasicHelp",
"lookup-data": data
});
This field pulls the autocomplete values from a URL, and has a very small increase to complexity of data structure. The suggestions will look identical to the first, but an underlying value you don't see is actually inserted into the input.
Almost identical to above, except this time we use a URL.
<div id="textWidgetLookupBasicRemote"></div>
<div id="textWidgetLookupBasicRemoteHelp">... all this stuff...</div>
$("#textWidgetLookupBasicRemote").jaffaText({
"label": "Remote: ",
"field": "autoTextLookupStatic2",
"help-content": "#textWidgetLookupBasicRemoteHelp",
"lookup-data": "data/static.json"
});
Finally, we have a pretty complicated usage of the widget, which involves searching a datasource (cross-domain or otherwise) and handling the data when it returns. For the sake of demonstration this example recreates the geonames.org search that the jQuery UI doco uses.
What the widget brings to the table here is the ability to 'walk' a JSON data structure and find any fields you have flagged as important. The fields can then be pushed into any templated Strings you like and distributed throughout your form as outputs. Try the demo and see what happens, although keep in mind that Geonames can be a tad slow at times. The original authors of Jaffa do something similar to this against a local Geonames cache for speed in production. As always, if you want more details on the particulars of this demo open the help text.
This usage represents a reasonable limit to what is offered by generic widgets. Anything more specific should probably be spun off into its own widget, or even make direct use of underlying library features. As the help text points out, this lookup has constraints you can exceed if you were to make direct use of jQuery UI.
So the top of our config looks fairly normal, but after providing a data source URL we then provide two new config elements that are reasonably complicated. Below they are explored in more detail:
lookup-request All of the config for building a request before it is sent.data-type Defaults to 'json'. You would only need
to change this to 'jsonp' if you are making a cross-domain request.term-field The field your search term will be sent
in. Defaults to 'q', although you probably should provide this as 'q' is just a
guess to try before failing.term-quote Flag whether or not search term(s) should
be quoted on sending. Defaults to true.lookup-parser All of the config for parsing a response after the data source returns.results-path An array of Strings that are used as JSON
properties to 'walk' down heirarchical data from the root. This should target an array
where each element is considered an 'item' in the suggestions drop-down. There is no
default value.fields Each property in this object is an alias you'd
like to use to create a 'field' from the data returned. The Arrays assigned to each
property are a path used just as above, allowing you to address deeply nested data
structures if required (the demo is only lightly nested).outputs The output properties/keys serve two purposes:
label and
value outputs are very important (the UI uses them).
The widget will try to insert them if you forget, but your data structure would have
to match the jQuery UI default for this to work. The real value comes from the fact
that additional properties beyond those minimums are kept in tact by the plugin and
become accessible again after an option is selected... bringing us to...<input />
in our demo) and update the value of the field to match. Failing this it will try to
treat it as a jQuery selector (anywhere in the document, not inside the widget) and
the elements contents (a basic $().html() call, also
used in the demo).Output values are basic template that will substitute in the field aliases from above wherever you put a placeholder. There is no additional logic going on under the hood when these templates are parsed, so missing values will just leave a placeholder. This would tend to make the widget mostly useful on stable data sources with fairly reliable data.
Overall this approach works, but within the tightly constrained parameters already demonstrated by the configuration. If the search pattern is particularly complicated the simple URL appending used will likely fail, or if the data returned requires massaging and more fine grained decision making during parsing then this widget will probably not help.
<div id="textWidgetLookupGeonames"></div>
<div>
<input class="jaffa-field span5" type="text" id="textWidgetLookupGeonamesOutput" size="40" value="Don't search here, search up there ^^^" />
<div id="textWidgetLookupGeonamesLatLong"></div>
</div>
<div id="textWidgetLookupGeonamesHelp">... all this stuff...</div>
$("#textWidgetLookupGeonames").jaffaText({
"label": "City Name: ",
"field": "autoTextLookupGeonames",
"help-content": "#textWidgetLookupGeonamesHelp",
"lookup-data": "http://ws.geonames.org/searchJSON?featureClass=P&style=full&maxRows=12",
"lookup-request": {
"data-type": "jsonp",
"term-field": "name_startsWith",
"term-quote": true
},
"lookup-parser": {
"results-path": ["geonames"],
"fields": {
"name": ["toponymName"],
"country": ["countryName"],
"lat": ["lat"],
"long": ["lng"]
},
"outputs": {
"label": "${name}, ${country}",
"value": "${name}, ${country}",
"textWidgetLookupGeonamesOutput": "${name}, ${country}",
"#textWidgetLookupGeonamesLatLong": "Lat: ${lat}, Long: ${long}"
}
}
});
The most basic of drop downs using static data. This particular widget also happens to back into server managed data that Jaffa grabbed at startup, so you should see that the second value is selected now despite it not being the default or first in the list. All Jaffa fields (and by extension widgets) are synch'd to server data during Jaffa's startup.
We have provided config with what the <option>
elements should contain (both value and label).
<div id="formSelectWidget"></div>
<div id="formSelectWidgetHelp">... all this stuff...</div>
$("#formSelectWidget").jaffaDropDown({
"label": "Drop Down / Select:",
"field": "formSelect",
"option-data": [
{"value": "value3", "label": "Value 3 (This label is not stored)"},
{"value": "value4", "label": "Value 4 (This label is not stored)"}
],
"help-content": "#formSelectWidgetHelp"
});
In counterpoint to the above example, this drop down has not loaded any server data, but we've explicitly defined a default whilst still allowing the user to 'opt-out'.
We've altered the label used on empty values to be a more contextually appropriate 'opt-out', and we've also defaulted a selection.
<div id="dropDownWidgetDefault"></div>
<div id="dropDownWidgetDefaultHelp">... all this stuff...</div>
$("#dropDownWidgetDefault").jaffaDropDown({
"label": "Gender:",
"field": "dropDownDefault",
"empty-text": "I'd Rather Not Say",
"option-data": [
{"value": "male", "label": "Male"},
{"value": "female", "label": "Female"}
],
"default-value": "male",
"help-content": "#dropDownWidgetDefaultHelp"
});
A drop-down is a fairly typical control for representing controlled vocabularies. You may load the vocabulary statically as we demonstrated on the previous tab, or you might instruct the widget to go and get the data from a vocabulary service or an AJAX interface lightly wrapping an application's database.
This drop down references a static file showing the colours of the rainbow. We still allow an empty value in the GUI, since we don't want the user to ignore the field and accept a default selection that will accidentally treated as valid. To make them select something we'll make it mandatory as well, so the form submission will not accept it. For the demo you can test with a blur() event however.
Obviously our static option data is gone, and has been
replaced with a json-data-url.
Another new addition here is the label-field
config instructing the widget that we would like to store
both the value and the label. The label is stored in a
hidden input, but you can look for that field name on the
'Data' tab, or in the page's DOM beside the drop-down with
your dev tools of choice.
<div id="dropDownWidgetSample"></div>
<div id="dropDownWidgetSampleHelp">... all this stuff...</div>
$("#dropDownWidgetSample").jaffaDropDown({
"label": "Pick a Color:",
"field": "rainbowColour",
"label-field": "rainbowColourLabel",
"json-data-url": "data/dropDownSample1.json",
"help-content": "#dropDownWidgetSampleHelp",
"mandatory": true
});
So if you were to have a look at the static file we linked above, you'll see there's some additional data embedded in there that we could make use of. This is very similar to the more complicated example we shown in the text widget above.
Some things that are {colour}: ...
{colour} as a hex colour:
As already mentioned the config syntax mostly mirrors that
of the more complicated text widget we've seen above. The
only notable difference would be loading the data.
json-data-url can be used
by any widget, since the underlying base widget will be
looking for it and will preload the data before the widget
comes online. Our text widget on the other hand needs to
use $().autocomplete()
for loading in real time.
We can also see here an example of fields that are slightly more nested, although it still follows the format explained in detail in the text widget's help.
<div id="dropDownWidgetComplex"></div>
<div id="dropDownWidgetComplexOutput" style>
<p><b>Some things that are <span class="dropDownWidgetComplexColour">{colour}</span></b>: <span id="dropDownWidgetComplexExample">...</span></p>
<p><b><span class="dropDownWidgetComplexColour">{colour}</span> as a hex colour</b>: <input class="jaffa-field span2" type="text" id="dropDownWidgetComplexHex" size="10"/></p>
</div>
<div id="dropDownWidgetComplexHelp">... all this stuff...</div>
$("#dropDownWidgetComplex").jaffaDropDown({
"label": "Pick a Colour: ",
"field": "rainbowColourComplex",
"help-content": "#dropDownWidgetComplexHelp",
"json-data-url": "data/dropDownSample1.json",
"default-value": "red",
"lookup-parser": {
"results-path": [],
"fields": {
"label": ["label"],
"value": ["value"],
"examples": ["moreData", "examples"],
"hexCode": ["moreData", "hexColor"]
},
"outputs": {
"label": "${label}",
"value": "${value}",
".dropDownWidgetComplexColour": "${label}",
"#dropDownWidgetComplexExample": "${examples}",
"dropDownWidgetComplexHex": "${hexCode}"
}
}
});
$("#formRadioWidget").jaffaRadioGroup({
"label": "Radio Group: ",
"field": "formRadio",
"label-field": "formRadioLabel",
"radio-data": {
"formRadio1": "Value 1",
"formRadio2": "Value 2",
"formRadio3": "Value 3"
}
});
Now we'll go a step further and give Jaffa a jQuery selector for where to put our help GUI elements, moving them above the widget.
TODO
Most widgets also offer a 'repeatable' version as well, adding in GUI elements required to expand/contract and manage the list. At face value, the core Jaffa code has no knowledge of this, but convention amongst widgets is to map this into fields of the form, so Jaffa has been made be aware of this convention in sorting field names.
TODO
A text field with validation attached, requiring at least one URL be entered, but allowing others
.
<div id="textWidgetUrlRepeatable"></div>
<div id="textWidgetUrlRepeatableHelp">... all this stuff...</div>
This help text has been applied to the top-level list widget, with some basic textual help inside each 'child'.
TODO