formBuilder v2.1.1

Basics

Setup

Download formBuilder either directly from the repository or by using bower.

$ bower install add-formbuilder

Bower dependencies:

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 or dataDefaultRequired option sets the default require statuses for all fields in the form.
  • The data-ignore-hidden attribute or ignoreHidden 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 or loadHidden 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

This is a tooltip

Paragraph One

Paragraph Two

Some link somewhere cool

This is a tooltip without a title

Paragraph One

Paragraph Two

Some link somewhere cool

Tooltip Header

Paragraph One

Paragraph Two

Link

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

No specified data type, will not throw errors for any input.
For uppercase text. If a user types in lowercase text, it is automatically converted to uppercase.
For integers and negative numbers. Will return a number on get().
For integers, negatives, and decimal numbers. Will return a number on get().
For state abbreviations [A-Z]. With a data-max of two.
For entering 5 digit or 9 digit (with or without '-') zip codes.
For email inputs. The correct format is a username, then a single '@', then an alphanumeric domain name, then a single '.', then a alphabetic domain extension 2-4 characters in length.
For currency inputs. Similar to the number field, but will automatically round to 2 decimal places on blur. Will return a number on get().
For currency inputs, allows negatives. Include data-allow-negative to enable this.
In addition, an addon with a '$' (by default) is prepended to the left of the field with a weight of -100. To change the currency symbol, set it with the data-currency-symbol attribute. To not show any currency symbol, add the data-hide-symbol attribute.
Max and min amounts for money can be set using the data-max-amount and/or the data-min-amount. An error message will be displayed if too large or too small an amount is entered.
For entering US phone numbers.
Extension of phone. Allows user to choose phone type. Will save the phone type and the number in an object. For more insight into how this data is stored look ahead to the Live Demo
For entering dates in the 'MM/DD/YYYY' format. A user may manually type the date or click the input field to open a datepicker and select a date.
For entering times. The data-military attribute may be set to display times in military time.
For entering dates and times in the 'MM/DD/YYYY h:MM AM/PM format. Combines the date and time data types.
For entering military times, performs in the same way as dateTime except time is 24 hours and does not include AM/PM

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

Click on the button to cycle the select options.

Other Select-Based Types

Created with arraySelectCreator(arrayOfOptions). A select created with an array of options where label == value. This type uses the next 10 years.
Created with booleanSelectCreator(trueLabel,falseLabel). A select created with two options where the trueLabel's value is true and the falseLabel's value is false. Can be used for localization support. With this type, 'Yes' is true and 'No' is false.

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 2016-01-05 the range would be [1995-06-07, 2020-02-20]
on 2000-05-20 the range would be [1995-06-07, 2020-02-20]
on 2016-01-05 the range would be [2015-01-05, 2016-02-29]
on 2000-05-20 the range would be [1995-01-01, 2000-06-30]
on 2016-01-05 the range would be [2015-01-05, 2016-03-05]
on 2000-05-20 the range would be [1995-05-20, 2000-07-19]
on 2016-01-05 the range would be (beg. of time, 2018-12-31]
on 2000-05-20 the range would be (beg. of time, 2002-12-31]
on 2016-01-05 the range would be [2016-02-06, 2019-12-01]
on 2000-05-20 the range would be [1999-02-06, 2003-12-01]
on 2016-01-05 the range would be [2016-01-11, end of time)
on 2000-05-20 the range would be [2000-05-08, end of time)
on 2016-01-05 the range would be [2015-01-05, 2017-12-31]
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 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)

// 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
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('
['+moment().format('HH:mm:ss')+']"'+data.text+'"
'); 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.

  1. Make a copy of the unminified English pack from /dist/locales/.
  2. 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.
  3. Replace all defined strings at the bottom of the file with new language equivalents.
  4. 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:

  1. Clone or fork the GitHub repository
  2. Copy /sass/themes/_default.scss into a new file
  3. Tweak the constants to make your new theme and change any other sass files needed
  4. Change the @import in the main /sass/formBuilder.scss from the default theme file to your own theme file
  5. Compile the new sass theme for distribution (see Building)
  6. Find your theme under /dist/css/formBuilder.css
  7. 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

Events Data

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'); } });