Angular 输入框实现自定义验证功能
此插件使用angular.js、jquery实现。(jquery的引入需在angular 之前)
用户可以 在输入框输入数据后验证 必填项、整数型、浮点型验证。
如果在form 里面的输入框验证,可以点击 提交按钮后,实现 必填项验证。
效果图如下:
(1)验证未通过时,背景标红等样式为
input.ng-invalid, select.ng-invalid { background-color: #ee82ee !important; border: 1px solid #ccc; } .qtip { position: absolute; max-width: 260px; display: none; min-width: 50px; font-size: 10.5px; line-height: 12px; direction: ltr; } .qtip-content { position: relative; padding: 5px 9px; overflow: hidden; text-align: left; word-wrap: break-word; } .qtip-rounded, .qtip-tipsy { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .qtipmodal-ie6fix { position: absolute !important; } .box-shadow-tips { background-color: #f63; border-color: #f5a88f; color: white; -moz-box-shadow: 2px 2px 2px #969696; -webkit-box-shaow: 2px 2px 2px #969696; box-shadow: 2px 2px 2px #969696; }
因为angular.js 内置验证未通过时,会自动为 标签 增加 .ng-invalid 样式,因为这里重写此样式
input.ng-invalid, select.ng-invalid { background-color: #ee82ee !important; border: 1px solid #ccc; }
(2)html 代码如下
<body ng-app="myapp"> <form name="baseinfoform"> <div ng-controller="testctrl"> <input type="text" ng-model="age" my-valid="r"><br> <input type="text" ng-model="name" my-valid="int fn:certcheck"><br> <input type="button" value="提交" ng-click="submit()"> </div> </form> </body>
(3)此插件使用 directive myvalid 实现
app.directive('myvalid', ['$parse', 'uitipsfactory', 'uivalidfactory', function ($parse, tips, valid) { var uivalidattridname = 'ui-valid-id'; return { restrict: 'a', require: 'ngmodel', link: function (scope, el, attrs, ctrl) { var validid = el.attr(uivalidattridname); if (!validid) { validid = math.guid(); el.attr(uivalidattridname, validid); } var getrules = function () { return attrs.myvalid; }; var lastoldrules; var validfn = function (value, oldrules) { var sp = '_'; var rules = getrules(); var r = valid.check(value, rules, scope, attrs.uivalidtips); if (lastoldrules && !oldrules) { oldrules = lastoldrules; } if (r.flag && oldrules) { rules = rules ? rules + ' ' + oldrules : oldrules; } if (rules) { var arrinner = rules.split(' '); var i = 0; for (; i < arrinner.length; i++) { var onerule = arrinner[i]; if (!onerule.trim()) { continue; } ctrl.$setvalidity(attrs.ngmodel + sp + onerule, r.flag ? true : onerule != r.rule); } } if (!r.flag) { tips.on(el, r.msg); } else { tips.off(el); } return r.flag; }; var init = function () { var rules = getrules(); if (!rules) { return; } var parsers = ctrl.$parsers; if (parsers && parsers.length > 0) { parsers.clean(); } parsers.unshift(function (value) { return validfn(value) ? value : undefined; }); }; scope.$watch(attrs.ngmodel, function (newval, oldval) { if (newval === oldval) { return; } if (ctrl.$modelvalue != undefined && (ctrl.$invalid || el.hasclass('ng-invalid'))) { validfn(ctrl.$modelvalue); } }); scope.$watch(getrules, function (newrules, oldrules) { init(); lastoldrules = oldrules; if (ctrl.$modelvalue === undefined || ctrl.$modelvalue === null) { var needvalid = false; el.hasclass('ng-invalid'); var isvalnan = ctrl.$viewvalue !== ctrl.$viewvalue; if (ctrl.$invalid || (ctrl.$viewvalue !== undefined && !isvalnan)) { needvalid = true; } if (needvalid) { ctrl.$setviewvalue(ctrl.$viewvalue); } } else { if (!ctrl.$dirty && attrs.dirtycheck) { console.log('----'); } else { validfn(ctrl.$modelvalue, oldrules); } } }); } } }]);
通过 监听 attrs.ngmodel,验证规则 rules ,ctrl.$parser 来实现 输入框内容改变的响应。
一旦使用此directive,则动态为当前输入框添加id,以便在 验证通过后,改变输入框的验证背景信息。
(4)验证逻辑处理 uivalidfactory
app.factory('uivalidfactory', ['$parse', 'uitipsfactory', function ($parse, tips) { return { check: function (val, rules, $scope, defaulttips, extendparam) { if (!rules) { return { flag: true }; } var rulesarr = rules.split(' '), isblank = val === null || val === undefined || val === '' || ('' + val === ''); //如果不是必填项 且没有输入值 则清除提示框 if ($.inarray('r', rulesarr) === -1 && isblank) { return { flag: true } } var i = 0, len = rulesarr.length; for (; i < len; i++) { var rule = rulesarr[i]; if (!rule) { continue; } var flag = true; if ('r' === rule) { //如果是必填项,有值 返回true flag = !isblank; } else if (rule.contains(':')) { //如果校验规则是 fn:ctrl.certcheck flag = this.checkrule(val, rule.split(/:/), $scope, extendparam); } else { //校验 规则是 int 用正则匹配 数字 邮箱 长度 var pat = this.pats[rule]; if (pat instanceof regexp) { if (angular.isstring(val)) { flag = this.mat(val, pat); } } else if (angular.isfunction(pat)) { flag = pat(val); } else { flag = false; } } //这是干什么的呢 if (angular.isstring(flag)) { return { flag: false, msg: flag, rule: rule } } if (flag === false) { var msg = this.getmsg(rule, defaulttips) || this.getmsg('tips.valid'); console.log(msg); return { flag: false, msg: msg, rule: rule } } } return { flag: true } }, checkrule: function (val, rulearr, $scope, extendparam) { //rulearr fn:certcheck var rule = rulearr[0]; if (rule === 'fn') { fnname = rulearr[1];//指定被调函数的名字 certcheck var fn = $parse(fnname)($scope); if (!fn) { return true; } return fn.call($scope, val, extendparam); } else { return true; } }, checkvalidform: function (formname) { //只检查必填项 //使用属性筛选器 获得里面所有的元素 var formcontext = $('form[name="{0}"],[ng-form="{0}"],[data-ng-form="{0}"]'.format(formname)), validlist = formcontext.find('[my-valid]');//validlist 不是数组,是伪数组 if (!validlist.length) { return; } var that = this, validflags = []; validlist.each(function () { var ele = $(this), val = ele.val(), rulestr = ele.attr('my-valid'); if (!rulestr) { return true; } if (angular.isstring(val)) { val = val.trim(); } var validrules = rulestr.split(' '); if ($.inarray('r', validrules) != -1 && !val) { var modelvalue = ele.attr('ng-model') || ele.attr('data-ng-model'); validflags.push(modelvalue); tips.on(ele, that.getmsg('r')); } } ); return validflags; }, mat: function (val, pat) { if (!pat) { return; } return pat.test(val); } , getmsg: function (rule, tips) { tips = tips || ''; //可以在界面上直接写 tips if (tips && tips.contains(':')) { return tips; } var msg = this.msgs[rule]; if (msg) { var params0 = tips.contains(':') ? tips.split(/:/)[0] : ''; var params1 = ''; if (rule.startswith('min') || rule.startswith('max')) { var rulearr = rule.split(/:/); params1 = rulearr[rulearr.length - 1]; } return msg.format(params0, params1); } else { } } , regpat: function (code, pat, msg) { if (this.pat[code]) { return; } this.pats[code] = pat; this.msgs[code] = msg; } , msgs: { 'r': '必填', 'int': '{0}必须为整数' } , pats: { 'int': /^[\-\+]?([0-9]+)$/ } } } ]) ;
通过获取输入框 ele.myvalid 验证规则,
1、如果是必填,则返回 标红此输入框,鼠标移上,则显示 验证信息 “必填””。
2、如果是整数、浮点型等验证,则通过 正则表达式进行验证。
3、如果是最大(max)、最小(min),则自定义逻辑。
4、如果是 fn 验证,则根据 对应controller中函数进行验证。
5、用户点击提交按钮,则 判断是否必填项,验证不通过,对应元素背景标红。
(5) 验证不通过,提示factory---uitipsfactory
app.factory('uitipsfactory', function () { return { filterclass: function (ele, invalid) { if (invalid) { //如果验证不通过 ele.removeclass('ng-valid').removeclass('ng-pristine').addclass('ng-invalid').addclass('ng-dirty'); } else { ele.removeclass('ng-invalid').addclass('ng-valid'); } }, on: function (ele, msg) { var lasttip = ele.data('last-tip'); if (lasttip && lasttip === msg) { return; } ele.data('last-tip', msg); this.filterclass(ele, true); var offset = ele.offset(); if (!offset.top && !offset.left && ele.is('hidden')) { offset = ele.show().offset(); } var id = ele.attr('ui-valid-id'); if (!id) { id = math.guid(); ele.attr('ui-valid-id', id); } if (id.contains('.')) { id = id.replace(/\./g, '_'); } var top = offset.top, left = offset.left; var gettips = function () { var _tip = $('#vtip_' + id); if (_tip.length) { _tip.html(msg).css({ 'display': 'none', 'top': top + 'px', 'left': left + ele.width() + 10 + 'px' }); } else { var html = '<div id="vtip_' + id + '" class="vtip qtip qtip-rounded box-shadow-tips">' + '<div class="qtip-content">' + msg + '</div>'; $(html).css({ 'display': 'none', 'position': 'absolute', 'top': top + 'px', 'left': left + ele.width() + 10 + 'px' }).appendto($('body')); } }; var bindtipsshow = function () { gettips(); ele.unbind('mouseenter mouseleave').bind('mouseenter', function () { var _tip = $('#vtip_' + id); if (_tip.is(':hidden')) { _tip.show(); } }).bind('mouseleave', function () { $('#vtip_' + id).hide(); }); }; bindtipsshow(); }, off: function (ele) { ele.data('last-tip', ''); this.filterclass(ele); var id = ele.attr('ui-valid-id'); if (!id) { return; } if (id.contains('.')) { id = id.replace(/\./g, '_'); } $('#vtip_' + id).remove(); ele.unbind('mouseenter mouseleave'); } } });
1、验证不通过,增加背景色,元素css处理如下
ele.removeclass('ng-valid').removeclass('ng-pristine').addclass('ng-invalid').addclass('ng-dirty');
验证通过,css处理如下
ele.removeclass('ng-invalid').addclass('ng-valid');
2、背景提示语,则是在body上增加一个div层。
(6)其他相关代码
var app = angular.module('myapp', []); app.controller('testctrl', ['$scope', 'uivalidfactory', function ($scope, uivalidfactory) { $scope.certcheck = function (val) { if (val > 32) { return "数字太大了"; } return true; }; $scope.submit = function () { if (!uivalidfactory.checkvalidform($scope.baseinfoform.$name)) { } }; }] ); math.guid = function () { var a = "", b = 1; for (; b <= 32; b++) { var c = math.floor(math.random() * 16).tostring(16); a += c; if (b === 8 || b === 12 || b === 16 || b === 20) { a += '-'; } } return a; }; string.prototype.contains = string.prototype.contains || function (a) { return this.indexof(a) != -1; }; string.prototype.format = string.prototype.format || function () { var a = array.prototype.slice.call(arguments); return this.replace(/\{(\d+)}/g, function (c, b) { return a[b]; }) };
整个代码如下:
<!doctype html> <html> <head lang="en"> <meta charset="utf-8"> <title></title> <script src="jquery-1.11.1.js"></script> <script src="angular.js"></script> <style type="text/css"> input.ng-invalid, select.ng-invalid { background-color: #ee82ee !important; border: 1px solid #ccc; } .qtip { position: absolute; max-width: 260px; display: none; min-width: 50px; font-size: 10.5px; line-height: 12px; direction: ltr; } .qtip-content { position: relative; padding: 5px 9px; overflow: hidden; text-align: left; word-wrap: break-word; } .qtip-rounded, .qtip-tipsy { -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } .qtipmodal-ie6fix { position: absolute !important; } .box-shadow-tips { background-color: #f63; border-color: #f5a88f; color: white; -moz-box-shadow: 2px 2px 2px #969696; -webkit-box-shaow: 2px 2px 2px #969696; box-shadow: 2px 2px 2px #969696; } </style> </head> <body ng-app="myapp"> <form name="baseinfoform"> <div ng-controller="testctrl"> <input type="text" ng-model="age" my-valid="r"><br> <input type="text" ng-model="name" my-valid="int fn:certcheck"><br> <input type="button" value="提交" ng-click="submit()"> </div> </form> </body> <script type="text/javascript"> var app = angular.module('myapp', []); app.controller('testctrl', ['$scope', 'uivalidfactory', function ($scope, uivalidfactory) { $scope.certcheck = function (val) { if (val > 32) { return "数字太大了"; } return true; }; $scope.submit = function () { if (!uivalidfactory.checkvalidform($scope.baseinfoform.$name)) { } }; }] ); math.guid = function () { var a = "", b = 1; for (; b <= 32; b++) { var c = math.floor(math.random() * 16).tostring(16); a += c; if (b === 8 || b === 12 || b === 16 || b === 20) { a += '-'; } } return a; }; string.prototype.contains = string.prototype.contains || function (a) { return this.indexof(a) != -1; }; string.prototype.format = string.prototype.format || function () { var a = array.prototype.slice.call(arguments); return this.replace(/\{(\d+)}/g, function (c, b) { return a[b]; }) }; app.factory('uitipsfactory', function () { return { filterclass: function (ele, invalid) { if (invalid) { //如果验证不通过 ele.removeclass('ng-valid').removeclass('ng-pristine').addclass('ng-invalid').addclass('ng-dirty'); } else { ele.removeclass('ng-invalid').addclass('ng-valid'); } }, on: function (ele, msg) { var lasttip = ele.data('last-tip'); if (lasttip && lasttip === msg) { return; } ele.data('last-tip', msg); this.filterclass(ele, true); var offset = ele.offset(); if (!offset.top && !offset.left && ele.is('hidden')) { offset = ele.show().offset(); } var id = ele.attr('ui-valid-id'); if (!id) { id = math.guid(); ele.attr('ui-valid-id', id); } if (id.contains('.')) { id = id.replace(/\./g, '_'); } var top = offset.top, left = offset.left; var gettips = function () { var _tip = $('#vtip_' + id); if (_tip.length) { _tip.html(msg).css({ 'display': 'none', 'top': top + 'px', 'left': left + ele.width() + 10 + 'px' }); } else { var html = '<div id="vtip_' + id + '" class="vtip qtip qtip-rounded box-shadow-tips">' + '<div class="qtip-content">' + msg + '</div>'; $(html).css({ 'display': 'none', 'position': 'absolute', 'top': top + 'px', 'left': left + ele.width() + 10 + 'px' }).appendto($('body')); } }; var bindtipsshow = function () { gettips(); ele.unbind('mouseenter mouseleave').bind('mouseenter', function () { var _tip = $('#vtip_' + id); if (_tip.is(':hidden')) { _tip.show(); } }).bind('mouseleave', function () { $('#vtip_' + id).hide(); }); }; bindtipsshow(); }, off: function (ele) { ele.data('last-tip', ''); this.filterclass(ele); var id = ele.attr('ui-valid-id'); if (!id) { return; } if (id.contains('.')) { id = id.replace(/\./g, '_'); } $('#vtip_' + id).remove(); ele.unbind('mouseenter mouseleave'); } } }); app.factory('uivalidfactory', ['$parse', 'uitipsfactory', function ($parse, tips) { return { check: function (val, rules, $scope, defaulttips, extendparam) { if (!rules) { return { flag: true }; } var rulesarr = rules.split(' '), isblank = val === null || val === undefined || val === '' || ('' + val === ''); //如果不是必填项 且没有输入值 则清除提示框 if ($.inarray('r', rulesarr) === -1 && isblank) { return { flag: true } } var i = 0, len = rulesarr.length; for (; i < len; i++) { var rule = rulesarr[i]; if (!rule) { continue; } var flag = true; if ('r' === rule) { //如果是必填项,有值 返回true flag = !isblank; } else if (rule.contains(':')) { //如果校验规则是 fn:ctrl.certcheck flag = this.checkrule(val, rule.split(/:/), $scope, extendparam); } else { //校验 规则是 int 用正则匹配 数字 邮箱 长度 var pat = this.pats[rule]; if (pat instanceof regexp) { if (angular.isstring(val)) { flag = this.mat(val, pat); } } else if (angular.isfunction(pat)) { flag = pat(val); } else { flag = false; } } //这是干什么的呢 if (angular.isstring(flag)) { return { flag: false, msg: flag, rule: rule } } if (flag === false) { var msg = this.getmsg(rule, defaulttips) || this.getmsg('tips.valid'); console.log(msg); return { flag: false, msg: msg, rule: rule } } } return { flag: true } }, checkrule: function (val, rulearr, $scope, extendparam) { //rulearr fn:certcheck var rule = rulearr[0]; if (rule === 'fn') { fnname = rulearr[1];//指定被调函数的名字 certcheck var fn = $parse(fnname)($scope); if (!fn) { return true; } return fn.call($scope, val, extendparam); } else { return true; } }, checkvalidform: function (formname) { //只检查必填项 //使用属性筛选器 获得里面所有的元素 var formcontext = $('form[name="{0}"],[ng-form="{0}"],[data-ng-form="{0}"]'.format(formname)), validlist = formcontext.find('[my-valid]');//validlist 不是数组,是伪数组 if (!validlist.length) { return; } var that = this, validflags = []; validlist.each(function () { var ele = $(this), val = ele.val(), rulestr = ele.attr('my-valid'); if (!rulestr) { return true; } if (angular.isstring(val)) { val = val.trim(); } var validrules = rulestr.split(' '); if ($.inarray('r', validrules) != -1 && !val) { var modelvalue = ele.attr('ng-model') || ele.attr('data-ng-model'); validflags.push(modelvalue); tips.on(ele, that.getmsg('r')); } } ); return validflags; }, mat: function (val, pat) { if (!pat) { return; } return pat.test(val); } , getmsg: function (rule, tips) { tips = tips || ''; //可以在界面上直接写 tips if (tips && tips.contains(':')) { return tips; } var msg = this.msgs[rule]; if (msg) { var params0 = tips.contains(':') ? tips.split(/:/)[0] : ''; var params1 = ''; if (rule.startswith('min') || rule.startswith('max')) { var rulearr = rule.split(/:/); params1 = rulearr[rulearr.length - 1]; } return msg.format(params0, params1); } else { } } , regpat: function (code, pat, msg) { if (this.pat[code]) { return; } this.pats[code] = pat; this.msgs[code] = msg; } , msgs: { 'r': '必填', 'int': '{0}必须为整数' } , pats: { 'int': /^[\-\+]?([0-9]+)$/ } } } ]) ; app.directive('myvalid', ['$parse', 'uitipsfactory', 'uivalidfactory', function ($parse, tips, valid) { var uivalidattridname = 'ui-valid-id'; return { restrict: 'a', require: 'ngmodel', link: function (scope, el, attrs, ctrl) { var validid = el.attr(uivalidattridname); if (!validid) { validid = math.guid(); el.attr(uivalidattridname, validid); } var getrules = function () { return attrs.myvalid; }; var lastoldrules; var validfn = function (value, oldrules) { var sp = '_'; var rules = getrules(); var r = valid.check(value, rules, scope, attrs.uivalidtips); if (lastoldrules && !oldrules) { oldrules = lastoldrules; } if (r.flag && oldrules) { rules = rules ? rules + ' ' + oldrules : oldrules; } if (rules) { var arrinner = rules.split(' '); var i = 0; for (; i < arrinner.length; i++) { var onerule = arrinner[i]; if (!onerule.trim()) { continue; } ctrl.$setvalidity(attrs.ngmodel + sp + onerule, r.flag ? true : onerule != r.rule); } } if (!r.flag) { tips.on(el, r.msg); } else { tips.off(el); } return r.flag; }; var init = function () { var rules = getrules(); if (!rules) { return; } var parsers = ctrl.$parsers; if (parsers && parsers.length > 0) { parsers.clean(); } parsers.unshift(function (value) { return validfn(value) ? value : undefined; }); }; scope.$watch(attrs.ngmodel, function (newval, oldval) { if (newval === oldval) { return; } if (ctrl.$modelvalue != undefined && (ctrl.$invalid || el.hasclass('ng-invalid'))) { validfn(ctrl.$modelvalue); } }); scope.$watch(getrules, function (newrules, oldrules) { init(); lastoldrules = oldrules; if (ctrl.$modelvalue === undefined || ctrl.$modelvalue === null) { var needvalid = false; el.hasclass('ng-invalid'); var isvalnan = ctrl.$viewvalue !== ctrl.$viewvalue; if (ctrl.$invalid || (ctrl.$viewvalue !== undefined && !isvalnan)) { needvalid = true; } if (needvalid) { ctrl.$setviewvalue(ctrl.$viewvalue); } } else { if (!ctrl.$dirty && attrs.dirtycheck) { console.log('----'); } else { validfn(ctrl.$modelvalue, oldrules); } } }); } } }]); </script> </html>
上一篇: axios取消上传