angular-formly
Version:
AngularJS directive which takes JSON representing a form and renders to HTML
296 lines (268 loc) • 8.63 kB
JavaScript
import angular from 'angular-fix'
import utils from '../other/utils'
export default formlyConfig
// @ngInject
function formlyConfig(formlyUsabilityProvider, formlyErrorAndWarningsUrlPrefix, formlyApiCheck) {
const typeMap = {}
const templateWrappersMap = {}
const defaultWrapperName = 'default'
const _this = this
const getError = formlyUsabilityProvider.getFormlyError
angular.extend(this, {
setType,
getType,
getTypes,
getTypeHeritage,
setWrapper,
getWrapper,
getWrapperByType,
removeWrapperByName,
removeWrappersForType,
disableWarnings: false,
extras: {
disableNgModelAttrsManipulator: false,
fieldTransform: [],
ngModelAttrsManipulatorPreferUnbound: false,
removeChromeAutoComplete: false,
parseKeyArrays: false,
defaultHideDirective: 'ng-if',
getFieldId: null,
},
templateManipulators: {
preWrapper: [],
postWrapper: [],
},
$get: () => this,
})
function setType(options) {
if (angular.isArray(options)) {
const allTypes = []
angular.forEach(options, item => {
allTypes.push(setType(item))
})
return allTypes
} else if (angular.isObject(options)) {
checkType(options)
if (options.extends) {
extendTypeOptions(options)
}
typeMap[options.name] = options
return typeMap[options.name]
} else {
throw getError(`You must provide an object or array for setType. You provided: ${JSON.stringify(arguments)}`)
}
}
function checkType(options) {
formlyApiCheck.throw(formlyApiCheck.formlyTypeOptions, options, {
prefix: 'formlyConfig.setType',
url: 'settype-validation-failed',
})
if (!options.overwriteOk) {
checkOverwrite(options.name, typeMap, options, 'types')
} else {
options.overwriteOk = undefined
}
}
function extendTypeOptions(options) {
const extendsType = getType(options.extends, true, options)
extendTypeControllerFunction(options, extendsType)
extendTypeLinkFunction(options, extendsType)
extendTypeDefaultOptions(options, extendsType)
utils.reverseDeepMerge(options, extendsType)
extendTemplate(options, extendsType)
}
function extendTemplate(options, extendsType) {
if (options.template && extendsType.templateUrl) {
delete options.templateUrl
} else if (options.templateUrl && extendsType.template) {
delete options.template
}
}
function extendTypeControllerFunction(options, extendsType) {
const extendsCtrl = extendsType.controller
if (!angular.isDefined(extendsCtrl)) {
return
}
const optionsCtrl = options.controller
if (angular.isDefined(optionsCtrl)) {
options.controller = function($scope, $controller) {
$controller(extendsCtrl, {$scope})
$controller(optionsCtrl, {$scope})
}
options.controller.$inject = ['$scope', '$controller']
} else {
options.controller = extendsCtrl
}
}
function extendTypeLinkFunction(options, extendsType) {
const extendsFn = extendsType.link
if (!angular.isDefined(extendsFn)) {
return
}
const optionsFn = options.link
if (angular.isDefined(optionsFn)) {
options.link = function() {
extendsFn(...arguments)
optionsFn(...arguments)
}
} else {
options.link = extendsFn
}
}
function extendTypeDefaultOptions(options, extendsType) {
const extendsDO = extendsType.defaultOptions
if (!angular.isDefined(extendsDO)) {
return
}
const optionsDO = options.defaultOptions || {}
const optionsDOIsFn = angular.isFunction(optionsDO)
const extendsDOIsFn = angular.isFunction(extendsDO)
if (extendsDOIsFn) {
options.defaultOptions = function defaultOptions(opts, scope) {
const extendsDefaultOptions = extendsDO(opts, scope)
const mergedDefaultOptions = {}
utils.reverseDeepMerge(mergedDefaultOptions, opts, extendsDefaultOptions)
let extenderOptionsDefaultOptions = optionsDO
if (optionsDOIsFn) {
extenderOptionsDefaultOptions = extenderOptionsDefaultOptions(mergedDefaultOptions, scope)
}
utils.reverseDeepMerge(extenderOptionsDefaultOptions, extendsDefaultOptions)
return extenderOptionsDefaultOptions
}
} else if (optionsDOIsFn) {
options.defaultOptions = function defaultOptions(opts, scope) {
const newDefaultOptions = {}
utils.reverseDeepMerge(newDefaultOptions, opts, extendsDO)
return optionsDO(newDefaultOptions, scope)
}
}
}
function getType(name, throwError, errorContext) {
if (!name) {
return undefined
}
const type = typeMap[name]
if (!type && throwError === true) {
throw getError(
`There is no type by the name of "${name}": ${JSON.stringify(errorContext)}`
)
} else {
return type
}
}
function getTypes() {
return typeMap
}
function getTypeHeritage(parent) {
const heritage = []
let type = parent
if (angular.isString(type)) {
type = getType(parent)
}
parent = type.extends
while (parent) {
type = getType(parent)
heritage.push(type)
parent = type.extends
}
return heritage
}
function setWrapper(options, name) {
if (angular.isArray(options)) {
return options.map(wrapperOptions => setWrapper(wrapperOptions))
} else if (angular.isObject(options)) {
options.types = getOptionsTypes(options)
options.name = getOptionsName(options, name)
checkWrapperAPI(options)
templateWrappersMap[options.name] = options
return options
} else if (angular.isString(options)) {
return setWrapper({
template: options,
name,
})
}
}
function getOptionsTypes(options) {
if (angular.isString(options.types)) {
return [options.types]
}
if (!angular.isDefined(options.types)) {
return []
} else {
return options.types
}
}
function getOptionsName(options, name) {
return options.name || name || options.types.join(' ') || defaultWrapperName
}
function checkWrapperAPI(options) {
formlyUsabilityProvider.checkWrapper(options)
if (options.template) {
formlyUsabilityProvider.checkWrapperTemplate(options.template, options)
}
if (!options.overwriteOk) {
checkOverwrite(options.name, templateWrappersMap, options, 'templateWrappers')
} else {
delete options.overwriteOk
}
checkWrapperTypes(options)
}
function checkWrapperTypes(options) {
const shouldThrow = !angular.isArray(options.types) || !options.types.every(angular.isString)
if (shouldThrow) {
throw getError(`Attempted to create a template wrapper with types that is not a string or an array of strings`)
}
}
function checkOverwrite(property, object, newValue, objectName) {
if (object.hasOwnProperty(property)) {
warn('overwriting-types-or-wrappers', [
`Attempting to overwrite ${property} on ${objectName} which is currently`,
`${JSON.stringify(object[property])} with ${JSON.stringify(newValue)}`,
`To supress this warning, specify the property "overwriteOk: true"`,
].join(' '))
}
}
function getWrapper(name) {
return templateWrappersMap[name || defaultWrapperName]
}
function getWrapperByType(type) {
/* eslint prefer-const:0 */
const wrappers = []
for (let name in templateWrappersMap) {
if (templateWrappersMap.hasOwnProperty(name)) {
if (templateWrappersMap[name].types && templateWrappersMap[name].types.indexOf(type) !== -1) {
wrappers.push(templateWrappersMap[name])
}
}
}
return wrappers
}
function removeWrapperByName(name) {
const wrapper = templateWrappersMap[name]
delete templateWrappersMap[name]
return wrapper
}
function removeWrappersForType(type) {
const wrappers = getWrapperByType(type)
if (!wrappers) {
return undefined
}
if (!angular.isArray(wrappers)) {
return removeWrapperByName(wrappers.name)
} else {
wrappers.forEach((wrapper) => removeWrapperByName(wrapper.name))
return wrappers
}
}
function warn() {
if (!_this.disableWarnings && console.warn) {
/* eslint no-console:0 */
const args = Array.prototype.slice.call(arguments)
const warnInfoSlug = args.shift()
args.unshift('Formly Warning:')
args.push(`${formlyErrorAndWarningsUrlPrefix}${warnInfoSlug}`)
console.warn(...args)
}
}
}