From 59eb76e1222e0b49c4bf30c4b38c867bded12755 Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 08:35:18 +0000 Subject: [PATCH 01/10] testing firefox issue --- client/src/js/ng-django-forms.js | 143 +++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 8 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index d8d8bdc..7cd251d 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -137,7 +137,7 @@ djng_forms_module.directive('ngModel', function() { }); -djng_forms_module.directive('validateMultipleFields', function() { +djng_forms_module.directive('validateMultipleFields', function($timeout) { return { restrict: 'A', require: '^?form', @@ -145,15 +145,29 @@ djng_forms_module.directive('validateMultipleFields', function() { var formCtrl, subFields, checkboxElems = []; function validate(event) { + // $timeout(function(){ + console.log('change') + var valid = false; + angular.forEach(checkboxElems, function(checkbox) { + console.log(checkbox) + valid = valid || checkbox.checked; + }); + console.log(valid) + formCtrl.$setValidity('required', valid); + if (event && angular.isString(subFields)) { + formCtrl[subFields].$dirty = true; + formCtrl[subFields].$pristine = false; + } + // }) + } + + function click(event) { + console.log('click') var valid = false; angular.forEach(checkboxElems, function(checkbox) { valid = valid || checkbox.checked; }); - formCtrl.$setValidity('required', valid); - if (event && angular.isString(subFields)) { - formCtrl[subFields].$dirty = true; - formCtrl[subFields].$pristine = false; - } + console.log(valid) } if (!controller) @@ -166,10 +180,15 @@ djng_forms_module.directive('validateMultipleFields', function() { } angular.forEach(element.find('input'), function(elem) { if (subFields.indexOf(elem.name) >= 0) { + //elem.on('change', validate) + console.log(elem) checkboxElems.push(elem); } }); - element.on('change', validate); + element.find('input').on('change', validate); + + //element.on('change', validate); + //element.on('click', click); validate(); } }; @@ -286,7 +305,7 @@ djng_forms_module.factory('djangoForm', function() { } else { // this field is a composite of input elements angular.forEach(field, function(subField, subKey) { - if (angular.isArray(subField.$viewChangeListeners)) { + if (subField && angular.isArray(subField.$viewChangeListeners)) { resetFieldValidity(subField); } }); @@ -299,4 +318,112 @@ djng_forms_module.factory('djangoForm', function() { }; }); + +djng_forms_module.factory('djangoForm2', function() { + + return { + + setErrors: setErrors + } + + /* ============================ */ + + function setErrors(form, errors) { + + _clearFormMessage(form); + _displayErrors(form, errors); + + return _isNotEmpty(errors) + }; + + function _clearFormMessage(form) { + + form.$message = ''; + }; + + function _displayErrors(form, errors) { + + angular.forEach(errors, function(error, key) { + + var field, + + message = error[0]; + + if (form.hasOwnProperty(key)) { + + field = form[key]; + field.$dirty = true; + + /* + * check to see if the field already has an error. + * + * If so return, as, + * + * - either the field has set its own error. This can occur + * when a field is 'required' and you submit it with no value. + * + * - we already have a 'rejected' error in the field + * and the user has just resubmitted it without changing the + * field value. + * + * This also prevents the listener being added multiple times + */ + + if(_errorCount(field) > 0) + return; + + field.$setValidity('rejected', false); + field.$message = message; + field.$setPristine(); + + if (angular.isArray(field.$viewChangeListeners)) { + _addRejectionResetHandler(field); + } else { + // this field is a composite of input elements + angular.forEach(field, function(subField, subKey) { + if (subField && angular.isArray(subField.$viewChangeListeners)) { + _addRejectionResetHandler(subField); + } + }); + } + + } else { + + form.$message = message; + form.$setPristine(); + } + }); + } + + function _errorCount(field) { + var error, + count = 0; + + for(error in field.$error) { + count++; + } + + return count; + } + + function _addRejectionResetHandler(field) { + var pos = field.$viewChangeListeners.push(function() { + //console.log(field) + console.log('field') + field.$viewChangeListeners.splice(pos - 1, 1); + field.$setValidity('rejected', true); + field.$message = ""; + }) + } + + function _isNotEmpty(obj) { + for (var p in obj) { + if (obj.hasOwnProperty(p)) + return true; + } + return false; + } +}) + + })(window.angular); From f6b90266fbeb3a8db2493fe42368e4f9306fa330 Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 12:27:55 +0000 Subject: [PATCH 02/10] tweaks --- client/src/js/ng-django-forms.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index 7cd251d..a4e8bb4 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -147,6 +147,7 @@ djng_forms_module.directive('validateMultipleFields', function($timeout) { function validate(event) { // $timeout(function(){ console.log('change') + console.log(formCtrl) var valid = false; angular.forEach(checkboxElems, function(checkbox) { console.log(checkbox) @@ -158,7 +159,7 @@ djng_forms_module.directive('validateMultipleFields', function($timeout) { formCtrl[subFields].$dirty = true; formCtrl[subFields].$pristine = false; } - // }) + //}) } function click(event) { From 7e538da8ee9bf5362bb9eda20d69a3f43d7e057d Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 15:52:33 +0000 Subject: [PATCH 03/10] fix for FF checkbox change validate-multiple-fields issue --- client/src/js/ng-django-forms.js | 90 +++++++++++++++----------------- 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index a4e8bb4..140c4ca 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -137,60 +137,54 @@ djng_forms_module.directive('ngModel', function() { }); -djng_forms_module.directive('validateMultipleFields', function($timeout) { +djng_forms_module.directive('validateMultipleFields', function($compile) { return { restrict: 'A', require: '^?form', - link: function(scope, element, attrs, controller) { - var formCtrl, subFields, checkboxElems = []; - - function validate(event) { - // $timeout(function(){ - console.log('change') - console.log(formCtrl) - var valid = false; - angular.forEach(checkboxElems, function(checkbox) { - console.log(checkbox) - valid = valid || checkbox.checked; - }); - console.log(valid) - formCtrl.$setValidity('required', valid); - if (event && angular.isString(subFields)) { - formCtrl[subFields].$dirty = true; - formCtrl[subFields].$pristine = false; - } - //}) - } - - function click(event) { - console.log('click') - var valid = false; - angular.forEach(checkboxElems, function(checkbox) { - valid = valid || checkbox.checked; - }); - console.log(valid) - } - - if (!controller) - return; - formCtrl = controller; - try { - subFields = angular.fromJson(attrs.validateMultipleFields); - } catch (SyntaxError) { - subFields = attrs.validateMultipleFields; - } + compile: function(element, attrs) { angular.forEach(element.find('input'), function(elem) { - if (subFields.indexOf(elem.name) >= 0) { - //elem.on('change', validate) - console.log(elem) - checkboxElems.push(elem); - } + elem = angular.element(elem) + elem.attr('ng-change', 'changed()'); }); - element.find('input').on('change', validate); - //element.on('change', validate); - //element.on('click', click); - validate(); + return { + + post: function(scope, element, attrs, controller) { + var formCtrl, subFields, checkboxElems = []; + + scope.changed = function() { + validate(true) + } + + function validate(trigger) { + var valid = false; + angular.forEach(checkboxElems, function(checkbox) { + valid = valid || checkbox.checked; + }); + formCtrl.$setValidity('required', valid); + if (trigger && angular.isString(subFields)) { + formCtrl[subFields].$dirty = true; + formCtrl[subFields].$pristine = false; + } + } + + if (!controller) + return; + formCtrl = controller; + try { + subFields = angular.fromJson(attrs.validateMultipleFields); + } catch (SyntaxError) { + subFields = attrs.validateMultipleFields; + } + angular.forEach(element.find('input'), function(elem) { + if (subFields.indexOf(elem.name) >= 0) { + checkboxElems.push(elem); + } + }); + + validate(); + } + } } }; }); From e6a2ab27c3bdfca45149a355bacc228ce7a55021 Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 16:12:59 +0000 Subject: [PATCH 04/10] removed $compile --- client/src/js/ng-django-forms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index 140c4ca..a0b0926 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -137,7 +137,7 @@ djng_forms_module.directive('ngModel', function() { }); -djng_forms_module.directive('validateMultipleFields', function($compile) { +djng_forms_module.directive('validateMultipleFields', function() { return { restrict: 'A', require: '^?form', From bf41cac3c8550b4f103beb914c24ff1fcd7ca607 Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 19:25:34 +0000 Subject: [PATCH 05/10] moved rejected error cleanup into method on form, so that it can be removed from anywhere --- client/src/js/ng-django-forms.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index a0b0926..68cc515 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -160,12 +160,21 @@ djng_forms_module.directive('validateMultipleFields', function() { var valid = false; angular.forEach(checkboxElems, function(checkbox) { valid = valid || checkbox.checked; + console.log('checkbox', checkbox); + console.log('checkbox.clearRejected', checkbox.clearRejected) + if(checkbox.clearRejected !== undefined) { + checkbox.clearRejected('validateMultipleFields'); + } }); + formCtrl.$setValidity('required', valid); + //formCtrl.$setValidity('rejected', true); + if (trigger && angular.isString(subFields)) { formCtrl[subFields].$dirty = true; formCtrl[subFields].$pristine = false; } + console.log(formCtrl) } if (!controller) @@ -255,12 +264,11 @@ djng_forms_module.factory('djangoForm', function() { } function resetFieldValidity(field) { - field.rejectedListenerPos = field.$viewChangeListeners.push(function() { - // changing the field the server complained about, resets the form into valid state + var pos = field.$viewChangeListeners.push(field.clearRejected = function() { field.$setValidity('rejected', true); - field.$viewChangeListeners.splice(field.rejectedListenerPos, 1); - delete field.rejectedListenerPos; - }) - 1; + field.$viewChangeListeners.splice(pos - 1, 1); + delete field.clearRejected; + }) } return { @@ -275,16 +283,15 @@ djng_forms_module.factory('djangoForm', function() { if (form.hasOwnProperty(key)) { field = form[key]; field.$message = ''; - field.$setValidity('rejected', true); - if (field.rejectedListenerPos !== undefined) { - field.$viewChangeListeners.splice(field.rejectedListenerPos, 1); - delete field.rejectedListenerPos; + if(field.clearRejected) { + field.clearRejected(); } } }); } // add the new upstream errors angular.forEach(errors, function(errors, key) { + console.log('key', key) var field; if (errors.length > 0) { if (key === NON_FIELD_ERRORS) { From 529525eade927fba93da0b462c6a525cb11b141f Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 19:29:05 +0000 Subject: [PATCH 06/10] make copy of rejected before we loop as calling field.$setValidity(rejected, true) modifies the error array --- client/src/js/ng-django-forms.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index 68cc515..bbe4b0e 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -265,6 +265,7 @@ djng_forms_module.factory('djangoForm', function() { function resetFieldValidity(field) { var pos = field.$viewChangeListeners.push(field.clearRejected = function() { + field.$message = ''; field.$setValidity('rejected', true); field.$viewChangeListeners.splice(pos - 1, 1); delete field.clearRejected; @@ -278,11 +279,12 @@ djng_forms_module.factory('djangoForm', function() { // remove errors from this form, which may have been rejected by an earlier validation form.$message = ''; if (form.$error.hasOwnProperty('rejected')) { - angular.forEach(form.$error.rejected, function(rejected) { + // make copy of rejected before we loop as calling field.$setValidity('rejected', true) modifies the error array + var rejected = form.$error.rejected.concat(); + angular.forEach(rejected, function(rejected) { var field, key = rejected.$name; if (form.hasOwnProperty(key)) { field = form[key]; - field.$message = ''; if(field.clearRejected) { field.clearRejected(); } From d943233ac2b42bc1aef598263d1e3fa17039d52d Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 20:01:55 +0000 Subject: [PATCH 07/10] changed validate-multiple-field to deal with checkbox controls instead of elements, so that rejected errors can be cleared --- client/src/js/ng-django-forms.js | 142 +++++-------------------------- 1 file changed, 22 insertions(+), 120 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index bbe4b0e..fdc5ee8 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -137,7 +137,7 @@ djng_forms_module.directive('ngModel', function() { }); -djng_forms_module.directive('validateMultipleFields', function() { +djng_forms_module.directive('validateMultipleFields', function($parse) { return { restrict: 'A', require: '^?form', @@ -150,7 +150,7 @@ djng_forms_module.directive('validateMultipleFields', function() { return { post: function(scope, element, attrs, controller) { - var formCtrl, subFields, checkboxElems = []; + var formCtrl, subFields, checkboxCtrls = []; scope.changed = function() { validate(true) @@ -158,23 +158,20 @@ djng_forms_module.directive('validateMultipleFields', function() { function validate(trigger) { var valid = false; - angular.forEach(checkboxElems, function(checkbox) { - valid = valid || checkbox.checked; - console.log('checkbox', checkbox); - console.log('checkbox.clearRejected', checkbox.clearRejected) - if(checkbox.clearRejected !== undefined) { - checkbox.clearRejected('validateMultipleFields'); + angular.forEach(checkboxCtrls, function(checkbox) { + valid = valid || checkbox.$modelValue; + if(checkbox.clearRejected) { + checkbox.clearRejected(); } }); formCtrl.$setValidity('required', valid); - //formCtrl.$setValidity('rejected', true); + formCtrl.$setValidity('rejected', true); if (trigger && angular.isString(subFields)) { formCtrl[subFields].$dirty = true; formCtrl[subFields].$pristine = false; } - console.log(formCtrl) } if (!controller) @@ -187,9 +184,13 @@ djng_forms_module.directive('validateMultipleFields', function() { } angular.forEach(element.find('input'), function(elem) { if (subFields.indexOf(elem.name) >= 0) { - checkboxElems.push(elem); + checkboxCtrls.push(formCtrl[elem.name]); } }); + + scope.$on('$destroy', function() { + + }) validate(); } @@ -267,8 +268,13 @@ djng_forms_module.factory('djangoForm', function() { var pos = field.$viewChangeListeners.push(field.clearRejected = function() { field.$message = ''; field.$setValidity('rejected', true); + console.log(field.$viewChangeListeners) field.$viewChangeListeners.splice(pos - 1, 1); delete field.clearRejected; + console.log('removing rejected') + console.log(field); + console.log(field.$viewChangeListeners) + console.log('------------------------'); }) } @@ -279,7 +285,11 @@ djng_forms_module.factory('djangoForm', function() { // remove errors from this form, which may have been rejected by an earlier validation form.$message = ''; if (form.$error.hasOwnProperty('rejected')) { - // make copy of rejected before we loop as calling field.$setValidity('rejected', true) modifies the error array + /* + * make copy of rejected before we loop as calling + * field.$setValidity('rejected', true) modifies the error array + * so only every other one was being removed + */ var rejected = form.$error.rejected.concat(); angular.forEach(rejected, function(rejected) { var field, key = rejected.$name; @@ -293,7 +303,6 @@ djng_forms_module.factory('djangoForm', function() { } // add the new upstream errors angular.forEach(errors, function(errors, key) { - console.log('key', key) var field; if (errors.length > 0) { if (key === NON_FIELD_ERRORS) { @@ -323,111 +332,4 @@ djng_forms_module.factory('djangoForm', function() { }); -djng_forms_module.factory('djangoForm2', function() { - - return { - - setErrors: setErrors - } - - /* ============================ */ - - function setErrors(form, errors) { - - _clearFormMessage(form); - _displayErrors(form, errors); - - return _isNotEmpty(errors) - }; - - function _clearFormMessage(form) { - - form.$message = ''; - }; - - function _displayErrors(form, errors) { - - angular.forEach(errors, function(error, key) { - - var field, - - message = error[0]; - - if (form.hasOwnProperty(key)) { - - field = form[key]; - field.$dirty = true; - - /* - * check to see if the field already has an error. - * - * If so return, as, - * - * - either the field has set its own error. This can occur - * when a field is 'required' and you submit it with no value. - * - * - we already have a 'rejected' error in the field - * and the user has just resubmitted it without changing the - * field value. - * - * This also prevents the listener being added multiple times - */ - - if(_errorCount(field) > 0) - return; - - field.$setValidity('rejected', false); - field.$message = message; - field.$setPristine(); - - if (angular.isArray(field.$viewChangeListeners)) { - _addRejectionResetHandler(field); - } else { - // this field is a composite of input elements - angular.forEach(field, function(subField, subKey) { - if (subField && angular.isArray(subField.$viewChangeListeners)) { - _addRejectionResetHandler(subField); - } - }); - } - - } else { - - form.$message = message; - form.$setPristine(); - } - }); - } - - function _errorCount(field) { - var error, - count = 0; - - for(error in field.$error) { - count++; - } - - return count; - } - - function _addRejectionResetHandler(field) { - var pos = field.$viewChangeListeners.push(function() { - //console.log(field) - console.log('field') - field.$viewChangeListeners.splice(pos - 1, 1); - field.$setValidity('rejected', true); - field.$message = ""; - }) - } - - function _isNotEmpty(obj) { - for (var p in obj) { - if (obj.hasOwnProperty(p)) - return true; - } - return false; - } -}) - - })(window.angular); From 81507b42dab5703815654030b3f13d46496a6d03 Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 20:13:02 +0000 Subject: [PATCH 08/10] rejected error listeners now being cleaned up from subfields before being re-added --- client/src/js/ng-django-forms.js | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index fdc5ee8..008619c 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -268,15 +268,14 @@ djng_forms_module.factory('djangoForm', function() { var pos = field.$viewChangeListeners.push(field.clearRejected = function() { field.$message = ''; field.$setValidity('rejected', true); - console.log(field.$viewChangeListeners) field.$viewChangeListeners.splice(pos - 1, 1); delete field.clearRejected; - console.log('removing rejected') - console.log(field); - console.log(field.$viewChangeListeners) - console.log('------------------------'); }) } + + function isField(field) { + return angular.isArray(field.$viewChangeListeners) + } return { // setErrors takes care of updating prepared placeholder fields for displaying form errors @@ -295,8 +294,15 @@ djng_forms_module.factory('djangoForm', function() { var field, key = rejected.$name; if (form.hasOwnProperty(key)) { field = form[key]; - if(field.clearRejected) { + if (isField(field) && field.clearRejected) { field.clearRejected(); + } else { + // this field is a composite of input elements + angular.forEach(field, function(subField, subKey) { + if (subField && isField(subField) && subField.clearRejected) { + subField.clearRejected(); + } + }); } } }); @@ -313,12 +319,12 @@ djng_forms_module.factory('djangoForm', function() { field.$message = errors[0]; field.$setValidity('rejected', false); field.$setPristine(); - if (angular.isArray(field.$viewChangeListeners)) { + if (isField(field)) { resetFieldValidity(field); } else { // this field is a composite of input elements angular.forEach(field, function(subField, subKey) { - if (subField && angular.isArray(subField.$viewChangeListeners)) { + if (subField && isField(subField)) { resetFieldValidity(subField); } }); From 6153e8c8895ab395d82e60ef59571b381b068b0d Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Wed, 3 Dec 2014 21:54:08 +0000 Subject: [PATCH 09/10] added child scope to directive to handle individual changed method for child ng-change --- client/src/js/ng-django-forms.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index 008619c..048631e 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -141,6 +141,8 @@ djng_forms_module.directive('validateMultipleFields', function($parse) { return { restrict: 'A', require: '^?form', + // create child scope for changed method + scope: true, compile: function(element, attrs) { angular.forEach(element.find('input'), function(elem) { elem = angular.element(elem) @@ -167,6 +169,7 @@ djng_forms_module.directive('validateMultipleFields', function($parse) { formCtrl.$setValidity('required', valid); formCtrl.$setValidity('rejected', true); + formCtrl.$message = '' if (trigger && angular.isString(subFields)) { formCtrl[subFields].$dirty = true; @@ -187,10 +190,6 @@ djng_forms_module.directive('validateMultipleFields', function($parse) { checkboxCtrls.push(formCtrl[elem.name]); } }); - - scope.$on('$destroy', function() { - - }) validate(); } @@ -283,7 +282,8 @@ djng_forms_module.factory('djangoForm', function() { setErrors: function(form, errors) { // remove errors from this form, which may have been rejected by an earlier validation form.$message = ''; - if (form.$error.hasOwnProperty('rejected')) { + if (form.$error.hasOwnProperty('rejected') && + angular.isArray(form.$error.rejected)) { /* * make copy of rejected before we loop as calling * field.$setValidity('rejected', true) modifies the error array @@ -297,6 +297,7 @@ djng_forms_module.factory('djangoForm', function() { if (isField(field) && field.clearRejected) { field.clearRejected(); } else { + field.$message = ''; // this field is a composite of input elements angular.forEach(field, function(subField, subKey) { if (subField && isField(subField) && subField.clearRejected) { From cc0c04d0ca99ceb4a1f15807790e3ecdc4f5ceb5 Mon Sep 17 00:00:00 2001 From: jamesrobb Date: Thu, 4 Dec 2014 11:33:11 +0000 Subject: [PATCH 10/10] removed extraneous $parse injection --- client/src/js/ng-django-forms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/js/ng-django-forms.js b/client/src/js/ng-django-forms.js index 048631e..357a3aa 100644 --- a/client/src/js/ng-django-forms.js +++ b/client/src/js/ng-django-forms.js @@ -137,7 +137,7 @@ djng_forms_module.directive('ngModel', function() { }); -djng_forms_module.directive('validateMultipleFields', function($parse) { +djng_forms_module.directive('validateMultipleFields', function() { return { restrict: 'A', require: '^?form',