From 38f1553442b12162d2a0f0f02ba421e19076de7b Mon Sep 17 00:00:00 2001 From: jbsaff Date: Mon, 27 Apr 2015 15:45:59 -0500 Subject: [PATCH 1/5] Checkboxes decorator had misspelled schema-validate directive. Fixed typo. --- src/directives/decorators/bootstrap/checkboxes.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/directives/decorators/bootstrap/checkboxes.html b/src/directives/decorators/bootstrap/checkboxes.html index d420ea7c7..3d65ca8c1 100644 --- a/src/directives/decorators/bootstrap/checkboxes.html +++ b/src/directives/decorators/bootstrap/checkboxes.html @@ -9,7 +9,7 @@ sf-changed="form" class="{{form.fieldHtmlClass}}" ng-model="titleMapValues[$index]" - schema-vaidate="form" + schema-validate="form" name="{{form.key.slice(-1)[0]}}"> From a31cdbeb56085c24783b8ae696422292661b4062 Mon Sep 17 00:00:00 2001 From: jbsaff Date: Tue, 28 Apr 2015 12:35:12 -0500 Subject: [PATCH 2/5] Extended schema-validate directive to handle cleaning the model when a form field element triggers the $destroy. Uses a new service, based on Select, to traverse the model and update it to the value chosen as part of the configured destroyStrategy. This destroyStrategy can be configured at the field, or as part of the forms global options. If both are defined, the field-level strategy will override. --- docs/index.md | 9 ++++ src/directives/schema-validate.js | 67 ++++++++++++++++++++++++- src/services/unselect.js | 83 +++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/services/unselect.js diff --git a/docs/index.md b/docs/index.md index 514dbbd21..090229785 100644 --- a/docs/index.md +++ b/docs/index.md @@ -634,6 +634,7 @@ General options most field types can handle: labelHtmlClass: "street" // CSS Class(es) to be added to the label of the field (or similar) copyValueTo: ["address.street"], // Copy values to these schema keys. condition: "person.age < 18" // Show or hide field depending on an angular expression + destroyStrategy: '' // Update the model when the field is destoyed, e.g. when condition is not longer satisfied. } ``` @@ -823,6 +824,14 @@ function FormCtrl($scope) { Note that arrays inside arrays won't work with conditions. +### destroyStrategy +By default, when a field is removed from the DOM and the $destroy event is broadcast, the schema-validate directive +will update the model to set the field value to undefined. This can be overridden by setting the destroyStrategy +on a field to one of null, empty string (""), undefined, or "retain". Any other value will be ignored and the default +behavior will apply. The empty string option only applies to fields that have a type of string; using the empty string +with other field types will just be set to the default destroyStrategy. If you'd like to set the destroyStrategy for +an entire form, add it to the formDefaults in the [globalOptions](#global-options) + Specific options and types diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index cfa335260..e4f6a5876 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -1,4 +1,6 @@ -angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', function(sfValidator, sfSelect) { +angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', 'sfUnselect', + function(sfValidator, sfSelect, sfUnselect) { + return { restrict: 'A', scope: false, @@ -8,7 +10,6 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele require: 'ngModel', link: function(scope, element, attrs, ngModel) { - // We need the ngModelController on several places, // most notably for errors. // So we emit it up to the decorator directive so it can put it on scope. @@ -101,6 +102,68 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele }); + + var DEFAULT_DESTROY_STRATEGY; + if (scope.options && scope.options.formDefaults) { + var formDefaultDestroyStrategy = scope.options.formDefaults.destroyStrategy; + var isValidFormDefaultDestroyStrategy = (formDefaultDestroyStrategy === undefined || + formDefaultDestroyStrategy === '' || + formDefaultDestroyStrategy === null || + formDefaultDestroyStrategy === 'retain'); + if (isValidFormDefaultDestroyStrategy) { + DEFAULT_DESTROY_STRATEGY = formDefaultDestroyStrategy; + } + else { + console.warn('Unrecognized formDefaults.destroyStrategy: \'%s\'. Used undefined instead.', + formDefaultDestroyStrategy); + DEFAULT_DESTROY_STRATEGY = undefined; + } + } + + // Clean up the model when the corresponding form field is $destroy-ed. + // Default behavior can be supplied as a formDefault, and behavior can be overridden in the form definition. + scope.$on('$destroy', function() { + var form = getForm(); + var destroyStrategy = form.destroyStrategy; // Either set in form definition, or as part of formDefaults. + var schemaType = getSchemaType(); + + if (destroyStrategy && destroyStrategy !== 'retain' ) { + // Don't recognize the strategy, so give a warning. + console.warn('Unrecognized destroyStrategy: \'%s\'. Used default instead.', destroyStrategy); + destroyStrategy = DEFAULT_DESTROY_STRATEGY; + } + else if (schemaType !== 'string' && destroyStrategy === '') { + // Only 'string' type fields can have an empty string value as a valid option. + console.warn('Attempted to use empty string destroyStrategy on non-string form type. Used default instead.'); + destroyStrategy = DEFAULT_DESTROY_STRATEGY; + } + + if (destroyStrategy === 'retain') { + return; // Valid option to avoid destroying data in the model. + } + + destroyUsingStrategy(destroyStrategy); + + function destroyUsingStrategy(strategy) { + var strategyIsDefined = (strategy === null || strategy === '' || typeof strategy == undefined); + if (!strategyIsDefined){ + strategy = DEFAULT_DESTROY_STRATEGY; + } + sfUnselect(scope.form.key, scope.model, strategy); + } + + function getSchemaType() { + if (form.schema) { + schemaType = form.schema.type; + } + else { + schemaType = null; + } + } + }); + + + scope.schemaError = function() { return error; }; diff --git a/src/services/unselect.js b/src/services/unselect.js new file mode 100644 index 000000000..e56904cb6 --- /dev/null +++ b/src/services/unselect.js @@ -0,0 +1,83 @@ + +angular.module('schemaForm').factory('sfUnselect', ['sfPath', function(sfPath) { + var numRe = /^\d+$/; + + /** + * @description + * Utility method to clear deep properties without + * throwing errors when things are not defined. + * DOES NOT create objects when they are missing. + * + * Based on sfSelect. + * + * ex. + * var foo = Unselect('address.contact.name',obj, null) + * var bar = Unselect('address.contact.name',obj, undefined) + * Unselect('address.contact.name',obj,'') + * + * @param {string} projection A dot path to the property you want to set + * @param {object} obj (optional) The object to project on, defaults to 'this' + * @param {Any} unselectValue The value to set; if parts of the path of + * the projection is missing empty objects will NOT be created. + * @returns {Any|undefined} returns the value at the end of the projection path + * or undefined if there is none. + */ + return function(projection, obj, unselectValue) { + if (!obj) { + obj = this; + } + //Support [] array syntax + var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; + //console.log(parts); + + if (parts.length === 1) { + //Special case, just setting one variable + + //console.log('Only 1 variable in parts'); + obj[parts[0]] = unselectValue; + return obj; + } + + if (typeof obj[parts[0]] === 'undefined') { + // If top-level part isn't defined. + var isArray = numRe.test(parts[1]); + if (isArray) { + //console.info('Expected array as top-level part, but is already undefined. Returning.'); + return undefined; + } + else if (parts.length > 2) { + obj[parts[0]] = {}; + } + } + + var value = obj[parts[0]]; + for (var i = 1; i < parts.length; i++) { + // Special case: We allow JSON Form syntax for arrays using empty brackets + // These will of course not work here so we exit if they are found. + if (parts[i] === '') { + return undefined; + } + + var tmp = value[parts[i]]; + if (i === parts.length - 1 ) { + //End of projection; setting the value + + //console.log('Value set using destroyStrategy.'); + value[parts[i]] = unselectValue; + return unselectValue; + } else { + // Make sure to NOT create new objects on the way if they are not there. + // We need to look ahead to check if array is appropriate. + // Believe that if an array/object isn't present/defined, we can return. + + //console.log('Processing part %s', parts[i]); + if (typeof tmp === 'undefined' || tmp === null) { + //console.log('Part is undefined; returning.'); + return undefined; + } + value = tmp; + } + } + return value; + }; +}]); From 7b53c63625003ebc735471b60cdefe9e37f1b371 Mon Sep 17 00:00:00 2001 From: jbsaff Date: Tue, 28 Apr 2015 12:46:36 -0500 Subject: [PATCH 3/5] Revert "Extended schema-validate directive to handle cleaning the model when a form field element triggers the $destroy. Uses a new service, based on Select, to traverse the model and update it to the value chosen as part of the configured destroyStrategy. This destroyStrategy can be configured at the field, or as part of the forms global options. If both are defined, the field-level strategy will override." This reverts commit a31cdbeb56085c24783b8ae696422292661b4062. --- docs/index.md | 9 ---- src/directives/schema-validate.js | 67 +------------------------ src/services/unselect.js | 83 ------------------------------- 3 files changed, 2 insertions(+), 157 deletions(-) delete mode 100644 src/services/unselect.js diff --git a/docs/index.md b/docs/index.md index 090229785..514dbbd21 100644 --- a/docs/index.md +++ b/docs/index.md @@ -634,7 +634,6 @@ General options most field types can handle: labelHtmlClass: "street" // CSS Class(es) to be added to the label of the field (or similar) copyValueTo: ["address.street"], // Copy values to these schema keys. condition: "person.age < 18" // Show or hide field depending on an angular expression - destroyStrategy: '' // Update the model when the field is destoyed, e.g. when condition is not longer satisfied. } ``` @@ -824,14 +823,6 @@ function FormCtrl($scope) { Note that arrays inside arrays won't work with conditions. -### destroyStrategy -By default, when a field is removed from the DOM and the $destroy event is broadcast, the schema-validate directive -will update the model to set the field value to undefined. This can be overridden by setting the destroyStrategy -on a field to one of null, empty string (""), undefined, or "retain". Any other value will be ignored and the default -behavior will apply. The empty string option only applies to fields that have a type of string; using the empty string -with other field types will just be set to the default destroyStrategy. If you'd like to set the destroyStrategy for -an entire form, add it to the formDefaults in the [globalOptions](#global-options) - Specific options and types diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index e4f6a5876..cfa335260 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -1,6 +1,4 @@ -angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', 'sfUnselect', - function(sfValidator, sfSelect, sfUnselect) { - +angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', function(sfValidator, sfSelect) { return { restrict: 'A', scope: false, @@ -10,6 +8,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele require: 'ngModel', link: function(scope, element, attrs, ngModel) { + // We need the ngModelController on several places, // most notably for errors. // So we emit it up to the decorator directive so it can put it on scope. @@ -102,68 +101,6 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele }); - - var DEFAULT_DESTROY_STRATEGY; - if (scope.options && scope.options.formDefaults) { - var formDefaultDestroyStrategy = scope.options.formDefaults.destroyStrategy; - var isValidFormDefaultDestroyStrategy = (formDefaultDestroyStrategy === undefined || - formDefaultDestroyStrategy === '' || - formDefaultDestroyStrategy === null || - formDefaultDestroyStrategy === 'retain'); - if (isValidFormDefaultDestroyStrategy) { - DEFAULT_DESTROY_STRATEGY = formDefaultDestroyStrategy; - } - else { - console.warn('Unrecognized formDefaults.destroyStrategy: \'%s\'. Used undefined instead.', - formDefaultDestroyStrategy); - DEFAULT_DESTROY_STRATEGY = undefined; - } - } - - // Clean up the model when the corresponding form field is $destroy-ed. - // Default behavior can be supplied as a formDefault, and behavior can be overridden in the form definition. - scope.$on('$destroy', function() { - var form = getForm(); - var destroyStrategy = form.destroyStrategy; // Either set in form definition, or as part of formDefaults. - var schemaType = getSchemaType(); - - if (destroyStrategy && destroyStrategy !== 'retain' ) { - // Don't recognize the strategy, so give a warning. - console.warn('Unrecognized destroyStrategy: \'%s\'. Used default instead.', destroyStrategy); - destroyStrategy = DEFAULT_DESTROY_STRATEGY; - } - else if (schemaType !== 'string' && destroyStrategy === '') { - // Only 'string' type fields can have an empty string value as a valid option. - console.warn('Attempted to use empty string destroyStrategy on non-string form type. Used default instead.'); - destroyStrategy = DEFAULT_DESTROY_STRATEGY; - } - - if (destroyStrategy === 'retain') { - return; // Valid option to avoid destroying data in the model. - } - - destroyUsingStrategy(destroyStrategy); - - function destroyUsingStrategy(strategy) { - var strategyIsDefined = (strategy === null || strategy === '' || typeof strategy == undefined); - if (!strategyIsDefined){ - strategy = DEFAULT_DESTROY_STRATEGY; - } - sfUnselect(scope.form.key, scope.model, strategy); - } - - function getSchemaType() { - if (form.schema) { - schemaType = form.schema.type; - } - else { - schemaType = null; - } - } - }); - - - scope.schemaError = function() { return error; }; diff --git a/src/services/unselect.js b/src/services/unselect.js deleted file mode 100644 index e56904cb6..000000000 --- a/src/services/unselect.js +++ /dev/null @@ -1,83 +0,0 @@ - -angular.module('schemaForm').factory('sfUnselect', ['sfPath', function(sfPath) { - var numRe = /^\d+$/; - - /** - * @description - * Utility method to clear deep properties without - * throwing errors when things are not defined. - * DOES NOT create objects when they are missing. - * - * Based on sfSelect. - * - * ex. - * var foo = Unselect('address.contact.name',obj, null) - * var bar = Unselect('address.contact.name',obj, undefined) - * Unselect('address.contact.name',obj,'') - * - * @param {string} projection A dot path to the property you want to set - * @param {object} obj (optional) The object to project on, defaults to 'this' - * @param {Any} unselectValue The value to set; if parts of the path of - * the projection is missing empty objects will NOT be created. - * @returns {Any|undefined} returns the value at the end of the projection path - * or undefined if there is none. - */ - return function(projection, obj, unselectValue) { - if (!obj) { - obj = this; - } - //Support [] array syntax - var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; - //console.log(parts); - - if (parts.length === 1) { - //Special case, just setting one variable - - //console.log('Only 1 variable in parts'); - obj[parts[0]] = unselectValue; - return obj; - } - - if (typeof obj[parts[0]] === 'undefined') { - // If top-level part isn't defined. - var isArray = numRe.test(parts[1]); - if (isArray) { - //console.info('Expected array as top-level part, but is already undefined. Returning.'); - return undefined; - } - else if (parts.length > 2) { - obj[parts[0]] = {}; - } - } - - var value = obj[parts[0]]; - for (var i = 1; i < parts.length; i++) { - // Special case: We allow JSON Form syntax for arrays using empty brackets - // These will of course not work here so we exit if they are found. - if (parts[i] === '') { - return undefined; - } - - var tmp = value[parts[i]]; - if (i === parts.length - 1 ) { - //End of projection; setting the value - - //console.log('Value set using destroyStrategy.'); - value[parts[i]] = unselectValue; - return unselectValue; - } else { - // Make sure to NOT create new objects on the way if they are not there. - // We need to look ahead to check if array is appropriate. - // Believe that if an array/object isn't present/defined, we can return. - - //console.log('Processing part %s', parts[i]); - if (typeof tmp === 'undefined' || tmp === null) { - //console.log('Part is undefined; returning.'); - return undefined; - } - value = tmp; - } - } - return value; - }; -}]); From 2839f68306571f2809e8d75c72aca82d1c07ac68 Mon Sep 17 00:00:00 2001 From: jbsaff Date: Tue, 28 Apr 2015 12:48:04 -0500 Subject: [PATCH 4/5] Revert a31cdbe..7b53c63 This rolls back to commit a31cdbeb56085c24783b8ae696422292661b4062. --- docs/index.md | 9 ++++ src/directives/schema-validate.js | 67 ++++++++++++++++++++++++- src/services/unselect.js | 83 +++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/services/unselect.js diff --git a/docs/index.md b/docs/index.md index 514dbbd21..090229785 100644 --- a/docs/index.md +++ b/docs/index.md @@ -634,6 +634,7 @@ General options most field types can handle: labelHtmlClass: "street" // CSS Class(es) to be added to the label of the field (or similar) copyValueTo: ["address.street"], // Copy values to these schema keys. condition: "person.age < 18" // Show or hide field depending on an angular expression + destroyStrategy: '' // Update the model when the field is destoyed, e.g. when condition is not longer satisfied. } ``` @@ -823,6 +824,14 @@ function FormCtrl($scope) { Note that arrays inside arrays won't work with conditions. +### destroyStrategy +By default, when a field is removed from the DOM and the $destroy event is broadcast, the schema-validate directive +will update the model to set the field value to undefined. This can be overridden by setting the destroyStrategy +on a field to one of null, empty string (""), undefined, or "retain". Any other value will be ignored and the default +behavior will apply. The empty string option only applies to fields that have a type of string; using the empty string +with other field types will just be set to the default destroyStrategy. If you'd like to set the destroyStrategy for +an entire form, add it to the formDefaults in the [globalOptions](#global-options) + Specific options and types diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index cfa335260..e4f6a5876 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -1,4 +1,6 @@ -angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', function(sfValidator, sfSelect) { +angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', 'sfUnselect', + function(sfValidator, sfSelect, sfUnselect) { + return { restrict: 'A', scope: false, @@ -8,7 +10,6 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele require: 'ngModel', link: function(scope, element, attrs, ngModel) { - // We need the ngModelController on several places, // most notably for errors. // So we emit it up to the decorator directive so it can put it on scope. @@ -101,6 +102,68 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele }); + + var DEFAULT_DESTROY_STRATEGY; + if (scope.options && scope.options.formDefaults) { + var formDefaultDestroyStrategy = scope.options.formDefaults.destroyStrategy; + var isValidFormDefaultDestroyStrategy = (formDefaultDestroyStrategy === undefined || + formDefaultDestroyStrategy === '' || + formDefaultDestroyStrategy === null || + formDefaultDestroyStrategy === 'retain'); + if (isValidFormDefaultDestroyStrategy) { + DEFAULT_DESTROY_STRATEGY = formDefaultDestroyStrategy; + } + else { + console.warn('Unrecognized formDefaults.destroyStrategy: \'%s\'. Used undefined instead.', + formDefaultDestroyStrategy); + DEFAULT_DESTROY_STRATEGY = undefined; + } + } + + // Clean up the model when the corresponding form field is $destroy-ed. + // Default behavior can be supplied as a formDefault, and behavior can be overridden in the form definition. + scope.$on('$destroy', function() { + var form = getForm(); + var destroyStrategy = form.destroyStrategy; // Either set in form definition, or as part of formDefaults. + var schemaType = getSchemaType(); + + if (destroyStrategy && destroyStrategy !== 'retain' ) { + // Don't recognize the strategy, so give a warning. + console.warn('Unrecognized destroyStrategy: \'%s\'. Used default instead.', destroyStrategy); + destroyStrategy = DEFAULT_DESTROY_STRATEGY; + } + else if (schemaType !== 'string' && destroyStrategy === '') { + // Only 'string' type fields can have an empty string value as a valid option. + console.warn('Attempted to use empty string destroyStrategy on non-string form type. Used default instead.'); + destroyStrategy = DEFAULT_DESTROY_STRATEGY; + } + + if (destroyStrategy === 'retain') { + return; // Valid option to avoid destroying data in the model. + } + + destroyUsingStrategy(destroyStrategy); + + function destroyUsingStrategy(strategy) { + var strategyIsDefined = (strategy === null || strategy === '' || typeof strategy == undefined); + if (!strategyIsDefined){ + strategy = DEFAULT_DESTROY_STRATEGY; + } + sfUnselect(scope.form.key, scope.model, strategy); + } + + function getSchemaType() { + if (form.schema) { + schemaType = form.schema.type; + } + else { + schemaType = null; + } + } + }); + + + scope.schemaError = function() { return error; }; diff --git a/src/services/unselect.js b/src/services/unselect.js new file mode 100644 index 000000000..e56904cb6 --- /dev/null +++ b/src/services/unselect.js @@ -0,0 +1,83 @@ + +angular.module('schemaForm').factory('sfUnselect', ['sfPath', function(sfPath) { + var numRe = /^\d+$/; + + /** + * @description + * Utility method to clear deep properties without + * throwing errors when things are not defined. + * DOES NOT create objects when they are missing. + * + * Based on sfSelect. + * + * ex. + * var foo = Unselect('address.contact.name',obj, null) + * var bar = Unselect('address.contact.name',obj, undefined) + * Unselect('address.contact.name',obj,'') + * + * @param {string} projection A dot path to the property you want to set + * @param {object} obj (optional) The object to project on, defaults to 'this' + * @param {Any} unselectValue The value to set; if parts of the path of + * the projection is missing empty objects will NOT be created. + * @returns {Any|undefined} returns the value at the end of the projection path + * or undefined if there is none. + */ + return function(projection, obj, unselectValue) { + if (!obj) { + obj = this; + } + //Support [] array syntax + var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; + //console.log(parts); + + if (parts.length === 1) { + //Special case, just setting one variable + + //console.log('Only 1 variable in parts'); + obj[parts[0]] = unselectValue; + return obj; + } + + if (typeof obj[parts[0]] === 'undefined') { + // If top-level part isn't defined. + var isArray = numRe.test(parts[1]); + if (isArray) { + //console.info('Expected array as top-level part, but is already undefined. Returning.'); + return undefined; + } + else if (parts.length > 2) { + obj[parts[0]] = {}; + } + } + + var value = obj[parts[0]]; + for (var i = 1; i < parts.length; i++) { + // Special case: We allow JSON Form syntax for arrays using empty brackets + // These will of course not work here so we exit if they are found. + if (parts[i] === '') { + return undefined; + } + + var tmp = value[parts[i]]; + if (i === parts.length - 1 ) { + //End of projection; setting the value + + //console.log('Value set using destroyStrategy.'); + value[parts[i]] = unselectValue; + return unselectValue; + } else { + // Make sure to NOT create new objects on the way if they are not there. + // We need to look ahead to check if array is appropriate. + // Believe that if an array/object isn't present/defined, we can return. + + //console.log('Processing part %s', parts[i]); + if (typeof tmp === 'undefined' || tmp === null) { + //console.log('Part is undefined; returning.'); + return undefined; + } + value = tmp; + } + } + return value; + }; +}]); From dac6d83a9faf35fd676c9b3a0e8647b8c7a9fac6 Mon Sep 17 00:00:00 2001 From: jbsaff Date: Tue, 28 Apr 2015 12:49:17 -0500 Subject: [PATCH 5/5] Revert 38f1553..2839f68 This rolls back to commit 38f1553442b12162d2a0f0f02ba421e19076de7b. --- docs/index.md | 9 ---- src/directives/schema-validate.js | 67 +------------------------ src/services/unselect.js | 83 ------------------------------- 3 files changed, 2 insertions(+), 157 deletions(-) delete mode 100644 src/services/unselect.js diff --git a/docs/index.md b/docs/index.md index 090229785..514dbbd21 100644 --- a/docs/index.md +++ b/docs/index.md @@ -634,7 +634,6 @@ General options most field types can handle: labelHtmlClass: "street" // CSS Class(es) to be added to the label of the field (or similar) copyValueTo: ["address.street"], // Copy values to these schema keys. condition: "person.age < 18" // Show or hide field depending on an angular expression - destroyStrategy: '' // Update the model when the field is destoyed, e.g. when condition is not longer satisfied. } ``` @@ -824,14 +823,6 @@ function FormCtrl($scope) { Note that arrays inside arrays won't work with conditions. -### destroyStrategy -By default, when a field is removed from the DOM and the $destroy event is broadcast, the schema-validate directive -will update the model to set the field value to undefined. This can be overridden by setting the destroyStrategy -on a field to one of null, empty string (""), undefined, or "retain". Any other value will be ignored and the default -behavior will apply. The empty string option only applies to fields that have a type of string; using the empty string -with other field types will just be set to the default destroyStrategy. If you'd like to set the destroyStrategy for -an entire form, add it to the formDefaults in the [globalOptions](#global-options) - Specific options and types diff --git a/src/directives/schema-validate.js b/src/directives/schema-validate.js index e4f6a5876..cfa335260 100644 --- a/src/directives/schema-validate.js +++ b/src/directives/schema-validate.js @@ -1,6 +1,4 @@ -angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', 'sfUnselect', - function(sfValidator, sfSelect, sfUnselect) { - +angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSelect', function(sfValidator, sfSelect) { return { restrict: 'A', scope: false, @@ -10,6 +8,7 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele require: 'ngModel', link: function(scope, element, attrs, ngModel) { + // We need the ngModelController on several places, // most notably for errors. // So we emit it up to the decorator directive so it can put it on scope. @@ -102,68 +101,6 @@ angular.module('schemaForm').directive('schemaValidate', ['sfValidator', 'sfSele }); - - var DEFAULT_DESTROY_STRATEGY; - if (scope.options && scope.options.formDefaults) { - var formDefaultDestroyStrategy = scope.options.formDefaults.destroyStrategy; - var isValidFormDefaultDestroyStrategy = (formDefaultDestroyStrategy === undefined || - formDefaultDestroyStrategy === '' || - formDefaultDestroyStrategy === null || - formDefaultDestroyStrategy === 'retain'); - if (isValidFormDefaultDestroyStrategy) { - DEFAULT_DESTROY_STRATEGY = formDefaultDestroyStrategy; - } - else { - console.warn('Unrecognized formDefaults.destroyStrategy: \'%s\'. Used undefined instead.', - formDefaultDestroyStrategy); - DEFAULT_DESTROY_STRATEGY = undefined; - } - } - - // Clean up the model when the corresponding form field is $destroy-ed. - // Default behavior can be supplied as a formDefault, and behavior can be overridden in the form definition. - scope.$on('$destroy', function() { - var form = getForm(); - var destroyStrategy = form.destroyStrategy; // Either set in form definition, or as part of formDefaults. - var schemaType = getSchemaType(); - - if (destroyStrategy && destroyStrategy !== 'retain' ) { - // Don't recognize the strategy, so give a warning. - console.warn('Unrecognized destroyStrategy: \'%s\'. Used default instead.', destroyStrategy); - destroyStrategy = DEFAULT_DESTROY_STRATEGY; - } - else if (schemaType !== 'string' && destroyStrategy === '') { - // Only 'string' type fields can have an empty string value as a valid option. - console.warn('Attempted to use empty string destroyStrategy on non-string form type. Used default instead.'); - destroyStrategy = DEFAULT_DESTROY_STRATEGY; - } - - if (destroyStrategy === 'retain') { - return; // Valid option to avoid destroying data in the model. - } - - destroyUsingStrategy(destroyStrategy); - - function destroyUsingStrategy(strategy) { - var strategyIsDefined = (strategy === null || strategy === '' || typeof strategy == undefined); - if (!strategyIsDefined){ - strategy = DEFAULT_DESTROY_STRATEGY; - } - sfUnselect(scope.form.key, scope.model, strategy); - } - - function getSchemaType() { - if (form.schema) { - schemaType = form.schema.type; - } - else { - schemaType = null; - } - } - }); - - - scope.schemaError = function() { return error; }; diff --git a/src/services/unselect.js b/src/services/unselect.js deleted file mode 100644 index e56904cb6..000000000 --- a/src/services/unselect.js +++ /dev/null @@ -1,83 +0,0 @@ - -angular.module('schemaForm').factory('sfUnselect', ['sfPath', function(sfPath) { - var numRe = /^\d+$/; - - /** - * @description - * Utility method to clear deep properties without - * throwing errors when things are not defined. - * DOES NOT create objects when they are missing. - * - * Based on sfSelect. - * - * ex. - * var foo = Unselect('address.contact.name',obj, null) - * var bar = Unselect('address.contact.name',obj, undefined) - * Unselect('address.contact.name',obj,'') - * - * @param {string} projection A dot path to the property you want to set - * @param {object} obj (optional) The object to project on, defaults to 'this' - * @param {Any} unselectValue The value to set; if parts of the path of - * the projection is missing empty objects will NOT be created. - * @returns {Any|undefined} returns the value at the end of the projection path - * or undefined if there is none. - */ - return function(projection, obj, unselectValue) { - if (!obj) { - obj = this; - } - //Support [] array syntax - var parts = typeof projection === 'string' ? sfPath.parse(projection) : projection; - //console.log(parts); - - if (parts.length === 1) { - //Special case, just setting one variable - - //console.log('Only 1 variable in parts'); - obj[parts[0]] = unselectValue; - return obj; - } - - if (typeof obj[parts[0]] === 'undefined') { - // If top-level part isn't defined. - var isArray = numRe.test(parts[1]); - if (isArray) { - //console.info('Expected array as top-level part, but is already undefined. Returning.'); - return undefined; - } - else if (parts.length > 2) { - obj[parts[0]] = {}; - } - } - - var value = obj[parts[0]]; - for (var i = 1; i < parts.length; i++) { - // Special case: We allow JSON Form syntax for arrays using empty brackets - // These will of course not work here so we exit if they are found. - if (parts[i] === '') { - return undefined; - } - - var tmp = value[parts[i]]; - if (i === parts.length - 1 ) { - //End of projection; setting the value - - //console.log('Value set using destroyStrategy.'); - value[parts[i]] = unselectValue; - return unselectValue; - } else { - // Make sure to NOT create new objects on the way if they are not there. - // We need to look ahead to check if array is appropriate. - // Believe that if an array/object isn't present/defined, we can return. - - //console.log('Processing part %s', parts[i]); - if (typeof tmp === 'undefined' || tmp === null) { - //console.log('Part is undefined; returning.'); - return undefined; - } - value = tmp; - } - } - return value; - }; -}]);