Basics
Setup
Download formBuilder either directly from the repository or by using bower.
$ bower install add-formbuilder
Bower dependencies:
- jquery (1.8.3 - 1.11.3)
- jquery-ui (1.9.2)
- normalize.css
- moment
- spinjs [nested]
- jquery-timepicker-jt [nested]
- bootstrap-datepicker [nested]
You will also need a jquery-ui theme.
You must include all of the dependencies. There is a compiled nested version of formBuilder with some of the dependencies included and minified for convenience.
<html>
<head>
...
<!-- Default CSS setup (nested) -->
<link type="text/css" rel="stylesheet" href="/bower_components/normalize.css/normalize.css">
<link type="text/css" rel="stylesheet" href="/bower_components/add-formbuilder/dist/css/formBuilder.nested.min.css">
<!-- Optional CSS setup (non-nested)
<link type="text/css" rel="stylesheet" href="/bower_components/normalize.css/normalize.css">
<link type="text/css" rel="stylesheet" href="/bower_components/jquery-timepicker-jt/jquery.timepicker.css">
<link type="text/css" rel="stylesheet" href="/bower_components/bootstrap-datepicker/dist/css/bootstrap-datepicker.standalone.css">
<link type="text/css" rel="stylesheet" href="/bower_components/add-formbuilder/dist/css/formBuilder.css">
-->
</head>
<body>
...
<!-- Default JavaScript setup (nested) -->
<script type="text/javascript" src="/bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="/bower_components/jquery-ui/ui/minified/jquery-ui.min.js"></script>
<script type="text/javascript" src="/bower_components/moment/min/moment.min.js"></script>
<script type="text/javascript" src="/bower_components/add-formbuilder/dist/formBuilder.nested.min.js"></script>
<!-- Optional JavaScript setup (non-nested)
<script type="text/javascript" src="/bower_components/jquery/dist/jquery.min.js"></script>
<script type="text/javascript" src="/bower_components/jquery-ui/ui/minified/jquery-ui.min.js"></script>
<script type="text/javascript" src="/bower_components/moment/min/moment.min.js"></script>
<script type="text/javascript" src="/bower_components/spinjs/spin.min.js"></script>
<script type="text/javascript" src="/bower_components/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script>
<script type="text/javascript" src="/bower_components/jquery-timepicker-jt/jquery.timepicker.min.js"></script>
<script type="text/javascript" src="/bower_components/add-formbuilder/dist/formBuilder.min.js"></script>
-->
</body>
<html>
Applying formBuilder
Forms are made into formBuilder forms by initializing them as formBuilder widgets. FormBuilder will search for form fields and construct them automatically.
$('form').formBuilder();
Form element attributes/options may be added to specify different widget options.
-
The
data--default-required
attribute ordataDefaultRequired
option sets the default require statuses for all fields in the form. -
The
data-ignore-hidden
attribute orignoreHidden
option when set will ignore fields that are not visible. They will not be validated nor will any data be retrieved from them. -
The
data-load-hidden
attribute orloadHidden
option when set will hide the form while it is being constructed. Once complete, it will show the form. It is suggested that if you wish to use this that you should also include "style= display:none"
$('form#formExampleDefaults').formBuilder();
$('form#formExampleChanged').formBuilder({
dataRequired: true, // Default = false
ignoreHidden: true, // Default = false
loadHidden: false // Default = true
});
Required Inputs
The data-required attribute if applied to the input element will prevent form submission if it is not valid
The data-default-required attribute applied to the form element will set the default data-required state of all nested inputs
Using Namespaces
Objects that contain data from the form can be created and saved using formbuilder. In order to store an object with the data group your inputFields together using a common name, and then use dot notation to give each item its own unique identifier.
These objects can also be nested in order to create more specific groupings if desired. Say for example you wanted to store two sets of information about a group of students, like their grade and their age. You could do it by having them all share one name to be one big group, and then divide them up into two smaller groups using dot notation.
For further insight on to how these objects store their data, look ahead to the data output in the Live Demo.
Field Enhancements
Preinput/Postinput
The data-preinput attribute adds a block of text before any input
The data-postinput attribute adds a block of text after any input
Both can be used at the same time
Prefix/Suffix
Will add text at front/end of form field that can only be visible once the user has begun entering text into the field
The data-prefix
The data-suffix
Using both
Labels
The data-label attribute adds text above any input field
Placeholder Text
The data-empty or data-placeholder attribute will display placeholder text when an input field is empty
Max
the data-max attribute adds a maximum character requirement, enforced while typed
Input Groups
Use input groups to keep inputs inline
This is done by simply coding each desired input directly after the one before it
Tooltip
You can add a tooltip box after a form field that will display a hovering widget on-click. You need a div of class tooltip with an optional title attribute
Data Types
Input Types
The data-type attribute on input elements is used by formBuilder to construct each type of field. FormBuilder comes with many types that do not require any extra coding. Custom types may also be created (see Customization).
FormBuilder types are recommended to be used on input elements of type "text" or "password". They cannot be used with "checkbox", "radio", or "submit" types, which each have their own setups. Other HTML5 types may still work but are not suggested.
To use a type, initialize the input's parent form as a formBuilder widget. The widget will automatically create the necessary inputField() widget on the input and manage its status. Alternatively, the inputField widget may be manually applied to an input outside of a formBuilder form (see inputField API Reference).
For displaying uneditable values. Displays value attribute where the input field would be. Displayed as html.
Select
Normal select tags will still work, but you can also use the formBuilder select input, which works in the same way as the code data-type.
The value is what is submitted to the form while the label is what the user can search for. Note: The data-options must be valid JSON with double quotes.
<input type='text' data-label='formBuilder select' data-type='select' data-options='[
{"value":"submitted to form", "label":"Searched Item"},
{"value":"true", "label":"Yes"},
{"value":"false", "label":"No"},
{"value":"1", "label":"option 1"},
{"value":"2", "label":"option 2"},
{"value":"3", "label":"option 3"},
{"value":"4", "label":"option 4"},
{"value":"5", "label":"option 5"},
{"value":"100", "label":"Some label"},
{"value":"AAA", "label":"123 Some St."},
{"value":"BBB", "label":"Some Business Name Inc."},
{"value":"Hodor", "label":"Bran"}
]'>
The options in the select may be set with a function
Other Select-Based Types
Checkboxes
FormBuilder checkboxes are very similar to normal HTML checkboxes, minus the need for an extra label tag. To add a label, add the data-label attribute. Labels can be made of html or just raw text. Checkboxes may can also be marked as required with the data-required attribute. If a checkbox is marked as required and it is unchecked then it will turn red to alert the user that it is a required field.
Checkboxes are made using the selectionField widget and they inherit many of the same functions as the inputField widget. This does not include the other input types or most field enhancements. Checkboxes have validation methods, different states, and they can be set/get. When used in a formBuilder form, they are seen as inputs with a boolean value.
Checkboxes can be grouped using the selection-field-group class, but it is not necessary. A group of checkboxes can be aligned horizontally with the selection-horizontal class.
Radio Groups
FormBuilder radio groups are very similar to normal HTML radio groups, minus the need for an extra label tag. To add a label, add the data-label attribute. Labels can be made of html or just raw text. They may can also be marked as required with the data-required attribute. A radio group is seen as required if at least one of its options is marked required.
Radio groups are made using the selectionField widget and inherits many of the same functions as the inputField widget. This does not include the other input types or most field enhancements. Radio groups have validation, states, and can be set/get. When used in a formBuilder form, the entire group is seen as one input whose value is determined by the checked radio button.
Radio boxes can be grouped using the selection-field-group class, but it is not necessary. A group of radio boxes can be aligned horizontally with the selection-horizontal class.
Textarea
No extra attributes are required. Note: pre/post-inputs will be placed on the outside of this area.
Array Field
You can use array fields in a formBuilder form to allow variable amounts of the same input-field-group to be submitted.
- Items can be added to the array with the bottom '+' addon.
- Items can be reordered with the left '::' addon.
- Items can be removed from the arrray with the right addon icon.
- The main div must have a name attribute
This is a basic example of an arrayField of an input-field-group containing a utext input.
You can add a message next to the add button using the data-addmessage attribute.
ArrayFields can have labels like other fields
Date Offsets
The date data type has two attribute options, data-min-date
and data-max-date
, for setting min/max valid dates and those that are visible in the datepicker widget. These dates may be set by either a specific date string in the format "YYYY-MM-DD"
or by a collection of date offsets.
Date offsets must match the format /[+-]*\d+\!?[dmyw]/gi
and is an offset from the current date. Multple offsets can be in one string and are applied in order of unit size, largest to smallest. Multiple offsets with the same unit will be evaluated left to right.
By default, each offset will round the date to the start or end of the offset unit when subtracting or adding, respectively. This can be disabled with the data-no-rounding
attribute. Combining addition/subtration with the same unit is particularly useful when you want to get a specific date that is partially relative to the current date. As a result, +0x/-0x can be used to move to the end/start of unit x, changing any smaller units to match that fact.
Rounding may also be disabled on a per-offset unit basis by adding a '!' before the unit character. Other offsets without it will still be rounded unless the no-rounding option is set. For example in "+1!y+0m" will evaluate as "the end of this month next year".
Examples for displayed values (local):
on 2000-05-20 the range would be [1995-06-07, 2020-02-20]
on 2000-05-20 the range would be [1995-01-01, 2000-06-30]
on 2000-05-20 the range would be [1995-05-20, 2000-07-19]
on 2000-05-20 the range would be (beg. of time, 2002-12-31]
on 2000-05-20 the range would be [1999-02-06, 2003-12-01]
on 2000-05-20 the range would be [2000-05-08, end of time)
on 2000-05-20 the range would be [1999-05-08, 2001-12-31]
Standalone Widgets
Date Range Picker
The Date-Range Picker creates two input fields for a beginning and ending date. Different ranges can be selected using the drop down panel. Arrow buttons can be used to move date ranges forwards or backwards by the allotted amount of time. This widget uses the date data-type and follows its rules for validation.
To create a date-range picker, initialize the dateRange widget on an empty div element.
$('#dateRangeExample').dateRange();
Date Time Range Picker
The Date-Time-Range Picker creates two input fields for a beginning and ending date and also inclues time fields for each date. Different ranges can be selected using the drop down panel. Arrow buttons can be used to move date ranges forwards or backwards by the allotted amount of time. This widget uses the date data-type and the time-data type and follows their rules for validation. If no time is selected, then the time fields will default to 12:00am and 11:59pm.
To create a date-time-range picker, initialize the dateRange widget on an empty div element.
$('#dateTimeRangeExample').dateTimeRange();
Drop Down Panel
Drop down panel widgets may be used to add an extra hovering container to any element. To create a drop down panel, initialize the jquery widget on your drop down jquery object by calling dropDownPanel(options). Drop downs are shown when the user clicks on the target element, and/or focuses (if possible) on the focusTarget element. They can be hidden by clicking outside of the drop down and the target element or by pressing the escape key. Drop downs are inserted after the target and as a result, will move up and down the page with the element when scrolling.
This is a basic dropdown with just a paragraph inside.
This is a span with a drop down
This is a basic dropdown with just a paragraph inside.
var panel = $('#dropDownPanelBasic');
$('#panel').dropDownPanel({
target: $('#target0')
});
$('A drop down panel is just a normal container').dropDownPanel({
target: $('#target1')
});
When used on an input field, a drop down will target the first input by default, rather than all of its other field items with it. This can be disabled by setting the targetInput option to false
This is a generic dropdown. It can be used to add supporting information to an input type by adding a nested formBuilder form and using its results to display the target's value in a certain format. For example, you could use it for location information. (Note: this example does not use the form, it is only visual)
This is a generic dropdown. It can be used to add supporting information to an
input type by adding a nested formBuilder form and using its results to display
the target's value in a certain format. For example, you could use it for location
information. (Note: this example does not use the form, it is only visual)
// Must initialize the form first
$('form#exampleForm').formBuilder();
var ddp = $('#dropDownPanelInputField');
var ddp2 = ddp.clone();
ddp.dropDownPanel({
target: $('.dropDownPanelInputFieldTarget0')
});
ddp2.dropDownPanel({
target: $('.dropDownPanelInputFieldTarget1'),
targetInput: false
});
ddp.find('form').formBuilder();
ddp2.find('form').formBuilder();
Here is an example using a custom type that uses a drop down in its setUp(). Each type instance will have its own dropDownPanel instance and container.
$.formBuilder.inputField.types.customLocation = {
_dropDownTemplate:
'' +
'Enter the location information below. In this customLocation type, the input field above is only used as a visual. The actual data is retrieved from this subform as an object. However, the parent form will see still see this as a single input.
' +
'' +
'
' +
'',
setUp: function(ifw) {
var self = this,
e = self.e = ifw.element;
self.ifw = ifw;
self.ddp = $(self._dropDownTemplate).dropDownPanel({
target: e,
afterclose: function() {
ifw.validate(true);
}
});
self.form = self.ddp.find('form').formBuilder();
self.viewBtn = self.ddp.find('button');
self.form.on('change', function(ev){
self._updateTarget();
});
self.viewBtn.on('click', function(ev){
var data = self.form.formBuilder('get'),
query = '';
if(data.address) {
query += data.address;
}
if(data.city) {
query += ' ' + data.city;
}
if(data.state) {
query += ' ' + data.state;
}
if(query.trim()) {
window.open('http://maps.google.com?q='+query);
}
});
// disable input to the source element
e.inputFilter({
pattern: /[]/ //accept nothing
});
e.on('keydown', function(ev){
// ignore backspaces
if(ev.keyCode === 8) {
return false;
}
});
ifw.placeholder('Location name, Address');
},
_updateTarget: function() {
var self = this,
data = self.form.formBuilder('get');
if(data.name && data.address && data.city && data.state) {
self.e.val(data.name + ', ' + data.address);
self.viewBtn.prop('disabled', false);
self.ifw.redraw();
} else {
self.e.val('');
self.viewBtn.prop('disabled', true);
self.ifw.redraw();
}
},
converter: {
toField: function(value, ifw) {
var self = this;
self.form.formBuilder('set', value);
self._updateTarget();
return ;
},
fromField: function(value, ifw) {
var self = this;
return self.form.formBuilder('get');
}
},
validate: function(ifw) {
var self = this;
if(!self.form.formBuilder('validate')) {
return {
message: 'invalid'
};
}
self._updateTarget();
},
isEmpty: function() {
var self = this,
fields = self.form.formBuilder('getFields'),
empty = true;
fields.each(function(i, field){
if(!$(field).inputField('isEmpty')) {
empty = false;
return false;
}
});
return empty;
},
tearDown: function() {
var self = this;
self.ddp.remove();
}
};
// Initialze the form
$('form#exampleForm').formBuilder();
Text Submitter
Text submitter can be used for submitting multiple text messages from the same text area. The textSubmitter widget must be applied manually and does not need to be inside of a form.
Simple example using defaults:
$('#textSubmitterDefault').textSubmitter();
The width, placeholder, rows, and send instruction options may be set.
$('#textSubmitterOptions').textSubmitter({
width: '400px',
placeholder: 'Some placeholder',
sendInstruction: 'Some sendInstruction',
rows: 2
});
The submit event is fired when a the enter key is pressed. The event passes an object in the format {text:'What the user types in', onComplete: (callback function)}. The onComplete function will reset the textarea. This example has a 200ms submit setTimeout to simulate server lag time.
Submitted Messages
var messages = $('#textSubmitterSubmitBox .messages');
$('#textSubmitterSubmit').textSubmitter({
width: messages.outerWidth() + 15,
rows: 1,
placeholder: 'Type a message!',
ignoreEmptySubmit: true,
submit: function(ev, data){
setTimeout(function(){
data.text = data.text.trim();
if(data.text) {
messages.append('
');
messages.scrollTop(messages[0].scrollHeight);
data.onComplete(true);
} else {
data.onComplete(false);
}
}, 200);
}
});Submit Button
The submitButton widget can be used to handle form submission. It is styled by the jQuery UI theme and has a spinner animation during the submission process. This widget is not required when using formBuilder, but it does make the process very easy to handle.
A complete example of using the submitButton widget can be found in the Live Demo and complete documentaion on the API Reference page.
Customization
Creating Types
FormBuilder input types are stored as normal objects, and new types may be added to formBuilder in order to specialize forms for fit your needs. All custom type objects must be added to the $.formBuilder.inputField.types object. The properties of the type objects are used as prototypes when creating instances of each type. The type must be defined in the types object before the inputField is initialized on the input element or it will default to type "text".
In order to work correctly with formBuilder, each type must have a set of base methods. These are listed here in the API Reference. Below is an example a full custom type. This is a very simple type to represent and format U.S. Social Security Numbers. Note: If you were to make your own SSN type it is suggested to make it more complex.
$.formBuilder.inputField.types.SSN = {
setUp: function(ifw) {
var self = this,
e = ifw.element;
// Add a placeholder
ifw.placeholder('XXX-XX-XXXX');
// Set the characters a user can enter
e.inputFilter({
pattern: /[0-9]/,
max: 11,
extraFilter: function(val, inText) {
// A special character maximimum
if(val.replace(/[^0-9]/g,'').length < 9) {
return inText;
}
}
});
// Replace the input with a formatted input
e.on('blur', function(){
e.val(self.format(e.val()));
});
},
converter: {
toField: function(value, ifw) {
return this.format(value);
},
fromField: function(value, ifw) {
return parseInt(this.format(value).replace(/\-/g,''),10);
}
},
validate: function(ifw) {
if(!this.format(ifw.element.val()).match(/^[0-9]{3}\-[0-9]{2}\-[0-9]{4}$/)) {
return {
message: 'invalid'
};
}
},
//- This is an extra function specifically for this type
format: function(text) {
if(!text) {
return '';
}
//- Remove non-digits
text = text.replace(/[^0-9]/g,'');
text = text.substring(0, 10);
//- add correct dashes
if(text.length <= 3) {
return text;
} else if(text.length <= 5) {
return text.substring(0,3) + '-' + text.substring(3);
} else {
return text.substring(0,3) + '-' + text.substring(3,5) + '-' + text.substring(5);
}
}
};
$('fullCustomTypeForm').formBuilder();
Creating Regex Types
Custom types that only need regex validation and key input filtering can be easily
generated using the $.formBuilder.inputField.createRegexType
function.
$.extend($.formBuilder.inputField.types,{
'swear': $.formBuilder.inputField.createRegexType(/^[\!@#\$%\&*]*$/, /[\!@#\$%\&*]/),
'theLetterF': $.formBuilder.inputField.createRegexType(/^[fF]*$/, /[fF]/,{
toUpper: false
}),
'luckySeven': $.formBuilder.inputField.createRegexType(/^[7]*$/, /[7]/,{},3)
});
$('simpleCustomTypeForm').formBuilder();
Creating Field Widgets
Using custom field widgets with formBuilder allows you to make new input methods beyond simple input fields. Like input types, they require a set of functions to work with formBuilder (see fieldWidget). Any widget that handles data can be made a field widget with these functions. Widgets with nested formBuilder forms can be used to make form section templates as well.
To load an element as a field widget add the data-load-widget-as-field="widgetName"
attribute. The element must be inside of a form element. When the form element is initialized
as a formBuilder widget, it will automatically initialize the widget with "widgetName" on the
element. This element must have a name attribute. Labels may also be applied to field
widgets. The form data-default-required
attribute will be passed into the widget
initialization as the required
option.
Here is an example of a HTML5 canvas color picker as a custom field widget. Both the custom
type colorHex
and widget customColorPicker
were made for this
example. As a type, the colorHex could be used outside of this picker. The picker uses the
inputField to handle the typed input, while also drawing color slices around it for a quick
selection. This widget can be reused easily with formBuilder, with options avaliable for
instance specific needs. If you cannot see the color picker, upgrade your browser.
.picker {
display: inline-block;
}
.picker-wrapper {
position: relative;
z-index: 0;
}
.input-field.picker-field {
position: absolute;
z-index: 10;
}
.picker-canvas {
display: block;
z-index: 5;
}
/**
* Create Custom Field Widget
*/
// You can use the formBuilder utility functions if needed
var util = $.formBuilder.util;
$.formBuilder.inputField.types.colorHex = {
_validRegex: /^[ABCDEF0-9]{6}$/,
setUp: function(ifw) {
var self = this,
e = ifw.element;
e.inputFilter({
pattern: /[ABCDEFabcdef0-9]/,
max: 6,
toUpper: true
});
e.css('font-family', 'monospace');
e.blur(function(){
if(!e.val().match(self._validRegex)) {
ifw.clear();
}
});
ifw.placeholder('Color Hex');
},
converter: {
toField: function(value, ifw) {
var self = this;
// Make sure is string
value = (''+value).toUpperCase();
// Remove '#' if added
if(value.length > 1 && value[0] === '#') {
value = value.substring(1);
}
return value.match(self._validRegex)? value : '';
},
fromField: function(value, ifw) {
var self = this;
return value.match(self._validRegex)? '#' + value.toUpperCase() : '';
}
},
validate: function(ifw) {
var self = this;
return ifw.get().match(self._validRegex)? undefined : {
message: ''
};
}
};
$.widget('examples.customColorPicker', {
/**
* Any options needed
* Optional.
*/
options: {
required: false, // passed by formBuilder
randomColors: false,
randomCount: 10,
outerDiameter: 250.0,
innerDiameter: 80.0,
padding: 10.0,
divider: 8.0,
background: '#FBFBFB'
},
/**
* jQuery widget constructor
* Required.
*/
_create: function() {
var self = this,
e = self.element,
o = self.options;
// Load any options from DOM (overrides any passed options if set)
util.loadDomData(e, o, ['outerDiameter','innerDiameter', 'padding','divider', 'background', 'randomCount']);
util.loadDomToggleData(e, o, ['required','randomColors']);
self.sideLength = o.outerDiameter + o.padding;
self.wrapper = $('').appendTo(e);
self.canvas = $('').appendTo(self.wrapper);
self.field = $('').appendTo(self.wrapper).inputField({
required: o.required
});
self.isSupported = !!self.canvas[0].getContext;
if(self.isSupported) {
e.addClass('picker');
// Convert strings to floats
o.outerDiameter = parseFloat(o.outerDiameter);
o.innerDiameter = parseFloat(o.innerDiameter);
self._setColors();
var fieldWrapper = self.field.inputField('getField');
setTimeout(function() {
self.fieldColor = -1;
self._drawPicker();
fieldWrapper.css({
left: self.sideLength/2 - fieldWrapper.outerWidth()/2.0,
top: self.sideLength/2 - fieldWrapper.outerHeight()/2.0
});
}, 10);
// Check for hover + check events events
self.canvas.on('mousemove', function(ev) {
for(var i = 0; i < self.colors.length; ++i) {
if(self._pointInSlice(ev.offsetX, ev.offsetY, i)) {
self.hoverColor = i;
self._drawPicker();
self.canvas.css('cursor','pointer');
return;
}
self.hoverColor = -1;
self._drawPicker();
self.canvas.css('cursor','default');
}
}).on('click', function(ev) {
for(var i = 0; i < self.colors.length; ++i) {
if(self._pointInSlice(ev.offsetX, ev.offsetY, i)) {
self.field.focus().val(self.colors[i].substring(1)).keydown().blur();
self.fieldColor = self.get();
self._drawPicker();
return;
}
}
}).on('mouseleave', function() {
if(self.hoverColor >= 0) {
self.hoverColor = -1;
self._drawPicker();
self.canvas.css('cursor','default');
}
});
self.field.on('change keydown', function() {
self.fieldColor = self.get();
self._drawPicker();
});
} else {
console.log('Canvas is unsupported, only the inputField will be used for the customColorPicker');
self.canvas.remove();
self.canvas = undefined;
}
},
/**
* jQuery widget destructor
* Optional, suggested.
*/
_destroy: function() {
var self = this,
e = self.element;
self.canvas.remove();
self.field.remove();
e.empty();
},
/**
* Used to set field.
* @param {any} Any data needed by the widget to set its value
* Required.
*/
set: function(data) {
var self = this;
self.field.inputField('set', data);
self.fieldColor = self.get();
self._drawPicker();
},
/**
* Used to retrieve the data from the field.
* @return {any} Data from field. Must match set() data format
* Required.
*/
get: function() {
return this.field.inputField('get');
},
/**
* Checks for dirty state. A value is dirty when
* what is entered is differs from the last set()
* @return {Boolean} clean/dirty
* Required.
*/
isDirty: function() {
return this.field.inputField('isDirty');
},
/**
* Removes the dirty state and calls any needed events.
* Note: This should not change the value of the widget,
* that is what clear() is for.
* Required.
*/
clearDirty: function() {
this.field.inputField('clearDirty');
},
/**
* Empties all entered inputs.
* Required.
*/
clear: function() {
this.field.inputField('clear');
},
/**
* Preforms a visual effect to bring the user's attention
* to the widget. Can be used for errors or simply left
* empty.
* Required.
*/
flash: function() {
},
/**
* Runs input validation on the widget's inputs. This can
* be anything specific to the widget.
* @return {Boolean} valid/invalid
*/
validate: function() {
return this.field.inputField('validate');
},
/**
* Custom functions to handle canvas
*/
_defaultColors: ['#FF0000','#FF7F00','#FFFF00','#00FF00','#00FFFF','#0000FF','#8B00FF', '#000000'],
_setColors: function() {
var self = this,
o = self.options,
i;
self.colors = [];
if(o.randomColors) {
if(o.randomCount < 2) {
o.randomCount = 2;
}
for(i = 0; i < o.randomCount; ++i) {
self.colors[i] = '#' + Math.floor((Math.random() * 0xFFFFFF)).toString(16).toUpperCase();
}
} else {
for(i = 0; i < self._defaultColors.length; ++i) {
self.colors[i] = self._defaultColors[i];
}
}
},
_drawPicker: function() {
var self = this,
c = self.colors,
o = self.options,
ctx = self.canvas[0].getContext('2d'),
origin = self.sideLength/2.0,
sliceRadians = 2*Math.PI/c.length,
iRadius = o.innerDiameter/2.0,
oRadius = o.outerDiameter/2.0,
i;
// Reset canvas
ctx.clearRect(0,0,self.canvas.width, self.canvas.height);
// Draw color slices
var drawSlice = function(color, i, isHover) {
var r = i*sliceRadians,
path = new Path2D();
path.moveTo(
origin+iRadius*Math.cos(r),
origin+iRadius*Math.sin(r)
);
path.lineTo(
origin+oRadius*Math.cos(r),
origin+oRadius*Math.sin(r)
);
path.arc(
origin, origin, oRadius, r, r+sliceRadians, false
);
r += sliceRadians;
path.lineTo(
origin+iRadius*Math.cos(r),
origin+iRadius*Math.sin(r)
);
path.arc(
origin, origin, iRadius, r, r-sliceRadians, true
);
path.lineTo(
origin+oRadius*Math.cos(r-sliceRadians),
origin+oRadius*Math.sin(r-sliceRadians)
);
ctx.fillStyle = color;
ctx.fill(path);
ctx.strokeStyle = isHover? color : o.background;
ctx.lineWidth = o.divider;
ctx.stroke(path);
};
// Draw normal slices
for(i = 0; i < c.length; ++i) {
if(self.hoverColor !== i) {
drawSlice(c[i], i, false);
}
}
// Draw hover slice
if(self.hoverColor >= 0) {
drawSlice(c[self.hoverColor], self.hoverColor, true);
}
// Draw center
var circle = new Path2D();
circle.moveTo(origin, iRadius);
circle.arc(origin, origin, iRadius + o.divider, 0, 2*Math.PI, false);
ctx.fillStyle = (self.fieldColor.length === 7)? self.fieldColor : o.background;
ctx.fill(circle);
},
// Check point against a simple trapazoid around a slice (curves ignored)
_pointInSlice: function(x, y, i) {
var self = this,
o = self.options,
c = self.colors,
sliceRadians = 2*Math.PI/c.length,
origin = self.sideLength/2.0,
r1 = sliceRadians*i,
r2, piDeg, pRadius, theta;
// point radius
pRadius = Math.sqrt(Math.pow(origin - x, 2) + Math.pow(origin - y, 2)); //distance formula
/**
* 1. farthest perpendicular edge to centerline
* 2. closest perpendicular edge to centerline
*/
if((o.outerDiameter/2.0 < pRadius) || (pRadius < o.innerDiameter/2.0*Math.cos(sliceRadians/2))) {
return false;
}
r2 = r1 + sliceRadians;
// adjust pt + theta to quadrant
x -= origin;
y -= origin;
theta = Math.atan(y/x);
if(x < 0) {
theta += Math.PI;
} else if(x >= 0 && y < 0) {
theta += 2*Math.PI;
}
/**
* 1. radians to right edge
* 2. radians to left edge
*/
if(theta < r1 || r2 < theta) {
return false;
}
// Inside all boundary checks
return true;
}
});
Adding Locale Support
English is the default language setting. To change it you must include the desired
language package script from /dist/locales/
above where you include the formBuilder
script. English (en) and Spanish (es) language packages are precompiled into formBuilder
so you do not need to include them.
FormBuilder will attempt to retrieve the preferred language from the browser automatically. The first language code match sets the language. If no match is found, it will default to English.
<script type='text/javascript' src='/bower_componenets/formBuilder.min.js'></script>
If you want to manually choose a language, define $.formBuilder.lang.code
above where
you have included any formBuilder javascript and below jQuery.
<script type='text/javascript'>
// Manual language choice
$.formBuilder = {};
$.formBuilder.lang = {};
$.formBuilder.lang.code = 'en'; // English
// $.formBuilder.lang.code = 'es'; // Spanish
</script>
<script type='text/javascript' src='/bower_componenets/formBuilder.min.js'></script>
If you do not see a language pack for what you are looking for, creating one is very simple.
- Make a copy of the unminified English pack from
/dist/locales/
. - Update the language code string and acceptedCodes string array to match the new language. The former is the code used by formBuilder and the latter is the codes it looks for from the browser when detecting the language. You can find a list of standard codes here.
- Replace all defined strings at the bottom of the file with new language equivalents.
- Use this new pack like any other pack, loading it before the main formBuilder script.
Custom Themes
FormBuilder allows for the creation of custom themes by rebuilding the Sass files.
The themes are located under /sass/themes
. To make your own custom
theme, follow these steps:
- Clone or fork the GitHub repository
- Copy
/sass/themes/_default.scss
into a new file - Tweak the constants to make your new theme and change any other sass files needed
-
Change the @import in the main
/sass/formBuilder.scss
from the default theme file to your own theme file - Compile the new sass theme for distribution (see Building)
- Find your theme under
/dist/css/formBuilder.css
- Use this new file in place of the original formBuilder.css file in your project
Live Demo
To get a feel of how formBuilder is used, try out this example.
Form
Results
Source
var formExample = $('form#example:first').formBuilder({
beforeset: function(ev) {
},
afterset: function(ev) {
}
});
var saveForm = function(data, done) {
// Save data to server
$.ajax({
url: 'someserver.com',
type: 'POST',
data: data,
success: function(result) {
//handle your server result
done();
},
error: function() {
//connection error
done();
}
});
};
formExample.find('button[type="submit"]').submitButton({
color: '#FF0000',
beforesubmit: function(ev) {
// Runs any presubmission stuff
},
submit: function(ev, done) {
// Run validation
if(!formExample.formBuilder('validate')) {
// The form is invalid somewhere
done();
return;
}
saveForm(formExample.formBuilder('get'), done);
},
aftersubmit: function(ev) {
// Run any post-submission stuff
}
});
var events = $('#example-events');
var data = $('#example-data');
var eventNum = 1;
var logEvent = function(str) {
events.val(events.val()+'\n['+(eventNum++)+'] '+str);
events.scrollTop(events[0].scrollHeight);
};
events.val('[0] Start');
var formExample = $('form#example:first').formBuilder({
beforeset: function(ev) {
logEvent('beforeset called on formBuilder');
},
afterset: function(ev) {
logEvent('afterset called on formBuilder');
}
});
logEvent('Initialized');
var saveForm = function(data, done) {
// Save data to server (setTimeout is just used in the example to simulate the lag time, use $.ajax instead)
setTimeout(function(){
// If the server-side save was good, set it back to clean
logEvent('server save complete');
done()
}, 2000);
/* Example server call
$.ajax({
url: 'someserver.com',
type: 'POST',
data: data,
success: function(result) {
//handle your server result
done();
},
error: function() {
//connection error
done();
}
});
*/
};
formExample.find('button[type="submit"]').submitButton({
color: '#FF0000',
beforesubmit: function(ev) {
// Runs any presubmission stuff
logEvent('beforesubmit called');
data.val('');
},
submit: function(ev, done) {
logEvent('submit called');
// Run validation
if(!formExample.formBuilder('validate')) {
// The form is invalid somewhere
logEvent('has invalid');
done();
return;
}
logEvent('all valid');
var currData = formExample.formBuilder('get');
data.val(JSON.stringify(currData,null,2));
saveForm(currData, done);
},
aftersubmit: function(ev) {
// Run any post-submission stuff
logEvent('aftersubmit called');
}
});