Skip to content

Commit

Permalink
fix: move custom control registration into its own class and initiali…
Browse files Browse the repository at this point in the history
…se it per formBuilder instance. This ensures definitions set in one formBuilder do not interact with definitions in other formBuilder instances
  • Loading branch information
lucasnetau committed Oct 18, 2023
1 parent 24cc643 commit 25046b8
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 122 deletions.
2 changes: 1 addition & 1 deletion src/js/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export default class control {
/**
* Retrieve the class for a specified control type
* @param {String} type type of control we are looking up
* @param {String} subtype if specified we'll try to find
* @param {String} [subtype] if specified we'll try to find
* a class mapped to this subtype. If none found, fall back to the type.
* @return {Class} control subclass as defined in the call to register
*/
Expand Down
110 changes: 7 additions & 103 deletions src/js/control/custom.js
Original file line number Diff line number Diff line change
@@ -1,122 +1,27 @@
import control from '../control'
import mi18n from 'mi18n'

/**
* Support for custom controls
* Implementing support for custom templates being passed as options to formBuilder/Render
* @extends control
*/
export default class controlCustom extends control {
/**
* Override the register method to allow passing 'templates' configuration data
* @param {Object} templates an object/hash of template data as defined https://formbuilder.online/docs/formBuilder/options/templates/
* @param {Array} fields
*/
static register(templates = {}, fields = []) {
controlCustom.customRegister = {}

if (!controlCustom.def) {
controlCustom.def = {
icon: {},
i18n: {},
}
}

// store the template data against a static property
controlCustom.templates = templates

// prepare i18n locale definition
const locale = mi18n.locale
if (!controlCustom.def.i18n[locale]) {
controlCustom.def.i18n[locale] = {}
}

// register each defined template against this class
control.register(Object.keys(templates), controlCustom)

// build the control label & icon definitions
for (const field of fields) {
let type = field.type
field.attrs = field.attrs || {}
if (!type) {
if (!field.attrs.type) {
this.error('Ignoring invalid custom field definition. Please specify a type property.')
continue
}
type = field.attrs.type
}

// default icon & label lookup
let lookup = field.subtype || type

// if there is no template defined for this type, check if we already have this type/subtype registered
if (!templates[type]) {
// check that this type is already registered
const controlClass = control.getClass(type, field.subtype)
if (!controlClass) {
this.error(
'Error while registering custom field: ' +
type +
(field.subtype ? ':' + field.subtype : '') +
'. Unable to find any existing defined control or template for rendering.',
)
continue
}

// generate a random key & map the settings against it
lookup = field.datatype ? field.datatype : `${type}-${Math.floor(Math.random() * 9000 + 1000)}`

controlCustom.customRegister[lookup] = jQuery.extend(field, {
type: type,
class: controlClass,
})
}

// map label & icon
controlCustom.def.i18n[locale][lookup] = field.label
controlCustom.def.icon[lookup] = field.icon
}
}

/**
* Returns any custom fields that map to an existing type/subtype combination
* @param {string|false} type optional type of control we want to look up
* subtypes of. If not specified will return all types
* @return {Array} registered custom lookup keys
*/
static getRegistered(type = false) {
if (type) {
return control.getRegistered(type)
}
return Object.keys(controlCustom.customRegister)
}

/**
* Retrieve the class for a specified control type
* @param {string} lookup - custom control lookup to check for
* @return {Class} control subclass as defined in the call to register
*/
static lookup(lookup) {
return controlCustom.customRegister[lookup]
}

/**
* Class configuration - return the icons & label translations defined in register
* @return {object} definition object
*/
static get definition() {
return controlCustom.def
constructor(config, preview, template) {
super(config,preview)
this.template = template
}

/**
* build a custom control defined in the templates option
* @return {{field: any, layout: any}} DOM Element to be injected into the form.
*/
build() {
let custom = controlCustom.templates[this.type]
let custom = this.template
if (!custom) {
return this.error(
'Invalid custom control type. Please ensure you have registered it correctly as a template option.',
/* istanbul ignore next */
return control.error(
`Invalid custom control type '${this.type}'. Please ensure you have registered it correctly as a template option.`,
)
}

Expand Down Expand Up @@ -158,4 +63,3 @@ export default class controlCustom extends control {
}
}
}
controlCustom.customRegister = {}
23 changes: 16 additions & 7 deletions src/js/controls.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import './control/index'
import control from './control'
import controlCustom from './control/custom'
import customControls from './customControls'
import { unique, hyphenCase, markup as m } from './utils'
import { empty } from './dom'
import fontConfig from '../fonts/config.json'
Expand All @@ -20,8 +20,7 @@ export default class Controls {
constructor(opts, d) {
this.opts = opts
this.dom = d.controls
this.custom = controlCustom
this.getClass = control.getClass
this.custom = new customControls
this.getRegistered = control.getRegistered
// ability for controls to have their own configuration / options
// of the format control identifier (type, or type.subtype): {options}
Expand All @@ -47,13 +46,12 @@ export default class Controls {
control.loadCustom(opts.controls)
// register any passed custom templates & fields
if (Object.keys(opts.fields).length) {
controlCustom.register(opts.templates, opts.fields)
this.custom.register(opts.templates, opts.fields)
}

// retrieve a full list of loaded controls
const registeredControls = control.getRegistered()
this.registeredControls = registeredControls
const customFields = controlCustom.getRegistered()
const customFields = this.custom.getRegistered()
if (customFields) {
jQuery.merge(registeredControls, customFields)
}
Expand All @@ -72,7 +70,7 @@ export default class Controls {
for (let i = 0; i < registeredControls.length; i++) {
const type = registeredControls[i]
// first check if this is a custom control
let custom = controlCustom.lookup(type)
let custom = this.custom.lookup(type)
let controlClass
if (custom) {
controlClass = custom.class
Expand Down Expand Up @@ -186,4 +184,15 @@ export default class Controls {
})
this.dom.appendChild(fragment)
}

/**
* Retrieve the class for a specified control type
* @param {String} type type of control we are looking up
* @param {String} [subtype] if specified we'll try to find
* a class mapped to this subtype. If none found, fall back to the type.
* @return {Class} control subclass as defined in the call to register
*/
getClass(type, subtype) {
return this.custom.getClass(type) || control.getClass(type, subtype)
}
}
Loading

0 comments on commit 25046b8

Please sign in to comment.