更新1:根据评论的反馈,问题将得到加强
更新2:已取得一些进展。需要额外的帮助才能通过。请阅读以下内容
更新3:使用$compile(el[0])(scope);
编译元素时,提供的示例代码中的错误修复导致表行重复。
在页面加载时,将从数据库中检索字段名称列表,该列表指示使用ajax调用getRequiredFieldInfo()
需要哪些字段。在执行指令check-if-required
下的相关角度代码之前,必须成功完成此调用才能操作required
属性。该指令必须遍历所有输入字段,并根据从数据库中检索的列表将其标记为需要。
我做了一些研究,发现这篇文章似乎最符合我的要求:
https://stackoverflow.com/a/28207652/4180447
最后在这里找到了一个有效的jsfiddle版本(更新):
http://jsfiddle.net/tarekahf/d50tr99u/
我可以使用以下简单方法:
<input name="firstName" type="text" foo ng-required="isFieldRequired('firstName')" />
函数isFieldRequired()
将检查列表中是否找到传递的字段名称,它将返回true。
这种方法的问题是我必须将此功能添加到可能需要的每个字段。
此外,每次都必须传递字段名称。为了提高效率,我将不得不在父元素div
或fieldset
上使用一个指令,它允许我访问所有子元素,并处理所有输入元素所需的属性。 / p>
此指令需要更改如下:
要添加到将在需要时处理和修改required
属性的字段组的父元素。
将元素名称与要设置的字段列表进行比较,并相应地应用更改。
更新的代码(因为我正在研究解决方案):
风格
input.ng-invalid, li.ng-invalid {
background:#F84072;
border: 2px red solid;
}
HTML - NAVIGATION TABS:
<ul class="nav nav-pills">
<li ng-class="{'ng-invalid':mainForm.homeForm.$invalid && mainPromiseResolved}" class="active"><a data-toggle="pill" href="#home"><%=homeTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.clientForm.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu1"><%=clientTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.appraiserForm.$invalid && mainPromiseResolved}"> <a data-toggle="pill" href="#menu2"><%=appraiserTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.propertyForm.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu3"><%=propertyTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.serviceForm.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu4"><%=servicesTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.constructionStage.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu5"><%=constructionStageTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.costForm.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu6"><%=costTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.certificationForm.$invalid && mainPromiseResolved}" ng-click="redrawCanvas()"><a data-toggle="pill" href="#menu7"><%=certificationTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.photosForm.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu8"><%=photoTabName%></a></li>
<li ng-class="{'ng-invalid':mainForm.mapForm.$invalid && mainPromiseResolved}"><a data-toggle="pill" href="#menu9"><%=locationTabName%></a></li>
</ul>
HTML - 表单
<div id="menu2" class="tab-pane fade" ng-form="appraiserForm">
<fieldset ng-disabled="isAppraiserSigned()" check-if-required>
<input type="text" id="appraiser_name" name="appraiser_name" ng-model="sigRoles.appraiser.roleNameModel" style="width: 536px; ">
<input type="text" id="appraiser_company" style="width: 536px; ">
...
...
</fieldset>
</div>
的javascrip:
app.controller('formMainController', ['$scope', '$timeout', '$q', function($scope, $timeout, $q) {
$scope.runProcessAndInit = function () {
var q = $q.defer(); //Create a promise controller
angular.element(document).ready(function(){
//perform all client updates here
q.resolve('success'); //notify execution is completed successfully - inside document 'ready' event.
})
return q.promise; //return the promise object.
}
//mainPromiseResolved is used to indicate all ajax calls and client updates are done.
$scope.mainPromiseResolved = false;
$scope.mainPromise = $scope.runProcessAndInit();
$scope.mainPromise.then(function(success) {
//debugger;
$scope.$broadcast('event:force-model-update');
//mainPromiseResolved is mainly used in angular validation to prevent showing errors until all client updates are done.
$scope.mainPromiseResolved = true;
return 'main promise done';
})
$scope.isFieldRequired = function (prmFieldName) {
var isFound = false;
var oRequiredField = formView.getRequiredField();
findField: {
for(var subformName in oRequiredField) {
isFound = prmFieldName in oRequiredField[subformName];
if (isFound) {
break findField;
}
}
}
return isFound;
}
function getRequiredFieldInfo() {
var q = $q.defer();
var appUrl = getAppURL();
$.get(appUrl + "/servlet/..."
+ "×tamp=" + new Date().getTime(),
function(data, status){
//console.log("json fields:" + data);
var obj = JSON.parse(data);
formView.setRequiredField(obj);
q.resolve('success');
// console.log(JSON.stringify(formView.getRequiredField()));
});
return q.promise;
}
$scope.requiredFieldsPromise = getRequiredFieldInfo();
}]);
app.directive('checkIfRequired', ['$compile', function ($compile) {
return {
require: '?ngModel',
link: function (scope, el, attrs, ngModel) {
if (!ngModel) {
//return;
}
//debugger;
var children = $(":input", el);
angular.element(document).ready(function (){
scope.requiredFieldsPromise.then(function(success) {
//remove the attribute to avoid recursive calls
el.removeAttr('check-if-required');
//Comment line below as it caused duplication in table raws, and I don't know why.
//$compile(el[0])(scope);
angular.forEach(children, function(value, key) {
//debugger;
if (scope.isFieldRequired(value.id)) {
angular.element(value).attr('required', true);
//el.removeAttr('check-if-required');
$compile(value)(scope);
}
});
})
})
}
};
}]);
我已经取得了一些进展。但是,我还需要更多的帮助。以下是状态:
required
属性。完成:循环来自给定角元素el
的子输入元素,该元素传递给链接函数function (scope, el, attrs, ngModel)
。
完成:如果required
为真,则为每个子元素添加isFieldRequired(fieldName)
属性?
完成:使用promise确保在执行角度代码之前完成所有ajax数据库调用和客户端更新。
如果子元素嵌套在另一个ng-form
子表单或div
元素中,如何递归循环子元素?
如何确保每个元素都有ngModel对象?
如何将指令限制为div
,fieldsset
或类似元素?
塔雷克
答案 0 :(得分:0)
以下代码将满足主要要求,此外,对于div
块下的每个元素,它将允许添加属性check-if-required-expr
。如果未找到字段所需字段,则可以使用此新属性调用范围布尔表达式来确定required
属性。
我想知道是否有办法使用标准ng-required
指令而不是自定义属性check-if-required-expr
,它基本上与ng-required
相同。如果我使用ng-required
,唯一的问题是如果在列表中指定了它,它可能会覆盖必填字段的逻辑。
这里的问题是:有没有办法找出是否设置了required
属性,如果是,则不检查所需的表达式,否则,执行ng-required
表达式。< / p>
<div id='signature-pad' class="m-signature-pad break" ng-class="{'ng-invalid':certificationForm[theRoleData.signatureBase64].$invalid && mainPromiseResolved}" check-if-required>
...
<div class="m-signature-pad--body">
<canvas id="appraiser_signature_section" redraw ng-signature-pad="signature" ng-hide="isSigned()">
</canvas>
<img ng-src="{{signatureDataURL()}}" ng-hide="!isSigned()" load-signature-image>
<input id="{{theRoleData.signatureBase64}}" name="{{theRoleData.signatureBase64}}" type="text" ng-hide="true" ng-model="signatureBase64" check-if-required-expr="sigDetailsAvail(theRoleData)" force-model-update/>
</div>
...
</div>
基本上,在上面的HTML中,input
字段check-if-required-expr
表示如果在list of required fields
中找不到此字段,则执行表达式以确定字段是否为必需的。
//Define directive check-if-required
//This directive will loop over all child input elements and add the required attributes if needed
app.directive('checkIfRequired', ['$compile', '$timeout', '$parse', function ($compile, $timeout, $parse) {
return {
/*require: '?ngModel',*/
require: '?^form',
link: function (scope, el, attrs, ngForm) {
/*if (!ngModel) {
return;
}*/
var saveIsValidationRequired;
var children;
saveIsValidationRequired = scope.isValidationRequired; //Save current flag value
scope.stopExecValidations();
el.removeAttr('check-if-required');
$timeout(function() {
//Get all input elements of the descendants of `el`
children = $(":input", el);
//Run the following as early as possible but just wait (using promise) until
// the list of required fields is retrieved from Database
//scope.requiredFieldsPromise.then(function(success) {
scope.requiredFieldsPromise.then(function(success) {
//The line below caused duplication of the table in construction stage, so it is removed and no impact
//$compile(el[0])(scope);
angular.forEach(children, function(child, key) {
var elmScope;
var elmModel;
try {
if(child && child.id) {
elmScope = angular.element(child).scope() || scope;
elmModel = angular.element(child).controller('ngModel');
if (ngForm && elmModel && ngForm[elmModel.$name]) {
scope.$watch(function(){
//Watch the errors for the defined field - convert to JSON string.
return JSON.stringify(ngForm[elmModel.$name].$error);
}, function (newValue, oldValue){
//The validation error message will be placed on the element 'title' attribute which will be the field 'tooltip'.
var maxlength;
var minlength;
if (angular.isDefined(newValue)) {
if (ngForm[elmModel.$name].$error.maxlength) {
//If invalid, add the error message if number of entered characters is more than the defined maximum
maxlength = scope.$eval(angular.element(child).attr('ng-maxlength'));
child.title = ("Number of characters entered should not exceed '{0}' characters.").format(maxlength);
} else {
//Remove the error if valid.
child.removeAttribute('title');
}
}
});
}
if (scope.isFieldRequired(child.id)) {
angular.element(child).attr('ng-required', "true");
$compile(child)(elmScope);
}
//Check if the element is not in "Required" list, and it has an expression to control requried, then
//... add the attribute 'ng-required' with the expression specified to the element and compile.
if (!angular.element(child).prop('required') && child.attributes.hasOwnProperty("check-if-required-expr")) {
var isRequiredExpr = child.attributes["check-if-required-expr"].child;
angular.element(child).attr('ng-required', isRequiredExpr);
$compile(child)(elmScope);
}
var validObjects = scope.getFieldValidation(child.id);
if (angular.isArray(validObjects)) {
for (var idx=0; idx < validObjects.length; idx++) {
var validObject = validObjects[idx];
var test = validObject.test || "true"; //if not exist, it means the rule should always be applied
var minLenExp = validObject.minlen;
var maxLenExp = validObject.maxlen;
var isRequiredExp = validObject.required || false;
isRequiredExp = angular.isString(isRequiredExp)?isRequiredExp:isRequiredExp.toString();
//scope.$evalAsync(function(){
if (test && (minLenExp || maxLenExp || isRequiredExp)) {
var testEval = scope.$eval(test, elmScope);
if (testEval) {
if (minLenExp) {
angular.element(child).attr('ng-minlength', minLenExp);
}
if (maxLenExp) {
angular.element(child).attr('ng-maxlength', maxLenExp);
}
//If the "required" expression is '*skip*' then simply skip.
//If '*skip*' is used, this means the required validation is already defined in code
//and no need to replace it.
if (isRequiredExp && isRequiredExp != '*skip*') {
angular.element(child).attr('ng-required', isRequiredExp);
}
//Change how '$compile()' is used.
// After reserach, found there is bug in Angular which is causing the fillowing issues when using '$compile()':
// 1. Duplicate values for drop-down list items.
// 2. Inteference with dateppciker Angular UI Bootstrap control
// If this still happes, more research is needed to resolve this problem.
// This is still work-in-progress. More research is needed.
//The compile statement below will be replaced ...
$compile(child)(elmScope, function (clone) {
angular.element(child).after(clone);
angular.element(child).remove();
});
//Apply only the first matching validation rule
break;
}
}
}
}
}
} catch (e) {
console.error("Error occuured in 'checkIfRequired' directive while applying validation logic on element ID '%s'. Error is: '%s'", child.id, e);
}
});
//If saved flag value is ture, enable validation
if (saveIsValidationRequired) {
scope.startExecValidations();
}
});
});
//})
}
};
}]);