From 74e97faf63e7373baaec5b3ee92990502ce8deec Mon Sep 17 00:00:00 2001 From: Akos Vandra Date: Sun, 25 Oct 2015 19:11:30 +0100 Subject: [PATCH] add support for feedback icons and problem list --- README.md | 60 +++++++++++++++-- src/showErrors.js | 168 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 217 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 5dd10ec..8353ba0 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,61 @@ To force the validity check, broadcast the `show-errors-check-validity` event. ```javascript $scope.save = function() { $scope.$broadcast('show-errors-check-validity'); - + if ($scope.userForm.$valid) { // save the user } } ``` +Show Feedback Icons +--- +Feedback icons are supported through the ShowErrorIcons directive. + +#### Example + +```html +
+
+ + +
+ +
+``` + +Show Validation error hints +--- +Validation error hints are supported through the ShowErrorHint directive. +The span that has this added directive will be shown if the specified validation error occured. This integrates well with the ui-validator module for example. + +Do not forget to add the css fix from below if you intend to show a bullet point list + +#### Example + +```css +li.help-block { + display: list-item +} + +``` + +```html +
+
+ +
    +
  • It is not unique!
  • +
  • Too short!
  • +
  • This field is required!
  • +
  • All errors {{userForm.firstName.$error}}
  • +
