UNPKG

angular-ui-grid

Version:

A data grid for Angular

589 lines (527 loc) 23.4 kB
/*! * ui-grid - v4.9.1 - 2020-10-26 * Copyright (c) 2020 ; License: MIT */ (function () { 'use strict'; /** * @ngdoc overview * @name ui.grid.validate * @description * * # ui.grid.validate * * <div class="alert alert-warning" role="alert"><strong>Alpha</strong> This feature is in development. There will almost certainly be breaking api changes, or there are major outstanding bugs.</div> * * This module provides the ability to validate cells upon change. * * Design information: * ------------------- * * Validation is not based on angularjs validation, since it would work only when editing the field. * * Instead it adds custom properties to any field considered as invalid. * * <br/> * <br/> * * <div doc-module-components="ui.grid.expandable"></div> */ var module = angular.module('ui.grid.validate', ['ui.grid']); /** * @ngdoc service * @name ui.grid.validate.service:uiGridValidateService * * @description Services for validation features */ module.service('uiGridValidateService', ['$sce', '$q', '$http', 'i18nService', 'uiGridConstants', function ($sce, $q, $http, i18nService, uiGridConstants) { var service = { /** * @ngdoc object * @name validatorFactories * @propertyOf ui.grid.validate.service:uiGridValidateService * @description object containing all the factories used to validate data.<br/> * These factories will be in the form <br/> * ``` * { * validatorFactory: function(argument) { * return function(newValue, oldValue, rowEntity, colDef) { * return true || false || promise * } * }, * messageFunction: function(argument) { * return string * } * } * ``` * * Promises should return true or false as result according to the result of validation. */ validatorFactories: {}, /** * @ngdoc service * @name setExternalFactoryFunction * @methodOf ui.grid.validate.service:uiGridValidateService * @description Adds a way to retrieve validators from an external service * <p>Validators from this external service have a higher priority than default * ones * @param {function} externalFactoryFunction a function that accepts name and argument to pass to a * validator factory and that returns an object with the same properties as * you can see in {@link ui.grid.validate.service:uiGridValidateService#properties_validatorFactories validatorFactories} */ setExternalFactoryFunction: function(externalFactoryFunction) { service.externalFactoryFunction = externalFactoryFunction; }, /** * @ngdoc service * @name clearExternalFactory * @methodOf ui.grid.validate.service:uiGridValidateService * @description Removes any link to external factory from this service */ clearExternalFactory: function() { delete service.externalFactoryFunction; }, /** * @ngdoc service * @name getValidatorFromExternalFactory * @methodOf ui.grid.validate.service:uiGridValidateService * @description Retrieves a validator by executing a validatorFactory * stored in an external service. * @param {string} name the name of the validator to retrieve * @param {object} argument an argument to pass to the validator factory */ getValidatorFromExternalFactory: function(name, argument) { return service.externalFactoryFunction(name, argument).validatorFactory(argument); }, /** * @ngdoc service * @name getMessageFromExternalFactory * @methodOf ui.grid.validate.service:uiGridValidateService * @description Retrieves a message stored in an external service. * @param {string} name the name of the validator * @param {object} argument an argument to pass to the message function */ getMessageFromExternalFactory: function(name, argument) { return service.externalFactoryFunction(name, argument).messageFunction(argument); }, /** * @ngdoc service * @name setValidator * @methodOf ui.grid.validate.service:uiGridValidateService * @description Adds a new validator to the service * @param {string} name the name of the validator, must be unique * @param {function} validatorFactory a factory that return a validatorFunction * @param {function} messageFunction a function that return the error message */ setValidator: function(name, validatorFactory, messageFunction) { service.validatorFactories[name] = { validatorFactory: validatorFactory, messageFunction: messageFunction }; }, /** * @ngdoc service * @name getValidator * @methodOf ui.grid.validate.service:uiGridValidateService * @description Returns a validator registered to the service * or retrieved from the external factory * @param {string} name the name of the validator to retrieve * @param {object} argument an argument to pass to the validator factory * @returns {object} the validator function */ getValidator: function(name, argument) { if (service.externalFactoryFunction) { var validator = service.getValidatorFromExternalFactory(name, argument); if (validator) { return validator; } } if (!service.validatorFactories[name]) { throw ("Invalid validator name: " + name); } return service.validatorFactories[name].validatorFactory(argument); }, /** * @ngdoc service * @name getMessage * @methodOf ui.grid.validate.service:uiGridValidateService * @description Returns the error message related to the validator * @param {string} name the name of the validator * @param {object} argument an argument to pass to the message function * @returns {string} the error message related to the validator */ getMessage: function(name, argument) { if (service.externalFactoryFunction) { var message = service.getMessageFromExternalFactory(name, argument); if (message) { return message; } } return service.validatorFactories[name].messageFunction(argument); }, /** * @ngdoc service * @name isInvalid * @methodOf ui.grid.validate.service:uiGridValidateService * @description Returns true if the cell (identified by rowEntity, colDef) is invalid * @param {object} rowEntity the row entity of the cell * @param {object} colDef the colDef of the cell * @returns {boolean} true if the cell is invalid */ isInvalid: function (rowEntity, colDef) { return rowEntity['$$invalid'+colDef.name]; }, /** * @ngdoc service * @name setInvalid * @methodOf ui.grid.validate.service:uiGridValidateService * @description Makes the cell invalid by adding the proper field to the entity * @param {object} rowEntity the row entity of the cell * @param {object} colDef the colDef of the cell */ setInvalid: function (rowEntity, colDef) { rowEntity['$$invalid'+colDef.name] = true; }, /** * @ngdoc service * @name setValid * @methodOf ui.grid.validate.service:uiGridValidateService * @description Makes the cell valid by removing the proper error field from the entity * @param {object} rowEntity the row entity of the cell * @param {object} colDef the colDef of the cell */ setValid: function (rowEntity, colDef) { delete rowEntity['$$invalid'+colDef.name]; }, /** * @ngdoc service * @name setError * @methodOf ui.grid.validate.service:uiGridValidateService * @description Adds the proper error to the entity errors field * @param {object} rowEntity the row entity of the cell * @param {object} colDef the colDef of the cell * @param {string} validatorName the name of the validator that is failing */ setError: function(rowEntity, colDef, validatorName) { if (!rowEntity['$$errors'+colDef.name]) { rowEntity['$$errors'+colDef.name] = {}; } rowEntity['$$errors'+colDef.name][validatorName] = true; }, /** * @ngdoc service * @name clearError * @methodOf ui.grid.validate.service:uiGridValidateService * @description Removes the proper error from the entity errors field * @param {object} rowEntity the row entity of the cell * @param {object} colDef the colDef of the cell * @param {string} validatorName the name of the validator that is failing */ clearError: function(rowEntity, colDef, validatorName) { if (!rowEntity['$$errors'+colDef.name]) { return; } if (validatorName in rowEntity['$$errors'+colDef.name]) { delete rowEntity['$$errors'+colDef.name][validatorName]; } }, /** * @ngdoc function * @name getErrorMessages * @methodOf ui.grid.validate.service:uiGridValidateService * @description returns an array of i18n-ed error messages. * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for * @param {object} colDef the column whose errors we are looking for * @returns {array} An array of strings containing all the error messages for the cell */ getErrorMessages: function(rowEntity, colDef) { var errors = []; if (!rowEntity['$$errors'+colDef.name] || Object.keys(rowEntity['$$errors'+colDef.name]).length === 0) { return errors; } Object.keys(rowEntity['$$errors'+colDef.name]).sort().forEach(function(validatorName) { errors.push(service.getMessage(validatorName, colDef.validators[validatorName])); }); return errors; }, /** * @ngdoc function * @name getFormattedErrors * @methodOf ui.grid.validate.service:uiGridValidateService * @description returns the error i18n-ed and formatted in html to be shown inside the page. * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for * @param {object} colDef the column whose errors we are looking for * @returns {object} An object that can be used in a template (like a cellTemplate) to display the * message inside the page (i.e. inside a div) */ getFormattedErrors: function(rowEntity, colDef) { var msgString = "", errors = service.getErrorMessages(rowEntity, colDef); if (!errors.length) { return; } errors.forEach(function(errorMsg) { msgString += errorMsg + "<br/>"; }); return $sce.trustAsHtml('<p><b>' + i18nService.getSafeText('validate.error') + '</b></p>' + msgString ); }, /** * @ngdoc function * @name getTitleFormattedErrors * @methodOf ui.grid.validate.service:uiGridValidateService * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html * title attribute. * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for * @param {object} colDef the column whose errors we are looking for * @returns {object} An object that can be used in a template (like a cellTemplate) to display the * message inside an html title attribute */ getTitleFormattedErrors: function(rowEntity, colDef) { var newLine = "\n", msgString = "", errors = service.getErrorMessages(rowEntity, colDef); if (!errors.length) { return; } errors.forEach(function(errorMsg) { msgString += errorMsg + newLine; }); return $sce.trustAsHtml(i18nService.getSafeText('validate.error') + newLine + msgString); }, /** * @ngdoc function * @name getTitleFormattedErrors * @methodOf ui.grid.validate.service:uiGridValidateService * @description Executes all validators on a cell (identified by row entity and column definition) and sets or clears errors * @param {object} rowEntity the row entity of the cell we want to run the validators on * @param {object} colDef the column definition of the cell we want to run the validators on * @param {object} newValue the value the user just entered * @param {object} oldValue the value the field had before */ runValidators: function(rowEntity, colDef, newValue, oldValue, grid) { if (newValue === oldValue) { // If the value has not changed we perform no validation return; } if (typeof(colDef.name) === 'undefined' || !colDef.name) { throw new Error('colDef.name is required to perform validation'); } service.setValid(rowEntity, colDef); var validateClosureFactory = function(rowEntity, colDef, validatorName) { return function(value) { if (!value) { service.setInvalid(rowEntity, colDef); service.setError(rowEntity, colDef, validatorName); if (grid) { grid.api.validate.raise.validationFailed(rowEntity, colDef, newValue, oldValue); } } }; }; var promises = []; for (var validatorName in colDef.validators) { service.clearError(rowEntity, colDef, validatorName); var validatorFunction = service.getValidator(validatorName, colDef.validators[validatorName]); // We pass the arguments as oldValue, newValue so they are in the same order // as ng-model validators (modelValue, viewValue) var promise = $q.when(validatorFunction(oldValue, newValue, rowEntity, colDef)) .then(validateClosureFactory(rowEntity, colDef, validatorName)); promises.push(promise); } return $q.all(promises); }, /** * @ngdoc function * @name createDefaultValidators * @methodOf ui.grid.validate.service:uiGridValidateService * @description adds the basic validators to the list of service validators */ createDefaultValidators: function() { service.setValidator('minLength', function (argument) { return function (oldValue, newValue) { if (newValue === undefined || newValue === null || newValue === '') { return true; } return newValue.length >= argument; }; }, function(argument) { return i18nService.getSafeText('validate.minLength').replace('THRESHOLD', argument); }); service.setValidator('maxLength', function (argument) { return function (oldValue, newValue) { if (newValue === undefined || newValue === null || newValue === '') { return true; } return newValue.length <= argument; }; }, function(threshold) { return i18nService.getSafeText('validate.maxLength').replace('THRESHOLD', threshold); }); service.setValidator('required', function (argument) { return function (oldValue, newValue) { if (argument) { return !(newValue === undefined || newValue === null || newValue === ''); } return true; }; }, function() { return i18nService.getSafeText('validate.required'); }); }, initializeGrid: function (scope, grid) { grid.validate = { isInvalid: service.isInvalid, getErrorMessages: service.getErrorMessages, getFormattedErrors: service.getFormattedErrors, getTitleFormattedErrors: service.getTitleFormattedErrors, runValidators: service.runValidators }; /** * @ngdoc object * @name ui.grid.validate.api:PublicApi * * @description Public Api for validation feature */ var publicApi = { events: { validate: { /** * @ngdoc event * @name validationFailed * @eventOf ui.grid.validate.api:PublicApi * @description raised when one or more failure happened during validation * <pre> * gridApi.validate.on.validationFailed(scope, function(rowEntity, colDef, newValue, oldValue){...}) * </pre> * @param {object} rowEntity the options.data element whose validation failed * @param {object} colDef the column whose validation failed * @param {object} newValue new value * @param {object} oldValue old value */ validationFailed: function (rowEntity, colDef, newValue, oldValue) { } } }, methods: { validate: { /** * @ngdoc function * @name isInvalid * @methodOf ui.grid.validate.api:PublicApi * @description checks if a cell (identified by rowEntity, colDef) is invalid * @param {object} rowEntity gridOptions.data[] array instance we want to check * @param {object} colDef the column whose errors we want to check * @returns {boolean} true if the cell value is not valid */ isInvalid: function(rowEntity, colDef) { return grid.validate.isInvalid(rowEntity, colDef); }, /** * @ngdoc function * @name getErrorMessages * @methodOf ui.grid.validate.api:PublicApi * @description returns an array of i18n-ed error messages. * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for * @param {object} colDef the column whose errors we are looking for * @returns {array} An array of strings containing all the error messages for the cell */ getErrorMessages: function (rowEntity, colDef) { return grid.validate.getErrorMessages(rowEntity, colDef); }, /** * @ngdoc function * @name getFormattedErrors * @methodOf ui.grid.validate.api:PublicApi * @description returns the error i18n-ed and formatted in html to be shown inside the page. * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for * @param {object} colDef the column whose errors we are looking for * @returns {object} An object that can be used in a template (like a cellTemplate) to display the * message inside the page (i.e. inside a div) */ getFormattedErrors: function (rowEntity, colDef) { return grid.validate.getFormattedErrors(rowEntity, colDef); }, /** * @ngdoc function * @name getTitleFormattedErrors * @methodOf ui.grid.validate.api:PublicApi * @description returns the error i18n-ed and formatted in javaScript to be shown inside an html * title attribute. * @param {object} rowEntity gridOptions.data[] array instance whose errors we are looking for * @param {object} colDef the column whose errors we are looking for * @returns {object} An object that can be used in a template (like a cellTemplate) to display the * message inside an html title attribute */ getTitleFormattedErrors: function (rowEntity, colDef) { return grid.validate.getTitleFormattedErrors(rowEntity, colDef); } } } }; grid.api.registerEventsFromObject(publicApi.events); grid.api.registerMethodsFromObject(publicApi.methods); if (grid.edit) { grid.api.edit.on.afterCellEdit(scope, function(rowEntity, colDef, newValue, oldValue) { grid.validate.runValidators(rowEntity, colDef, newValue, oldValue, grid); }); } service.createDefaultValidators(); } }; return service; }]); /** * @ngdoc directive * @name ui.grid.validate.directive:uiGridValidate * @element div * @restrict A * @description Adds validating features to the ui-grid directive. * @example <example module="app"> <file name="app.js"> var app = angular.module('app', ['ui.grid', 'ui.grid.edit', 'ui.grid.validate']); app.controller('MainCtrl', ['$scope', function ($scope) { $scope.data = [ { name: 'Bob', title: 'CEO' }, { name: 'Frank', title: 'Lowly Developer' } ]; $scope.columnDefs = [ {name: 'name', enableCellEdit: true, validators: {minLength: 3, maxLength: 9}, cellTemplate: 'ui-grid/cellTitleValidator'}, {name: 'title', enableCellEdit: true, validators: {required: true}, cellTemplate: 'ui-grid/cellTitleValidator'} ]; }]); </file> <file name="index.html"> <div ng-controller="MainCtrl"> <div ui-grid="{ data: data, columnDefs: columnDefs }" ui-grid-edit ui-grid-validate></div> </div> </file> </example> */ module.directive('uiGridValidate', ['gridUtil', 'uiGridValidateService', function (gridUtil, uiGridValidateService) { return { priority: 0, replace: true, require: '^uiGrid', scope: false, compile: function () { return { pre: function ($scope, $elm, $attrs, uiGridCtrl) { uiGridValidateService.initializeGrid($scope, uiGridCtrl.grid); }, post: function ($scope, $elm, $attrs, uiGridCtrl) { } }; } }; }]); })(); angular.module('ui.grid.validate').run(['$templateCache', function($templateCache) { 'use strict'; $templateCache.put('ui-grid/cellTitleValidator', "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" title=\"{{grid.validate.getTitleFormattedErrors(row.entity,col.colDef)}}\">{{COL_FIELD CUSTOM_FILTERS}}</div>" ); $templateCache.put('ui-grid/cellTooltipValidator', "<div class=\"ui-grid-cell-contents\" ng-class=\"{invalid:grid.validate.isInvalid(row.entity,col.colDef)}\" tooltip-html-unsafe=\"{{grid.validate.getFormattedErrors(row.entity,col.colDef)}}\" tooltip-enable=\"grid.validate.isInvalid(row.entity,col.colDef)\" tooltip-append-to-body=\"true\" tooltip-placement=\"top\" title=\"TOOLTIP\">{{COL_FIELD CUSTOM_FILTERS}}</div>" ); }]);