1
0
mirror of synced 2024-11-25 14:56:03 +03:00

Removes the original source code

This commit is contained in:
Justin Schroeder 2020-02-18 13:08:40 -05:00
parent 778d6b8731
commit 6690ab3576
8 changed files with 1 additions and 1010 deletions

View File

@ -1,5 +1,5 @@
{
"name": "@braid/vue-formulate-next",
"name": "@braid/vue-formulate",
"version": "2.0.0-alpha.1",
"description": "The easiest way to build forms in Vue.",
"main": "dist/formulate.umd.js",

View File

@ -1,181 +0,0 @@
<template>
<form
@submit.prevent="submit"
class="formulate-form"
>
<slot />
</form>
</template>
<script>
import {equals, reduce} from '../utils'
import cloneDeep from 'clone-deep'
export default {
props: {
name: {
type: String,
required: true
},
module: {
type: [String, Boolean],
default: function () {
return this.$formulate.options.vuexModule
}
},
initial: {
type: Object,
default: () => ({})
},
behavior: {
type: String,
default: 'blur'
},
showErrors: {
type: [Boolean, Object],
default: () => ({})
},
errors: {
type: Object,
default: () => ({})
},
prevent: {
type: String,
default: 'validation'
}
},
data () {
return {
parentIdentifier: 'vue-formulate-wrapper-element',
forceErrors: null,
fieldInitials: {},
whenFinishedValidating: Promise.resolve()
}
},
computed: {
m () {
return `${this.module ? this.module + '/' : ''}`
},
hasErrors () {
return this.$store.getters[`${this.m}hasErrors`][this.name] || false
},
hasValidationErrors () {
return this.$store.getters[`${this.m}hasValidationErrors`][this.name] || false
},
values () {
return cloneDeep(this.$store.getters[`${this.m}formValues`][this.name] || {})
},
storeErrors () {
return this.$store.getters[`${this.m}formErrors`][this.name] || {}
},
validationErrors () {
return this.$store.getters[`${this.m}formValidationErrors`][this.name] || {}
},
fields () {
return this.$store.getters[`${this.m}formMeta`][this.name] || []
},
shouldShowErrors () {
if (this.forceErrors === false || this.forceErrors === true) {
return this.forceErrors
}
if (this.showErrors === false || this.showErrors === true) {
return this.showErrors
}
return this.behavior === 'live'
},
mergedInitial () {
return Object.assign({}, this.initial, this.fieldInitials)
}
},
watch: {
mergedInitial () {
this.hydrate(this.mergedInitial)
},
values () {
this.updateFormValidation()
}
},
created () {
this.hydrate(this.mergedInitial)
},
mounted () {
this.hydrate(this.mergedInitial)
},
methods: {
registerField (field, data) {
this.$store.commit(`${this.m}setFieldMeta`, {form: this.name, field, data})
if (data.type !== 'submit') {
this.$store.commit(`${this.m}setFieldValue`, {
field,
value: this.mergedInitial.hasOwnProperty(field) ? this.mergedInitial[field] : undefined,
form: this.name
})
}
},
async deregisterField (field) {
await this.whenFinishedValidating
this.$store.commit(`${this.m}removeField`, {
form: this.name,
field
})
},
hydrate (values) {
for (let field of this.fields) {
if (field.type !== 'submit' && typeof this.values[field.name] === 'undefined') {
this.$store.commit(`${this.m}setFieldValue`, {
field: field.name,
value: values[field.name],
form: this.name
})
}
}
},
setInitial (field, value) {
this.fieldInitials = Object.assign({}, this.fieldInitials, {[field]: value})
},
update (change) {
this.$store.commit(`${this.m}setFieldValue`, Object.assign(change, {
form: this.name
}))
},
updateFieldErrors (change) {
this.$store.commit(`${this.m}setFieldErrors`, Object.assign(change, {
form: this.name
}))
},
updateFieldValidationErrors (change) {
this.$store.commit(`${this.m}setFieldValidationErrors`, Object.assign(change, {
form: this.name
}))
},
async validateField ({field, validation, label}) {
let errors = await this.$formulate.validationErrors({
field,
value: this.values[field],
label
}, validation, this.values)
if (!equals(errors || [], (this.validationErrors[field] || []))) {
if (this.fields.find(f => f.name === field)) {
this.updateFieldValidationErrors({field, errors: errors || []})
}
}
return errors
},
async updateFormValidation () {
await this.whenFinishedValidating
this.whenFinishedValidating = Promise.all(this.fields.map(async field => this.validateField({
field: field.name,
validation: field.validation,
label: field.validationLabel || field.label || field.name
})))
},
submit () {
if ((this.prevent === 'validation' && this.hasValidationErrors) || (this.prevent === 'any' && this.hasErrors)) {
this.forceErrors = true
} else {
this.$emit('submit', Object.assign({}, this.values))
}
}
}
}
</script>

View File

@ -1,458 +0,0 @@
<template>
<div :class="classes">
<slot name="prefix" />
<div
class="formulate-element-input-wrapper"
:data-type="type"
:data-classification="classification"
:data-is-disabled="disabled"
>
<!-- TEXT STYLE INPUTS -->
<label
:for="id"
v-text="label"
v-if="label && (!isBoxInput || optionList.length > 1)"
/>
<input
ref="input"
:class="elementClasses"
:type="type"
:name="name"
v-model="val"
v-bind="attributes"
v-if="isTextInput"
@blur="setBlurState"
@focus="setFocusState"
:disabled="disabled"
:step="step"
>
<textarea
ref="textarea"
:class="elementClasses"
:type="type"
:name="name"
v-model="val"
v-bind="attributes"
v-if="isTextareaInput"
@blur="setBlurState"
@focus="setFocusState"
:disabled="disabled"
/>
<!-- BUTTON INPUTS -->
<button
:type="type"
:class="elementClasses"
v-if="isButtonInput"
:disabled="disabled || (type === 'submit' && (form.hasErrors && form.behavior === 'live'))"
>
<slot
v-if="$slots.button"
name="button"
/>
<span
v-text="label || name"
v-else
/>
</button>
<!-- SELECT INPUTS -->
<select
v-bind="attributes"
v-if="isSelectInput"
:class="elementClasses"
:name="name"
v-model="val"
@blur="setBlurState"
@focus="setFocusState"
:disabled="disabled"
>
<template
v-if="!optionGroups"
>
<option
v-for="option in optionList"
:value="option.value"
:key="option.id"
v-bind="option.attributes || {}"
v-text="option.label"
/>
</template>
<template
v-else
>
<optgroup
v-for="(list, label) in optionGroups"
:key="label"
:label="label"
>
<option
v-for="option in createOptionList(list)"
:value="option.value"
:key="option.value"
v-bind="option.attributes || {}"
v-text="option.label"
/>
</optgroup>
</template>
</select>
<!-- BOX INPUTS -->
<div
class="formulate-element-box-input-wrap"
v-if="isBoxInput"
>
<div
class="formulate-element-box-input-group"
v-for="option in optionList"
:key="option.id"
>
<input
type="radio"
:class="elementClasses"
:name="name"
:id="option.id"
:value="option.value"
:key="`${option.id}-input`"
v-bind="attributes"
v-model="val"
v-if="type === 'radio'"
@blur="setBlurState"
@focus="setFocusState"
:disabled="disabled || option.disabled"
>
<input
type="checkbox"
:class="elementClasses"
:name="name"
:id="option.id"
:value="option.value"
:key="`${option.id}-input`"
v-bind="attributes"
v-model="val"
v-if="type === 'checkbox'"
@blur="setBlurState"
@focus="setFocusState"
:disabled="disabled || option.disabled"
>
<label
:for="option.id"
:key="`${option.id}-label`"
v-text="option.label"
/>
</div>
</div>
<!-- CUSTOM SLOT INPUTS -->
<slot v-if="hasCustomInput" />
<!-- UNSUPPORTED INPUT -->
<div
style="background-color: red; color: white"
v-if="isUnsupportedInput"
v-text="`Unsupported field type: “${type}”.`"
/>
</div>
<div
class="formulate-help"
v-if="help"
v-text="help"
/>
<transition
name="formulate-errors"
>
<transition-group
tag="ul"
name="formulate-error-list"
class="formulate-errors"
v-if="shouldShowErrors && localAndValidationErrors.length"
>
<li
v-for="error in localAndValidationErrors"
v-text="error"
:key="error"
/>
</transition-group>
</transition>
<slot name="suffix" />
</div>
</template>
<script>
import {inputTypes, equals, reduce, filter} from '../utils'
import shortid from 'shortid'
export default {
props: {
type: {
type: [String, Boolean],
default: 'text'
},
name: {
type: String,
required: true
},
initial: {
type: [String, Number, Boolean],
default: false
},
validation: {
type: [String, Boolean],
default: false
},
errors: {
type: Array,
default: () => []
},
label: {
type: [String, Boolean],
default: false
},
id: {
type: [String],
default: () => shortid.generate()
},
min: {
type: [String, Number, Boolean],
default: () => false
},
max: {
type: [String, Number, Boolean],
default: () => false
},
maxlength: {
type: [String, Number, Boolean],
default: () => false
},
pattern: {
type: [String, Number, Boolean],
default: () => false
},
minlength: {
type: [String, Number, Boolean],
default: () => false
},
placeholder: {
type: [String, Number, Boolean],
default: () => false
},
step: {
type: [String, Number, Boolean],
default: () => false
},
options: {
type: [Object, Array],
default: () => []
},
optionGroups: {
type: [Boolean, Object],
default: false,
validator: function (value) {
if (value === false) {
return true
} else if (typeof value === 'boolean') {
return false
}
return true
}
},
multiple: {
type: Boolean,
default: false
},
showErrors: {
type: [Object, Boolean],
default: () => ({})
},
validationLabel: {
type: [String, Boolean],
default: false
},
elementClasses: {
type: [String, Array, Object],
default: () => {}
},
disabled: {
type: Boolean,
default: false
},
help: {
type: [Boolean, String],
default: false
}
},
data () {
return {
errorBlurState: false,
focusState: false
}
},
computed: {
classification () {
if (this.isTextInput) return 'text'
if (this.isTextareaInput) return 'textarea'
if (this.isBoxInput) return 'box'
if (this.isButtonInput) return 'button'
if (this.isSelectInput) return 'select'
if (this.hasCustomInput) return 'custom'
return 'unsupported'
},
hasCustomInput () {
return (this.$slots.default && this.$slots.default.length)
},
isTextInput () {
return !this.hasCustomInput && inputTypes.text.includes(this.type)
},
isTextareaInput () {
return !this.hasCustomInput && inputTypes.textarea.includes(this.type)
},
isButtonInput () {
return !this.hasCustomInput && inputTypes.button.includes(this.type)
},
isSelectInput () {
return !this.hasCustomInput && inputTypes.select.includes(this.type)
},
isBoxInput () {
return !this.hasCustomInput && inputTypes.box.includes(this.type)
},
isUnsupportedInput () {
return (!this.hasCustomInput && !this.isTextInput && !this.isButtonInput && !this.isSelectInput && !this.isBoxInput && !this.isTextareaInput)
},
form () {
let parent = this.$parent
while (parent && parent.$data && parent.$data.parentIdentifier !== 'vue-formulate-wrapper-element') {
parent = parent.$parent
}
if (!parent.$data || parent.$data.parentIdentifier !== 'vue-formulate-wrapper-element') {
throw new Error('FormulateElement has no FormulateWrapper element')
}
return parent
},
values () {
return this.form.values
},
value () {
let value = this.values[this.name]
if (value === undefined) {
switch (this.type) {
case 'color':
return '#000000'
case 'checkbox':
if (this.optionList.length > 1) {
return []
}
break
}
}
return value
},
module () {
return this.form.$props['module']
},
formName () {
return this.form.$props['name']
},
classes () {
return {
'formulate-element': true,
[`formulate-element--type--${this.type}`]: true,
'formulate-element--has-value': !!this.value,
'formulate-element--has-errors': this.localAndValidationErrors.length && this.shouldShowErrors,
'formulate-element--has-prefix': !!this.$slots.prefix,
'formulate-element--has-suffix': !!this.$slots.suffix,
'formulate-element--has-focus': !!this.focusState
}
},
validationErrors () {
return this.form.validationErrors[this.name] || []
},
storeErrors () {
return this.form.storeErrors[this.name] || []
},
formErrors () {
return this.form.errors[this.name] || []
},
localAndValidationErrors () {
return this.errors.concat(this.validationErrors).concat(this.formErrors)
},
shouldShowErrors () {
let show = this.form.shouldShowErrors
if (this.form.behavior === 'blur') {
show = show || this.errorBlurState
}
if (this.showErrors === false || this.showErrors === true) {
show = this.showErrors
}
return show
},
attributes () {
return ['min', 'max', 'minlength', 'maxlength', 'placeholder', 'id', 'multiple', 'pattern']
.filter(prop => this[prop] !== false)
.reduce((attributes, attr) => {
attributes[attr] = this[attr]
return attributes
}, {})
},
optionList () {
return this.createOptionList(this.options)
},
val: {
set (value) {
this.form.update({field: this.name, value})
if (this.isTextInput) {
this.$refs.input.value = value
}
},
get () {
return this.value
}
}
},
watch: {
localAndValidationErrors () {
if (!equals(this.localAndValidationErrors, this.storeErrors)) {
this.form.updateFieldErrors({
field: this.name,
errors: this.localAndValidationErrors
})
}
},
initial () {
this.form.update({field: this.name, value: this.initial})
}
},
created () {
if (typeof window === 'undefined') {
this.register()
}
},
mounted () {
this.register()
},
beforeDestroy () {
this.form.deregisterField(this.name)
},
methods: {
register () {
this.form.registerField(
this.name,
filter(this.$props, (prop, value) => ['name', 'type', 'id', 'label', 'validation', 'validationLabel'].includes(prop))
)
if (this.initial !== false) {
this.form.setInitial(this.name, this.initial)
}
},
setBlurState () {
this.errorBlurState = true
this.focusState = false
},
setFocusState () {
this.focusState = true
},
createOptionList (options) {
if (!Array.isArray(options)) {
return reduce(options, (options, value, label) => options.concat({value, label, id: shortid.generate()}), [])
} else if (Array.isArray(options) && !options.length) {
return [{value: this.name, label: (this.label || this.name), id: shortid.generate()}]
}
return options
}
}
}
</script>

View File

@ -1,7 +0,0 @@
export default {
required: ({label, value}) => `${label} is required`,
email: ({label, value}) => `${label} is invalid.`,
confirmed: ({label, value}) => `${label} does not match the confirmation field.`,
number: ({label, value}) => `${label} is not a number`,
default: ({label, value}) => `This field is invalid.`
}

View File

@ -1,115 +0,0 @@
import FormulateGroup from './components/Formulate'
import FormulateElement from './components/FormulateElement'
import DefaultRules from './rules'
import DefaultErrors from './errors'
class Formulate {
/**
* Initialize vue-formulate.
*/
constructor () {
this.defaultOptions = {
registerComponents: true,
tags: {
Formulate: 'formulate',
FormulateElement: 'formulate-element'
},
errors: {},
rules: {},
vuexModule: false
}
this.errors = DefaultErrors
this.rules = DefaultRules
}
/**
* Install vue-formulate as an instance of Vue.
* @param {Vue} Vue
*/
install (Vue, options = {}) {
Vue.prototype.$formulate = this
options = Object.assign(this.defaultOptions, options)
if (options.registerComponents) {
Vue.component(options.tags.Formulate, FormulateGroup)
Vue.component(options.tags.FormulateElement, FormulateElement)
}
if (options.errors) {
this.errors = Object.assign(this.errors, options.errors)
}
if (options.rules) {
this.rules = Object.assign(this.rules, options.rules)
}
this.options = options
}
/**
* Given a string of rules parse them out to relevant pieces/parts
* @param {string} rulesString
*/
parseRules (rulesString) {
return rulesString.split('|')
.map(rule => rule.trim())
.map(rule => rule.match(/([a-zA-Z0-9]+)\((.*)?\)/) || [null, rule, ''])
.map(([ruleString, rule, args]) => Object.assign({}, {rule}, args ? {
args: args.split(',').map(arg => arg.trim())
} : {args: []}))
}
/**
* Return the function that generates a validation error message for a given
* validation rule.
* @param {string} rule
*/
errorFactory (rule) {
return this.errors[rule] ? this.errors[rule] : this.errors['default']
}
/**
* Given a particular field, value, validation rules, and form values
* perform asynchronous field validation.
* @param {Object} validatee
* @param {string} rulesString
* @param {Object} values
*/
async validationErrors ({field, value, label}, rulesString, values) {
return rulesString ? Promise.all(
this.parseRules(rulesString)
.map(({rule, args}) => {
if (typeof this.rules[rule] !== 'function') {
throw new Error(`Validation rule is invalid: ${rule}`)
}
return this.rules[rule]({field, value, label, error: this.errorFactory(rule), values}, ...args)
})
).then(responses => responses.reduce((errors, error) => {
return error ? (Array.isArray(errors) ? errors.concat(error) : [error]) : errors
}, false)) : false
}
}
const formulate = new Formulate()
export default formulate
export * from './store'
/**
* Mapper to allow bindings to the vuex store for custom fields.
* @param {Object} definitions
*/
export const mapModels = (definitions) => {
const models = {}
for (let mapTo in definitions) {
let [form, field] = definitions[mapTo].split('/')
models[mapTo] = {
set (value) {
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
this.$store.commit(`${m}setFieldValue`, {form, field, value})
},
get () {
let m = formulate.options.vuexModule ? `${formulate.options.vuexModule}/` : ''
if (this.$store.getters[`${m}formValues`][form]) {
return this.$store.getters[`${m}formValues`][form][field]
}
return ''
}
}
}
return models
}

View File

@ -1,41 +0,0 @@
export default {
/**
* Validate a required field.
* @param {Object} field
* @param {string} label
*/
async required ({value, error}) {
return (!value || (Array.isArray(value) && !value.length)) ? error(...arguments) : false
},
/**
* Validates the field contains only numbers
* @param {Object} field
* @param {string} label
*/
async number ({value, error}) {
return isNaN(value) ? error(...arguments) : false
},
/**
* Validate email addresses
* @param {Object} field
* @param {string} label
*/
async email ({value, error}) {
// eslint-disable-next-line
var re = /^(?:[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&amp;'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/;
return (value && !re.test(value.toLowerCase())) ? error(...arguments) : false
},
/**
* Check if a particular field is matches another field in the form.
* @param {Object} field
* @param {string} label
* @param {string} confirmField (uses `${field}_confirmation` by default)
*/
async confirmed ({field, value, error, values}, confirmField) {
confirmField = confirmField || `${field}_confirmation`
return (value && value !== values[confirmField]) ? error(...arguments) : false
}
}

View File

@ -1,112 +0,0 @@
import {map, reduce, filter} from './utils'
/**
* Curried function for creating the formState
* @param {Object} options
*/
export const formulateState = (options = {}) => () => Object.assign({
values: {},
errors: {},
validationErrors: {},
meta: {}
}, options)
/**
* Function for creating the formGetters
* @param {string} module
* @param {Object} getters
*/
export const formulateGetters = (moduleName = '', getters = {}) => Object.assign({
formValues (state) {
return state.values
},
formErrors (state) {
return state.errors
},
formValidationErrors (state) {
return state.validationErrors
},
formMeta (state) {
return reduce(state.meta, (forms, form, fields) => {
forms[form] = reduce(fields, (arr, field, data) => arr.concat(data), [])
return forms
}, {})
},
hasErrors (state) {
return map(state.errors, (form, errors) => {
return reduce(errors, (hasErrors, field, errors) => hasErrors || !!errors.length, false)
})
},
hasValidationErrors (state) {
return map(state.validationErrors, (form, errors) => {
return reduce(errors, (hasErrors, field, errors) => hasErrors || !!errors.length, false)
})
}
}, getters)
/**
* Function for creating the formActions
* @param {string} moduleName
* @param {Object} actions
*/
export const formulateActions = (moduleName = '', actions = {}) => Object.assign({
}, actions)
/**
* Function for creating the formMutations
* @param {Object} mutations
*/
export const formulateMutations = (mutations = {}) => Object.assign({
setFieldValue (state, {form, field, value}) {
state.values = Object.assign({}, state.values, {
[form]: Object.assign({}, state.values[form] || {}, {[field]: value})
})
},
setFieldErrors (state, {form, field, errors}) {
state.errors = Object.assign({}, state.errors, {
[form]: Object.assign({}, state.errors[form] || {}, {[field]: errors})
})
},
setFieldValidationErrors (state, {form, field, errors}) {
state.validationErrors = Object.assign({}, state.validationErrors, {
[form]: Object.assign({}, state.validationErrors[form] || {}, {[field]: errors})
})
},
setFieldMeta (state, {form, field, data}) {
state.meta = Object.assign({}, state.meta, {
[form]: Object.assign({}, state.meta[form] || {}, {[field]: data})
})
},
resetForm (state, form) {
if (state.values[form]) {
state.values = Object.assign({}, state.values, {
[form]: map(state.values[form], (key, value) => undefined)
})
}
},
removeField (state, {form, field}) {
for (let group in state) {
if (state[group][form] && state[group][form].hasOwnProperty(field)) {
state[group][form] = filter(state[group][form], (key, value) => key !== field)
}
}
},
removeFieldValidationErrors (state, {form, field}) {
state.validationErrors = Object.assign({}, state.validationErrors, {
[form]: filter(state.validationErrors[form] || {}, key => key !== field)
})
}
}, mutations)
/**
* Function for exposing a full vuex module.
* @param {string} moduleName
* @param {Object} validation
*/
export const formulateModule = (moduleName) => ({
state: formulateState(),
getters: formulateGetters(moduleName),
actions: formulateActions(moduleName),
mutations: formulateMutations(),
namespaced: true
})

View File

@ -1,95 +0,0 @@
import cloneDeep from 'clone-deep'
/**
* Compare the equality of two arrays.
* @param {Array} arr1
* @param {Array} arr2
*/
export function equals (arr1, arr2) {
var length = arr1.length
if (length !== arr2.length) return false
for (var i = 0; i < length; i++) {
if (arr1[i] !== arr2[i]) {
return false
}
}
return true
}
/**
* Function to map over an object.
* @param {Object} obj An object to map over
* @param {Function} callback
*/
export function map (original, callback) {
let obj = cloneDeep(original)
for (let key in obj) {
obj[key] = callback(key, obj[key])
}
return obj
}
/**
* Function to filter an object's properties
* @param {Object} original
* @param {Function} callback
*/
export function filter (original, callback) {
let obj = {}
for (let key in original) {
if (callback(key, original[key])) {
obj[key] = original[key]
}
}
return obj
}
/**
* Function to reduce an object's properties
* @param {Object} original
* @param {Function} callback
* @param {*} accumulator
*/
export function reduce (original, callback, accumulator) {
for (let key in original) {
accumulator = callback(accumulator, key, original[key])
}
return accumulator
}
/**
* Comprehensive list of input types supported.
*/
export const inputTypes = {
text: [
'text',
'email',
'number',
'color',
'date',
'datetime-local',
'hidden',
'month',
'password',
'range',
'search',
'tel',
'time',
'url',
'week'
],
button: [
'submit',
'button'
],
select: [
'select'
],
box: [
'radio',
'checkbox'
],
textarea: [
'textarea'
]
}