+
+ +
+``` + Reset --- If you have functionality to reset your form, you can broadcast the 'show-errors-reset' event to remove any errors on the form elements. @@ -91,7 +139,7 @@ $scope.reset = function() { Show Valid Entries --- -It's also possible to let the user know when they have entered valid values by applying the 'show-success' class that Bootstrap provides. +It's also possible to let the user know when they have entered valid values by applying the 'show-success' class that Bootstrap provides. You can either apply this globally or on an element by element basis. ##### Globally @@ -129,8 +177,8 @@ If your HTML code doesn't have a form-group class, the form group check can be s Custom Trigger --- -By default, the validation is not performed until the `blur` event is trigger on the input -element. However, there are some scenarios where this is not desirable, so it's possible to +By default, the validation is not performed until the `blur` event is trigger on the input +element. However, there are some scenarios where this is not desirable, so it's possible to override this with the `trigger` option. ##### By Input Element @@ -149,7 +197,7 @@ app.config(['showErrorsConfigProvider', function(showErrorsConfigProvider) { showErrorsConfigProvider.trigger('keypress'); }]); ``` - + ## Development ### Install Development Dependencies @@ -163,7 +211,7 @@ bower install ### Compile and Run the Unit Tests Just type `grunt` in the command line to compile and run the karma unit tests once. -If you want to have grunt watch for any file changes and automatically compile and run the karma +If you want to have grunt watch for any file changes and automatically compile and run the karma unit tests, then run the following command: ``` grunt karma:continuous:start watch diff --git a/src/showErrors.js b/src/showErrors.js index 934f59b..d067ebc 100644 --- a/src/showErrors.js +++ b/src/showErrors.js @@ -23,8 +23,7 @@ return showSuccess; }; linkFn = function(scope, el, attrs, formCtrl) { - var blurred, inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, trigger; - blurred = false; + var inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, trigger; options = scope.$eval(attrs.showErrors); showSuccess = getShowSuccess(options); trigger = getTrigger(options); @@ -35,13 +34,15 @@ throw "show-errors element has no child input elements with a 'name' attribute and a 'form-control' class"; } inputNgEl.bind(trigger, function() { - blurred = true; + scope.$apply(function() { + formCtrl[inputName].$blurred = true; + }) return toggleClasses(formCtrl[inputName].$invalid); }); scope.$watch(function() { return formCtrl[inputName] && formCtrl[inputName].$invalid; }, function(invalid) { - if (!blurred) { + if (!formCtrl[inputName].$blurred) { return; } return toggleClasses(invalid); @@ -53,7 +54,7 @@ return $timeout(function() { el.removeClass('has-error'); el.removeClass('has-success'); - return blurred = false; + return formCtrl[inputName].$blurred = false; }, 0, false); }); return toggleClasses = function(invalid) { @@ -78,6 +79,163 @@ } ]); + showErrorsModule.directive('showErrorIcons', [ + '$timeout', 'showErrorsConfig', '$interpolate', function($timeout, showErrorsConfig, $interpolate) { + var getShowSuccess, linkFn; + getShowSuccess = function(options) { + var showSuccess; + showSuccess = showErrorsConfig.showSuccess; + if (options && (options.showSuccess != null)) { + showSuccess = options.showSuccess; + } + return showSuccess; + }; + + linkFn = function(scope, el, attrs, formCtrl) { + var inputEl, inputName, inputNgEl, options, showSuccess, toggleClasses, trigger; + options = scope.$eval(attrs.showErrors); + showSuccess = getShowSuccess(options); + + inputEl = el[0].parentNode.querySelector('.form-control[name]'); + inputNgEl = angular.element(inputEl); + inputName = $interpolate(inputNgEl.attr('name') || '')(scope); + if (!inputName) { + throw "show-error-icons element's parent has no child input elements with a 'name' attribute and a 'form-control' class"; + } + + scope.$watch(function() { + return formCtrl[inputName] ^ formCtrl[inputName].$invalid ^ formCtrl[inputName].$blurred; + }, function() { + return toggleClasses(formCtrl[inputName].$blurred, formCtrl[inputName].$invalid); + }) + + scope.$on('show-errors-check-validity', function() { + return toggleClasses(true, formCtrl[inputName].$invalid); + }); + + scope.$on('show-errors-reset', function() { + return $timeout(function() { + toggleClasses(false) + }, 0, false); + }); + return toggleClasses = function(show, invalid) { + el.toggleClass('ng-hide', !show) + el.toggleClass('glyphicon', true) + el.toggleClass('glyphicon-remove', invalid); + if (showSuccess) { + return el.toggleClass('glyphicon-ok', !invalid); + } + }; + }; + return { + restrict: 'A', + require: '^form', + compile: function(elem, attrs) { + if (!elem.parent().children(".form-control[name]")) { + throw "show-error-icons must be applied on an element whose parent contains an input[name] element!" + } + + return linkFn; + } + }; + } + ]); + + showErrorsModule.directive('showErrorHint', [ + '$timeout', 'showErrorsConfig', '$interpolate', function($timeout, showErrorsConfig, $interpolate) { + var getShowSuccess, linkFn; + getShowSuccess = function(options) { + var showSuccess; + showSuccess = showErrorsConfig.showSuccess; + if (options && (options.showSuccess != null)) { + showSuccess = options.showSuccess; + } + return showSuccess; + }; + + linkFn = function(formgroup) { + return function(scope, el, attrs, formCtrl) { + var inputEl, inputName, inputNgEl, toggleClasses, validator; + + inputEl = formgroup[0].querySelector('input.form-control[name]'); + inputNgEl = angular.element(inputEl) + inputName = $interpolate(inputNgEl.attr('name') || '')(scope); + validator = scope.showErrorHint + + if (!inputName) { + throw "show-error-hint element's parent has no child input elements with a 'name' attribute and a 'form-control' class"; + } + + var update = function(force) { + return function() { + if (validator) { + return scope.toggleClasses((formCtrl[inputName].$blurred || force) && formCtrl[inputName].$error[validator]) + } else { + var errors = formCtrl[inputName].$error || {} + var keys = Object.keys(errors) + var errorCount = keys.filter(function(e) { return errors[e]; }).length + + return scope.toggleClasses((formCtrl[inputName].$blurred || force) && errorCount > 0) + } + } + } + + if (validator) { + scope.$watch(function() { + return formCtrl[inputName].$error[validator] + }, update(false)) + } else { + scope.$watch(function() { + return JSON.stringify(formCtrl[inputName].$error) + }, update(false)) + } + + scope.$watch(function() { + return formCtrl[inputName].$blurred + }, update(false)) + + scope.$on('show-errors-check-validity', function() { + return update(true)() + }); + + scope.$on('show-errors-reset', function() { + return $timeout(function() { + scope.toggleClasses(true) + }, 0, false); + }); + + scope.toggleClasses = function(show) { + el.toggleClass('ng-hide', !show) + }; + + return scope.toggleClasses + }; + } + + return { + restrict: 'A', + require: '^form', + scope: { + showErrorHint: '@' + }, + + compile: function(elem, attrs) { + + //Find first ancestor with a form control + while(elem && !elem[0].querySelector(".form-control[name]")) { + elem = elem.parent() + } + + if (!elem) { + throw "show-error-hint must be applied on an element whose parent contains an input[name] element!" + } + + return linkFn(elem); + } + }; + } + ]); + showErrorsModule.provider('showErrorsConfig', function() { var _showSuccess, _trigger; _showSuccess = false;