From 2e79b69de150437ffcb8fdcae15b509e365a39ed Mon Sep 17 00:00:00 2001 From: James Lucas Date: Mon, 19 Feb 2024 16:11:39 +1100 Subject: [PATCH 1/6] chore: add util function to return the first number value from a list or undefined --- src/js/utils.js | 5 +++++ tests/utils.test.js | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/js/utils.js b/src/js/utils.js index fc4cf5df8..cfd0597d6 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -702,6 +702,10 @@ export function titleCase(str) { ) } +export function firstNumberOrUndefined(...options) { + return options.find(x => typeof x === 'number') +} + const utils = { addEventListeners, attrString, @@ -733,6 +737,7 @@ const utils = { unique, validAttr, titleCase, + firstNumberOrUndefined, } /** diff --git a/tests/utils.test.js b/tests/utils.test.js index a89b2708c..b1fcdf6d5 100644 --- a/tests/utils.test.js +++ b/tests/utils.test.js @@ -1,7 +1,7 @@ const utils = require('./../src/js/utils.js') const { safeAttr, flattenArray, safeAttrName, hyphenCase, camelCase, bindEvents, attrString, nameAttr, markup, parsedHtml, escapeAttrs, getScripts, capitalize, addEventListeners, unique, escapeAttr, escapeHtml, - getAllGridRelatedClasses, subtract, safename, xmlParseAttrs, parseXML + getAllGridRelatedClasses, subtract, safename, xmlParseAttrs, parseXML, firstNumberOrUndefined } = require('../src/js/utils') describe('Test Util functions', () => { @@ -143,6 +143,12 @@ describe('Test Util functions', () => { expect(subtract([2],[1,2,3])).toEqual([1,3]) expect(subtract(['remove-me'],['classA','classB','remove-me','classC'])).toEqual(['classA','classB','classC']) }) + + test('firstNumberOrUndefined', () => { + expect(firstNumberOrUndefined(1)).toEqual(1) + expect(firstNumberOrUndefined(1,2,3)).toEqual(1) + expect(firstNumberOrUndefined(undefined,'',2,1)).toEqual(2) + }) }) describe('Test XML functions', () => { From 76db4331a648a926fb963de2e90ca1a0eeaa22f5 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Mon, 19 Feb 2024 16:12:50 +1100 Subject: [PATCH 2/6] fix: provide the value for a number attribute in the tUA array --- src/js/form-builder.js | 16 ++++++++--- tests/form-builder.test.js | 59 +++++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/src/js/form-builder.js b/src/js/form-builder.js index 1a03e425e..43575a211 100644 --- a/src/js/form-builder.js +++ b/src/js/form-builder.js @@ -33,7 +33,7 @@ import { safename, forceNumber, getContentType, - generateSelectorClassNames, + generateSelectorClassNames, firstNumberOrUndefined, } from './utils' import { attributeWillClobber, setElementContent, setSanitizerConfig } from './sanitizer' import fontConfig from '../fonts/config.json' @@ -682,8 +682,16 @@ function FormBuilder(opts, element, $) { if (attrValType !== 'undefined') { const orig = mi18n.get(attribute) const tUA = typeUserAttr[attribute] - const origValue = attrValType === 'boolean' ? tUA.value : (tUA.value || '') - tUA.value = values[attribute] || origValue + let origValue = tUA.value + if (attrValType === 'boolean') { + origValue = tUA.value + tUA[attribute] ??= tUA.value + } else if (attrValType === 'number') { + tUA[attribute] ??= firstNumberOrUndefined(values[attribute], origValue) + } else { + origValue ??= '' + tUA[attribute] ??= values[attribute] || origValue + } if (tUA.label) { i18n[attribute] = Array.isArray(tUA.label) ? mi18n.get(...tUA.label) || tUA.label[0] : tUA.label @@ -871,7 +879,7 @@ function FormBuilder(opts, element, $) { */ const numberAttribute = (attribute, values) => { const { class: classname, className, ...attrs } = values - const attrVal = (isNaN(attrs[attribute])) ? undefined : attrs[attribute] + const attrVal = (Number.isNaN(attrs[attribute])) ? undefined : attrs[attribute] const attrLabel = mi18n.get(attribute) || attribute const placeholder = mi18n.get(`placeholder.${attribute}`) diff --git a/tests/form-builder.test.js b/tests/form-builder.test.js index af753bb83..a0addd853 100644 --- a/tests/form-builder.test.js +++ b/tests/form-builder.test.js @@ -199,6 +199,38 @@ describe('FormBuilder stage names translated', () => { }) }) +describe('FormBuilder attribute setup', () => { + test('number control number attributes do not inherit field value when not set', async() => { + const config = {} + const fbWrap = $('
') + const fb = await fbWrap.formBuilder(config).promise + fb.actions.addField({ + type: 'number', + value: '5.2', + }) + expect(fbWrap.find('.value-wrap input').val()).toBe('5.2') + expect(fbWrap.find('.min-wrap input').val()).toBe('') + expect(fbWrap.find('.max-wrap input').val()).toBe('') + expect(fbWrap.find('.step-wrap input').val()).toBe('') + }) + test('number control number attributes do not inherit field value when set', async() => { + const config = {} + const fbWrap = $('
') + const fb = await fbWrap.formBuilder(config).promise + fb.actions.addField({ + type: 'number', + value: '5.2', + min: 2, + max: 10, + step: 1.0, + }) + expect(fbWrap.find('.value-wrap input').val()).toBe('5.2') + expect(fbWrap.find('.min-wrap input').val()).toBe('2') + expect(fbWrap.find('.max-wrap input').val()).toBe('10') + expect(fbWrap.find('.step-wrap input').val()).toBe('1') + }) + +}) describe('FormBuilder typeUserAttrs detection', () => { test('renders text/string user attribute', async () => { const config = { @@ -238,6 +270,27 @@ describe('FormBuilder typeUserAttrs detection', () => { expect(input.is(':checked')).toBeFalsy() }) test('renders number user attribute when value is number', async () => { + const config = { + typeUserAttrs: { + text: { + testAttribute: { + label: 'test', + value: 10, + }, + }, + }, + } + const fbWrap = $('
') + const fb = await fbWrap.formBuilder(config).promise + fb.actions.addField({ + type: 'text', + value: '1', + }) + const input = fbWrap.find('.testAttribute-wrap input') + expect(input.attr('type')).toBe('number') + expect(input.val()).toBe('10') + }) + test('renders number user attribute when value is zero', async () => { const config = { typeUserAttrs: { text: { @@ -250,9 +303,13 @@ describe('FormBuilder typeUserAttrs detection', () => { } const fbWrap = $('
') const fb = await fbWrap.formBuilder(config).promise - fb.actions.addField({ type: 'text'}) + fb.actions.addField({ + type: 'text', + value: '1', + }) const input = fbWrap.find('.testAttribute-wrap input') expect(input.attr('type')).toBe('number') + expect(input.val()).toBe('0') }) test('renders select user attribute when options given', async () => { const config = { From 8153745f73e1dea2d599bdcfdf762930e2290a26 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 20 Feb 2024 12:53:28 +1100 Subject: [PATCH 3/6] fix: selecting last field id when running in advanced bootstrap mode --- src/js/helpers.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/js/helpers.js b/src/js/helpers.js index 69bb0a9ed..782e4d349 100644 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -1045,6 +1045,7 @@ export default class Helpers { config.opts.notify.warning('Removing last field since no ID was supplied.') config.opts.notify.warning('Available IDs: ' + availableIds.join(', ')) fieldID = form.lastChild.id + fieldID = availableIds[availableIds.length-1] } const field = document.getElementById(fieldID) From c44dbc46fd757cffeef96c3b7752d5749d712237 Mon Sep 17 00:00:00 2001 From: James Lucas Date: Tue, 20 Feb 2024 12:54:29 +1100 Subject: [PATCH 4/6] fix: ensure that config is per instance of formBuilder --- src/js/config.js | 2 +- src/js/form-builder.js | 4 ++-- src/js/helpers.js | 52 +++++++++++++++++++++++------------------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/js/config.js b/src/js/config.js index e65d728a1..63b8b93db 100644 --- a/src/js/config.js +++ b/src/js/config.js @@ -106,7 +106,7 @@ export const defaultI18n = { location: 'assets/lang/', } -export const config = {} +export const instanceConfig = {} export const gridClassNames = { rowWrapperClass: 'rowWrapper', diff --git a/src/js/form-builder.js b/src/js/form-builder.js index 1a03e425e..2dc7fdec7 100644 --- a/src/js/form-builder.js +++ b/src/js/form-builder.js @@ -10,7 +10,7 @@ import Helpers from './helpers' import { defaultOptions, defaultI18n, - config, + instanceConfig, styles, gridClassNames, defaultTimeout, @@ -55,6 +55,7 @@ function FormBuilder(opts, element, $) { const formID = `frmb-${Date.now()}` const data = new Data(formID) const d = new Dom(formID) + const config = instanceConfig[formID] = {} /** @var formRows Allocated rows IDs in the builder */ let formRows = [] @@ -2470,7 +2471,6 @@ function FormBuilder(opts, element, $) { const pluginInit = function(options,elem) { const _this = this const { i18n, ...opts } = jQuery.extend({}, defaultOptions, options, true) - config.opts = opts this.i18nOpts = jQuery.extend({}, defaultI18n, i18n, true) const notInitialised = () => { diff --git a/src/js/helpers.js b/src/js/helpers.js index 782e4d349..c135ae2b9 100644 --- a/src/js/helpers.js +++ b/src/js/helpers.js @@ -18,7 +18,7 @@ import { getAllGridRelatedClasses, } from './utils' import events from './events' -import { config, defaultTimeout, styles } from './config' +import { instanceConfig, defaultTimeout, styles } from './config' import control from './control' import storageAvailable from 'storage-available' @@ -35,6 +35,7 @@ export default class Helpers { constructor(formId, layout, formBuilder) { this.data = instanceData[formId] this.d = instanceDom[formId] + this.config = instanceConfig[formId] this.doCancel = false this.layout = layout this.handleKeyDown = this.handleKeyDown.bind(this) @@ -82,7 +83,7 @@ export default class Helpers { */ beforeStop(event, ui) { const _this = this - const opts = config.opts + const opts = this.config.opts const form = _this.d.stage const lastIndex = form.childNodes.length - 1 const cancelArray = [] @@ -196,6 +197,7 @@ export default class Helpers { const formData = [] const d = this.d const _this = this + const config = this.config if (form.childNodes.length !== 0) { const fields = [] @@ -302,7 +304,7 @@ export default class Helpers { const data = this.data if (!formData) { - formData = config.opts.formData + formData = this.config.opts.formData } if (!formData) { @@ -319,7 +321,7 @@ export default class Helpers { }, } - data.formData = setData[config.opts.dataType](formData) || [] + data.formData = setData[this.config.opts.dataType](formData) || [] return data.formData } @@ -339,7 +341,7 @@ export default class Helpers { } // save action for current `dataType` - data.formData = doSave[config.opts.dataType](minify) + data.formData = doSave[this.config.opts.dataType](minify) // trigger formSaved event document.dispatchEvent(events.formSaved) @@ -365,6 +367,7 @@ export default class Helpers { * @return {Object} fieldData */ getAttrVals(field) { + const config = this.config const fieldData = Object.create(null) const attrs = field.querySelectorAll('[class*="fld-"]') forEach(attrs, index => { @@ -671,6 +674,7 @@ export default class Helpers { */ confirmRemoveAll(e) { const _this = this + const config = this.config const formID = e.target.id.match(/frmb-\d{13}/)[0] const stage = document.getElementById(formID) const i18n = mi18n.current @@ -703,7 +707,7 @@ export default class Helpers { addDefaultFields() { // Load default fields if none are set - config.opts.defaultFields.forEach(field => this.formBuilder.prepFieldVars(field)) + this.config.opts.defaultFields.forEach(field => this.formBuilder.prepFieldVars(field)) this.d.stage.classList.remove('empty') } @@ -714,7 +718,7 @@ export default class Helpers { */ removeAllFields(stage) { const i18n = mi18n.current - const opts = config.opts + const opts = this.config.opts const fields = stage.querySelectorAll(this.formBuilder.fieldSelector) const markEmptyArray = [] @@ -761,7 +765,7 @@ export default class Helpers { * @return {Array|false} fieldOrder */ setFieldOrder($cbUL) { - if (!config.opts.sortableControls) { + if (!this.config.opts.sortableControls) { return false } const JSON = window.JSON @@ -859,7 +863,7 @@ export default class Helpers { this.removeContainerProtection(rowContainer.attr('id')) - config.opts.onCloseFieldEdit($editPanel[0]) + this.config.opts.onCloseFieldEdit($editPanel[0]) document.dispatchEvent(events.fieldEditClosed) const prevHolder = liContainer.find('.prev-holder') @@ -929,7 +933,7 @@ export default class Helpers { liContainer.insertAfter(rowWrapper) this.formBuilder.currentEditPanel = $editPanel[0] - config.opts.onOpenFieldEdit($editPanel[0]) + this.config.opts.onOpenFieldEdit($editPanel[0]) document.dispatchEvent(events.fieldEditOpened) $(document).trigger('fieldOpened', [{ rowWrapperID: rowWrapper.attr('id') }]) @@ -959,6 +963,7 @@ export default class Helpers { * Controls follow scroll to the bottom of the editor */ stickyControls() { + const config = this.config const { controls, stage } = this.d const $cbWrap = $(controls).parent() const cbPosition = controls.getBoundingClientRect() @@ -1011,10 +1016,10 @@ export default class Helpers { * Open a dialog with the form's data */ showData() { - const formData = escapeHtml(this.getFormData(config.opts.dataType, true)) + const formData = escapeHtml(this.getFormData(this.config.opts.dataType, true)) const code = m('code', formData, { - className: `formData-${config.opts.dataType}`, + className: `formData-${this.config.opts.dataType}`, }) this.dialog(m('pre', code), false, 'data-dialog') @@ -1033,7 +1038,7 @@ export default class Helpers { const fields = form.getElementsByClassName('form-field') if (!fields.length) { - config.opts.notify.warning('No fields to remove') + this.config.opts.notify.warning('No fields to remove') return false } @@ -1041,16 +1046,15 @@ export default class Helpers { const availableIds = [].slice.call(fields).map(field => { return field.id }) - config.opts.notify.warning('fieldID required to remove specific fields.') - config.opts.notify.warning('Removing last field since no ID was supplied.') - config.opts.notify.warning('Available IDs: ' + availableIds.join(', ')) - fieldID = form.lastChild.id + this.config.opts.notify.warning('fieldID required to remove specific fields.') + this.config.opts.notify.warning('Removing last field since no ID was supplied.') + this.config.opts.notify.warning('Available IDs: ' + availableIds.join(', ')) fieldID = availableIds[availableIds.length-1] } const field = document.getElementById(fieldID) if (!field) { - config.opts.notify.warning('Field not found') + this.config.opts.notify.warning('Field not found') return false } @@ -1068,7 +1072,7 @@ export default class Helpers { } }) - const userEvents = Object.assign({}, config.opts.typeUserEvents['*'], config.opts.typeUserEvents[field.type]) + const userEvents = Object.assign({}, this.config.opts.typeUserEvents['*'], this.config.opts.typeUserEvents[field.type]) if (userEvents && userEvents.onremove) { userEvents.onremove(field) @@ -1132,7 +1136,7 @@ export default class Helpers { * @return {Array} subtypes */ processSubtypes(subtypeOpts) { - const disabledSubtypes = config.opts.disabledSubtypes + const disabledSubtypes = this.config.opts.disabledSubtypes // first register any passed subtype options against the appropriate type control class for (const fieldType in subtypeOpts) { if (subtypeOpts.hasOwnProperty(fieldType)) { @@ -1206,7 +1210,7 @@ export default class Helpers { * @return {HTMLElement[]} formActions btn-group */ formActionButtons() { - const opts = config.opts + const opts = this.config.opts return opts.actionButtons .map(btnData => { if (btnData.id && opts.disabledActionButtons.indexOf(btnData.id) === -1) { @@ -1253,7 +1257,7 @@ export default class Helpers { events: { click: evt => { _this.save() - config.opts.onSave(evt, _this.data.formData) + _this.config.opts.onSave(evt, _this.data.formData) }, }, }, @@ -1266,8 +1270,8 @@ export default class Helpers { // html labels are not available using xml dataType opts.disableHTMLLabels = true } - config.opts = Object.assign({}, { actionButtons: mergedActionButtons }, { fieldEditContainer }, opts) - return config.opts + _this.config.opts = Object.assign({}, { actionButtons: mergedActionButtons }, { fieldEditContainer }, opts) + return _this.config.opts } /** From 80d1a4a393516d8c089e08498da775d98788275f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 1 Mar 2024 19:37:10 +0000 Subject: [PATCH 5/6] chore(release): 3.19.2 [skip ci] ## [3.19.2](https://github.com/kevinchappell/formBuilder/compare/v3.19.1...v3.19.2) (2024-03-01) ### Bug Fixes * provide the value for a number attribute in the tUA array ([76db433](https://github.com/kevinchappell/formBuilder/commit/76db4331a648a926fb963de2e90ca1a0eeaa22f5)) --- CHANGELOG.md | 7 +++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 547321e14..b35dea391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [3.19.2](https://github.com/kevinchappell/formBuilder/compare/v3.19.1...v3.19.2) (2024-03-01) + + +### Bug Fixes + +* provide the value for a number attribute in the tUA array ([76db433](https://github.com/kevinchappell/formBuilder/commit/76db4331a648a926fb963de2e90ca1a0eeaa22f5)) + ## [3.19.1](https://github.com/kevinchappell/formBuilder/compare/v3.19.0...v3.19.1) (2024-02-09) diff --git a/package-lock.json b/package-lock.json index 3f32e201f..f9c838fa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "formBuilder", - "version": "3.19.1", + "version": "3.19.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "formBuilder", - "version": "3.19.1", + "version": "3.19.2", "license": "MIT", "dependencies": { "jquery": ">=3.4.1", diff --git a/package.json b/package.json index f540ce956..d8fcc5f97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formBuilder", - "version": "3.19.1", + "version": "3.19.2", "main": "dist/form-builder.min.js", "homepage": "https://formbuilder.online/", "repository": { From 08fa654e446e7e4aff1eb687153048e21eba0af2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 1 Mar 2024 20:01:18 +0000 Subject: [PATCH 6/6] chore(release): 3.19.3 [skip ci] ## [3.19.3](https://github.com/kevinchappell/formBuilder/compare/v3.19.2...v3.19.3) (2024-03-01) ### Bug Fixes * ensure that config is per instance of formBuilder ([c44dbc4](https://github.com/kevinchappell/formBuilder/commit/c44dbc46fd757cffeef96c3b7752d5749d712237)) * selecting last field id when running in advanced bootstrap mode ([8153745](https://github.com/kevinchappell/formBuilder/commit/8153745f73e1dea2d599bdcfdf762930e2290a26)) --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b35dea391..aa666ca58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [3.19.3](https://github.com/kevinchappell/formBuilder/compare/v3.19.2...v3.19.3) (2024-03-01) + + +### Bug Fixes + +* ensure that config is per instance of formBuilder ([c44dbc4](https://github.com/kevinchappell/formBuilder/commit/c44dbc46fd757cffeef96c3b7752d5749d712237)) +* selecting last field id when running in advanced bootstrap mode ([8153745](https://github.com/kevinchappell/formBuilder/commit/8153745f73e1dea2d599bdcfdf762930e2290a26)) + ## [3.19.2](https://github.com/kevinchappell/formBuilder/compare/v3.19.1...v3.19.2) (2024-03-01) diff --git a/package-lock.json b/package-lock.json index f9c838fa0..5aa11a377 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "formBuilder", - "version": "3.19.2", + "version": "3.19.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "formBuilder", - "version": "3.19.2", + "version": "3.19.3", "license": "MIT", "dependencies": { "jquery": ">=3.4.1", diff --git a/package.json b/package.json index d8fcc5f97..8e8292d80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "formBuilder", - "version": "3.19.2", + "version": "3.19.3", "main": "dist/form-builder.min.js", "homepage": "https://formbuilder.online/", "repository": {