Repository: kevinchappell/formBuilder (JavaScript)

Issue:
control.controlConfig shared between instances
### Description:

control.controlConfig static is global not per instance so subsequent loads of formBuilder will overwrite previous controlConfig data

### Environment Details:

 - formBuilder Version: Current
 - Browser: Any
 - OS: Any

### Expected Behavior

controlConfig is isolated per formBuilder instance

### Actual Behavior

controlConfig is global and can be overwritten by subsequent loads

### Steps to Reproduce




Discussion:
:tada: This issue has been resolved in version 3.16.13 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/formBuilder/v/3.16.13)
- [GitHub release](https://github.com/kevinchappell/formBuilder/releases/tag/v3.16.13)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:



Patch:
diff --git a/src/js/controls.js b/src/js/controls.js
index 78bb8a554..54f4ded8e 100644
--- a/src/js/controls.js
+++ b/src/js/controls.js
@@ -21,9 +21,6 @@ export default class Controls {
     this.opts = opts
     this.dom = d.controls
     this.getRegistered = control.getRegistered
-    // ability for controls to have their own configuration / options
-    // of the format control identifier (type, or type.subtype): {options}
-    control.controlConfig = opts.controlConfig || {}
     this.init()
   }
 
diff --git a/src/js/form-builder.js b/src/js/form-builder.js
index d02338aa8..8572bb3ea 100644
--- a/src/js/form-builder.js
+++ b/src/js/form-builder.js
@@ -75,7 +75,7 @@ function FormBuilder(opts, element, $) {
   if (!opts.layout) {
     opts.layout = layout
   }
-  const layoutEngine = new opts.layout(opts.layoutTemplates, true, opts.disableHTMLLabels)
+  const layoutEngine = new opts.layout(opts.layoutTemplates, true, opts.disableHTMLLabels, opts.controlConfig)
 
   const h = new Helpers(formID, layoutEngine, formBuilder)
   const m = markup
diff --git a/src/js/form-render.js b/src/js/form-render.js
index 29d492c9c..30e545992 100644
--- a/src/js/form-render.js
+++ b/src/js/form-render.js
@@ -197,7 +197,7 @@ class FormRender {
     const rendered = []
 
     // instantiate the layout class & loop through the field configuration
-    const engine = new opts.layout(opts.layoutTemplates, false, opts.disableHTMLLabels)
+    const engine = new opts.layout(opts.layoutTemplates, false, opts.disableHTMLLabels, opts.controlConfig)
     if (opts.formData.length) {
       for (let i = 0; i < opts.formData.length; i++) {
         const fieldData = opts.formData[i]
diff --git a/src/js/layout.js b/src/js/layout.js
index 48c2c1f7d..93baf1825 100644
--- a/src/js/layout.js
+++ b/src/js/layout.js
@@ -1,6 +1,7 @@
 // LAYOUT.JS
 import utils from './utils'
 import { getAllGridRelatedClasses } from './utils'
+import control from './control'
 
 const processClassName = (data, field) => {
   // wrap the output in a form-group div & return
@@ -43,12 +44,14 @@ export default class layout {
   /**
    * Prepare the templates for layout
    * @param {Object} templates object containing custom or overwrite templates
-   * @param {Boolean} preview - are we rendering a preview for the formBuilder stage
-   * @param {Boolean} disableHTMLLabels - do we render labels as HTML or plain text
+   * @param {Boolean} [preview=false] - are we rendering a preview for the formBuilder stage
+   * @param {Boolean} [disableHTMLLabels=false] - do we render labels as HTML or plain text
+   * @param {Array} [controlConfig={}] - ability for controls to have their own configuration / options of the format control identifier (type, or type.subtype): {options}
    */
-  constructor(templates, preview = false, disableHTMLLabels = false) {
+  constructor(templates, preview = false, disableHTMLLabels = false, controlConfig = {}) {
     this.preview = preview ?? false
     this.disableHTMLLabels = disableHTMLLabels ?? false
+    this.controlConfig = controlConfig ?? {}
 
     // supported templates for outputting a field
     // preferred layout template can be indicated by specifying a 'layout' in the return object of control::build
@@ -115,8 +118,9 @@ export default class layout {
     this.data = jQuery.extend({}, data)
 
     // build the control
-    const control = new renderControl(data, this.preview)
-    let field = control.build()
+    control.controlConfig = this.controlConfig
+    const controlInstance = new renderControl(data, this.preview)
+    let field = controlInstance.build()
     if (typeof field !== 'object' || !field.field) {
       field = { field: field }
     }
@@ -145,10 +149,10 @@ export default class layout {
     const element = this.processTemplate(elementTemplate, field.field, label, help)
 
     // execute prerender events
-    control.on('prerender')(element)
+    controlInstance.on('prerender')(element)
 
     // bind control on render events
-    element.addEventListener('fieldRendered', control.on('render'))
+    element.addEventListener('fieldRendered', controlInstance.on('render'))
     return element
   }
 


Test patch:
diff --git a/tests/form-builder.test.js b/tests/form-builder.test.js
index d026c917a..7ba47f98b 100644
--- a/tests/form-builder.test.js
+++ b/tests/form-builder.test.js
@@ -435,4 +435,54 @@ describe('async loading tests', () => {
 
     expect(wrap2.formBuilder('markup', 'div').outerHTML).toBe('<div></div>')
   })
+
+  test('controlConfig is loaded per instance', async () => {
+    const wrap1 = $('<div>')
+    const wrap2 = $('<div>')
+    const templates = {
+      text: function(fieldData) {
+        this.classConfig.callback()
+        return {
+          field: '<span>preview</span>'
+        }
+      }
+    }
+    const cb1 = jest.fn()
+    const cb2 = jest.fn()
+    const config1 = {
+      templates,
+      controlConfig: {
+        'text.text': {
+          callback: cb1
+        }
+      }
+    }
+    const config2 = {
+      templates,
+      controlConfig: {
+        'text.text': {
+          callback: cb2
+        }
+      }
+    }
+    const p1 = wrap1.formBuilder(config1).promise
+    const p2 = wrap2.formBuilder(config2).promise
+
+    const fb1 = await p1
+    const fb2 = await p2
+
+    const field = {
+      type: 'text',
+      class: 'form-control'
+    }
+    fb1.actions.addField(field)
+
+    expect(cb1.mock.calls).toHaveLength(1)
+    expect(cb2.mock.calls).toHaveLength(0)
+
+    fb2.actions.addField(field)
+    expect(cb1.mock.calls).toHaveLength(1)
+    expect(cb2.mock.calls).toHaveLength(1)
+  })
+
 })
\ No newline at end of file