';
$kcToggle.append(kctInner);
$kcToggle.click(function () {
element.attr('checked', !element.attr('checked'));
$(this).toggleClass('on');
});
};
$.fn.kcToggle = function (options) {
var toggle = this;
return toggle.each(function () {
var element = $(this);
if (element.data('kcToggle')) {
return;
}
var kcToggle = new Toggle(element, options);
element.data('kcToggle', kcToggle);
});
};
})(jQuery);
'use strict';
(function ($) {
var FormBuilder = function FormBuilder(options, element) {
var formBuilder = this;
var defaults = {
controlPosition: 'right',
controlOrder: ['autocomplete', 'button', 'checkbox', 'checkbox-group', 'date', 'file', 'header', 'hidden', 'paragraph', 'radio-group', 'select', 'text', 'textarea'],
dataType: 'xml',
/**
* Field types to be disabled
* ['text','select','textarea','radio-group','hidden','file','date','checkbox-group','checkbox','button','autocomplete']
*/
disableFields: ['autocomplete', 'hidden'],
// Uneditable fields or other content you would like to appear before and after regular fields:
append: false,
prepend: false,
// array of objects with fields values
// ex:
// defaultFields: [{
// label: 'First Name',
// name: 'first-name',
// required: 'true',
// description: 'Your first name',
// type: 'text'
// }, {
// label: 'Phone',
// name: 'phone',
// description: 'How can we reach you?',
// type: 'text'
// }],
defaultFields: [],
fieldRemoveWarn: false,
roles: {
1: 'Administrator'
},
messages: {
addOption: 'Add Option',
allFieldsRemoved: 'All fields were removed.',
allowSelect: 'Allow Select',
autocomplete: 'Autocomplete',
button: 'Button',
cannotBeEmpty: 'This field cannot be empty',
checkboxGroup: 'Checkbox Group',
checkbox: 'Checkbox',
checkboxes: 'Checkboxes',
className: 'Class',
clearAllMessage: 'Are you sure you want to clear all fields?',
clearAll: 'Clear',
close: 'Close',
content: 'Content',
copy: 'Copy To Clipboard',
dateField: 'Date Field',
description: 'Help Text',
descriptionField: 'Description',
devMode: 'Developer Mode',
editNames: 'Edit Names',
editorTitle: 'Form Elements',
editXML: 'Edit XML',
fieldDeleteWarning: false,
fieldVars: 'Field Variables',
fieldNonEditable: 'This field cannot be edited.',
fieldRemoveWarning: 'Are you sure you want to remove this field?',
fileUpload: 'File Upload',
formUpdated: 'Form Updated',
getStarted: 'Drag a field from the right to this area',
header: 'Header',
hide: 'Edit',
hidden: 'Hidden Input',
label: 'Label',
labelEmpty: 'Field Label cannot be empty',
limitRole: 'Limit access to one or more of the following roles:',
mandatory: 'Mandatory',
maxlength: 'Max Length',
minOptionMessage: 'This field requires a minimum of 2 options',
name: 'Name',
no: 'No',
off: 'Off',
on: 'On',
option: 'Option',
optional: 'optional',
optionLabelPlaceholder: 'Label',
optionValuePlaceholder: 'Value',
optionEmpty: 'Option value required',
paragraph: 'Paragraph',
placeholder: 'Placeholder',
placeholders: {
value: 'Value',
label: 'Label',
text: '',
textarea: '',
email: 'Enter you email',
placeholder: '',
className: 'space separated classes',
password: 'Enter your password'
},
preview: 'Preview',
radioGroup: 'Radio Group',
radio: 'Radio',
removeMessage: 'Remove Element',
remove: '×',
required: 'Required',
richText: 'Rich Text Editor',
roles: 'Access',
save: 'Save',
selectOptions: 'Options',
select: 'Select',
selectColor: 'Select Color',
selectionsMessage: 'Allow Multiple Selections',
size: 'Size',
sizes: {
xs: 'Extra Small',
sm: 'Small',
m: 'Default',
lg: 'Large'
},
style: 'Style',
styles: {
btn: {
'default': 'Default',
danger: 'Danger',
info: 'Info',
primary: 'Primary',
success: 'Success',
warning: 'Warning'
}
},
subtype: 'Type',
subtypes: {
text: ['text', 'password', 'email', 'color'],
button: ['button', 'submit'],
header: ['h1', 'h2', 'h3'],
paragraph: ['p', 'address', 'blockquote', 'canvas', 'output']
},
text: 'Text Field',
textArea: 'Text Area',
toggle: 'Toggle',
warning: 'Warning!',
viewXML: '</>',
yes: 'Yes'
},
notify: {
error: function error(message) {
return console.error(message);
},
success: function success(message) {
return console.log(message);
},
warning: function warning(message) {
return console.warn(message);
}
},
sortableControls: false,
prefix: 'form-builder-'
};
// @todo function to set parent types for subtypes
defaults.messages.subtypes.password = defaults.messages.subtypes.text;
defaults.messages.subtypes.email = defaults.messages.subtypes.text;
defaults.messages.subtypes.color = defaults.messages.subtypes.text;
defaults.messages.subtypes.submit = defaults.messages.subtypes.button;
var opts = $.extend(true, defaults, options),
elem = $(element),
frmbID = 'frmb-' + $('ul[id^=frmb-]').length++;
opts.formID = frmbID;
formBuilder.element = element;
var $sortableFields = $('
').attr('id', frmbID).addClass('frmb');
var _helpers = formBuilderHelpersFn(opts, formBuilder);
formBuilder.layout = _helpers.editorLayout(opts.controlPosition);
var lastID = frmbID + '-fld-1',
boxID = frmbID + '-control-box';
// create array of field objects to cycle through
var frmbFields = [{
label: opts.messages.textArea,
attrs: {
type: 'textarea',
className: 'text-area',
name: 'textarea'
}
}, {
label: opts.messages.text,
attrs: {
type: 'text',
className: 'text-input',
name: 'text-input'
}
}, {
label: opts.messages.select,
attrs: {
type: 'select',
className: 'select',
name: 'select'
}
}, {
label: opts.messages.radioGroup,
attrs: {
type: 'radio-group',
className: 'radio-group',
name: 'radio-group'
}
}, {
label: opts.messages.paragraph,
attrs: {
type: 'paragraph',
className: 'paragraph'
}
}, {
label: opts.messages.hidden,
attrs: {
type: 'hidden',
className: 'hidden-input',
name: 'hidden-input'
}
}, {
label: opts.messages.header,
attrs: {
type: 'header',
className: 'header'
}
}, {
label: opts.messages.fileUpload,
attrs: {
type: 'file',
className: 'file-input',
name: 'file-input'
}
}, {
label: opts.messages.dateField,
attrs: {
type: 'date',
className: 'calendar',
name: 'date-input'
}
}, {
label: opts.messages.checkboxGroup,
attrs: {
type: 'checkbox-group',
className: 'checkbox-group',
name: 'checkbox-group'
}
}, {
label: opts.messages.checkbox,
attrs: {
type: 'checkbox',
className: 'checkbox',
name: 'checkbox'
}
}, {
label: opts.messages.button,
attrs: {
type: 'button',
className: 'button-input',
name: 'button'
}
}, {
label: opts.messages.autocomplete,
attrs: {
type: 'autocomplete',
className: 'autocomplete',
name: 'autocomplete'
}
}];
frmbFields = _helpers.orderFields(frmbFields);
if (opts.disableFields) {
// remove disabledFields
frmbFields = frmbFields.filter(function (field) {
return !opts.disableFields.inArray(field.attrs.type);
});
}
// Create draggable fields for formBuilder
var cbUl = _helpers.markup('ul', null, { id: boxID, className: 'frmb-control' });
if (opts.sortableControls) {
cbUl.classList.add('sort-enabled');
}
var $cbUL = $(cbUl);
// Loop through
for (var i = frmbFields.length - 1; i >= 0; i--) {
var $field = $('', {
'class': 'icon-' + frmbFields[i].attrs.className,
'type': frmbFields[i].type,
'name': frmbFields[i].className,
'label': frmbFields[i].label
});
$field.data('newFieldData', frmbFields[i]);
var typeLabel = _helpers.markup('span', frmbFields[i].label);
$field.html(typeLabel).appendTo($cbUL);
}
var viewDataText = opts.dataType === 'xml' ? opts.messages.viewXML : opts.messages.viewJSON;
// Build our headers and action links
var viewData = _helpers.markup('button', viewDataText, {
id: frmbID + '-view-data',
type: 'button',
className: 'view-data btn btn-default'
}),
clearAll = _helpers.markup('button', opts.messages.clearAll, {
id: frmbID + '-clear-all',
type: 'button',
className: 'clear-all btn btn-default'
}),
saveAll = _helpers.markup('button', opts.messages.save, {
className: 'btn btn-primary ' + opts.prefix + 'save',
id: frmbID + '-save',
type: 'button'
}),
formActions = _helpers.markup('div', [clearAll, viewData, saveAll], {
className: 'form-actions btn-group'
}).outerHTML;
// Sortable fields
$sortableFields.sortable({
cursor: 'move',
opacity: 0.9,
revert: 150,
beforeStop: _helpers.beforeStop,
start: _helpers.startMoving,
stop: _helpers.stopMoving,
cancel: 'input, select, .disabled, .form-group, .btn',
placeholder: 'frmb-placeholder'
});
// ControlBox with different fields
$cbUL.sortable({
helper: 'clone',
opacity: 0.9,
connectWith: $sortableFields,
cursor: 'move',
placeholder: 'ui-state-highlight',
start: _helpers.startMoving,
stop: _helpers.stopMoving,
revert: 150,
beforeStop: _helpers.beforeStop,
update: function update(event, ui) {
if (_helpers.doCancel) {
return false;
}
event = event;
if (ui.item.parent()[0] === $sortableFields[0]) {
prepFieldVars(ui.item, true);
_helpers.doCancel = true;
} else {
_helpers.setFieldOrder($cbUL);
_helpers.doCancel = !opts.sortableControls;
}
}
});
var $stageWrap = $('', {
id: frmbID + '-stage-wrap',
'class': 'stage-wrap ' + formBuilder.layout.stage
});
var $formWrap = $('', {
id: frmbID + '-form-wrap',
'class': 'form-wrap form-builder' + _helpers.mobileClass()
});
elem.before($stageWrap).appendTo($stageWrap);
var cbWrap = $('', {
id: frmbID + '-cb-wrap',
'class': 'cb-wrap ' + formBuilder.layout.controls
}).append($cbUL[0], formActions);
$stageWrap.append($sortableFields, cbWrap);
$stageWrap.before($formWrap);
$formWrap.append($stageWrap, cbWrap);
var saveAndUpdate = _helpers.debounce(function (evt) {
if (evt) {
if (evt.type === 'keyup' && this.name === 'className') {
return false;
}
}
var $field = $(this).parents('.form-field:eq(0)');
_helpers.updatePreview($field);
_helpers.save();
});
// Save field on change
$sortableFields.on('change blur keyup', '.form-elements input, .form-elements select, .form-elements textarea', saveAndUpdate);
// Add append and prepend options if necessary
var nonEditableFields = function nonEditableFields() {
var cancelArray = [];
if (opts.prepend && !$('.disabled.prepend', $sortableFields).length) {
var prependedField = _helpers.markup('li', opts.prepend, { className: 'disabled prepend' });
cancelArray.push(true);
$sortableFields.prepend(prependedField);
}
if (opts.append && !$('.disabled.append', $sortableFields).length) {
var appendedField = _helpers.markup('li', opts.append, { className: 'disabled append' });
cancelArray.push(true);
$sortableFields.append(appendedField);
}
if (cancelArray.some(function (elem) {
return elem === true;
})) {
$stageWrap.removeClass('empty');
}
};
var prepFieldVars = function prepFieldVars($field) {
var isNew = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
var field = {};
if ($field instanceof jQuery) {
var fieldData = $field.data('newFieldData');
if (fieldData) {
field = fieldData.attrs;
field.label = fieldData.label;
} else {
var attrs = $field[0].attributes;
if (!isNew) {
field.values = $field.children().map(function (index, elem) {
return {
label: $(elem).text(),
value: $(elem).attr('value'),
selected: Boolean($(elem).attr('selected'))
};
});
}
for (var i = attrs.length - 1; i >= 0; i--) {
field[attrs[i].name] = attrs[i].value;
}
}
} else {
field = $field;
}
field.label = _helpers.htmlEncode(field.label);
field.name = isNew ? nameAttr(field) : field.name;
field.role = field.role;
field.className = field.className || field.class;
field.required = field.required === 'true' || field.required === true;
field.maxlength = field.maxlength;
field.toggle = field.toggle;
field.multiple = field.type.match(/(checkbox-group)/);
field.description = field.description !== undefined ? _helpers.htmlEncode(field.description) : '';
var match = /(?:^|\s)btn-(.*?)(?:\s|$)/g.exec(field.className);
if (match) {
field.style = match[1];
}
appendNewField(field);
$stageWrap.removeClass('empty');
};
// Parse saved XML template data
var getXML = function getXML() {
var xml = '';
if (formBuilder.formData) {
xml = formBuilder.formData;
} else if (elem.val() !== '') {
xml = $.parseXML(formBuilder.element.value.trim());
} else {
xml = false;
}
var fields = $(xml).find('field');
if (fields.length > 0) {
formBuilder.formData = xml;
fields.each(function () {
prepFieldVars($(this));
});
} else if (!xml) {
// Load default fields if none are set
if (opts.defaultFields && opts.defaultFields.length) {
opts.defaultFields.reverse();
for (var i = opts.defaultFields.length - 1; i >= 0; i--) {
prepFieldVars(opts.defaultFields[i]);
}
$stageWrap.removeClass('empty');
_helpers.save();
} else if (!opts.prepend && !opts.append) {
$stageWrap.addClass('empty').attr('data-content', opts.messages.getStarted);
}
}
$('li.form-field:not(.disabled)', $sortableFields).each(function () {
_helpers.updatePreview($(this));
});
nonEditableFields();
};
var loadData = function loadData() {
var doLoadData = {
xml: getXML,
json: function json() {
console.log('coming soon');
}
};
doLoadData[opts.dataType]();
};
// callback to track disabled tooltips
$sortableFields.on('mousemove', 'li.disabled', function (e) {
$('.frmb-tt', this).css({
left: e.offsetX - 16,
top: e.offsetY - 34
});
});
// callback to call disabled tooltips
$sortableFields.on('mouseenter', 'li.disabled', function () {
_helpers.disabledTT.add($(this));
});
// callback to call disabled tooltips
$sortableFields.on('mouseleave', 'li.disabled', function () {
_helpers.disabledTT.remove($(this));
});
var nameAttr = function nameAttr(field) {
var epoch = new Date().getTime();
return field.type + '-' + epoch;
};
// multi-line textarea
var appendTextarea = function appendTextarea(values) {
appendFieldLi(opts.messages.textArea, advFields(values), values);
};
var appendInput = function appendInput(values) {
var type = values.type || 'text';
appendFieldLi(opts.messages[type], advFields(values), values);
};
/**
* Add data for field with options [select, checkbox-group, radio-group]
*
* @todo refactor this nasty crap, its actually painful to look at
* @param {object} values
*/
var appendSelectList = function appendSelectList(values) {
if (!values.values || !values.values.length) {
values.values = [{
selected: true
}, {
selected: false
}];
values.values = values.values.map(function (elem, index) {
elem.label = opts.messages.option + ' ' + (index + 1);
elem.value = _helpers.hyphenCase(elem.label);
return elem;
});
}
var field = '';
field += advFields(values);
field += '
';
field += '';
field += '
';
if (values.type === 'select') {
field += '
';
field += '';
field += '';
field += '
';
}
field += '';
for (i = 0; i < values.values.length; i++) {
field += selectFieldOptions(values.name, values.values[i], values.values[i].selected, values.multiple);
}
field += '';
var addOption = _helpers.markup('a', opts.messages.addOption, { className: 'add add-opt' });
field += _helpers.markup('div', addOption, { className: 'option-actions' }).outerHTML;
field += '
';
field += '
';
appendFieldLi(opts.messages.select, field, values);
$('.sortable-options').sortable(); // making the dynamically added option fields sortable.
};
var appendNewField = function appendNewField(values) {
// TODO: refactor to move functions into this object
var appendFieldType = {
'select': appendSelectList,
'rich-text': appendTextarea,
'textarea': appendTextarea,
'radio-group': appendSelectList,
'checkbox-group': appendSelectList
};
values = values || '';
if (appendFieldType[values.type]) {
appendFieldType[values.type](values);
} else {
appendInput(values);
}
};
/**
* Build the editable properties for the field
* @param {object} values configuration object for advanced fields
* @return {String} markup for advanced fields
*/
var advFields = function advFields(values) {
var advFields = [],
key,
roles = values.role !== undefined ? values.role.split(',') : [];
// var fieldLabelLabel = _helpers.markup('label', opts.messages.label);
// var fieldLabelInput = _helpers.markup('input', null, {
// type: 'text',
// name: 'label',
// value: values.label,
// className: 'fld-label form-control'
// });
// var fieldLabel = _helpers.markup('div', [fieldLabelLabel, fieldLabelInput], {
// className: 'form-group label-wrap'
// });
advFields.push(textAttribute('label', values));
// advFields.push(fieldLabel.outerHTML);
values.size = values.size || 'm';
values.style = values.style || 'default';
advFields.push(fieldDescription(values));
advFields.push(subTypeField(values));
advFields.push(btnStyles(values.style, values.type));
// Placeholder
advFields.push(textAttribute('placeholder', values));
// Class
advFields.push(textAttribute('className', values));
advFields.push(textAttribute('name', values));
advFields.push('
');
advFields.push(' ');
advFields.push('
');
for (key in opts.roles) {
if ($.inArray(key, ['date', '4']) === -1) {
advFields.push(' ');
}
}
advFields.push('
');
advFields.push(textAttribute('maxlength', values));
return advFields.join('');
};
/**
* Description meta for field
*
* @param {Object} values field values
* @return {String} markup for attribute, @todo change to actual Node
*/
var fieldDescription = function fieldDescription(values) {
var noDescFields = ['header', 'paragraph', 'button'],
noMakeAttr = [],
descriptionField = '';
noDescFields = noDescFields.concat(opts.messages.subtypes.header, opts.messages.subtypes.paragraph);
if (noDescFields.indexOf(values.type) === -1) {
noMakeAttr.push(true);
}
if (noMakeAttr.some(function (elem) {
return elem === true;
})) {
var fieldDescLabel = _helpers.markup('label', opts.messages.description, { 'for': 'description-' + lastID }),
fieldDescInput = _helpers.markup('input', null, {
type: 'text',
className: 'fld-description form-control',
name: 'description',
id: 'description-' + lastID,
value: values.description
}),
fieldDesc = _helpers.markup('div', [fieldDescLabel, fieldDescInput], {
'class': 'form-group description-wrap'
});
descriptionField = fieldDesc.outerHTML;
}
return descriptionField;
};
/**
* Changes a fields type
*
* @param {Object} values
* @return {String} markup for type