%PDF- %PDF-
| Direktori : /home/opti/workflow/src/public/app-assets/vendors/formBuilder-master/src/js/ |
| Current File : /home/opti/workflow/src/public/app-assets/vendors/formBuilder-master/src/js/control.js |
// CONTROL.JS
import { camelCase, parsedHtml, markup, getStyles, getScripts, isCached } from './utils'
import mi18n from 'mi18n'
/**
* Base class for all control classes
* Defines the structure of a control class and some standard control methods
*/
export default class control {
/**
* initialise the control object
* @param {Object} config each control class receives a control configuration
* object ({name, label, etc})
* @param {Boolean} preview isPreview
*/
constructor(config, preview) {
this.rawConfig = jQuery.extend({}, config)
// make a copy of config so we don't change the object reference
config = jQuery.extend({}, config)
this.preview = preview
delete config.isPreview
if (this.preview) {
delete config.required
}
// process config - extract standard properties
const properties = ['label', 'description', 'subtype', 'required', 'disabled']
for (const prop of properties) {
this[prop] = config[prop]
delete config[prop]
}
// default fields
if (!config.id) {
if (config.name) {
config.id = config.name
} else {
config.id = 'control-' + Math.floor(Math.random() * 10000000 + 1)
}
}
this.id = config.id
this.type = config.type
if (this.description) {
config.title = this.description
}
// check for global class configuration
if (!control.controlConfig) {
control.controlConfig = {}
}
const classId = this.subtype ? this.type + '.' + this.subtype : this.type
this.classConfig = jQuery.extend({}, control.controlConfig[classId] || {})
// if subtype, update the config type for injecting into DOM elements
if (this.subtype) {
config.type = this.subtype
}
if (this.required) {
config['required'] = 'required'
config['aria-required'] = 'true'
}
// Allow setting disabled flag
if (this.disabled) {
config['disabled'] = 'disabled'
}
this.config = config
this.configure()
}
/**
* Getter to retrieve class configuration.
* Supports properties:
* - mi18n - a mi18n lookup, (or object of type: lookup for classes supporting multiple types)
* - i18n - for custom / plugin controls, translations for labels can be specified here as an object of locale: label (or an object of type: label for classes supporting multiple types).
* - icon - icon, or object of type: icon for defined types
* - inactive - array of inactive types that shouldn't appear in formBuilder interface (but still be supported for rendering purposes)
* @return {Object} configuration
*/
static get definition() {
return {}
}
/**
* Class method to register supported controls and their associated classes
* @param {Array} types - control type (or array of control types) to register
* against the specifed class
* @param {Class} controlClass - class to map against the types
* @param {String} parentType - optional - if defined, any classes registered
* will be registered as subtypes of this parent
*/
static register(types, controlClass, parentType) {
// store subtypes as <type>.<subtype> in the register
const prefix = parentType ? parentType + '.' : ''
// initialise the register
if (!control.classRegister) {
control.classRegister = {}
}
if (!Array.isArray(types)) {
types = [types]
}
// associate the controlClass with each passed control type
for (const type of types) {
// '.' is a restricted character for type names
if (type.indexOf('.') !== -1) {
// eslint-disable-next-line max-len
control.error(`Ignoring type ${type}. Cannot use the character '.' in a type name.`)
continue
}
control.classRegister[prefix + type] = controlClass
}
}
/**
* Looks up the classRegister & returns registered types or subtypes
* @param {String} type optional type of control we want to look up
* subtypes of. If not specified will return all types
* @return {Array} registered types (or subtypes)
*/
static getRegistered(type = false) {
const types = Object.keys(control.classRegister)
if (!types.length) {
return types
}
return types.filter(key => {
// if type is specified, then we want to return all subtypes
// of that type (registered with the key <type>.<subtype>)
if (type) {
return key.indexOf(type + '.') > -1
}
return key.indexOf('.') == -1
})
}
/**
* Retrieves an object of types mapped to an array of subtypes.
* Only returns types that have subtypes
* @return {Object} an object containing {type: array of subtypes}.
*/
static getRegisteredSubtypes() {
const types = {}
for (const key in control.classRegister) {
if (control.classRegister.hasOwnProperty(key)) {
const [type, subtype] = key.split('.')
if (!subtype) {
continue
}
if (!types[type]) {
types[type] = []
}
types[type].push(subtype)
}
}
return types
}
/**
* 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
*/
static getClass(type, subtype) {
const lookup = subtype ? type + '.' + subtype : type
const controlClass = control.classRegister[lookup] || control.classRegister[type]
if (!controlClass) {
return control.error(
'Invalid control type. (Type: ' +
type +
', Subtype: ' +
subtype +
'). Please ensure you have registered it, and imported it correctly.'
)
}
// set the _type field on the control class so we never lose it
return controlClass
}
/**
* support dynamic loading of custom control classes
* @param {Array} controls
*/
static loadCustom(controls) {
let controlClasses = []
if (controls) {
controlClasses = controlClasses.concat(controls)
}
// support for user loaded plugin controls
if (window.fbControls) {
controlClasses = controlClasses.concat(window.fbControls)
}
// loop through each defined custom control.
// expects a function that receives the master control class to inherit from (or optional classRegister to inherit from subclass)
// see src/js/control_plugins/ for an example
if (!this.fbControlsLoaded) {
for (const loadControl of controlClasses) {
loadControl(control, control.classRegister)
}
this.fbControlsLoaded = true
}
}
/**
* Retrieve a translated string
* By default looks for translations defined against the class (for plugin controls)
* Expects {locale1: {type: label}, locale2: {type: label}}, or {default: label}, or {local1: label, local2: label2}
* @param {String} lookup string to retrieve the label / translated string for
* @param {Object|Number|String} args - string or key/val pairs for string lookups with variables
* @return {String} the translated label
*/
static mi18n(lookup, args) {
const def = this.definition
let i18n = def.i18n || {}
const locale = mi18n.locale
i18n = i18n[locale] || i18n.default || i18n
const lookupCamel = this.camelCase(lookup)
// if translation is defined in the control, return it
const value = typeof i18n == 'object' ? i18n[lookupCamel] || i18n[lookup] : i18n
if (value) {
return value
}
// otherwise check the mi18n object - allow for mapping a lookup to a custom mi18n lookup
let mapped = def.mi18n
if (typeof mapped === 'object') {
mapped = mapped[lookupCamel] || mapped[lookup]
}
if (!mapped) {
mapped = lookupCamel
}
return mi18n.get(mapped, args)
}
/**
* Should this control type appear in the list of form controls
* @param {String} type
* @return {Boolean} isActive
*/
static active(type) {
return !Array.isArray(this.definition.inactive) || this.definition.inactive.indexOf(type) == -1
}
/**
* Retrieve the translated control label for a control type
* @param {String} type
* @return {String} translated control
*/
static label(type) {
return this.mi18n(type)
}
/**
* Retrieve the icon for a control type
* @param {String} type
* @return {String} icon
*/
static icon(type) {
// @todo - support for `icon-${attr.name}` - is this for inputSets? Doesnt look like it but can't see anything else that sets attr.name?
// https://formbuilder.online/docs/formBuilder/options/inputSets/
const def = this.definition
if (def && typeof def.icon === 'object') {
return def.icon[type]
}
return def.icon
}
/**
* this method is called by the constructor and should be overwritten for controls that need to
* process the configuration arguments prior to rendering
*/
configure() {}
/**
* this is the core method for all controls to produce the form elements to be injected into the dom
* the implementation in control.js will return
* Supported return configuration elements:
* - field - the DOM element
* - noLabel - this control shouldn't have a label (nor a space for a label)
* - hidden - this control shouldn't render anything visible to the page
* @return {Object} DOM Element to be injected into the form, or an object/hash of configuration as above
*/
build() {
const { label, type, ...data } = this.config
return this.markup(type, parsedHtml(label), data)
}
/**
* code to execute for supported events
* to implement an onRender event in a child class, simply define an onRender method
* @param {String} eventType - optional type of event to retrieve an event function for. If not specified all events returned
* @return {Function/Object} - function to execute for specified event, or all events of no eventType is specified
*/
on(eventType) {
const events = {
// executed just prior to the row being returned by the layout class. Receives the DOMelement about to be passed back
prerender: element => {},
// onRender event to execute code each time an instance of this control is injected into the DOM
render: evt => {
// check for a class render event - default to an empty function
const onRender = () => {
if (this.onRender) {
this.onRender()
}
}
// check for any css & javascript to include
if (this.css) {
getStyles(this.css)
}
if (this.js && !isCached(this.js)) {
getScripts(this.js).done(onRender)
} else {
onRender()
}
},
}
return eventType ? events[eventType] : events
}
/**
* centralised error handling
* @param {String} message message to output to the console
*/
static error(message) {
throw new Error(message)
}
/**
* wrap the utils.markup method
* ideally this would be inherited from a parent 'dom' type element supporting dom helper type methods
* @param {String} tag
* @param {Object|String|Array} content
* @param {Object} attributes
* @return {Object} DOM element
*/
markup(tag, content = '', attributes = {}) {
this.element = markup(tag, content, attributes)
return this.element
}
/**
* Converts escaped HTML into usable HTML
* @param {String} html escaped HTML
* @return {String} parsed HTML
*/
parsedHtml(html) {
return parsedHtml(html)
}
/**
* convert a hyphenated string to camelCase
* @param {String} str
* @return {String}
*/
static camelCase(str) {
return camelCase(str)
}
}