diff --git a/src/js/config.js b/src/js/config.js
index 238a4d6f2..02634ab5f 100644
--- a/src/js/config.js
+++ b/src/js/config.js
@@ -84,6 +84,10 @@ export const defaultOptions = {
typeUserAttrs: {},
typeUserDisabledAttrs: {},
typeUserEvents: {},
+ defaultGridColumnClass: 'col-md-12',
+ cancelGridModeDistance: 100,
+ enableColumnInsertMenu: false,
+ enableEnhancedBootstrapGrid: false,
}
export const styles = {
diff --git a/src/js/control/textarea.tinymce.js b/src/js/control/textarea.tinymce.js
index a2edac96d..8dff65282 100644
--- a/src/js/control/textarea.tinymce.js
+++ b/src/js/control/textarea.tinymce.js
@@ -10,8 +10,8 @@ import controlTextarea from './textarea'
* var renderOpts = {
* controlConfig: {
* 'textarea.tinymce': {
-* paste_data_images: false
-* }
+ * paste_data_images: false
+ * }
* }
* };
* ```
@@ -80,13 +80,25 @@ export default class controlTinymce extends controlTextarea {
// define options & allow them to be overwritten in the class config
const options = jQuery.extend(this.editorOptions, this.classConfig)
options.target = this.field
- // initialise the editor
- window.tinymce.init(options)
+
+ setTimeout(() => {
+ // initialise the editor
+ window.tinymce.init(options)
+ }, 100)
// Set userData
if (this.config.userData) {
window.tinymce.editors[this.id].setContent(this.parsedHtml(this.config.userData[0]))
}
+
+ if (window.lastFormBuilderCopiedTinyMCE) {
+ const timeout = setTimeout(() => {
+ window.tinymce.editors[this.id].setContent(this.parsedHtml(window.lastFormBuilderCopiedTinyMCE))
+ window.lastFormBuilderCopiedTinyMCE = null
+ clearTimeout(timeout)
+ }, 300)
+ }
+
return evt
}
}
diff --git a/src/js/form-builder.js b/src/js/form-builder.js
index 94b710b24..689c0f716 100644
--- a/src/js/form-builder.js
+++ b/src/js/form-builder.js
@@ -29,6 +29,22 @@ import {
import { css_prefix_text } from '../fonts/config.json'
const DEFAULT_TIMEOUT = 333
+const rowWrapperClassSelector = '.rowWrapper'
+const rowWrapperClass = rowWrapperClassSelector.replace('.', '')
+
+const colWrapperClassSelector = '.colWrapper'
+const colWrapperClass = colWrapperClassSelector.replace('.', '')
+
+const tmpColWrapperClassSelector = '.tempColWrapper'
+const tmpColWrapperClass = tmpColWrapperClassSelector.replace('.', '')
+
+const tmpRowPlaceholderClassSelector = '.tempRowWrapper'
+const tmpRowPlaceholderClass = tmpRowPlaceholderClassSelector.replace('.', '')
+
+const invisibleRowPlaceholderClassSelector = '.invisibleRowPlaceholder'
+const invisibleRowPlaceholderClass = invisibleRowPlaceholderClassSelector.replace('.', '')
+
+let isMoving = false
const FormBuilder = function (opts, element, $) {
const formBuilder = this
@@ -37,6 +53,12 @@ const FormBuilder = function (opts, element, $) {
const data = new Data(formID)
const d = new Dom(formID)
+ let formRows = []
+ formBuilder.preserveTempContainers = []
+ formBuilder.rowWrapperClassSelector = rowWrapperClassSelector
+ formBuilder.colWrapperClassSelector = colWrapperClassSelector
+ formBuilder.colWrapperClass = colWrapperClass
+
// prepare a new layout object with appropriate templates
if (!opts.layout) {
opts.layout = layout
@@ -57,7 +79,21 @@ const FormBuilder = function (opts, element, $) {
const $stage = $(d.stage)
const $cbUL = $(d.controls)
- // Sortable fields
+ let insertingNewControl = false
+ let insertTargetIsRow = false
+ let insertTargetIsColumn = false
+
+ let $targetInsertWrapper
+ let cloneControls
+
+ function enhancedBootstrapEnabled() {
+ if (!opts.enableEnhancedBootstrapGrid) {
+ return false
+ }
+
+ return true
+ }
+
$stage.sortable({
cursor: 'move',
opacity: 0.9,
@@ -75,33 +111,76 @@ const FormBuilder = function (opts, element, $) {
$stage.sortable('disable')
}
- // ControlBox with different fields
- $cbUL.sortable({
- helper: 'clone',
- opacity: 0.9,
- connectWith: $stage,
- cancel: '.formbuilder-separator',
- cursor: 'move',
- scroll: false,
- placeholder: 'ui-state-highlight',
- start: (evt, ui) => h.startMoving.call(h, evt, ui),
- stop: (evt, ui) => h.stopMoving.call(h, evt, ui),
- revert: 150,
- beforeStop: (evt, ui) => h.beforeStop.call(h, evt, ui),
- distance: 3,
- update: function (event, ui) {
- if (h.doCancel) {
- return false
- }
+ if (!enhancedBootstrapEnabled()) {
+ $cbUL.sortable({
+ helper: 'clone',
+ opacity: 0.9,
+ connectWith: $stage,
+ cancel: '.formbuilder-separator',
+ cursor: 'move',
+ scroll: false,
+ placeholder: 'ui-state-highlight',
+ start: (evt, ui) => h.startMoving.call(h, evt, ui),
+ stop: (evt, ui) => h.stopMoving.call(h, evt, ui),
+ revert: 150,
+ beforeStop: (evt, ui) => h.beforeStop.call(h, evt, ui),
+ distance: 3,
+ update: function (event, ui) {
+ if (h.doCancel) {
+ return false
+ }
- if (ui.item.parent()[0] === d.stage) {
- h.doCancel = true
- processControl(ui.item)
- } else {
+ if (ui.item.parent()[0] === d.stage) {
+ h.doCancel = true
+ processControl(ui.item)
+ } else {
+ h.setFieldOrder($cbUL)
+ h.doCancel = !opts.sortableControls
+ }
+ },
+ })
+ } else {
+ // ControlBox with different fields
+ $cbUL.sortable({
+ opacity: 0.9,
+ connectWith: rowWrapperClassSelector,
+ cancel: '.formbuilder-separator',
+ cursor: 'move',
+ scroll: false,
+ start: (evt, ui) => {
+ h.startMoving.call(h, evt, ui)
+ isMoving = true
+ },
+ stop: (evt, ui) => {
+ h.stopMoving.call(h, evt, ui)
+ isMoving = false
+ cleanupTempPlaceholders()
+ },
+ revert: 150,
+ beforeStop: (evt, ui) => {
+ h.beforeStop.call(h, evt, ui)
+ },
+ distance: 3,
+ update: function (event) {
+ isMoving = false
+ if (h.doCancel) {
+ return false
+ }
+
+ //If started to enter a control into row but then moved it back, hide the placeholders again
+ if ($(event.target).attr('id') == $cbUL.attr('id')) {
+ HideInvisibleRowPlaceholders()
+ }
h.setFieldOrder($cbUL)
h.doCancel = !opts.sortableControls
- }
- },
+ },
+ })
+ }
+
+ $cbUL.on('mouseenter', function () {
+ if (stageHasFields()) {
+ $stage.children(tmpRowPlaceholderClassSelector).addClass(invisibleRowPlaceholderClass)
+ }
})
const processControl = control => {
@@ -132,6 +211,8 @@ const FormBuilder = function (opts, element, $) {
const $editorWrap = $(d.editorWrap)
+ $('
').appendTo($editorWrap)
+
const cbWrap = m('div', d.controls, {
id: `${data.formID}-cb-wrap`,
className: `cb-wrap ${data.layout.controls}`,
@@ -141,6 +222,12 @@ const FormBuilder = function (opts, element, $) {
cbWrap.appendChild(d.formActions)
}
+ const gridModeHelp = m('div', '', {
+ id: `${data.formID}-gridModeHelp`,
+ className: 'grid-mode-help',
+ })
+ cbWrap.appendChild(gridModeHelp)
+
$editorWrap.append(d.stage, cbWrap)
if (element.type !== 'textarea') {
@@ -151,6 +238,16 @@ const FormBuilder = function (opts, element, $) {
}
$(d.controls).on('click', 'li', ({ target }) => {
+ //Prevent duplicate add when click & dragging control to specific spot
+ if (isMoving) {
+ return
+ }
+
+ //Remove initial placeholder if simply clicking to add field into blank stage
+ if (!stageHasFields()) {
+ $stage.find(tmpRowPlaceholderClassSelector).eq(0).remove()
+ }
+
const $control = $(target).closest('li')
h.stopIndex = undefined
processControl($control)
@@ -250,9 +347,15 @@ const FormBuilder = function (opts, element, $) {
const loadFields = function (formData) {
formData = h.getData(formData)
if (formData && formData.length) {
+ formData.forEach(field => {
+ CaptureRowData(field)
+ })
+
formData.forEach(fieldData => prepFieldVars(trimObj(fieldData)))
d.stage.classList.remove('empty')
} else if (opts.defaultFields && opts.defaultFields.length) {
+ config.opts.defaultFields.forEach(field => CaptureRowData(field))
+
h.addDefaultFields()
} else if (!opts.prepend && !opts.append) {
d.stage.classList.add('empty')
@@ -266,6 +369,14 @@ const FormBuilder = function (opts, element, $) {
h.save()
}
+ //Capture information of all the row- values so generating new values will not ever clash with existing data
+ function CaptureRowData(field) {
+ const gridRowValue = h.getRowValue(field.className)
+ if (gridRowValue && !formRows.includes(gridRowValue)) {
+ formRows.push(gridRowValue)
+ }
+ }
+
/**
* Add data for field with options [select, checkbox-group, radio-group]
*
@@ -906,6 +1017,7 @@ const FormBuilder = function (opts, element, $) {
// Append the new field to the editor
const appendNewField = function (values, isNew = true) {
+ const columnData = prepareFieldRow(values)
data.lastID = h.incrementId(data.lastID)
const type = values.type || 'text'
@@ -935,6 +1047,17 @@ const FormBuilder = function (opts, element, $) {
}),
]
+ if (enhancedBootstrapEnabled()) {
+ fieldButtons.push(
+ m('a', null, {
+ type: 'grid',
+ id: data.lastID + '-grid',
+ className: `grid-button btn ${css_prefix_text}grid`,
+ title: 'Grid Mode',
+ }),
+ )
+ }
+
if (disabledFieldButtons && Array.isArray(disabledFieldButtons)) {
fieldButtons = fieldButtons.filter(btnData => !disabledFieldButtons.includes(btnData.type))
}
@@ -962,7 +1085,9 @@ const FormBuilder = function (opts, element, $) {
}
liContents.push(m('span', '?', descAttrs))
- liContents.push(m('div', '', { className: 'prev-holder' }))
+ const prevHolder = m('div', '', { className: 'prev-holder', dataFieldId: data.lastID })
+ liContents.push(prevHolder)
+
const formElements = m('div', [advFields(values), m('a', mi18n.get('close'), { className: 'close-field' })], {
className: 'form-elements',
})
@@ -984,6 +1109,8 @@ const FormBuilder = function (opts, element, $) {
})
const $li = $(field)
+ AttatchColWrapperHandler($li)
+
$li.data('fieldData', { attrs: values })
if (typeof h.stopIndex !== 'undefined') {
@@ -997,6 +1124,68 @@ const FormBuilder = function (opts, element, $) {
// generate the control, insert it into the list item & add it to the stage
h.updatePreview($li)
+ let rowWrapperNode
+
+ if (enhancedBootstrapEnabled()) {
+ const targetRow = `div.row-${columnData.rowNumber}`
+
+ //Check if an overall row already exists for the field, else create one
+ if ($stage.children(targetRow).length) {
+ rowWrapperNode = $stage.children(targetRow)
+ } else {
+ rowWrapperNode = m('div', null, {
+ id: `${field.id}-row`,
+ className: `row row-${columnData.rowNumber} ${rowWrapperClass}`,
+ })
+ }
+
+ //Turn the placeholder into the new row. Copy some attributes over
+ if (insertingNewControl && insertTargetIsRow) {
+ $targetInsertWrapper.attr('id', rowWrapperNode.id)
+ $targetInsertWrapper.attr('class', rowWrapperNode.className)
+ $targetInsertWrapper.attr('style', '')
+ rowWrapperNode = $targetInsertWrapper
+ }
+
+ //Add a wrapper div for the field itself. This div will be the rendered representation
+ const colWrapperNode = m('div', null, {
+ id: `${field.id}-cont`,
+ className: `${columnData.columnSize} ${colWrapperClass}`,
+ })
+
+ if (insertingNewControl && insertTargetIsColumn) {
+ if ($targetInsertWrapper.attr('prepend') == 'true') {
+ $(colWrapperNode).prependTo(rowWrapperNode)
+ } else {
+ $(colWrapperNode).insertAfter(`#${$targetInsertWrapper.attr('appendAfter')}`)
+ }
+ }
+
+ //Control insert will take care of inserting itself
+ if (!insertTargetIsColumn) {
+ $(colWrapperNode).appendTo(rowWrapperNode)
+ }
+
+ //If inserting, use the existing index, do not always append to end
+ if (!insertingNewControl) {
+ $stage.append(rowWrapperNode)
+ }
+
+ $li.appendTo(colWrapperNode)
+
+ setupSortableRowWrapper(rowWrapperNode)
+
+ SetupInvisibleRowPlaceholders(rowWrapperNode)
+
+ //Record the fact that this field did not originally have column information stored.
+ //If no other fields were added to the same row and the user did not do anything with this information, then remove it when exporting the config
+ if (columnData.addedDefaultColumnClass) {
+ $li.attr('addedDefaultColumnClass', true)
+ }
+
+ h.tmpCleanPrevHolder($(prevHolder))
+ }
+
if (opts.typeUserEvents[type] && opts.typeUserEvents[type].onadd) {
opts.typeUserEvents[type].onadd(field)
}
@@ -1010,6 +1199,312 @@ const FormBuilder = function (opts, element, $) {
field.scrollIntoView({ behavior: 'smooth' })
}
}
+
+ if (enhancedBootstrapEnabled()) {
+ //Autosize entire row when using new insert mode
+ if (insertingNewControl && insertTargetIsColumn) {
+ autoSizeRowColumns(rowWrapperNode, true)
+ }
+
+ cleanupTempPlaceholders()
+ }
+
+ insertingNewControl = false
+ insertTargetIsRow = false
+ insertTargetIsColumn = false
+ }
+
+ function AttatchColWrapperHandler(colWrapper) {
+ if (!enhancedBootstrapEnabled()) {
+ return
+ }
+
+ colWrapper.mouseenter(function (e) {
+ if (!gridMode) {
+ HideInvisibleRowPlaceholders()
+
+ //Only show the placeholder for what is above/below the rowWrapper
+ $(this)
+ .closest(rowWrapperClassSelector)
+ .prevAll(tmpRowPlaceholderClassSelector)
+ .first()
+ .removeClass(invisibleRowPlaceholderClass)
+ $(this)
+ .closest(rowWrapperClassSelector)
+ .nextAll(tmpRowPlaceholderClassSelector)
+ .first()
+ .removeClass(invisibleRowPlaceholderClass)
+
+ gridModeTargetField = $(this)
+ gridModeStartX = e.pageX
+ gridModeStartY = e.pageY
+ }
+ })
+ }
+
+ function HideInvisibleRowPlaceholders() {
+ $stage.find(tmpRowPlaceholderClassSelector).addClass(invisibleRowPlaceholderClass)
+ }
+
+ function SetupInvisibleRowPlaceholders(rowWrapperNode) {
+ const wrapperClone = $(rowWrapperNode).clone()
+ wrapperClone.addClass(invisibleRowPlaceholderClass).addClass(tmpRowPlaceholderClass).html('')
+ wrapperClone.css('height', '1px')
+
+ wrapperClone.attr('class', wrapperClone.attr('class').replace('row-', ''))
+ wrapperClone.removeAttr('id')
+
+ if ($(rowWrapperNode).index() == 0) {
+ const wrapperClone2 = $(wrapperClone).clone()
+ $stage.prepend(wrapperClone2)
+ setupSortableRowWrapper(wrapperClone2)
+ }
+
+ wrapperClone.insertAfter($(rowWrapperNode))
+ setupSortableRowWrapper(wrapperClone)
+ }
+
+ function ResetAllInvisibleRowPlaceholders() {
+ $stage.children(tmpRowPlaceholderClassSelector).remove()
+
+ $stage.children(rowWrapperClassSelector).each((i, elem) => {
+ SetupInvisibleRowPlaceholders($(elem))
+ })
+ }
+
+ function setupSortableRowWrapper(rowWrapperNode) {
+ if (!enhancedBootstrapEnabled()) {
+ return
+ }
+
+ $(rowWrapperNode).sortable({
+ connectWith: [rowWrapperClassSelector],
+ cursor: 'move',
+ opacity: 0.9,
+ revert: 150,
+ tolerance: 'pointer',
+ helper: function (e, el) {
+ //Shrink the control a little while dragging so it's not in the way as much
+ const clone = el.clone()
+ clone.find('.field-actions').remove()
+ clone.css({ width: '20%', height: '100px', minHeight: '60px', overflow: 'hidden' })
+ return clone
+ },
+ over: function (event) {
+ const overTarget = $(event.target)
+ const overTargetIsPlaceholder = overTarget.hasClass(tmpRowPlaceholderClass)
+
+ if (!overTargetIsPlaceholder) {
+ removeColumnInsertButtons(overTarget)
+ }
+
+ overTarget.addClass('hoverDropStyleInverse')
+
+ if (!overTargetIsPlaceholder) {
+ HideInvisibleRowPlaceholders()
+
+ //Only show the placeholder for what is above/below the rowWrapper
+ overTarget
+ .prevAll(tmpRowPlaceholderClassSelector)
+ .first()
+ .removeClass(invisibleRowPlaceholderClass)
+ .css('height', '40px')
+ overTarget
+ .nextAll(tmpRowPlaceholderClassSelector)
+ .first()
+ .removeClass(invisibleRowPlaceholderClass)
+ .css('height', '40px')
+ }
+ },
+ out: function (event) {
+ $stage.children(tmpRowPlaceholderClassSelector).removeClass('hoverDropStyleInverse')
+ $(event.target).removeClass('hoverDropStyleInverse')
+ },
+ placeholder: 'hoverDropStyleInverse',
+ receive: function (event, ui) {
+ const senderIsControlsBox = $(ui.sender).attr('id') == $cbUL.attr('id')
+
+ const droppingToNewRow = $(ui.item).parent().hasClass(tmpRowPlaceholderClass)
+ const droppingToPlaceholderRow = $(ui.item).parent().hasClass(tmpRowPlaceholderClass)
+ const droppingToExistingRow =
+ $(ui.item).parent().hasClass(rowWrapperClass) && !$(ui.item).parent().hasClass(tmpRowPlaceholderClass)
+
+ if (droppingToNewRow && !senderIsControlsBox) {
+ const colWrapper = $(ui.item)
+
+ const columnData = prepareFieldRow({})
+
+ const rowWrapperNode = m('div', null, {
+ id: `${colWrapper.find('li').attr('id')}-row`,
+ className: `row row-${columnData.rowNumber} ${rowWrapperClass}`,
+ })
+
+ $(ui.item).parent().replaceWith(rowWrapperNode)
+ AttatchColWrapperHandler($(ui.item))
+
+ colWrapper.appendTo(rowWrapperNode)
+
+ setupSortableRowWrapper(rowWrapperNode)
+ syncFieldWithNewRow(colWrapper.attr('id'))
+ checkRowCleanup()
+ }
+
+ if (droppingToPlaceholderRow && senderIsControlsBox) {
+ insertTargetIsRow = true
+ insertingNewControl = true
+ $targetInsertWrapper = $(ui.item).parent()
+ }
+
+ if (droppingToExistingRow && senderIsControlsBox) {
+ //Look for the closest add control button and act as if that was used to add the control
+ if ($(ui.item).prev().hasClass('btnAddControl')) {
+ $targetInsertWrapper = $(ui.item).prev()
+ } else if ($(ui.item).next().hasClass('btnAddControl')) {
+ $targetInsertWrapper = $(ui.item).next()
+ } else {
+ $targetInsertWrapper = $(ui.item).attr('prepend', 'true')
+ }
+
+ const parentRowValue = h.getRowClass($(ui.item).parent().attr('class'))
+ $targetInsertWrapper.addClass(parentRowValue)
+
+ insertTargetIsColumn = true
+ insertingNewControl = true
+
+ h.stopIndex = undefined
+ }
+
+ cleanupTempPlaceholders()
+
+ if (insertingNewControl) {
+ h.doCancel = true
+ processControl(ui.item)
+ h.save.call(h)
+ }
+
+ ResetAllInvisibleRowPlaceholders()
+
+ const listFieldItem = $(ui.item).find('li')
+ if (listFieldItem.length) {
+ CheckTinyMCETransition(listFieldItem)
+ UpdatePreviewAndSave(listFieldItem)
+ }
+ },
+ start: function () {
+ cleanupTempPlaceholders()
+ },
+ stop: function (event, ui) {
+ $stage.children(tmpRowPlaceholderClassSelector).removeClass('hoverDropStyleInverse')
+ autoSizeRowColumns(ui.item.closest(rowWrapperClassSelector), true)
+ },
+ update: function (event, ui) {
+ syncFieldWithNewRow(ui.item.attr('id'))
+ },
+ })
+
+ $(rowWrapperNode).off('mouseenter')
+ $(rowWrapperNode).on('mouseenter', function (e) {
+ setupColumnInserts($(e.currentTarget))
+ })
+
+ $(rowWrapperNode).off('mouseleave')
+ $(rowWrapperNode).on('mouseleave', function (e) {
+ removeColumnInsertButtons($(e.currentTarget))
+ })
+ }
+
+ function CheckTinyMCETransition(fieldListItem) {
+ const isTinyMCE = fieldListItem.find('textarea[type="tinymce"]')
+ if (isTinyMCE.length) {
+ window.lastFormBuilderCopiedTinyMCE = window.tinymce.get(isTinyMCE.attr('id')).save()
+ }
+ }
+
+ function UpdatePreviewAndSave(fieldListItem) {
+ h.updatePreview(fieldListItem)
+ h.save.call(h)
+ }
+
+ function cleanupTempPlaceholders() {
+ $stage.find(colWrapperClassSelector).removeClass('colHoverTempStyle')
+ $stage.find(tmpColWrapperClassSelector).remove()
+ }
+
+ function setupColumnInserts(rowWrapper) {
+ if (!opts.enableColumnInsertMenu) {
+ return
+ }
+
+ $(rowWrapper)
+ .children(colWrapperClassSelector)
+ .each((i, elem) => {
+ const colWrapper = $(elem)
+ colWrapper.addClass('colHoverTempStyle')
+
+ if (colWrapper.index() == 0) {
+ $(
+ `
`,
+ ).insertBefore(colWrapper)
+ }
+
+ $(
+ `
`,
+ ).insertAfter(colWrapper)
+ })
+ }
+
+ function removeColumnInsertButtons(rowWrapper) {
+ rowWrapper.find(tmpColWrapperClassSelector).remove()
+ rowWrapper.find(colWrapperClassSelector).removeClass('colHoverTempStyle')
+ }
+
+ function prepareFieldRow(data) {
+ let result = {}
+
+ if (!enhancedBootstrapEnabled()) {
+ return result
+ }
+
+ result = h.tryParseColumnInfo(data)
+ TryCreateNew()
+
+ if (!formRows.includes(result.rowNumber)) {
+ formRows.push(result.rowNumber)
+ }
+
+ return result
+
+ function TryCreateNew() {
+ if (!result.rowNumber) {
+ //Column information wasn't defined, get new default configuration for one.
+ let nextRow
+ if (formRows.length == 0) {
+ nextRow = 1
+ } else {
+ nextRow = Math.max(...formRows) + 1
+ }
+
+ result.rowNumber = nextRow
+
+ //If inserting directly into column, use the correct rowNumber
+ if (insertingNewControl && insertTargetIsColumn) {
+ result.rowNumber = h.getRowValue($targetInsertWrapper.attr('class'))
+ }
+
+ result.columnSize = opts.defaultGridColumnClass
+
+ if (!data.className) {
+ data.className = ''
+ }
+
+ data.className += ` row-${result.rowNumber} ${result.columnSize}`
+ result.addedDefaultColumnClass = true
+ }
+ }
}
// Select field html, since there may be multiple
@@ -1060,6 +1555,9 @@ const FormBuilder = function (opts, element, $) {
const cloneItem = function cloneItem(currentItem) {
data.lastID = h.incrementId(data.lastID)
+
+ CheckTinyMCETransition(currentItem)
+
const currentId = currentItem.attr('id')
const type = currentItem.attr('type')
const ts = new Date().getTime()
@@ -1076,6 +1574,13 @@ const FormBuilder = function (opts, element, $) {
elem.setAttribute('for', newForId)
})
+ //Copy selects(includes subtype if applicable)
+ const selects = currentItem.find('select')
+ selects.each(function (i) {
+ const select = this
+ $clone.find('select').eq(i).val($(select).val())
+ })
+
$clone.attr('id', data.lastID)
$clone.attr('name', cloneName)
$clone.addClass('cloned')
@@ -1099,8 +1604,7 @@ const FormBuilder = function (opts, element, $) {
return false
}
- h.updatePreview($(evt.target).closest('.form-field'))
- h.save.call(h)
+ UpdatePreviewAndSave($(evt.target).closest('.form-field'))
}
}
@@ -1123,8 +1627,7 @@ const FormBuilder = function (opts, element, $) {
} else {
$option.slideUp('250', () => {
$option.remove()
- h.updatePreview($field)
- h.save.call(h)
+ UpdatePreviewAndSave($field)
})
}
})
@@ -1266,16 +1769,104 @@ const FormBuilder = function (opts, element, $) {
e.target.value = forceNumber(e.target.value)
})
+ $stage.on('click touchstart', '.btnAddControl', function (evt) {
+ const btn = $(evt.currentTarget)
+
+ cloneControls = $cbUL.clone()
+
+ cloneControls.hover(
+ function () {},
+ function () {
+ cloneControls.remove()
+ },
+ )
+
+ cloneControls.on('click', 'li', ({ target }) => {
+ insertTargetIsColumn = true
+ insertingNewControl = true
+ $targetInsertWrapper = btn
+
+ const $control = $(target).closest('li')
+ h.stopIndex = undefined
+ processControl($control)
+ h.save.call(h)
+
+ cloneControls.remove()
+ })
+
+ $stage.append(cloneControls)
+
+ if (btn.index() == 0) {
+ cloneControls.css({
+ position: 'fixed',
+ left: btn.offset().left,
+ top: btn.offset().top - $(window).scrollTop(),
+ })
+ } else {
+ cloneControls.css({
+ position: 'fixed',
+ left: btn.offset().left - 80,
+ top: btn.offset().top - $(window).scrollTop(),
+ })
+ }
+
+ //Ensure the bottom of the menu is visible when close to the bottom of page
+ const bottomOfClone = cloneControls.offset().top + cloneControls.outerHeight()
+ const bottomOfScreen = $(window).scrollTop() + $(window).innerHeight()
+ if (bottomOfClone > bottomOfScreen) {
+ cloneControls.css({ top: parseInt(cloneControls.css('top')) - (bottomOfClone - bottomOfScreen) })
+ }
+ })
+
// Copy field
$stage.on('click touchstart', `.${css_prefix_text}copy`, function (evt) {
evt.preventDefault()
const currentItem = $(evt.target).parent().parent('li')
const $clone = cloneItem(currentItem)
- $clone.insertAfter(currentItem)
- h.updatePreview($clone)
- h.save.call(h)
+ prepareCloneWrappers($clone, currentItem)
+ UpdatePreviewAndSave($clone)
+
+ h.tmpCleanPrevHolder($clone.find('.prev-holder'))
+
+ if (opts.editOnAdd) {
+ h.closeField(data.lastID, false)
+ }
})
+ function prepareCloneWrappers($clone, currentItem) {
+ if (!enhancedBootstrapEnabled()) {
+ $clone.insertAfter(currentItem)
+ return
+ }
+
+ const inputClassElement = $(`#className-${currentItem.attr('id')}`)
+ const columnData = prepareFieldRow({})
+
+ const rowWrapper = m('div', null, {
+ id: `${$clone.attr('id')}-row`,
+ className: `row row-${columnData.rowNumber} ${rowWrapperClass}`,
+ })
+
+ const colWrapper = m('div', null, {
+ id: `${$clone.attr('id')}-cont`,
+ className: `${h.getBootstrapColumnClass(inputClassElement.val())} ${colWrapperClass}`,
+ })
+ $(colWrapper).appendTo(rowWrapper)
+
+ let insertAfterElement
+ if (currentItem.parent().is('div')) {
+ insertAfterElement = currentItem.closest(rowWrapperClassSelector)
+ } else if (currentItem.parent().is('ul')) {
+ insertAfterElement = currentItem
+ }
+
+ $(rowWrapper).insertAfter(insertAfterElement)
+ $clone.appendTo(colWrapper)
+
+ setupSortableRowWrapper(rowWrapper)
+ syncFieldWithNewRow($clone.attr('id'))
+ }
+
// Delete field
$stage.on('click touchstart', '.delete-confirm', e => {
e.preventDefault()
@@ -1309,6 +1900,392 @@ const FormBuilder = function (opts, element, $) {
}
})
+ var gridMode = false
+ var gridModeTargetField
+ let gridModeStartX
+ let gridModeStartY
+ $stage.on('click touchstart', '.grid-button', e => {
+ e.preventDefault()
+
+ const ID = $(e.target).parents('.form-field:eq(0)').attr('id')
+ gridModeTargetField = $(document.getElementById(ID))
+ gridModeStartX = e.pageX
+ gridModeStartY = e.pageY
+
+ toggleGridModeActive()
+ })
+
+ //Use mousewheel to work resizing
+ $stage.bind('mousewheel', function (e) {
+ if (gridMode) {
+ e.preventDefault()
+
+ const parentCont = gridModeTargetField.closest('div')
+ const currentColValue = h.getBootstrapColumnValue(parentCont.attr('class'))
+
+ let nextColSize
+ if (e.originalEvent.wheelDelta / 120 > 0) {
+ nextColSize = parseInt(currentColValue) + 1
+ } else {
+ nextColSize = parseInt(currentColValue) - 1
+ }
+
+ if (nextColSize > 12) {
+ h.showToast('
Column Size cannot exceed 12')
+ return
+ }
+
+ if (nextColSize < 1) {
+ h.showToast('
Column Size cannot be less than 1')
+ return
+ }
+
+ //Check overall column value, do not allow the entire row to exceed 12
+ const rowWrapper = gridModeTargetField.closest(rowWrapperClassSelector)
+
+ let totalRowValueCount = nextColSize
+ rowWrapper.children(`div${colWrapperClassSelector}`).each((i, elem) => {
+ const colWrapper = $(`#${elem.id}`)
+ const fieldID = colWrapper.find('li').attr('id')
+
+ if (fieldID != gridModeTargetField.attr('id')) {
+ totalRowValueCount += h.getBootstrapColumnValue($(`#${fieldID}-cont`).attr('class'))
+ }
+ })
+
+ if (totalRowValueCount > 12) {
+ h.showToast('
There is a maximum of 12 columns per row')
+ return
+ }
+
+ h.syncBootstrapColumnWrapperAndClassProperty(gridModeTargetField.attr('id'), nextColSize)
+ gridModeTargetField.attr('manuallyChangedDefaultColumnClass', true)
+
+ buildGridModeCurrentRowInfo()
+ h.toggleHighlight(gridModeTargetField)
+ }
+ })
+
+ //Use W A S D or Arrow Keys to move the field up/down/left/right across the form
+ //Use R to auto-size all columns in the row equally
+ $(document).keydown(e => {
+ if (gridMode) {
+ e.preventDefault()
+ const rowWrapper = gridModeTargetField.closest(rowWrapperClassSelector)
+
+ if (e.keyCode == 87 || e.keyCode == 38) {
+ moveFieldUp(rowWrapper)
+ }
+
+ if (e.keyCode == 83 || e.keyCode == 40) {
+ moveFieldDown(rowWrapper)
+ }
+
+ if (e.keyCode == 65 || e.keyCode == 37) {
+ moveFieldLeft()
+ }
+
+ if (e.keyCode == 68 || e.keyCode == 39) {
+ moveFieldRight()
+ }
+
+ if (e.keyCode == 82) {
+ autoSizeRowColumns(rowWrapper, true)
+ }
+
+ buildGridModeCurrentRowInfo()
+ removeColumnInsertButtons(rowWrapper)
+ }
+ })
+
+ function moveFieldUp(rowWrapper) {
+ const previousRowSibling = rowWrapper.prevAll().not(tmpRowPlaceholderClassSelector).first()
+ if (previousRowSibling.length) {
+ $(gridModeTargetField.parent().parent()).swapWith(previousRowSibling)
+ } else {
+ return
+ }
+ h.toggleHighlight(gridModeTargetField)
+ }
+
+ function moveFieldDown(rowWrapper) {
+ const nextRowSibling = rowWrapper.nextAll().not(invisibleRowPlaceholderClassSelector).first()
+ if (nextRowSibling.length) {
+ $(gridModeTargetField.parent().parent()).swapWith(nextRowSibling)
+ } else {
+ return
+ }
+ h.toggleHighlight(gridModeTargetField)
+ }
+
+ function moveFieldLeft() {
+ const colSibling = gridModeTargetField.parent().prev()
+ if (colSibling.length) {
+ gridModeTargetField.parent().after(colSibling)
+ }
+ h.toggleHighlight(gridModeTargetField)
+ }
+
+ function moveFieldRight() {
+ const colSibling = gridModeTargetField.parent().next()
+ if (colSibling.length) {
+ gridModeTargetField.parent().before(colSibling)
+ }
+ h.toggleHighlight(gridModeTargetField)
+ }
+ function autoSizeRowColumns(rowWrapper, force = false) {
+ const childRowCount = rowWrapper.children(`div${colWrapperClassSelector}`).length
+ const newAutoCalcSizeValue = Math.floor(12 / childRowCount)
+
+ rowWrapper.children(`div${colWrapperClassSelector}`).each((i, elem) => {
+ const colWrapper = $(`#${elem.id}`)
+
+ //Don't auto-size the field if the user had manually adjusted it during this session
+ if (!force && colWrapper.find('li').attr('manuallyChangedDefaultColumnClass') == 'true') {
+ h.showToast(`Preserving column size of field ${i + 1} because you had personally adjusted it`, 4000)
+ return
+ }
+
+ h.syncBootstrapColumnWrapperAndClassProperty(elem.id.replace('-cont', ''), newAutoCalcSizeValue)
+ })
+ }
+
+ function syncFieldWithNewRow(fieldID) {
+ if (fieldID) {
+ const inputClassElement = $(`#className-${fieldID.replace('-cont', '')}`)
+ if (inputClassElement.val()) {
+ const oldRow = h.getRowClass(inputClassElement.val())
+ const wrapperRow = h.getRowClass(inputClassElement.closest(rowWrapperClassSelector).attr('class'))
+ inputClassElement.val(inputClassElement.val().replace(oldRow, wrapperRow))
+ checkRowCleanup()
+ }
+ }
+ }
+
+ //When mouse moves away a certain distance, cancel grid mode
+ $(document).mousemove(e => {
+ if (
+ gridMode &&
+ h.getDistanceBetweenPoints(gridModeStartX, gridModeStartY, e.pageX, e.pageY) > config.opts.cancelGridModeDistance
+ ) {
+ toggleGridModeActive(false)
+ }
+ })
+
+ $(document).on('checkRowCleanup', (event, data) => {
+ checkRowCleanup()
+
+ const rowWrapper = $(`#${data.rowWrapperID}`)
+ if (rowWrapper.length) {
+ autoSizeRowColumns(rowWrapper, true)
+ }
+
+ checkSetupBlankStage()
+ })
+
+ $(document).on('fieldOpened', (event, data) => {
+ const rowWrapper = $(`#${data.rowWrapperID}`)
+ if (rowWrapper.length) {
+ removeColumnInsertButtons(rowWrapper)
+ }
+ })
+
+ function checkRowCleanup() {
+ $stage.find(colWrapperClassSelector).each((i, elem) => {
+ const $colWrapper = $(elem)
+ if ($colWrapper.is(':empty') && !formBuilder.preserveTempContainers.includes($colWrapper.attr('id'))) {
+ $colWrapper.remove()
+ }
+ })
+
+ $stage
+ .children(rowWrapperClassSelector)
+ .not(tmpRowPlaceholderClassSelector)
+ .each((i, elem) => {
+ if ($(elem).children(colWrapperClassSelector).length == 0) {
+ const rowValue = h.getRowValue($(elem).attr('class'))
+ formRows = formRows.filter(x => x != rowValue)
+ $(elem).remove()
+ } else {
+ removeColumnInsertButtons($(elem))
+ }
+ })
+ }
+
+ function stageHasFields() {
+ return $stage.find('li').length > 0
+ }
+
+ function checkSetupBlankStage() {
+ if (stageHasFields() || !enhancedBootstrapEnabled()) {
+ return
+ }
+
+ const columnData = prepareFieldRow({})
+
+ const rowWrapperNode = m('div', null, {
+ id: `${h.incrementId(data.lastID)}-row`,
+ className: `row row-${columnData.rowNumber} ${rowWrapperClass}`,
+ })
+
+ $stage.append(rowWrapperNode)
+ setupSortableRowWrapper(rowWrapperNode)
+ ResetAllInvisibleRowPlaceholders()
+
+ //Create 1 invisible placeholder which will allow the initial drag anywhere in the stage
+ $stage
+ .find(tmpRowPlaceholderClassSelector)
+ .eq(0)
+ .removeClass(invisibleRowPlaceholderClass)
+ .css({ height: $stage.css('height'), backgroundColor: 'transparent' })
+ }
+
+ function toggleGridModeActive(active = true) {
+ if (active) {
+ gridMode = true
+ h.showToast('Starting Grid Mode - Use the mousewheel to resize.', 1500)
+
+ //Hide controls
+ $cbUL.css('display', 'none')
+ $(d.formActions).css('display', 'none')
+
+ //Cleanup temp artifacts
+ cleanupTempPlaceholders()
+
+ buildGridModeHelp()
+ h.closeAllEdit()
+ h.toggleHighlight(gridModeTargetField)
+ HideInvisibleRowPlaceholders()
+ } else {
+ h.showToast('Grid Mode Finished', 1500)
+
+ //If when exiting grid mode and the row columns end up being > 12 (This can happen if the user moved a column up/down and exited), auto-resize it.
+ const rowWrapper = gridModeTargetField.closest(rowWrapperClassSelector)
+ let totalRowValueCount = 0
+
+ rowWrapper.children(`div${colWrapperClassSelector}`).each((i, elem) => {
+ const colWrapper = $(`#${elem.id}`)
+ const fieldID = colWrapper.find('li').attr('id')
+ totalRowValueCount += h.getBootstrapColumnValue($(`#${fieldID}-cont`).attr('class'))
+ })
+
+ if (totalRowValueCount > 12) {
+ autoSizeRowColumns(rowWrapper, true)
+ }
+
+ gridMode = false
+ gridModeTargetField = null
+
+ $(gridModeHelp).html('')
+
+ //Show controls
+ $cbUL.css('display', 'unset')
+ $(d.formActions).css('display', 'unset')
+ }
+ }
+
+ function buildGridModeHelp() {
+ $(gridModeHelp).html(`
+
+
Grid Mode
+
+
+
+
+ Action |
+ Result |
+
+
+
+
+ MOUSEWHEEL |
+ Adjust the field column size |
+
+
+ W or ↑ |
+ Move entire row up |
+
+
+ S or ↓ |
+ Move entire row down |
+
+
+ A or ← |
+ Move field left within the row |
+
+
+ D or → |
+ Move field right within the row |
+
+
+ R |
+ Resize all fields within the row to be maximally equal |
+
+
+
+
+
+
Current Row Fields
+
+
+
+
+
+
+
+
+
+ Field |
+ Size |
+
+
+
+
+
+
+
+
+ `)
+
+ buildGridModeCurrentRowInfo()
+ }
+
+ function buildGridModeCurrentRowInfo() {
+ $(gridModeHelp).find('.gridHelpCurrentRow tbody').empty()
+
+ const rowWrapper = gridModeTargetField.closest(rowWrapperClassSelector)
+
+ rowWrapper.children(`div${colWrapperClassSelector}`).each((i, elem) => {
+ const colWrapper = $(`#${elem.id}`)
+ const fieldID = colWrapper.find('li').attr('id')
+ const fieldType = $(`#${fieldID}`).attr('type')
+
+ let label = $(`#label-${fieldID}`).html()
+ if (fieldType == 'hidden' || fieldType == 'paragraph') {
+ label = $(`#name-${fieldID}`).val()
+ }
+
+ if (!label) {
+ label = $(`#${fieldID}`).attr('id')
+ }
+
+ //Highlight the current field being worked on
+ let currentFieldClass = ''
+ if (gridModeTargetField.attr('id') == fieldID) {
+ currentFieldClass = 'currentGridModeFieldHighlight'
+ }
+
+ $(gridModeHelp).find('.gridHelpCurrentRow tbody').append(`
+
+ ${label} |
+
+ ${h.getBootstrapColumnValue($(`#${fieldID}-cont`).attr('class'))}
+ |
+
+ `)
+ })
+ }
+
// Update button style selection
$stage.on('click', '.style-wrap button', e => {
const $button = $(e.target)
@@ -1318,8 +2295,7 @@ const FormBuilder = function (opts, element, $) {
$btnStyle.val(styleVal)
$button.siblings('.btn').removeClass('selected')
$button.addClass('selected')
- h.updatePreview($btnStyle.closest('.form-field'))
- h.save()
+ UpdatePreviewAndSave($btnStyle.closest('.form-field'))
})
// Attach a callback to toggle required asterisk
@@ -1441,6 +2417,9 @@ const FormBuilder = function (opts, element, $) {
if (opts.stickyControls.enable) {
h.stickyControls($stage)
}
+
+ checkSetupBlankStage()
+
clearTimeout(onRenderTimeout)
}, 0)
})
diff --git a/src/js/form-render.js b/src/js/form-render.js
index b02faf503..5c5c0fc8c 100644
--- a/src/js/form-render.js
+++ b/src/js/form-render.js
@@ -100,7 +100,7 @@ class FormRender {
// Check if this rowID is created yet or not.
let rowGroupNode = document.getElementById(rowID)
if (!rowGroupNode) {
- rowGroupNode = utils.markup('div', null, { id: rowID, className: 'row form-inline' })
+ rowGroupNode = utils.markup('div', null, { id: rowID, className: 'row' })
renderedFormWrap.appendChild(rowGroupNode)
}
rowGroupNode.appendChild(field)
diff --git a/src/js/helpers.js b/src/js/helpers.js
index 480cf9a61..de70b87f3 100644
--- a/src/js/helpers.js
+++ b/src/js/helpers.js
@@ -14,6 +14,8 @@ import {
unique,
xmlAttrString,
flattenArray,
+ bootstrapColumnRegex,
+ getAllGridRelatedClasses,
} from './utils'
import events from './events'
import { config } from './config'
@@ -37,6 +39,7 @@ export default class Helpers {
this.layout = layout
this.handleKeyDown = this.handleKeyDown.bind(this)
this.formBuilder = formBuilder
+ this.toastTimer = null
}
/**
@@ -193,58 +196,94 @@ export default class Helpers {
const _this = this
if (form.childNodes.length !== 0) {
- // build data object
- forEach(form.childNodes, function (index, field) {
- const $field = $(field)
-
- if (!$field.hasClass('disabled-field')) {
- let fieldData = _this.getTypes($field)
- const $roleInputs = $('.roles-field:checked', field)
- const roleVals = $roleInputs.map(index => $roleInputs[index].value).get()
-
- fieldData = Object.assign({}, fieldData, _this.getAttrVals(field))
-
- if (fieldData.subtype) {
- if (fieldData.subtype === 'quill') {
- const id = `${fieldData.name}-preview`
- if (window.fbEditors.quill[id]) {
- const instance = window.fbEditors.quill[id].instance
- const data = instance.getContents()
- fieldData.value = window.JSON.stringify(data.ops)
- }
- } else if (fieldData.subtype === 'tinymce' && window.tinymce) {
- const id = `${fieldData.name}-preview`
- if (window.tinymce.editors[id]) {
- const editor = window.tinymce.editors[id]
- fieldData.value = editor.getContent()
+ const fields = []
+ //Get form-fields as expected(within rowWrapper)
+ forEach(form.childNodes, function (_index, fieldWrapper) {
+ const $fieldWrapper = $(fieldWrapper)
+
+ //Go one level deeper than the row container to find the li
+ $fieldWrapper.find('li.form-field').each(function (i, field) {
+ fields.push(field)
+ })
+ })
+
+ //Get form-fields that might still be currently editing and are temporarily outside a rowWrapper
+ forEach(form.childNodes, function (_index, testElement) {
+ const $testElement = $(testElement)
+ if ($testElement.is('li') && $testElement.hasClass('form-field')) {
+ fields.push(testElement)
+ }
+ })
+
+ if (fields.length) {
+ fields.forEach(field => {
+ const $field = $(field)
+
+ if (!$field.hasClass('disabled-field')) {
+ let fieldData = _this.getTypes($field)
+ const $roleInputs = $('.roles-field:checked', field)
+ const roleVals = $roleInputs.map(index => $roleInputs[index].value).get()
+
+ fieldData = Object.assign({}, fieldData, _this.getAttrVals(field))
+
+ if (fieldData.subtype) {
+ if (fieldData.subtype === 'quill') {
+ const id = `${fieldData.name}-preview`
+ if (window.fbEditors.quill[id]) {
+ const instance = window.fbEditors.quill[id].instance
+ const data = instance.getContents()
+ fieldData.value = window.JSON.stringify(data.ops)
+ }
+ } else if (fieldData.subtype === 'tinymce' && window.tinymce) {
+ const id = `${fieldData.name}-preview`
+ if (window.tinymce.editors[id]) {
+ const editor = window.tinymce.editors[id]
+ fieldData.value = editor.getContent()
+ }
}
}
- }
- if (roleVals.length) {
- fieldData.role = roleVals.join(',')
- }
+ if (roleVals.length) {
+ fieldData.role = roleVals.join(',')
+ }
- fieldData.className = fieldData.className || fieldData.class
+ fieldData.className = fieldData.className || fieldData.class
+
+ //If no other fields were added to the same row and the user did not do anything with this information, then remove it when exporting the config
+ if (
+ fieldData.className &&
+ $field.attr('addeddefaultcolumnclass') == 'true' &&
+ $field.closest(this.formBuilder.rowWrapperClassSelector).children().length == 1 &&
+ fieldData.className.includes(config.opts.defaultGridColumnClass)
+ ) {
+ const classes = getAllGridRelatedClasses(fieldData.className)
+
+ if (classes && classes.length > 0) {
+ classes.forEach(element => {
+ fieldData.className = fieldData.className.replace(element, '').trim()
+ })
+ }
+ }
- if (fieldData.className) {
- const match = /(?:^|\s)btn-(.*?)(?:\s|$)/g.exec(fieldData.className)
- if (match) {
- fieldData.style = match[1]
+ if (fieldData.className) {
+ const match = /(?:^|\s)btn-(.*?)(?:\s|$)/g.exec(fieldData.className)
+ if (match) {
+ fieldData.style = match[1]
+ }
}
- }
- fieldData = trimObj(fieldData)
+ fieldData = trimObj(fieldData)
- const multipleField = fieldData.type && fieldData.type.match(d.optionFieldsRegEx)
+ const multipleField = fieldData.type && fieldData.type.match(d.optionFieldsRegEx)
- if (multipleField) {
- fieldData.values = _this.fieldOptionData($field)
- }
+ if (multipleField) {
+ fieldData.values = _this.fieldOptionData($field)
+ }
- formData.push(fieldData)
- }
- })
+ formData.push(fieldData)
+ }
+ })
+ }
}
return formData
@@ -676,7 +715,7 @@ export default class Helpers {
removeAllFields(stage) {
const i18n = mi18n.current
const opts = config.opts
- const fields = stage.querySelectorAll('li.form-field')
+ const fields = stage.querySelectorAll(this.formBuilder.rowWrapperClassSelector)
const markEmptyArray = []
if (!fields.length) {
@@ -736,14 +775,12 @@ export default class Helpers {
*/
closeAllEdit() {
const _this = this
- const fields = $('> li.editing', _this.d.stage)
- const toggleBtns = $('.toggle-form', _this.d.stage)
- const editPanels = $('.frm-holder', fields)
-
- toggleBtns.removeClass('open')
- fields.removeClass('editing')
- $('.prev-holder', fields).show()
- editPanels.hide()
+
+ $(_this.d.stage)
+ .find('li.form-field')
+ .each((i, elem) => {
+ this.closeField(elem.id, false)
+ })
}
/**
@@ -757,10 +794,37 @@ export default class Helpers {
if (!field) {
return field
}
+
+ if ($(field).hasClass('editing')) {
+ return this.closeField(fieldId, animate)
+ } else {
+ return this.openField(fieldId, animate)
+ }
+ }
+
+ closeField(fieldId, animate = true) {
+ const _this = this
+
+ const field = document.getElementById(fieldId)
+ if (!field) {
+ return field
+ }
+
const $editPanel = $('.frm-holder', field)
const $preview = $('.prev-holder', field)
+
+ let currentlyEditing = false
+ if ($(field).hasClass('editing')) {
+ currentlyEditing = true
+ }
+
+ if (!currentlyEditing) {
+ return field
+ }
+
field.classList.toggle('editing')
$('.toggle-form', field).toggleClass('open')
+
if (animate) {
$preview.slideToggle(250)
$editPanel.slideToggle(250)
@@ -769,14 +833,84 @@ export default class Helpers {
$editPanel.toggle()
}
this.updatePreview($(field))
- if (field.classList.contains('editing')) {
- this.formBuilder.currentEditPanel = $editPanel[0]
- config.opts.onOpenFieldEdit($editPanel[0])
- document.dispatchEvent(events.fieldEditOpened)
+
+ const liContainer = $(`#${fieldId}`)
+ const rowContainer = $(`#${fieldId}-cont`)
+
+ //Put the li back in its place
+ rowContainer.append(liContainer)
+
+ this.removeContainerProtection(rowContainer.attr('id'))
+
+ config.opts.onCloseFieldEdit($editPanel[0])
+ document.dispatchEvent(events.fieldEditClosed)
+
+ const prevHolder = liContainer.find('.prev-holder')
+ const resultsTimeout = setTimeout(() => {
+ clearTimeout(resultsTimeout)
+ const cleanResults = _this.tmpCleanPrevHolder(prevHolder)
+
+ cleanResults.forEach(result => {
+ if (result['columnInfo'].columnSize) {
+ const currentClassRow = rowContainer.attr('class')
+ if (currentClassRow != result['columnInfo'].columnSize) {
+ //Keep the wrapping column div sync'd to the column property from the field
+ rowContainer.attr('class', `${result['columnInfo'].columnSize} ${this.formBuilder.colWrapperClass}`)
+ _this.tmpCleanPrevHolder(prevHolder)
+ }
+ }
+ })
+ }, 300)
+
+ return field
+ }
+
+ openField(fieldId, animate = true) {
+ const field = document.getElementById(fieldId)
+ if (!field) {
+ return field
+ }
+
+ const $editPanel = $('.frm-holder', field)
+ const $preview = $('.prev-holder', field)
+
+ let currentlyEditing = false
+ if ($(field).hasClass('editing')) {
+ currentlyEditing = true
+ }
+
+ if (currentlyEditing) {
+ return field
+ }
+
+ field.classList.toggle('editing')
+ $('.toggle-form', field).toggleClass('open')
+
+ if (animate) {
+ $preview.slideToggle(250)
+ $editPanel.slideToggle(250)
} else {
- config.opts.onCloseFieldEdit($editPanel[0])
- document.dispatchEvent(events.fieldEditClosed)
+ $preview.toggle()
+ $editPanel.toggle()
}
+ this.updatePreview($(field))
+
+ const liContainer = $(`#${fieldId}`)
+ const colWrapper = $(`#${fieldId}-cont`)
+ const rowWrapper = colWrapper.closest(this.formBuilder.rowWrapperClassSelector)
+
+ //Mark the container as something we don't want to cleanup immediately
+ this.formBuilder.preserveTempContainers.push(colWrapper.attr('id'))
+
+ //Temporarily move the li outside(keeping same relative overall spot in the form) so that the field details show in full width regardless of its column size
+ liContainer.insertAfter(rowWrapper)
+
+ this.formBuilder.currentEditPanel = $editPanel[0]
+ config.opts.onOpenFieldEdit($editPanel[0])
+ document.dispatchEvent(events.fieldEditOpened)
+
+ $(document).trigger('fieldOpened', [{ rowWrapperID: rowWrapper.attr('id') }])
+
return field
}
@@ -896,6 +1030,7 @@ export default class Helpers {
}
const $field = $(field)
+ const fieldRowWrapper = $field.closest(this.formBuilder.rowWrapperClassSelector)
if (!field) {
config.opts.notify.warning('Field not found')
return false
@@ -919,6 +1054,13 @@ export default class Helpers {
}
document.dispatchEvent(events.fieldRemoved)
+
+ this.removeContainerProtection(`${fieldID}-cont`)
+
+ setTimeout(() => {
+ $(document).trigger('checkRowCleanup', [{ rowWrapperID: fieldRowWrapper.attr('id') }])
+ }, 300)
+
return fieldRemoved
}
@@ -1121,6 +1263,8 @@ export default class Helpers {
* @return {Array|String} formData
*/
getFormData(type = 'js', formatted = false) {
+ this.closeAllEdit()
+
const h = this
const data = {
js: () => h.prepData(h.d.stage),
@@ -1130,4 +1274,172 @@ export default class Helpers {
return data[type](formatted)
}
+
+ tmpCleanPrevHolder($prevHolder) {
+ const _this = this
+ const cleanedFields = []
+
+ const formGroup = $prevHolder.find('.form-group')
+ tmpCleanColumnInfo(formGroup)
+
+ formGroup.find('*').each(function (i, field) {
+ tmpCleanColumnInfo($(field))
+ })
+
+ function tmpCleanColumnInfo($field) {
+ var classAttr = $field.attr('class')
+
+ if (typeof classAttr !== 'undefined' && classAttr !== false) {
+ const parseResult = _this.tryParseColumnInfo($field[0])
+
+ $field.attr('class', $field.attr('class').replace('col-', 'tmp-col-'))
+ $field.attr('class', $field.attr('class').replace('row', 'tmp-row'))
+
+ const result = {}
+ result['field'] = $field
+ result['columnInfo'] = parseResult
+ cleanedFields.push(result)
+ }
+ }
+
+ return cleanedFields
+ }
+
+ tryParseColumnInfo(data) {
+ const result = {}
+
+ if (data.className) {
+ const classes = getAllGridRelatedClasses(data.className)
+
+ if (classes && classes.length > 0) {
+ classes.forEach(element => {
+ if (element.startsWith('row-')) {
+ result['rowNumber'] = parseInt(element.replace('row-', '').trim())
+ } else {
+ result['columnSize'] = element
+ }
+ })
+ }
+ }
+
+ return result
+ }
+
+ //Remove one reference that protected this potentially empty container. There may be other open fields needing the container
+ removeContainerProtection(containerID) {
+ var index = this.formBuilder.preserveTempContainers.indexOf(containerID)
+ if (index !== -1) {
+ this.formBuilder.preserveTempContainers.splice(index, 1)
+ }
+ }
+
+ //Briefly highlight on/off
+ toggleHighlight(field, ms = 600) {
+ field.addClass('moveHighlight')
+ setTimeout(function () {
+ field.removeClass('moveHighlight')
+ }, ms)
+ }
+
+ showToast(msg, timeout = 3000) {
+ if (this.toastTimer != null) {
+ window.clearTimeout(this.toastTimer)
+ this.toastTimer = null
+ }
+
+ this.toastTimer = setTimeout(function () {
+ $('.snackbar').removeClass('show')
+ }, timeout)
+
+ $('.snackbar').addClass('show').html(msg)
+ }
+
+ getDistanceBetweenPoints(x1, y1, x2, y2) {
+ const y = x2 - x1
+ const x = y2 - y1
+
+ return Math.floor(Math.sqrt(x * x + y * y))
+ }
+
+ //Return full row name (row-1)
+ getRowClass(className) {
+ if (!className) {
+ return
+ }
+
+ const splitClasses = className.split(' ').filter(x => x.startsWith('row-'))
+ if (splitClasses && splitClasses.length > 0) {
+ return splitClasses[0]
+ }
+ }
+
+ //Return the row value i.e row-2 would return 2
+ getRowValue(className) {
+ if (!className) {
+ return 0
+ }
+
+ const rowClass = this.getRowClass(className)
+ if (rowClass) {
+ return parseInt(rowClass.split('-')[1])
+ }
+ }
+
+ //Example className of 'row row-1' would be changed for 'row row-4' where 4 is the newValue
+ changeRowClass(className, newValue) {
+ const rowClass = this.getRowClass(className)
+ return className.replace(rowClass, `row-${newValue}`)
+ }
+
+ //Return the column size i.e col-md-6 would return 6
+ getBootstrapColumnValue(className) {
+ if (!className) {
+ return 0
+ }
+
+ const bootstrapClass = this.getBootstrapColumnClass(className)
+ if (bootstrapClass) {
+ return parseInt(bootstrapClass.split('-')[2])
+ }
+ }
+
+ //Return the prefix (col-md)
+ getBootstrapColumnPrefix(className) {
+ if (!className) {
+ return 0
+ }
+
+ const bootstrapClass = this.getBootstrapColumnClass(className)
+ if (bootstrapClass) {
+ return `${bootstrapClass.split('-')[0]}-${bootstrapClass.split('-')[1]}`
+ }
+ }
+
+ //Return full class name (col-md-6)
+ getBootstrapColumnClass(className) {
+ if (!className) {
+ return
+ }
+
+ const splitClasses = className.split(' ').filter(className => bootstrapColumnRegex.test(className))
+ if (splitClasses && splitClasses.length > 0) {
+ return splitClasses[0]
+ }
+ }
+
+ //Example className of 'row row-1 col-md-6' would be changed for 'row row-1 col-md-4' where 4 is the newValue
+ changeBootstrapClass(className, newValue) {
+ const boostrapClass = this.getBootstrapColumnClass(className)
+ return className.replace(boostrapClass, `${this.getBootstrapColumnPrefix(className)}-${newValue}`)
+ }
+
+ syncBootstrapColumnWrapperAndClassProperty(fieldID, newValue) {
+ const colWrapper = $(`#${fieldID}-cont`)
+ colWrapper.attr('class', this.changeBootstrapClass(colWrapper.attr('class'), newValue))
+
+ const inputClassElement = $(`#className-${fieldID}`)
+ if (inputClassElement.val()) {
+ inputClassElement.val(this.changeBootstrapClass(inputClassElement.val(), newValue))
+ }
+ }
}
diff --git a/src/js/layout.js b/src/js/layout.js
index 43271f821..976c57842 100644
--- a/src/js/layout.js
+++ b/src/js/layout.js
@@ -1,23 +1,22 @@
// LAYOUT.JS
import utils from './utils'
+import { getAllGridRelatedClasses } from './utils'
const processClassName = (data, field) => {
// wrap the output in a form-group div & return
let className = data.id ? `formbuilder-${data.type} form-group field-${data.id}` : ''
if (data.className) {
- let classes = data.className.split(' ')
// Lift any col- and row- type class to the form-group wrapper. The row- class denotes the row group it should go to
- classes = classes.filter(className => /^col-(xs|sm|md|lg)-([^\s]+)/.test(className) || className.startsWith('row-'))
+ const classes = getAllGridRelatedClasses(data.className)
if (classes && classes.length > 0) {
className += ` ${classes.join(' ')}`
}
// Now that the col- types were lifted, remove from the actual input field
- for (let index = 0; index < classes.length; index++) {
- const element = classes[index]
- field.classList.remove(element)
+ if (field.classList) {
+ field.classList.remove(...classes)
}
}
@@ -50,18 +49,18 @@ export default class layout {
}
return this.markup('div', [label, field], {
- className: processClassName(data, field)
+ className: processClassName(data, field),
})
},
noLabel: (field, label, help, data) => {
return this.markup('div', field, {
- className: processClassName(data, field)
+ className: processClassName(data, field),
})
},
hidden: field => {
// no wrapper any any visible elements
return field
- }
+ },
}
// merge in any custom templates
@@ -150,7 +149,7 @@ export default class layout {
// generate a label element
return this.markup('label', labelContents, {
for: this.data.id,
- className: `formbuilder-${this.data.type}-label`
+ className: `formbuilder-${this.data.type}-label`,
})
}
@@ -171,7 +170,7 @@ export default class layout {
// generate the default help element
return this.markup('span', '?', {
className: 'tooltip-element',
- tooltip: this.data.description
+ tooltip: this.data.description,
})
}
diff --git a/src/js/utils.js b/src/js/utils.js
index c69928943..e191c286e 100644
--- a/src/js/utils.js
+++ b/src/js/utils.js
@@ -17,7 +17,7 @@ window.fbEditors = {
* @param {Object} attrs {attrName: attrValue}
* @return {Object} Object trimmed of null or undefined values
*/
-export const trimObj = function(attrs, removeFalse = false) {
+export const trimObj = function (attrs, removeFalse = false) {
const xmlRemove = [null, undefined, '']
if (removeFalse) {
xmlRemove.push(false)
@@ -40,7 +40,7 @@ export const trimObj = function(attrs, removeFalse = false) {
* @param {String} attr
* @return {Boolean}
*/
-export const validAttr = function(attr) {
+export const validAttr = function (attr) {
const invalid = [
'values',
'enableOther',
@@ -127,7 +127,7 @@ export const safeAttrName = name => {
export const hyphenCase = str => {
// eslint-disable-next-line no-useless-escape
str = str.replace(/[^\w\s\-]/gi, '')
- str = str.replace(/([A-Z])/g, function($1) {
+ str = str.replace(/([A-Z])/g, function ($1) {
return '-' + $1.toLowerCase()
})
@@ -162,10 +162,10 @@ export const bindEvents = (element, events) => {
* @param {Object} field
* @return {String} name
*/
-export const nameAttr = (function() {
+export const nameAttr = (function () {
let lepoch
let counter = 0
- return function(field) {
+ return function (field) {
const epoch = new Date().getTime()
if (epoch === lepoch) {
++counter
@@ -204,7 +204,7 @@ export const getContentType = content => {
* @param {Object} attributes
* @return {Object} DOM Element
*/
-export const markup = function(tag, content = '', attributes = {}) {
+export const markup = function (tag, content = '', attributes = {}) {
let contentType = getContentType(content)
const { events, ...attrs } = attributes
const field = document.createElement(tag)
@@ -308,7 +308,7 @@ export const parseOptions = options => {
export const parseUserData = userData => {
const data = []
- if(userData.length){
+ if (userData.length) {
const values = userData[0].getElementsByTagName('value')
for (let i = 0; i < values.length; i++) {
@@ -399,7 +399,7 @@ export const escapeAttrs = attrs => {
}
// forEach that can be used on nodeList
-export const forEach = function(array, callback, scope) {
+export const forEach = function (array, callback, scope) {
for (let i = 0; i < array.length; i++) {
callback.call(scope, i, array[i]) // passes back stuff we need
}
@@ -511,9 +511,7 @@ export const getStyles = (scriptScr, path) => {
link.href = (path || '') + src
document.head.appendChild(link)
} else {
- $(``)
- .attr('id', id)
- .appendTo($(document.head))
+ $(``).attr('id', id).appendTo($(document.head))
}
// record this is cached
@@ -527,7 +525,7 @@ export const getStyles = (scriptScr, path) => {
* @return {String} str capitalized string
*/
export const capitalize = str => {
- return str.replace(/\b\w/g, function(m) {
+ return str.replace(/\b\w/g, function (m) {
return m.toUpperCase()
})
}
@@ -609,7 +607,7 @@ export const forceNumber = str => str.replace(/[^0-9]/g, '')
// subtract the contents of 1 array from another
export const subtract = (arr, from) => {
- return from.filter(function(a) {
+ return from.filter(function (a) {
return !~this.indexOf(a)
}, arr)
}
@@ -640,6 +638,12 @@ export const removeStyle = id => {
return elem.parentElement.removeChild(elem)
}
+export const bootstrapColumnRegex = /^col-(xs|sm|md|lg)-([^\s]+)/
+
+export const getAllGridRelatedClasses = className => {
+ return className.split(' ').filter(x => bootstrapColumnRegex.test(x) || x.startsWith('row-'))
+}
+
/**
*
* @param {String} str
@@ -738,4 +742,19 @@ utils.splitObject = (obj, keys) => {
return [kept, rest]
}
+$.fn.swapWith = function (that) {
+ var $this = this
+ var $that = $(that)
+
+ // create temporary placeholder
+ var $temp = $('')
+
+ // 3-step swap
+ $this.before($temp)
+ $that.before($this)
+ $temp.before($that).remove()
+
+ return $this
+}
+
export default utils
diff --git a/src/sass/_stage.scss b/src/sass/_stage.scss
index 296cb3cc3..70fc1786a 100644
--- a/src/sass/_stage.scss
+++ b/src/sass/_stage.scss
@@ -36,7 +36,7 @@
overflow: hidden;
}
- > li {
+ li.form-field {
&:hover {
.field-actions {
opacity: 1;
@@ -54,7 +54,7 @@
}
}
- li {
+ li.form-field {
position: relative;
padding: 6px;
clear: both;
@@ -432,27 +432,29 @@
margin-left: 2%;
background: $input-border-color;
margin-bottom: 0;
- border-radius: 5px;
+ border-radius: 2px;
list-style: none;
padding: 0;
> li {
- display: flex;
cursor: move;
margin: 1px;
- padding-right: 28px;
+
+ padding: 6px;
+ background-color: $white;
&:nth-child(1) .remove {
display: none;
}
.remove {
- position: absolute;
+ position: relative;
opacity: 1;
- right: 8px;
+ float: right;
+ right: 14px;
height: 18px;
width: 18px;
- top: 14px;
+ top: 8px;
font-size: 12px;
padding: 0;
color: $error;
@@ -461,7 +463,7 @@
}
&:hover {
- background-color: $error;
+ background-color: $error !important;
text-decoration: none;
color: $white;
}
@@ -730,3 +732,139 @@
font-size: 12px;
cursor: default;
}
+
+/* ------------ Toast Message ------------ */
+.snackbar {
+ visibility: hidden; /* Hidden by default. Visible on click */
+ min-width: 250px; /* Set a default minimum width */
+ margin-left: -125px; /* Divide value of min-width by 2 */
+ background-color: #333; /* Black background color */
+ color: #fff; /* White text color */
+ text-align: center; /* Centered text */
+ border-radius: 2px; /* Rounded borders */
+ padding: 16px; /* Padding */
+ position: fixed; /* Sit on top of the screen */
+ z-index: 1; /* Add a z-index if needed */
+ left: 50%; /* Center the snackbar */
+ bottom: 30px; /* 30px from the bottom */
+}
+
+.snackbar.show {
+ visibility: visible;
+ -webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
+ animation: fadein 0.5s, fadeout 0.5s 2.5s;
+}
+
+@-webkit-keyframes fadein {
+ from {
+ bottom: 0;
+ opacity: 0;
+ }
+ to {
+ bottom: 30px;
+ opacity: 1;
+ }
+}
+
+@keyframes fadein {
+ from {
+ bottom: 0;
+ opacity: 0;
+ }
+ to {
+ bottom: 30px;
+ opacity: 1;
+ }
+}
+
+@-webkit-keyframes fadeout {
+ from {
+ bottom: 30px;
+ opacity: 1;
+ }
+ to {
+ bottom: 0;
+ opacity: 0;
+ }
+}
+
+@keyframes fadeout {
+ from {
+ bottom: 30px;
+ opacity: 1;
+ }
+ to {
+ bottom: 0;
+ opacity: 0;
+ }
+}
+/* ------------ END TOOLTIP ------------ */
+
+.ui-state-highlight {
+ border-radius: 3px;
+ border: 1px dashed #0d99f2;
+ border-radius: 3px;
+ background-color: #e5f5f8;
+ width: 12px;
+}
+
+.moveHighlight {
+ border: 1px dashed #0d99f2 !important;
+ background-color: #e5f5f8 !important;
+}
+
+.currentGridModeFieldHighlight {
+ background-color: #e5f5f8 !important;
+}
+
+.grid-mode-help {
+ background-color: $white;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+}
+
+.grid-mode-help-row1 {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ max-width: 1px;
+}
+.grid-mode-help-row2 {
+ white-space: nowrap;
+}
+
+.colHoverTempStyle {
+ padding-left: 7px !important;
+ padding-right: 7px !important;
+ flex: 95 1 0% !important;
+}
+
+.rowWrapper {
+ margin-left: 0px !important;
+ margin-right: 0px !important;
+}
+
+.btnAddControl {
+ border: 0;
+ background-color: unset;
+}
+
+.hoverColumnDropStyle {
+ border: 1px dashed #0d99f2;
+ border-radius: 3px;
+ background-color: #e5f5f8;
+ width: 20px;
+ position: fixed;
+ margin-left: 40px;
+}
+
+.hoverDropStyleInverse {
+ background-color: #0d99f2;
+ border: 1px dashed #e5f5f8;
+}
+
+.invisibleRowPlaceholder {
+ width: 0px !important;
+ position: fixed !important;
+ left: -100px !important;
+}
diff --git a/src/sass/base/_font.scss b/src/sass/base/_font.scss
index 77bd0a614..cd4beec6d 100644
--- a/src/sass/base/_font.scss
+++ b/src/sass/base/_font.scss
@@ -1,6 +1,7 @@
@font-face {
font-family: 'formbuilder-icons';
- src: url('data:application/octet-stream;base64,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') format('woff');
+ src: url('data:application/octet-stream;base64,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')
+ format('woff');
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
@@ -12,52 +13,95 @@
}
}
*/
-
- [class^="formbuilder-icon-"]:before, [class*=" formbuilder-icon-"]:before {
- font-family: "formbuilder-icons";
+
+[class^='formbuilder-icon-']:before,
+[class*=' formbuilder-icon-']:before {
+ font-family: 'formbuilder-icons';
font-style: normal;
font-weight: normal;
speak: never;
-
+
display: inline-block;
text-decoration: inherit;
width: 1em;
- margin-right: .2em;
+ margin-right: 0.2em;
text-align: center;
/* opacity: .8; */
-
+
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
-
+
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
-
+
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
- margin-left: .2em;
-
+ margin-left: 0.2em;
+
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
-
+
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
-.formbuilder-icon-autocomplete:before { content: '\e800'; } /* '' */
-.formbuilder-icon-date:before { content: '\e801'; } /* '' */
-.formbuilder-icon-checkbox:before { content: '\e802'; } /* '' */
-.formbuilder-icon-checkbox-group:before { content: '\e803'; } /* '' */
-.formbuilder-icon-radio-group:before { content: '\e804'; } /* '' */
-.formbuilder-icon-rich-text:before { content: '\e805'; } /* '' */
-.formbuilder-icon-select:before { content: '\e806'; } /* '' */
-.formbuilder-icon-textarea:before { content: '\e807'; } /* '' */
-.formbuilder-icon-text:before { content: '\e808'; } /* '' */
-.formbuilder-icon-pencil:before { content: '\e809'; } /* '' */
-.formbuilder-icon-file:before { content: '\e80a'; } /* '' */
-.formbuilder-icon-hidden:before { content: '\e80b'; } /* '' */
-.formbuilder-icon-cancel:before { content: '\e80c'; } /* '' */
-.formbuilder-icon-button:before { content: '\e80d'; } /* '' */
-.formbuilder-icon-header:before { content: '\e80f'; } /* '' */
-.formbuilder-icon-paragraph:before { content: '\e810'; } /* '' */
-.formbuilder-icon-number:before { content: '\e811'; } /* '' */
-.formbuilder-icon-copy:before { content: '\f24d'; } /* '' */
\ No newline at end of file
+.formbuilder-icon-autocomplete:before {
+ content: '\e800';
+} /* '' */
+.formbuilder-icon-date:before {
+ content: '\e801';
+} /* '' */
+.formbuilder-icon-checkbox:before {
+ content: '\e802';
+} /* '' */
+.formbuilder-icon-checkbox-group:before {
+ content: '\e803';
+} /* '' */
+.formbuilder-icon-radio-group:before {
+ content: '\e804';
+} /* '' */
+.formbuilder-icon-rich-text:before {
+ content: '\e805';
+} /* '' */
+.formbuilder-icon-select:before {
+ content: '\e806';
+} /* '' */
+.formbuilder-icon-textarea:before {
+ content: '\e807';
+} /* '' */
+.formbuilder-icon-text:before {
+ content: '\e808';
+} /* '' */
+.formbuilder-icon-pencil:before {
+ content: '\e809';
+} /* '' */
+.formbuilder-icon-file:before {
+ content: '\e80a';
+} /* '' */
+.formbuilder-icon-hidden:before {
+ content: '\e80b';
+} /* '' */
+.formbuilder-icon-cancel:before {
+ content: '\e80c';
+} /* '' */
+.formbuilder-icon-button:before {
+ content: '\e80d';
+} /* '' */
+.formbuilder-icon-header:before {
+ content: '\e80f';
+} /* '' */
+.formbuilder-icon-paragraph:before {
+ content: '\e810';
+} /* '' */
+.formbuilder-icon-number:before {
+ content: '\e811';
+} /* '' */
+.formbuilder-icon-copy:before {
+ content: '\f24d';
+} /* '' */
+.formbuilder-icon-grid:before {
+ content: url("data:image/svg+xml; utf8,
");
+}
+.formbuilder-icon-plus:before {
+ content: url("data:image/svg+xml; utf8,
");
+}