Adds new registry class
This commit is contained in:
parent
978a209e3e
commit
f7248d029c
2
dist/formulate.esm.js
vendored
2
dist/formulate.esm.js
vendored
File diff suppressed because one or more lines are too long
6
dist/formulate.min.js
vendored
6
dist/formulate.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/formulate.umd.js
vendored
2
dist/formulate.umd.js
vendored
File diff suppressed because one or more lines are too long
9
dist/snow.css
vendored
9
dist/snow.css
vendored
@ -28,7 +28,14 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin-bottom: .25em; }
|
margin-bottom: .25em; }
|
||||||
.formulate-input .formulate-input-group-item {
|
.formulate-input .formulate-input-group-item {
|
||||||
margin-bottom: .5em; }
|
margin-bottom: 1.5em;
|
||||||
|
padding: 1.5em;
|
||||||
|
border: 1px solid #efefef;
|
||||||
|
border-radius: .25em; }
|
||||||
|
.formulate-input .formulate-input-group-item:last-child {
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
border-bottom: 0;
|
||||||
|
padding-bottom: 0; }
|
||||||
.formulate-input:last-child {
|
.formulate-input:last-child {
|
||||||
margin-bottom: 0; }
|
margin-bottom: 0; }
|
||||||
.formulate-input[data-classification='text'] input {
|
.formulate-input[data-classification='text'] input {
|
||||||
|
2
dist/snow.min.css
vendored
2
dist/snow.min.css
vendored
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@ import library from './libs/library'
|
|||||||
import rules from './libs/rules'
|
import rules from './libs/rules'
|
||||||
import mimes from './libs/mimes'
|
import mimes from './libs/mimes'
|
||||||
import FileUpload from './FileUpload'
|
import FileUpload from './FileUpload'
|
||||||
import { arrayify, parseLocale } from './libs/utils'
|
import { arrayify, parseLocale, has } from './libs/utils'
|
||||||
import isPlainObject from 'is-plain-object'
|
import isPlainObject from 'is-plain-object'
|
||||||
import { en } from '@braid/vue-formulate-i18n'
|
import { en } from '@braid/vue-formulate-i18n'
|
||||||
import fauxUploader from './libs/faux-uploader'
|
import fauxUploader from './libs/faux-uploader'
|
||||||
@ -210,7 +210,7 @@ class Formulate {
|
|||||||
}
|
}
|
||||||
if (locale) {
|
if (locale) {
|
||||||
const option = parseLocale(locale)
|
const option = parseLocale(locale)
|
||||||
.find(locale => Object.prototype.hasOwnProperty.call(this.options.locales, locale))
|
.find(locale => has(this.options.locales, locale))
|
||||||
if (option) {
|
if (option) {
|
||||||
selection = option
|
selection = option
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { shallowEqualObjects, arrayify } from './libs/utils'
|
import { shallowEqualObjects, arrayify, has } from './libs/utils'
|
||||||
|
import Registry from './libs/registry'
|
||||||
import FormSubmission from './FormSubmission'
|
import FormSubmission from './FormSubmission'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -55,7 +56,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
registry: {},
|
registry: new Registry(),
|
||||||
internalFormModelProxy: {},
|
internalFormModelProxy: {},
|
||||||
formShouldShowErrors: false,
|
formShouldShowErrors: false,
|
||||||
errorObservers: [],
|
errorObservers: [],
|
||||||
@ -87,13 +88,13 @@ export default {
|
|||||||
},
|
},
|
||||||
initialValues () {
|
initialValues () {
|
||||||
if (
|
if (
|
||||||
Object.prototype.hasOwnProperty.call(this.$options.propsData, 'formulateValue') &&
|
has(this.$options.propsData, 'formulateValue') &&
|
||||||
typeof this.formulateValue === 'object'
|
typeof this.formulateValue === 'object'
|
||||||
) {
|
) {
|
||||||
// If there is a v-model on the form, use those values as first priority
|
// If there is a v-model on the form, use those values as first priority
|
||||||
return Object.assign({}, this.formulateValue) // @todo - use a deep clone to detach reference types
|
return Object.assign({}, this.formulateValue) // @todo - use a deep clone to detach reference types
|
||||||
} else if (
|
} else if (
|
||||||
Object.prototype.hasOwnProperty.call(this.$options.propsData, 'values') &&
|
has(this.$options.propsData, 'values') &&
|
||||||
typeof this.values === 'object'
|
typeof this.values === 'object'
|
||||||
) {
|
) {
|
||||||
// If there are values, use them as secondary priority
|
// If there are values, use them as secondary priority
|
||||||
@ -135,9 +136,9 @@ export default {
|
|||||||
typeof newValue === 'object'
|
typeof newValue === 'object'
|
||||||
) {
|
) {
|
||||||
for (const field in newValue) {
|
for (const field in newValue) {
|
||||||
if (this.registry.hasOwnProperty(field) &&
|
if (this.registry.has(field) &&
|
||||||
!shallowEqualObjects(newValue[field], this.internalFormModelProxy[field]) &&
|
!shallowEqualObjects(newValue[field], this.internalFormModelProxy[field]) &&
|
||||||
!shallowEqualObjects(newValue[field], this.registry[field].internalModelProxy[field])
|
!shallowEqualObjects(newValue[field], this.registry.get(field).internalModelProxy[field])
|
||||||
) {
|
) {
|
||||||
this.setFieldValue(field, newValue[field])
|
this.setFieldValue(field, newValue[field])
|
||||||
this.registry[field].context.model = newValue[field]
|
this.registry[field].context.model = newValue[field]
|
||||||
@ -184,7 +185,7 @@ export default {
|
|||||||
this.errorObservers.push(observer)
|
this.errorObservers.push(observer)
|
||||||
if (observer.type === 'form') {
|
if (observer.type === 'form') {
|
||||||
observer.callback(this.mergedFormErrors)
|
observer.callback(this.mergedFormErrors)
|
||||||
} else if (Object.prototype.hasOwnProperty.call(this.mergedFieldErrors, observer.field)) {
|
} else if (has(this.mergedFieldErrors, observer.field)) {
|
||||||
observer.callback(this.mergedFieldErrors[observer.field])
|
observer.callback(this.mergedFieldErrors[observer.field])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,21 +197,16 @@ export default {
|
|||||||
Object.assign(this.internalFormModelProxy, { [field]: value })
|
Object.assign(this.internalFormModelProxy, { [field]: value })
|
||||||
this.$emit('input', Object.assign({}, this.internalFormModelProxy))
|
this.$emit('input', Object.assign({}, this.internalFormModelProxy))
|
||||||
},
|
},
|
||||||
getUniqueRegistryName (base, count = 0) {
|
|
||||||
if (Object.prototype.hasOwnProperty.call(this.registry, base + (count || ''))) {
|
|
||||||
return this.getUniqueRegistryName(base, count + 1)
|
|
||||||
}
|
|
||||||
return base + (count || '')
|
|
||||||
},
|
|
||||||
register (field, component) {
|
register (field, component) {
|
||||||
// Don't re-register fields... @todo come up with another way of handling this that doesn't break multi option
|
// Don't re-register fields... @todo come up with another way of handling this that doesn't break multi option
|
||||||
if (Object.prototype.hasOwnProperty.call(this.registry, field)) {
|
if (this.registry.has(field)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
this.registry[field] = component
|
this.registry.add(field, component)
|
||||||
const hasVModelValue = Object.prototype.hasOwnProperty.call(component.$options.propsData, 'formulateValue')
|
const hasVModelValue = has(component.$options.propsData, 'formulateValue')
|
||||||
const hasValue = Object.prototype.hasOwnProperty.call(component.$options.propsData, 'value')
|
const hasValue = has(component.$options.propsData, 'value')
|
||||||
if (
|
if (
|
||||||
|
!component.context.isSubField() &&
|
||||||
!hasVModelValue &&
|
!hasVModelValue &&
|
||||||
this.hasInitialValue &&
|
this.hasInitialValue &&
|
||||||
this.initialValues[field]
|
this.initialValues[field]
|
||||||
@ -222,6 +218,8 @@ export default {
|
|||||||
(hasVModelValue || hasValue) &&
|
(hasVModelValue || hasValue) &&
|
||||||
!shallowEqualObjects(component.internalModelProxy, this.initialValues[field])
|
!shallowEqualObjects(component.internalModelProxy, this.initialValues[field])
|
||||||
) {
|
) {
|
||||||
|
// In this case, the field is v-modeled or has an initial value and the
|
||||||
|
// form has no value or a different value, so use the field value
|
||||||
this.setFieldValue(field, component.internalModelProxy)
|
this.setFieldValue(field, component.internalModelProxy)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -246,9 +244,9 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
showErrors () {
|
showErrors () {
|
||||||
for (const fieldName in this.registry) {
|
this.registry.map(input => {
|
||||||
this.registry[fieldName].formShouldShowErrors = true
|
input.formShouldShowErrors = true
|
||||||
}
|
})
|
||||||
},
|
},
|
||||||
getFormValues () {
|
getFormValues () {
|
||||||
return this.internalFormModelProxy
|
return this.internalFormModelProxy
|
||||||
@ -257,13 +255,10 @@ export default {
|
|||||||
this.$emit('validation', errorObject)
|
this.$emit('validation', errorObject)
|
||||||
},
|
},
|
||||||
hasValidationErrors () {
|
hasValidationErrors () {
|
||||||
const resolvers = []
|
return Promise.all(this.registry.reduce((resolvers, cmp, name) => {
|
||||||
for (const fieldName in this.registry) {
|
resolvers.push(cmp.getValidationErrors())
|
||||||
if (typeof this.registry[fieldName].getValidationErrors === 'function') {
|
return resolvers
|
||||||
resolvers.push(this.registry[fieldName].getValidationErrors())
|
}, [])).then((errorObjects) => {
|
||||||
}
|
|
||||||
}
|
|
||||||
return Promise.all(resolvers).then((errorObjects) => {
|
|
||||||
return errorObjects.some(item => item.hasErrors)
|
return errorObjects.some(item => item.hasErrors)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,8 @@ export default {
|
|||||||
provide () {
|
provide () {
|
||||||
return {
|
return {
|
||||||
formulateFormSetter: this.setFieldValue,
|
formulateFormSetter: this.setFieldValue,
|
||||||
formulateFormRegister: this.register
|
formulateFormRegister: this.register,
|
||||||
|
isSubField: () => true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -63,7 +64,7 @@ export default {
|
|||||||
))
|
))
|
||||||
this.context.model = values
|
this.context.model = values
|
||||||
},
|
},
|
||||||
register () {
|
register (field, component) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import context from './libs/context'
|
import context from './libs/context'
|
||||||
import { shallowEqualObjects, parseRules, snakeToCamel, arrayify } from './libs/utils'
|
import { shallowEqualObjects, parseRules, snakeToCamel, arrayify, has } from './libs/utils'
|
||||||
import nanoid from 'nanoid/non-secure'
|
import nanoid from 'nanoid/non-secure'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -79,7 +79,8 @@ export default {
|
|||||||
formulateFormRegister: { default: undefined },
|
formulateFormRegister: { default: undefined },
|
||||||
getFormValues: { default: () => () => ({}) },
|
getFormValues: { default: () => () => ({}) },
|
||||||
observeErrors: { default: undefined },
|
observeErrors: { default: undefined },
|
||||||
removeErrorObserver: { default: undefined }
|
removeErrorObserver: { default: undefined },
|
||||||
|
isSubField: { default: () => () => false }
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
prop: 'formulateValue',
|
prop: 'formulateValue',
|
||||||
@ -290,9 +291,9 @@ export default {
|
|||||||
classification = (classification === 'box' && this.options) ? 'group' : classification
|
classification = (classification === 'box' && this.options) ? 'group' : classification
|
||||||
if (classification === 'box' && this.checked) {
|
if (classification === 'box' && this.checked) {
|
||||||
return this.value || true
|
return this.value || true
|
||||||
} else if (Object.prototype.hasOwnProperty.call(this.$options.propsData, 'value') && classification !== 'box') {
|
} else if (has(this.$options.propsData, 'value') && classification !== 'box') {
|
||||||
return this.value
|
return this.value
|
||||||
} else if (Object.prototype.hasOwnProperty.call(this.$options.propsData, 'formulateValue')) {
|
} else if (has(this.$options.propsData, 'formulateValue')) {
|
||||||
return this.formulateValue
|
return this.formulateValue
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
|
@ -58,6 +58,7 @@ export default {
|
|||||||
component,
|
component,
|
||||||
hasLabel,
|
hasLabel,
|
||||||
slotComponents,
|
slotComponents,
|
||||||
|
isSubField,
|
||||||
...context
|
...context
|
||||||
} = this.context
|
} = this.context
|
||||||
return this.options.map(option => this.groupItemContext(
|
return this.options.map(option => this.groupItemContext(
|
||||||
|
@ -37,6 +37,7 @@ export default {
|
|||||||
validationErrors: this.validationErrors,
|
validationErrors: this.validationErrors,
|
||||||
value: this.value,
|
value: this.value,
|
||||||
visibleValidationErrors: this.visibleValidationErrors,
|
visibleValidationErrors: this.visibleValidationErrors,
|
||||||
|
isSubField: this.isSubField,
|
||||||
...this.typeContext
|
...this.typeContext
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
69
src/libs/registry.js
Normal file
69
src/libs/registry.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Component registry with inherent depth to handle complex nesting. This is
|
||||||
|
* important for features such as grouped fields.
|
||||||
|
*/
|
||||||
|
class Registry {
|
||||||
|
/**
|
||||||
|
* Create a new registry of components.
|
||||||
|
*/
|
||||||
|
constructor () {
|
||||||
|
this.registry = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to the registry.
|
||||||
|
* @param {string|array} key
|
||||||
|
* @param {vue} component
|
||||||
|
*/
|
||||||
|
add (name, component) {
|
||||||
|
this.registry.set(name, component)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an item from the registry.
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
remove (name) {
|
||||||
|
this.registry.delete(name)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the registry has the given key.
|
||||||
|
* @param {string|array} key
|
||||||
|
*/
|
||||||
|
has (key) {
|
||||||
|
return this.registry.has(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map over the registry (recursively).
|
||||||
|
* @param {function} callback
|
||||||
|
*/
|
||||||
|
map (callback) {
|
||||||
|
const value = {}
|
||||||
|
this.registry.forEach((component, field) => Object.assign(value, { [field]: callback(component, field) }))
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the keys of the registry.
|
||||||
|
*/
|
||||||
|
keys () {
|
||||||
|
return Array.from(this.registry.keys())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduce the registry.
|
||||||
|
* @param {function} callback
|
||||||
|
*/
|
||||||
|
reduce (callback, accumulator) {
|
||||||
|
this.registry.forEach((component, field) => {
|
||||||
|
accumulator = callback(accumulator, component, field)
|
||||||
|
})
|
||||||
|
return accumulator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Registry
|
@ -206,3 +206,18 @@ export function parseLocale (locale) {
|
|||||||
return options.length ? options : [segment]
|
return options.length ? options : [segment]
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for Object.prototype.hasOwnProperty.call (space saving)
|
||||||
|
*/
|
||||||
|
export function has (ctx, prop) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(ctx, prop)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a registry object, map over it recursively entering groups.
|
||||||
|
* @param {Object} registry key => component
|
||||||
|
*/
|
||||||
|
export function mapRegistry (registry) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="formulate-input-group-add-more">
|
<div class="formulate-input-group-add-more">
|
||||||
<button @click="$emit('add')">
|
<FormulateInput
|
||||||
Add more
|
type="button"
|
||||||
</button>
|
label="Add more"
|
||||||
|
@click.native="$emit('add')"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ describe('FormulateForm', () => {
|
|||||||
propsData: { formulateValue: { testinput: 'has initial value' } },
|
propsData: { formulateValue: { testinput: 'has initial value' } },
|
||||||
slots: { default: '<FormulateInput type="text" name="subinput1" /><FormulateInput type="checkbox" name="subinput2" />' }
|
slots: { default: '<FormulateInput type="text" name="subinput1" /><FormulateInput type="checkbox" name="subinput2" />' }
|
||||||
})
|
})
|
||||||
expect(Object.keys(wrapper.vm.registry)).toEqual(['subinput1', 'subinput2'])
|
expect(wrapper.vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can set a field’s initial value', async () => {
|
it('can set a field’s initial value', async () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user