constructor(options = {}) {
    // initialise defaults & options
    const defaults = {
      layout: layout, // by default use the layout class, but support a child class being defined & passed as an option
      layoutTemplates: {}, // allow custom override layout templates to be defined
      controls: {}, // custom controls
      controlConfig: {}, // additional configuration for controls
      container: false, // string selector or Node element
      dataType: 'json',
      disableHTMLLabels: false,
      formData: [],
      i18n: Object.assign({}, defaultI18n),
      messages: {
        formRendered: 'Form Rendered',
        noFormData: 'No form data.',
        other: 'Other',
        selectColor: 'Select Color',
        invalidControl: 'Invalid control',
      },
      onRender: () => {},
      render: true,
      sanitizerOptions: {
        clobberingProtection: {
          document: true,
          form: false,
          namespaceAttributes: true, //clobbered names will be prefixed with user-content-
        },
        backendOrder: ['dompurify','sanitizer','fallback'],

      },
      templates: {}, // custom inline defined templates
      notify: {
        error: /* istanbul ignore next */ error => {
          console.log(error)
        },
        success: /* istanbul ignore next */ success => {
          console.log(success)
        },
        warning: /* istanbul ignore next */ warning => {
          console.warn(warning)
        },
      },
    }
    this.options = jQuery.extend(true, defaults, options)
    this.instanceContainers = []

    //Override any sanitizer configuration
    setSanitizerConfig(this.options.sanitizerOptions)

    if (!mi18n.current) {
      mi18n.init(this.options.i18n)
    }

    // parse any passed formData
    if (this.options.formData) {
      this.options.formData = this.parseFormData(this.options.formData)
    } else {
      this.options.formData = []
    }

    // ability for controls to have their own configuration / options of the format control identifier (type, or type.subtype): {options}
    control.controlConfig = options.controlConfig || {}

    // load in any custom specified controls, or preloaded plugin controls
    control.loadCustom(options.controls)

    // register any passed custom templates
    this.templatedControls = new customControls(this.options.templates)

    /**
     * Extend Element prototype to allow us to append fields
     *
     * @param {Array} fields array of elements
     */
    if (typeof Element.prototype.appendFormFields !== 'function') {
      Element.prototype.appendFormFields = function (fields) {
        if (!Array.isArray(fields)) {
          fields = [fields]
        }
        const renderedFormWrap = utils.markup('div', null, {
          className: 'rendered-form formbuilder-embedded-bootstrap',
        })
        this.appendChild(renderedFormWrap)

        fields.forEach(field => {
          // Determine if rows are being used. If so, create the row and append to its row-{group}
          // If the fields have row-, create & append to the appropriate row
          const [rowGroup] = field.className.match(/row-([^\s]+)/) || []
          if (rowGroup) {
            const rowID = this.id ? `${this.id}-row-${rowGroup}` : `row-${rowGroup}`

            // 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' })
              renderedFormWrap.appendChild(rowGroupNode)
            }
            rowGroupNode.appendChild(field)
          } else {
            // Append without row
            renderedFormWrap.appendChild(field)
          }

          field.dispatchEvent(new Event('fieldRendered', { bubbles: true, cancelable: false} ))
        })
      }
    }

    /**
     * Extend Element prototype to remove content
     */
    if (typeof Element.prototype.emptyContainer !== 'function') {
      Element.prototype.emptyContainer = function () {
        const element = this
        while (element.lastChild) {
          element.removeChild(element.lastChild)
        }
      }
    }
  }
Create & configure a new FormRender instance
@param {Object} options - an object hash of supported options