1
0
mirror of synced 2025-02-16 20:53:13 +03:00

Adds new registry class

This commit is contained in:
Justin Schroeder 2020-04-22 00:00:02 -04:00
parent 978a209e3e
commit f7248d029c
15 changed files with 138 additions and 46 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

9
dist/snow.css vendored
View File

@ -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

File diff suppressed because one or more lines are too long

View File

@ -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
} }

View File

@ -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)
}) })
} }

View File

@ -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) {
} }
} }

View File

@ -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 ''

View File

@ -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(

View File

@ -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
View 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

View File

@ -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) {
//
}

View File

@ -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>

View File

@ -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 fields initial value', async () => { it('can set a fields initial value', async () => {