├── .npmignore ├── README.md ├── configure └── field.js ├── fields ├── abstract.js ├── components │ ├── base.vue │ ├── color │ │ ├── color.vue │ │ ├── common │ │ │ ├── Alpha.vue │ │ │ ├── Checkboard.vue │ │ │ ├── EditableInput.vue │ │ │ ├── Hue.vue │ │ │ └── Saturation.vue │ │ ├── index.vue │ │ └── mixin │ │ │ └── color.js │ ├── date.vue │ ├── select.vue │ ├── switch.vue │ └── text.vue ├── index.js └── render-field.js ├── form.vue ├── format └── index.js ├── index.js ├── model ├── MixinData.js ├── MixinMethods.js ├── MixinProps.js ├── MixinWatch.js └── index.js ├── package.json └── result.jpg /.npmignore: -------------------------------------------------------------------------------- 1 | .git -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📚 vue-genesis-forms ![](https://scrutinizer-ci.com/g/BlackMix/vue-genesis-forms/badges/build.png?b=master) ![](https://img.shields.io/npm/dm/vue-genesis-forms.svg) 2 | 3 | ` 🔥 support working: ` 4 | * input, textarea, switch, select, colorpick, datepicker 5 | 6 | ` 🎨 next ` 7 | * mult select, toggle, radio 8 | 9 | # simple Demo 10 | [Link](https://codesandbox.io/s/j1z40p4k7w) 11 | 12 | `example usage:` 13 | App.vue 14 | 15 | ```html 16 | 21 | 22 | 80 | ``` 81 | 82 | # Result: 83 | ![image](https://raw.githubusercontent.com/BlackMix/vue-genesis-forms/master/result.jpg) 84 | 85 | # Credits: 86 | *Genesis* [Link](https://github.com/phpzm/genesis) -------------------------------------------------------------------------------- /configure/field.js: -------------------------------------------------------------------------------- 1 | // import { formatOptions, formatPhone, formatMoney, formatDateTime, formatDate, formatBoolean } from '@/bootstrap/support/format' 2 | 3 | export default (field, label, component = 'text', scopes = []) => { 4 | const defaults = ['index', 'view', 'create', 'edit'] 5 | 6 | if (!Array.isArray(scopes)) { 7 | scopes = defaults 8 | } 9 | if (!scopes.length) { 10 | scopes = defaults 11 | } 12 | 13 | return { 14 | field: field, 15 | label: label, 16 | scopes: scopes, 17 | form: {component: component}, 18 | grid: {}, 19 | all: {}, 20 | $scopes (scopes) { 21 | this.scopes = scopes 22 | return this 23 | }, 24 | $in (scope) { 25 | this.scopes = [scope] 26 | return this 27 | }, 28 | $out (scope) { 29 | this.scopes = defaults.filter(item => item !== scope) 30 | return this 31 | }, 32 | $form (form) { 33 | return this.$assign('form', form) 34 | }, 35 | $grid (grid) { 36 | return this.$assign('grid', grid) 37 | }, 38 | $all (all) { 39 | return this.$assign('all', all) 40 | }, 41 | $assign (property, options) { 42 | this[property] = Object.assign({}, this[property], options) 43 | return this 44 | }, 45 | $filter (rule = 'like', value = '', component = '') { 46 | this.grid.filter = {rule, value, component} 47 | return this 48 | }, 49 | $tab (name) { 50 | this.form.tab = name 51 | return this 52 | }, 53 | $readonly () { 54 | this.form.disabled = true 55 | return this 56 | }, 57 | $pk () { 58 | this.primaryKey = true 59 | return this.$readonly().$out('create').$grid({is: '60px'}) 60 | }, 61 | $validate (rule, value = true) { 62 | if (!this.form.validate) { 63 | this.form.validate = {} 64 | } 65 | this.form.validate[rule] = value 66 | return this 67 | }, 68 | $required (require = true) { 69 | if (require) { 70 | this.$validate('required') 71 | } 72 | return this 73 | }, 74 | $link (path) { 75 | this.grid.format = (value, row) => { 76 | let href = String(path) 77 | Object.keys(row).forEach(property => { 78 | href = href.replace(`{${property}}`, row[property]) 79 | }) 80 | return `${value}` 81 | } 82 | return this 83 | }, 84 | $img (className = 'avatar') { 85 | this.grid.format = (value) => { 86 | return `` 87 | } 88 | return this 89 | }, 90 | $checkbox () { 91 | this.form.component = 'checkbox' 92 | // this.grid.format = formatBoolean 93 | return this 94 | }, 95 | $color (options = null) { 96 | this.form.component = 'field-color' 97 | if (options) { 98 | this.form.colorType = options 99 | } 100 | return this 101 | }, 102 | $date (format = null) { 103 | if (format) { 104 | this.form.format = format 105 | } 106 | this.form.component = 'field-date' 107 | // this.grid.format = formatDate 108 | return this 109 | }, 110 | $datetime () { 111 | this.form.component = 'date' 112 | this.form.time = true 113 | // this.grid.format = formatDateTime 114 | return this 115 | }, 116 | $file () { 117 | this.form.component = 'file' 118 | return this 119 | }, 120 | $html () { 121 | this.form.component = 'html' 122 | return this 123 | }, 124 | $input () { 125 | this.form.component = 'field-text' 126 | this.form.input = 'input' 127 | return this 128 | }, 129 | $money () { 130 | this.form.component = 'money' 131 | // this.grid.format = formatMoney 132 | return this 133 | }, 134 | $numeric () { 135 | this.form.component = 'numeric' 136 | return this 137 | }, 138 | $password () { 139 | this.form.component = 'field-text' 140 | this.form.input = 'input' 141 | this.form.type = 'password' 142 | return this 143 | }, 144 | $phone () { 145 | this.form.component = 'phone' 146 | // this.grid.format = formatPhone 147 | return this 148 | }, 149 | $radio (options = []) { 150 | this.form.component = 'radio' 151 | if (!options.length) { 152 | options = [{value: 1, label: 'Sim'}, {value: 0, label: 'Não'}] 153 | } 154 | this.form.options = options 155 | // this.grid.format = formatOptions(options) 156 | return this 157 | }, 158 | $search () { 159 | this.form.component = 'select' 160 | return this 161 | }, 162 | $select (options = [], multiple = false) { 163 | this.form.component = 'field-select' 164 | this.form.placeholder = '.:. Selecione uma opção .:.' 165 | this.form.multiple = multiple 166 | this.form.options = options 167 | this.form.expanded = true 168 | // this.grid.format = formatOptions(options) 169 | return this 170 | }, 171 | $switch (valueTrue, valueFalse) { 172 | this.form.component = 'field-switch' 173 | this.form.valueTrue = valueTrue 174 | this.form.valueFalse = valueFalse 175 | return this 176 | }, 177 | $pivot (options = {}) { 178 | this.form.component = 'pivot' 179 | this.form.options = options 180 | return this 181 | }, 182 | $separator () { 183 | this.form.component = 'separator' 184 | return this 185 | }, 186 | $text (value = '') { 187 | this.form.component = 'field-text' 188 | this.form.input = 'input' 189 | this.form.type = 'text' 190 | this.form.data = value 191 | return this 192 | }, 193 | $textarea (options = null, value) { 194 | if (options) { 195 | this.form[options] = value 196 | } 197 | this.form.component = 'field-text' 198 | this.form.input = 'textarea' 199 | return this 200 | }, 201 | $time () { 202 | this.form.component = 'time' 203 | return this 204 | }, 205 | $toggle () { 206 | this.form.component = 'toggle' 207 | return this 208 | }, 209 | $wysiwyg () { 210 | this.form.component = 'wysiwyg' 211 | return this 212 | }, 213 | $event (type, action) { 214 | if (!this.form.events) { 215 | this.form.events = {} 216 | } 217 | this.form.events[type] = action 218 | return this 219 | }, 220 | $render () { 221 | const base = { 222 | field: this.field, 223 | label: this.label, 224 | primaryKey: this.primaryKey, 225 | scopes: this.scopes 226 | } 227 | if (this.grid.filter && !this.grid.filter.component) { 228 | this.grid.filter.component = this.form.component 229 | } 230 | return Object.assign({}, base, this.form, this.all) 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /fields/abstract.js: -------------------------------------------------------------------------------- 1 | import { unMask } from '../format' 2 | 3 | export default { 4 | props: { 5 | value: { 6 | default: undefined 7 | }, 8 | data: { 9 | default: '' 10 | }, 11 | label: { 12 | type: String, 13 | default: '' 14 | }, 15 | type: { 16 | type: String, 17 | default: 'text' 18 | }, 19 | input: { 20 | type: String, 21 | default: 'input' 22 | }, 23 | field: { 24 | type: String, 25 | default: 'text' 26 | }, 27 | placeholder: { 28 | type: String, 29 | default: '' 30 | }, 31 | small: { 32 | type: String, 33 | default: '' 34 | }, 35 | width: { 36 | default: '12' 37 | }, 38 | minHeight: { 39 | default: null 40 | }, 41 | validate: { 42 | default: undefined 43 | }, 44 | tooltip: { 45 | default: '' 46 | }, 47 | title: { 48 | type: String, 49 | default: 'Este campo possui critérios de validação' 50 | }, 51 | mask: { 52 | type: String, 53 | default: '' 54 | }, 55 | className: { 56 | type: Array, 57 | default: () => ([]) 58 | }, 59 | inline: { 60 | type: Boolean, 61 | default: false 62 | }, 63 | disabled: { 64 | type: Boolean, 65 | default: false 66 | }, 67 | editable: { 68 | type: Boolean, 69 | default: true 70 | }, 71 | errors: { 72 | type: Array, 73 | default: () => ([]) 74 | }, 75 | visible: { 76 | type: Boolean, 77 | default: true 78 | }, 79 | events: { 80 | type: Object, 81 | default: () => ({}) 82 | }, 83 | max: { 84 | default: () => undefined 85 | }, 86 | cleanable: { 87 | type: Boolean, 88 | default: () => true 89 | }, 90 | cleaning: { 91 | default: () => undefined 92 | } 93 | }, 94 | computed: { 95 | classNames () { 96 | const classNames = [] 97 | 98 | const width = 'column is-' + String(this.inline ? '100' : this.width) 99 | classNames.push(width) 100 | // classNames.push(this.$options.name) 101 | return classNames 102 | }, 103 | problems () { 104 | if (!Array.isArray(this.errors)) { 105 | return [] 106 | } 107 | return this.errors.filter(error => !error.status).map(error => ({ 108 | path: 'validation.' + error.rule, 109 | parameters: error.parameters 110 | })) 111 | }, 112 | name () { 113 | return this.field 114 | } 115 | }, 116 | data: () => ({ 117 | id: '', 118 | maxlength: '' 119 | }), 120 | methods: { 121 | updateValue (value) { 122 | if (this.mask) { 123 | value = unMask(this.mask, value) 124 | } 125 | if (this.pattern) { 126 | value = unMask(this.pattern, value) 127 | } 128 | this.$emit('input', value, this) 129 | }, 130 | /** 131 | * @param {Object} 132 | */ 133 | focus () { 134 | if (this.events.focus && typeof this.events.focus === 'function') { 135 | this.$emit('event', 'focus', this) 136 | } 137 | }, 138 | /** 139 | * @param {Object} 140 | */ 141 | blur () { 142 | if (this.events.blur && typeof this.events.blur === 'function') { 143 | this.$emit('event', 'blur', this) 144 | } 145 | }, 146 | /** 147 | * @param {Object} 148 | */ 149 | keypress () { 150 | if (this.events.keypress && typeof this.events.keypress === 'function') { 151 | this.$emit('event', 'keypress', this) 152 | } 153 | }, 154 | /** 155 | * @param {Object} 156 | */ 157 | keyup () { 158 | if (this.events.keyup && typeof this.events.keyup === 'function') { 159 | this.$emit('event', 'keyup', this) 160 | } 161 | }, 162 | /** 163 | * @param {Object} 164 | */ 165 | enter () { 166 | if (this.events.enter && typeof this.events.enter === 'function') { 167 | this.$emit('event', 'enter', this) 168 | } 169 | } 170 | }, 171 | created () { 172 | this.id = Math.random() 173 | this.maxlength = this.max 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /fields/components/base.vue: -------------------------------------------------------------------------------- 1 | 87 | 88 | 91 | -------------------------------------------------------------------------------- /fields/components/color/color.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 131 | 132 | 242 | -------------------------------------------------------------------------------- /fields/components/color/common/Alpha.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 85 | 86 | -------------------------------------------------------------------------------- /fields/components/color/common/Checkboard.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 66 | 67 | 77 | -------------------------------------------------------------------------------- /fields/components/color/common/EditableInput.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 80 | 81 | -------------------------------------------------------------------------------- /fields/components/color/common/Hue.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 139 | 140 | 176 | -------------------------------------------------------------------------------- /fields/components/color/common/Saturation.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 102 | 103 | -------------------------------------------------------------------------------- /fields/components/color/index.vue: -------------------------------------------------------------------------------- 1 | 81 | 82 | 98 | -------------------------------------------------------------------------------- /fields/components/color/mixin/color.js: -------------------------------------------------------------------------------- 1 | import tinycolor from 'tinycolor2' 2 | 3 | function _colorChange (data, oldHue) { 4 | var alpha = data && data.a 5 | var color 6 | 7 | // hsl is better than hex between conversions 8 | if (data && data.hsl) { 9 | color = tinycolor(data.hsl) 10 | } else if (data && data.hex && data.hex.length > 0) { 11 | color = tinycolor(data.hex) 12 | } else { 13 | color = tinycolor(data) 14 | } 15 | 16 | if (color && (color._a === undefined || color._a === null)) { 17 | color.setAlpha(alpha || 1) 18 | } 19 | 20 | var hsl = color.toHsl() 21 | var hsv = color.toHsv() 22 | 23 | if (hsl.s === 0) { 24 | hsv.h = hsl.h = data.h || (data.hsl && data.hsl.h) || oldHue || 0 25 | } 26 | 27 | return { 28 | hsl: hsl, 29 | hex: color.toHexString().toUpperCase(), 30 | rgba: color.toRgb(), 31 | hsv: hsv, 32 | oldHue: data.h || oldHue || hsl.h, 33 | source: data.source, 34 | a: data.a || color.getAlpha() 35 | } 36 | } 37 | 38 | export default { 39 | props: ['value'], 40 | data () { 41 | return { 42 | val: _colorChange(this.value) 43 | } 44 | }, 45 | computed: { 46 | colors: { 47 | get () { 48 | return this.val 49 | }, 50 | set (newVal) { 51 | this.val = newVal 52 | this.$emit('input', newVal) 53 | } 54 | } 55 | }, 56 | watch: { 57 | value (newVal) { 58 | this.val = _colorChange(newVal) 59 | } 60 | }, 61 | methods: { 62 | colorChange (data, oldHue) { 63 | this.oldHue = this.colors.hsl.h 64 | this.colors = _colorChange(data, oldHue || this.oldHue) 65 | }, 66 | isValidHex (hex) { 67 | return tinycolor(hex).isValid() 68 | }, 69 | simpleCheckForValidColor (data) { 70 | var keysToCheck = ['r', 'g', 'b', 'a', 'h', 's', 'l', 'v'] 71 | var checked = 0 72 | var passed = 0 73 | 74 | for (var i = 0; i < keysToCheck.length; i++) { 75 | var letter = keysToCheck[i] 76 | if (data[letter]) { 77 | checked++ 78 | if (!isNaN(data[letter])) { 79 | passed++ 80 | } 81 | } 82 | } 83 | 84 | if (checked === passed) { 85 | return data 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /fields/components/date.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 79 | 80 | 233 | -------------------------------------------------------------------------------- /fields/components/select.vue: -------------------------------------------------------------------------------- 1 | 122 | -------------------------------------------------------------------------------- /fields/components/switch.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | -------------------------------------------------------------------------------- /fields/components/text.vue: -------------------------------------------------------------------------------- 1 | 99 | -------------------------------------------------------------------------------- /fields/index.js: -------------------------------------------------------------------------------- 1 | export { default as FieldText } from './components/text.vue' 2 | export { default as FieldSelect } from './components/select.vue' 3 | export { default as FieldSwitch } from './components/switch.vue' 4 | export { default as FieldColor } from './components/color/index.vue' 5 | export { default as FieldDate } from './components/date.vue' 6 | -------------------------------------------------------------------------------- /fields/render-field.js: -------------------------------------------------------------------------------- 1 | export const fieldRender = (h, el, ctx) => { 2 | return h('field', { class: el.classNames, props: { 3 | id: el.id, 4 | inline: el.inline, 5 | problems: el.problems, 6 | label: el.label, 7 | validate: el.validate, 8 | title: el.title, 9 | tooltip: el.tooltip, 10 | editable: el.editable, 11 | visible: el.visible 12 | } }, [ctx]) 13 | } -------------------------------------------------------------------------------- /form.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /format/index.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export const get = (object, keys, defaultVal) => { 4 | keys = Array.isArray(keys) ? keys : keys.split('.') 5 | object = object[keys[0]] 6 | if (object && keys.length > 1 ) { 7 | return get( object, keys.slice(1)) 8 | } 9 | return object === undefined ? defaultVal : object 10 | } 11 | 12 | export const mask = (pattern, value) => { 13 | let masked = '' 14 | value = unMask(pattern, value) 15 | if (!value) { 16 | return '' 17 | } 18 | let j = 0 19 | for (let i = 0; i < pattern.length; i++) { 20 | if (pattern[i] === '*') { 21 | masked += '*' 22 | continue 23 | } 24 | if (j > value.length - 1) { 25 | return masked 26 | } 27 | if (pattern[i] === '#') { 28 | masked += value[j] 29 | j++ 30 | continue 31 | } 32 | masked += pattern[i] 33 | } 34 | return masked 35 | } 36 | 37 | export const noDuplicates = (array) => { 38 | const a = array.concat() 39 | for (let i = 0; i < a.length; ++i) { 40 | for (let j = i + 1; j < a.length; ++j) { 41 | if (a[i] === a[j]) { 42 | a.splice(j--, 1) 43 | } 44 | } 45 | } 46 | return a 47 | } 48 | 49 | export const unMask = (pattern, value) => { 50 | const chars = noDuplicates(String(pattern).replace(/[#,*]/g, '').split('')) 51 | return String(value).replace(new RegExp('[' + chars.join(',') + ']', 'g'), '') 52 | } 53 | 54 | export const parseDate = (value, format = 'DD/MM/YYYY') => { 55 | if (!String(value).substring(0, 10).match(/^\d{4}-\d{2}-\d{2}$/)) { 56 | return '' 57 | } 58 | const date = moment(value) 59 | if (!date.isValid()) { 60 | return '' 61 | } 62 | return moment(value).format(format) 63 | } 64 | 65 | export const formatDate = (value) => { 66 | return parseDate(value) 67 | } 68 | 69 | export const formatDateTime = (value) => { 70 | return parseDate(value, 'DD/MM/YYYY HH:mm') 71 | } 72 | 73 | export const formatBoolean = (value) => { 74 | let icon = 'check_box_outline_blank' 75 | if (value) { 76 | icon = 'check_box' 77 | } 78 | return '' + icon + '' 79 | } 80 | 81 | export const formatMoney = (value) => { 82 | return '
' + money(value) + '
' 83 | } 84 | 85 | export const formatPhone = (value) => { 86 | let pattern = '(##) ####-####' 87 | if (length > 13) { 88 | pattern = '(##) #-####-####' 89 | } 90 | return mask(pattern, value) 91 | } 92 | 93 | export const money = (value, precision = 2) => { 94 | if (typeof value === 'undefined') { 95 | value = 0 96 | } 97 | value = Number(value).toFixed(precision) 98 | 99 | value = String(value) 100 | .replace(/\D/g, '') 101 | .replace(/(\d)(\d{11})$/, '$1.$2') 102 | .replace(/(\d)(\d{8})$/, '$1.$2') 103 | .replace(/(\d)(\d{5})$/, '$1.$2') 104 | 105 | return value.replace(/(\d)(\d{2})$/, '$1,$2') 106 | } 107 | 108 | export const formatOptions = (options) => { 109 | return (value) => { 110 | if (Array.isArray(options)) { 111 | const option = options.find(option => String(option.value) === String(value)) 112 | return get(option, 'label', '') 113 | } 114 | return value 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Buefy from 'buefy' 3 | 4 | // F // later I fix it, no time to fix it now, so add full Buefy 5 | Vue.use(Buefy) 6 | 7 | import fields from './configure/field' 8 | import * as FieldsComponents from './fields' 9 | 10 | const components = Object.assign({}, FieldsComponents) 11 | 12 | Object.keys(components).forEach(key => { 13 | Vue.component(key, components[key]) 14 | }) 15 | 16 | export const field = (field, label, component, scopes = []) => { 17 | return fields(field, label, component, scopes) 18 | } 19 | 20 | export const filter = (fields, scope) => { 21 | return fields.filter(field => scope ? field.scopes && field.scopes.includes(scope) : true) 22 | } 23 | 24 | export { default as AppForm } from './form.vue' 25 | -------------------------------------------------------------------------------- /model/MixinData.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data: () => ({ 3 | components: {}, 4 | schemas: {}, 5 | record: {}, 6 | tabSeletecd: '', 7 | currentStep: '', 8 | modified: false 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /model/MixinMethods.js: -------------------------------------------------------------------------------- 1 | import * as Validators from 'vuelidate/lib/validators' 2 | 3 | const arrayToObject = (accumulate, item) => { 4 | accumulate[item.field] = item 5 | return accumulate 6 | } 7 | 8 | export default { 9 | methods: { 10 | /** 11 | */ 12 | touch () { 13 | this.$v.$touch() 14 | }, 15 | /** 16 | */ 17 | reset () { 18 | this.$v.$reset() 19 | }, 20 | /** 21 | * @param fields 22 | * @returns {Object} 23 | */ 24 | generateValidations (fields) { 25 | if (!Array.isArray(fields)) { 26 | return {} 27 | } 28 | const validations = {} 29 | fields 30 | .filter(schema => !!schema.validate) 31 | .forEach(schema => { 32 | validations[schema.field] = this.configureValidation(schema.validate) 33 | }) 34 | return validations 35 | }, 36 | /** 37 | * @param {Object} validate 38 | * @return {Object} 39 | */ 40 | configureValidation (validate) { 41 | const configure = {} 42 | Object.keys(validate).forEach(property => { 43 | let action = Validators[property] 44 | if (!action.length) { 45 | configure[property] = action 46 | return true 47 | } 48 | configure[property] = action((validate[property])) 49 | }) 50 | return configure 51 | }, 52 | /** 53 | */ 54 | updateComponents () { 55 | const components = {} 56 | if (this.tabs.length) { 57 | this.tabs.forEach(tab => { 58 | components[tab.name] = this.fields.filter(field => field.tab === tab.name).reduce(arrayToObject, {}) 59 | }) 60 | } 61 | if (this.steps.length) { 62 | this.steps.forEach(step => { 63 | components[step.name] = this.fields.filter(field => field.step === step.name).reduce(arrayToObject, {}) 64 | }) 65 | } 66 | this.components = components 67 | }, 68 | /** 69 | */ 70 | updateSchemas () { 71 | this.schemas = this.fields.reduce(arrayToObject, {}) 72 | }, 73 | /** 74 | */ 75 | updateRecord () { 76 | const reduce = (accumulate, field) => { 77 | accumulate[field] = this.data[field] || this.schemas[field].default 78 | if (this.$route.query[field]) { 79 | accumulate[field] = this.$route.query[field] 80 | } 81 | return accumulate 82 | } 83 | const record = Object.keys(this.schemas).reduce(reduce, {}) 84 | this.setRecord(record) 85 | }, 86 | /** 87 | * @param {Object} record 88 | * @returns this 89 | */ 90 | setRecord (record) { 91 | this.record = record 92 | this.executeChange() 93 | return this 94 | }, 95 | /** 96 | * @param {string} field 97 | * @param {Object} parameters 98 | */ 99 | isProgrammatically (parameters) { 100 | if (typeof parameters !== 'object') { 101 | return false 102 | } 103 | const args = [...parameters] 104 | if (args.length <= 1) { 105 | return false 106 | } 107 | return parameters[1] === true 108 | }, 109 | /** 110 | * @param {string} field 111 | * @param {Object} parameters 112 | */ 113 | formInput (field, parameters = []) { 114 | const programmatically = this.isProgrammatically(parameters) 115 | if (programmatically) { 116 | return 117 | } 118 | if (this.$v.record[field]) { 119 | this.$v.record[field].$touch() 120 | } 121 | // pass errors to fields 122 | this.schemas[field].errors = this.getErrors(field) 123 | 124 | // emit changes to parent 125 | if (!this.readonly) { 126 | this.fireEvent(field, 'change') 127 | this.$emit('form~input', this.record) 128 | } 129 | 130 | // get invalid fields 131 | const reduce = (accumulate, key) => { 132 | if (this.$v.record[key].$invalid) { 133 | accumulate[key] = true 134 | } 135 | return accumulate 136 | } 137 | const invalids = Object.keys(this.$v.record).reduce(reduce, {}) 138 | // emit invalids to parent 139 | this.$emit('form~valid', !this.$v.$invalid, invalids) 140 | }, 141 | /** 142 | * @param {string} event 143 | * @param {Vue} $field 144 | * @param {Object} parameters 145 | */ 146 | formEvent (event, $field, parameters = {}) { 147 | const field = $field.field 148 | this.fireEvent(field, event, parameters) 149 | }, 150 | /** 151 | * @param {string} field 152 | * @param {string} event 153 | * @param {Object} parameters 154 | */ 155 | fireEvent (field, event, parameters = {}) { 156 | if (this.schemas[field] && this.schemas[field].events && typeof this.schemas[field].events[event] === 'function') { 157 | this.schemas[field].events[event](this.record, this.schemas, this, parameters) 158 | } 159 | }, 160 | /** 161 | */ 162 | executeChange () { 163 | if (typeof this.change === 'function') { 164 | this.change(this.record, this.schemas, this) 165 | } 166 | this.modified = true 167 | }, 168 | /** 169 | * @param {String} field 170 | * @return {Array} 171 | */ 172 | getErrors (field) { 173 | const errors = [] 174 | if (this.schemas[field].validate && this.$v.record[field] && this.$v.record[field].$error) { 175 | Object.keys(this.schemas[field].validate).forEach(rule => { 176 | const status = this.$v.record[field][rule] 177 | const parameters = this.$v.record[field].$params[rule] 178 | errors.push({rule, status, parameters}) 179 | }) 180 | } 181 | return errors 182 | }, 183 | /** 184 | * @param {string} namespace 185 | * @param {AxiosResponse} response 186 | */ 187 | fireWatch (namespace, response) { 188 | if (this.watches[namespace] && typeof this.watches[namespace] === 'function') { 189 | this.watches[namespace](this.record, this.schemas, this, response) 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /model/MixinProps.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | tabs: { 4 | type: Array, 5 | default: () => ([]) 6 | }, 7 | tab: { 8 | type: String, 9 | default: () => '' 10 | }, 11 | steps: { 12 | type: Array, 13 | default: () => ([]) 14 | }, 15 | step: { 16 | type: String, 17 | default: () => '' 18 | }, 19 | fields: { 20 | type: Array, 21 | default: () => ([]) 22 | }, 23 | data: { 24 | type: Object, 25 | default: () => ({}) 26 | }, 27 | readonly: { 28 | type: Boolean, 29 | default: () => (false) 30 | }, 31 | watches: { 32 | type: Object, 33 | default: () => ({}) 34 | }, 35 | change: { 36 | type: Function 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /model/MixinWatch.js: -------------------------------------------------------------------------------- 1 | export default { 2 | watch: { 3 | /** 4 | * @param {Object} record 5 | * @param {Object} previous 6 | */ 7 | data (record, previous) { 8 | if (!record !== previous) { 9 | this.setRecord(record) 10 | this.fireWatch('set/record') 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /model/index.js: -------------------------------------------------------------------------------- 1 | export { default as data } from './MixinData' 2 | export { default as methods } from './MixinMethods' 3 | export { default as props } from './MixinProps' 4 | export { default as watch } from './MixinWatch' 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-genesis-forms", 3 | "version": "0.0.21", 4 | "description": "vue easy create forms", 5 | "main": "index.js", 6 | "readmeFilename": "README.md" , 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/BlackMix/vue-genesis-forms.git" 13 | }, 14 | "keywords": [ 15 | "vue", 16 | "forms", 17 | "genesis" 18 | ], 19 | "author": { 20 | "name": "BlackMix", 21 | "email": "mixchatmix3@gmail.com", 22 | "homepage": "https://github.com/BlackMix" 23 | }, 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/BlackMix/vue-genesis-forms/issues" 27 | }, 28 | "homepage": "https://github.com/BlackMix/vue-genesis-forms#readme", 29 | "dependencies": { 30 | "bulma": "^0.7.1", 31 | "buefy": "^0.6.6", 32 | "stylus": "^0.54.5", 33 | "stylus-loader": "^3.0.2", 34 | "tinycolor2":"^1.4.1", 35 | "lodash.throttle":"^4.1.1", 36 | "moment": "^2.22.2", 37 | "vue": "^2.5.16" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackMix/vue-genesis-forms/6a71d49ebbc082826560fe5dcd4cc645c3b373ed/result.jpg --------------------------------------------------------------------------------