From 7809cdd2e3fc1fd5c0c2568669a5f56c30f07d56 Mon Sep 17 00:00:00 2001 From: Justin Schroeder Date: Tue, 29 Oct 2019 23:33:31 -0400 Subject: [PATCH] Adds form level data binding --- dist/formulate.esm.js | 85 +++++++++++++++++++++++++-------- dist/formulate.min.js | 85 +++++++++++++++++++++++++-------- dist/formulate.umd.js | 85 +++++++++++++++++++++++++-------- src/FormulateForm.vue | 37 ++++++++++++++ src/FormulateInput.vue | 22 +++++---- src/libs/context.js | 21 ++++++-- test/FormulateInputText.test.js | 5 ++ 7 files changed, 271 insertions(+), 69 deletions(-) diff --git a/dist/formulate.esm.js b/dist/formulate.esm.js index 4692b1f..19d34d9 100644 --- a/dist/formulate.esm.js +++ b/dist/formulate.esm.js @@ -164,7 +164,8 @@ var context = { nameOrFallback: nameOrFallback, typeContext: typeContext, elementAttributes: elementAttributes, - logicalLabelPosition: logicalLabelPosition + logicalLabelPosition: logicalLabelPosition, + isVmodeled: isVmodeled }; /** @@ -237,6 +238,16 @@ function nameOrFallback () { return this.name } +/** + * Determines if this formulate element is v-modeled or not. + */ +function isVmodeled () { + return !!(this.$options.propsData.hasOwnProperty('formulateValue') && + this._events && + Array.isArray(this._events.input) && + this._events.input.length) +} + /** * Given an object or array of options, create an array of objects with label, * value, and id. @@ -272,19 +283,21 @@ function defineModel (context) { * Get the value from a model. **/ function modelGetter () { - if (this.type === 'checkbox' && !Array.isArray(this.formulateValue) && this.options) { + var model = this.isVmodeled ? 'formulateValue' : 'internalModelProxy'; + if (this.type === 'checkbox' && !Array.isArray(this[model]) && this.options) { return [] } - if (!this.formulateValue) { + if (!this[model]) { return '' } - return this.formulateValue + return this[model] } /** * Set the value from a model. **/ function modelSetter (value) { + this.internalModelProxy = value; this.$emit('input', value); if (this.context.name && typeof this.formulateFormSetter === 'function') { this.formulateFormSetter(this.context.name, value); @@ -315,7 +328,7 @@ var script = { }, /* eslint-disable */ formulateValue: { - default: undefined + default: '' }, value: { default: false @@ -353,7 +366,8 @@ var script = { data: function data () { return { defaultId: nanoid(9), - localAttributes: {} + localAttributes: {}, + internalModelProxy: this.formulateValue } }, computed: Object.assign({}, context, @@ -370,19 +384,24 @@ var script = { this.updateLocalAttributes(value); }, deep: true + }, + internalModelProxy: function internalModelProxy (newValue, oldValue) { + if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { + this.context.model = newValue; + } + }, + formulateValue: function formulateValue (newValue, oldValue) { + if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { + this.context.model = newValue; + } } }, created: function created () { if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') { - this.formulateFormRegister(this.name, this); + this.formulateFormRegister(this.nameOrFallback, this); } this.updateLocalAttributes(this.$attrs); }, - mounted: function mounted () { - if (this.debug) { - console.log('MOUNTED:' + this.$options.name + ':' + this.type); - } - }, methods: { updateLocalAttributes: function updateLocalAttributes (value) { if (!shallowEqualObjects(value, this.localAttributes)) { @@ -584,13 +603,6 @@ __vue_render__._withStripped = true; ); // -// -// -// -// -// -// -// var script$1 = { provide: function provide () { @@ -627,6 +639,36 @@ var script$1 = { set: function set (value) { this.$emit('input', value); } + }, + hasFormulateValue: function hasFormulateValue () { + return this.formulateValue && typeof this.formulateValue === 'object' + }, + isVmodeled: function isVmodeled () { + return !!(this.$options.propsData.hasOwnProperty('formulateValue') && + this._events && + Array.isArray(this._events.input) && + this._events.input.length) + } + }, + watch: { + formulateValue: { + handler: function handler (newValue, oldValue) { + if (this.isVmodeled && + newValue && + typeof newValue === 'object' + ) { + for (var field in newValue) { + if (this.registry.hasOwnProperty(field) && !shallowEqualObjects(newValue[field], this.registry[field].internalModelProxy)) { + // If the value of the formulateValue changed (probably as a prop) + // and it doesn't match the internal proxied value of the registered + // component, we set it explicitly. Its important we check the + // model proxy here since the model itself is not fully synchronous. + this.registry[field].context.model = newValue[field]; + } + } + } + }, + deep: true } }, methods: { @@ -637,6 +679,11 @@ var script$1 = { }, register: function register (field, component) { this.registry[field] = component; + if (!component.$options.propsData.hasOwnProperty('formulateValue') && this.hasFormulateValue && this.formulateValue[field]) { + // In the case that the form is carrying an initial value and the + // element is not, set it directly. + component.context.model = this.formulateValue[field]; + } } } }; diff --git a/dist/formulate.min.js b/dist/formulate.min.js index 6a71e0a..7773d35 100644 --- a/dist/formulate.min.js +++ b/dist/formulate.min.js @@ -167,7 +167,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) { nameOrFallback: nameOrFallback, typeContext: typeContext, elementAttributes: elementAttributes, - logicalLabelPosition: logicalLabelPosition + logicalLabelPosition: logicalLabelPosition, + isVmodeled: isVmodeled }; /** @@ -240,6 +241,16 @@ var Formulate = (function (exports, isPlainObject, nanoid) { return this.name } + /** + * Determines if this formulate element is v-modeled or not. + */ + function isVmodeled () { + return !!(this.$options.propsData.hasOwnProperty('formulateValue') && + this._events && + Array.isArray(this._events.input) && + this._events.input.length) + } + /** * Given an object or array of options, create an array of objects with label, * value, and id. @@ -275,19 +286,21 @@ var Formulate = (function (exports, isPlainObject, nanoid) { * Get the value from a model. **/ function modelGetter () { - if (this.type === 'checkbox' && !Array.isArray(this.formulateValue) && this.options) { + var model = this.isVmodeled ? 'formulateValue' : 'internalModelProxy'; + if (this.type === 'checkbox' && !Array.isArray(this[model]) && this.options) { return [] } - if (!this.formulateValue) { + if (!this[model]) { return '' } - return this.formulateValue + return this[model] } /** * Set the value from a model. **/ function modelSetter (value) { + this.internalModelProxy = value; this.$emit('input', value); if (this.context.name && typeof this.formulateFormSetter === 'function') { this.formulateFormSetter(this.context.name, value); @@ -318,7 +331,7 @@ var Formulate = (function (exports, isPlainObject, nanoid) { }, /* eslint-disable */ formulateValue: { - default: undefined + default: '' }, value: { default: false @@ -356,7 +369,8 @@ var Formulate = (function (exports, isPlainObject, nanoid) { data: function data () { return { defaultId: nanoid(9), - localAttributes: {} + localAttributes: {}, + internalModelProxy: this.formulateValue } }, computed: Object.assign({}, context, @@ -373,19 +387,24 @@ var Formulate = (function (exports, isPlainObject, nanoid) { this.updateLocalAttributes(value); }, deep: true + }, + internalModelProxy: function internalModelProxy (newValue, oldValue) { + if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { + this.context.model = newValue; + } + }, + formulateValue: function formulateValue (newValue, oldValue) { + if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { + this.context.model = newValue; + } } }, created: function created () { if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') { - this.formulateFormRegister(this.name, this); + this.formulateFormRegister(this.nameOrFallback, this); } this.updateLocalAttributes(this.$attrs); }, - mounted: function mounted () { - if (this.debug) { - console.log('MOUNTED:' + this.$options.name + ':' + this.type); - } - }, methods: { updateLocalAttributes: function updateLocalAttributes (value) { if (!shallowEqualObjects(value, this.localAttributes)) { @@ -587,13 +606,6 @@ var Formulate = (function (exports, isPlainObject, nanoid) { ); // - // - // - // - // - // - // - // var script$1 = { provide: function provide () { @@ -630,6 +642,36 @@ var Formulate = (function (exports, isPlainObject, nanoid) { set: function set (value) { this.$emit('input', value); } + }, + hasFormulateValue: function hasFormulateValue () { + return this.formulateValue && typeof this.formulateValue === 'object' + }, + isVmodeled: function isVmodeled () { + return !!(this.$options.propsData.hasOwnProperty('formulateValue') && + this._events && + Array.isArray(this._events.input) && + this._events.input.length) + } + }, + watch: { + formulateValue: { + handler: function handler (newValue, oldValue) { + if (this.isVmodeled && + newValue && + typeof newValue === 'object' + ) { + for (var field in newValue) { + if (this.registry.hasOwnProperty(field) && !shallowEqualObjects(newValue[field], this.registry[field].internalModelProxy)) { + // If the value of the formulateValue changed (probably as a prop) + // and it doesn't match the internal proxied value of the registered + // component, we set it explicitly. Its important we check the + // model proxy here since the model itself is not fully synchronous. + this.registry[field].context.model = newValue[field]; + } + } + } + }, + deep: true } }, methods: { @@ -640,6 +682,11 @@ var Formulate = (function (exports, isPlainObject, nanoid) { }, register: function register (field, component) { this.registry[field] = component; + if (!component.$options.propsData.hasOwnProperty('formulateValue') && this.hasFormulateValue && this.formulateValue[field]) { + // In the case that the form is carrying an initial value and the + // element is not, set it directly. + component.context.model = this.formulateValue[field]; + } } } }; diff --git a/dist/formulate.umd.js b/dist/formulate.umd.js index ec9e009..884d098 100644 --- a/dist/formulate.umd.js +++ b/dist/formulate.umd.js @@ -170,7 +170,8 @@ nameOrFallback: nameOrFallback, typeContext: typeContext, elementAttributes: elementAttributes, - logicalLabelPosition: logicalLabelPosition + logicalLabelPosition: logicalLabelPosition, + isVmodeled: isVmodeled }; /** @@ -243,6 +244,16 @@ return this.name } + /** + * Determines if this formulate element is v-modeled or not. + */ + function isVmodeled () { + return !!(this.$options.propsData.hasOwnProperty('formulateValue') && + this._events && + Array.isArray(this._events.input) && + this._events.input.length) + } + /** * Given an object or array of options, create an array of objects with label, * value, and id. @@ -278,19 +289,21 @@ * Get the value from a model. **/ function modelGetter () { - if (this.type === 'checkbox' && !Array.isArray(this.formulateValue) && this.options) { + var model = this.isVmodeled ? 'formulateValue' : 'internalModelProxy'; + if (this.type === 'checkbox' && !Array.isArray(this[model]) && this.options) { return [] } - if (!this.formulateValue) { + if (!this[model]) { return '' } - return this.formulateValue + return this[model] } /** * Set the value from a model. **/ function modelSetter (value) { + this.internalModelProxy = value; this.$emit('input', value); if (this.context.name && typeof this.formulateFormSetter === 'function') { this.formulateFormSetter(this.context.name, value); @@ -321,7 +334,7 @@ }, /* eslint-disable */ formulateValue: { - default: undefined + default: '' }, value: { default: false @@ -359,7 +372,8 @@ data: function data () { return { defaultId: nanoid(9), - localAttributes: {} + localAttributes: {}, + internalModelProxy: this.formulateValue } }, computed: Object.assign({}, context, @@ -376,19 +390,24 @@ this.updateLocalAttributes(value); }, deep: true + }, + internalModelProxy: function internalModelProxy (newValue, oldValue) { + if (!this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { + this.context.model = newValue; + } + }, + formulateValue: function formulateValue (newValue, oldValue) { + if (this.isVmodeled && !shallowEqualObjects(newValue, oldValue)) { + this.context.model = newValue; + } } }, created: function created () { if (this.formulateFormRegister && typeof this.formulateFormRegister === 'function') { - this.formulateFormRegister(this.name, this); + this.formulateFormRegister(this.nameOrFallback, this); } this.updateLocalAttributes(this.$attrs); }, - mounted: function mounted () { - if (this.debug) { - console.log('MOUNTED:' + this.$options.name + ':' + this.type); - } - }, methods: { updateLocalAttributes: function updateLocalAttributes (value) { if (!shallowEqualObjects(value, this.localAttributes)) { @@ -590,13 +609,6 @@ ); // - // - // - // - // - // - // - // var script$1 = { provide: function provide () { @@ -633,6 +645,36 @@ set: function set (value) { this.$emit('input', value); } + }, + hasFormulateValue: function hasFormulateValue () { + return this.formulateValue && typeof this.formulateValue === 'object' + }, + isVmodeled: function isVmodeled () { + return !!(this.$options.propsData.hasOwnProperty('formulateValue') && + this._events && + Array.isArray(this._events.input) && + this._events.input.length) + } + }, + watch: { + formulateValue: { + handler: function handler (newValue, oldValue) { + if (this.isVmodeled && + newValue && + typeof newValue === 'object' + ) { + for (var field in newValue) { + if (this.registry.hasOwnProperty(field) && !shallowEqualObjects(newValue[field], this.registry[field].internalModelProxy)) { + // If the value of the formulateValue changed (probably as a prop) + // and it doesn't match the internal proxied value of the registered + // component, we set it explicitly. Its important we check the + // model proxy here since the model itself is not fully synchronous. + this.registry[field].context.model = newValue[field]; + } + } + } + }, + deep: true } }, methods: { @@ -643,6 +685,11 @@ }, register: function register (field, component) { this.registry[field] = component; + if (!component.$options.propsData.hasOwnProperty('formulateValue') && this.hasFormulateValue && this.formulateValue[field]) { + // In the case that the form is carrying an initial value and the + // element is not, set it directly. + component.context.model = this.formulateValue[field]; + } } } }; diff --git a/src/FormulateForm.vue b/src/FormulateForm.vue index 79e2aed..32fede1 100644 --- a/src/FormulateForm.vue +++ b/src/FormulateForm.vue @@ -7,6 +7,8 @@