Skip to content

Commit

Permalink
fix(labels): html in xml labels breaks forms (#858)
Browse files Browse the repository at this point in the history
disabledHTMLLabels option is now set when option.dataType is === 'xml'

BREAKING CHANGE:

Existing forms created using xml dataType that have html labels will have their labels converted to text

Note: the next major release will drop xml support completely
  • Loading branch information
kevinchappell committed Nov 7, 2018
1 parent a0c282c commit ea29e79
Show file tree
Hide file tree
Showing 13 changed files with 87 additions and 39 deletions.
4 changes: 2 additions & 2 deletions demo/assets/js/demo.min.js

Large diffs are not rendered by default.

Binary file modified demo/assets/js/demo.min.js.gz
Binary file not shown.
6 changes: 3 additions & 3 deletions demo/assets/js/form-builder.min.js

Large diffs are not rendered by default.

Binary file modified demo/assets/js/form-builder.min.js.gz
Binary file not shown.
6 changes: 3 additions & 3 deletions demo/assets/js/form-render.min.js

Large diffs are not rendered by default.

Binary file modified demo/assets/js/form-render.min.js.gz
Binary file not shown.
3 changes: 2 additions & 1 deletion demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
</head>

<body>
<label for="toggleBootstrap">Toggle Bootstrap <input type="checkbox" id="toggleBootstrap" /></label>
<label for="toggleBootstrap">Toggle Bootstrap <input type="checkbox" id="toggleBootstrap" /></label> |
dataType: <label>XML <input type="radio" name="demo-dataType" value="xml" class="demo-dataType" /></label><label>JSON <input type="radio" name="demo-dataType" value="json" class="demo-dataType" /></label>
<div class="content">
<h1 class="formbuilder-title">jQuery formBuilder -
<button class="editForm">Render</button>
Expand Down
3 changes: 2 additions & 1 deletion src/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
</head>

<body>
<label for="toggleBootstrap">Toggle Bootstrap <input type="checkbox" id="toggleBootstrap" /></label>
<label for="toggleBootstrap">Toggle Bootstrap <input type="checkbox" id="toggleBootstrap" /></label> |
dataType: <label>XML <input type="radio" name="demo-dataType" value="xml" class="demo-dataType" /></label><label>JSON <input type="radio" name="demo-dataType" value="json" class="demo-dataType" /></label>
<div class="content">
<h1 class="formbuilder-title">jQuery formBuilder -
<button class="editForm">Render</button>
Expand Down
22 changes: 17 additions & 5 deletions src/demo/js/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ import { builderActions, renderActions, demoActions } from './actionButtons'
const localeSessionKey = 'formBuilder-locale'
const defaultLocale = 'en-US'

const dataTypes = document.querySelectorAll('.demo-dataType')
const dataType = window.sessionStorage.getItem('dataType') || 'json'
const changeDataType = ({ target }) => {
window.sessionStorage.setItem('dataType', target.value)
demoActions.resetDemo()
}
for (let i = 0; i < dataTypes.length; i++) {
if (dataType === dataTypes[i].value) {
dataTypes[i].checked = true
}
dataTypes[i].addEventListener('click', changeDataType, false)
}

const toggleBootStrap = ({ target }) => {
if (!target.checked) {
removeStyle('bootstrap')
Expand All @@ -18,7 +31,6 @@ const toggleBootStrap = ({ target }) => {

document.getElementById('toggleBootstrap').addEventListener('click', toggleBootStrap, false)

const dataType = 'json'
jQuery(function($) {
const fields = [
{
Expand Down Expand Up @@ -184,7 +196,7 @@ jQuery(function($) {
text: ['datetime-local'],
},
onSave: (e, formData) => {
window.sessionStorage.setItem('formData', JSON.stringify(formData))
window.sessionStorage.setItem('formData', formData)
toggleEdit()
},
onAddField: fieldId => {
Expand Down Expand Up @@ -220,7 +232,7 @@ jQuery(function($) {
let editing = true

if (formData) {
fbOptions.formData = JSON.parse(formData)
fbOptions.formData = formData
}

/**
Expand All @@ -232,13 +244,13 @@ jQuery(function($) {
if (!editing) {
$('.build-wrap').formBuilder('setData', $('.render-wrap').formRender('userData'))
} else {
const formRenderData = $('.build-wrap').formBuilder('getData')
const formRenderData = $('.build-wrap').formBuilder('getData', dataType)
$('.render-wrap').formRender({
formData: formRenderData,
templates: templates,
dataType,
})
window.sessionStorage.setItem('formData', JSON.stringify(formRenderData))
window.sessionStorage.setItem('formData', formRenderData)
}
return (editing = !editing)
}
Expand Down
4 changes: 2 additions & 2 deletions src/js/form-render.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mi18n from 'mi18n'
import utils from './utils'
import utils, { parseXML } from './utils'
import events from './events'
import layout from './layout'
import control from './control'
Expand Down Expand Up @@ -296,7 +296,7 @@ class FormRender {
*/
parseFormData(formData) {
const setData = {
xml: formData => utils.parseXML(formData),
xml: formData => parseXML(formData),
json: formData => window.JSON.parse(formData),
}
if (typeof formData !== 'object') {
Expand Down
44 changes: 30 additions & 14 deletions src/js/helpers.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import mi18n from 'mi18n'
import { instanceDom, empty, optionFieldsRegEx, remove } from './dom'
import { instanceDom, empty, remove, optionFields } from './dom'
import { instanceData } from './data'
import {
mobileClass,
Expand All @@ -12,6 +12,8 @@ import {
parseXML,
capitalize,
unique,
xmlAttrString,
flattenArray,
} from './utils'
import events from './events'
import { config } from './config'
Expand Down Expand Up @@ -146,31 +148,31 @@ export default class Helpers {

/**
* XML save
*
* @param {Object} form sortableFields node
* @return {String} xml in string
*/
xmlSave(form) {
const formData = this.prepData(form)
const xmlSerializer = new XMLSerializer()
const fields = []
const fields = ['<fields>']

formData.forEach(field => {
let fieldContent = null
const hasContent = optionFields.includes(field.type)
const { values, ...fieldData } = field
const optionFields = optionFieldsRegEx
let fieldHTML = [`<field ${xmlAttrString(fieldData)} ${hasContent ? '' : '/'}>`]

// Handle options
if (field.type.match(optionFields)) {
fieldContent = values.map(option => m('option', option.label, option))
if (hasContent) {
const options = values.map(option => m('option', option.label, option).outerHTML)
fieldHTML = fieldHTML.concat([options, '</field>'])
}

const fieldHTML = m('field', fieldContent, fieldData).outerHTML
fields.push(fieldHTML)
})

const formTemplate = m('form-template', m('fields', fields.join('')))
fields.push('</fields>')

const formTemplate = m('form-template', flattenArray(fields).join(''))
return xmlSerializer.serializeToString(formTemplate)
}

Expand Down Expand Up @@ -261,7 +263,12 @@ export default class Helpers {

const setData = {
xml: formData => (Array.isArray(formData) ? formData : parseXML(formData)),
json: formData => (typeof formData === 'string' ? window.JSON.parse(formData) : formData),
json: formData => {
if (typeof formData === 'string') {
return window.JSON.parse(formData)
}
return formData
},
}

data.formData = setData[config.opts.dataType](formData) || []
Expand Down Expand Up @@ -316,7 +323,7 @@ export default class Helpers {
const attr = attrs[index]
const name = camelCase(attr.getAttribute('name'))
const value = [
[attr.attributes.contenteditable, () => escapeHtml(attr.innerHTML)],
[attr.attributes.contenteditable, () => config.opts.dataType === 'xml' ? escapeHtml(attr.innerHTML) : attr.innerHTML],
[attr.type === 'checkbox', () => attr.checked],
[attr.attributes.multiple, () => $(attr).val()],
[true, () => attr.value],
Expand Down Expand Up @@ -368,6 +375,7 @@ export default class Helpers {

$field.data('fieldData', previewData)


// determine the control class for this type, and then process it through the layout engine
const custom = controlCustom.lookup(previewData.type)
const controlClass = custom ? custom.class : control.getClass(previewData.type, previewData.subtype)
Expand Down Expand Up @@ -828,9 +836,12 @@ export default class Helpers {
* Open a dialog with the form's data
*/
showData() {
// const data = this.data
// const formData = utils.escapeHtml(data.formData)
const formData = this.getFormData('json', true)
let formData = this.getFormData(config.opts.dataType, true)

if (config.opts.dataType === 'xml') {
formData = escapeHtml(formData)
}

const code = m('code', formData, {
className: `formData-${config.opts.dataType}`,
})
Expand Down Expand Up @@ -1067,6 +1078,11 @@ export default class Helpers {

opts.fields = opts.fields.concat(replaceFields)
opts.disableFields = opts.disableFields.concat(replaceFields.map(({ type }) => type && type))

if (opts.dataType === 'xml') {
// html labels are not available using xml dataType
opts.disableHTMLLabels = true
}
config.opts = Object.assign({}, { actionButtons: mergedActionButtons }, { fieldEditContainer }, opts)
return config.opts
}
Expand Down
2 changes: 2 additions & 0 deletions src/js/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,9 @@ export default class layout {
* @return {DOMElement}
*/
processTemplate(template, ...args) {

let processed = this.templates[template](...args, this.data)

if (processed.jquery) {
processed = processed[0]
}
Expand Down
32 changes: 24 additions & 8 deletions src/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ export const validAttr = function(attr) {
return !invalid.includes(attr)
}

/**
* Convert an attrs object into a string for xml node
* @param {Object} attrs object of attributes for markup
* @return {string}
*/
export const xmlAttrString = attrs =>
Object.entries(attrs)
.map(([key, val]) => `${hyphenCase(key)}="${val}"`)
.join(' ')

/**
* Convert an attrs object into a string
*
Expand Down Expand Up @@ -89,6 +99,14 @@ export const safeAttr = (name, value) => {
}
}

/**
* recursively flatten a nested array
* @param {Array} arr to be flattened
* @return {Array} flattened array
*/
export const flattenArray = arr =>
arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flattenArray(val) : val), [])

export const safeAttrName = name => {
const safeAttr = {
className: 'class',
Expand Down Expand Up @@ -148,10 +166,10 @@ export const nameAttr = function(field) {
}

/**
* Determine content type
* @param {Node | String | Array | Object} content
* @return {String}
*/
* Determine content type
* @param {Node | String | Array | Object} content
* @return {String}
*/
export const getContentType = content => {
if (content === undefined) {
return content
Expand Down Expand Up @@ -237,7 +255,7 @@ export const parseAttrs = elem => {
}

if (attrVal) {
data[attrs[attr].name] = attrVal
data[camelCase(attrs[attr].name)] = attrVal
}
})

Expand Down Expand Up @@ -535,9 +553,7 @@ export const mobileClass = () => {
* @return {String} converter string
*/
export const safename = str => {
return str
.replace(/\s/g, '-')
.replace(/[^a-zA-Z0-9[\]_-]/g, '')
return str.replace(/\s/g, '-').replace(/[^a-zA-Z0-9[\]_-]/g, '')
}

/**
Expand Down

0 comments on commit ea29e79

Please sign in to comment.