Угловая динамика требует проверки группы полей с выделенным полем, если недействителен

ОБНОВЛЕНИЕ 1: вопрос будет усилен на основе отзывов от комментариев.
ОБНОВЛЕНИЕ 2: Был достигнут определенный прогресс. Требуется additinoal h $ compile (el [0]) (scope); p для прохождения. Пожалуйста, прочтите ниже.
ОБНОВЛЕНИЕ 3: Исправление ошибок в предоставленном примере кода, вызывающем дублирование в строках таблицы при компиляции используемого элемента getRequiredFieldInfo().

При загрузке страницы список имен полей извлекается из базы данных, которая указывает, какие поля являются check-if-required, используя ajax-вызов required. Этот вызов должен быть успешно завершен перед выполнением соответствующего углового кода в соответствии с директивой для управления атрибутом. Эта директива должна пересекать все поля div и отмечать их как набор полей на основе списка, который извлекается из базы данных.<input name="firstName" type="text" foo ng-required="isFieldRequired('firstName')" />isFieldRequired()

Я провел некоторое исследование и нашел этот пост, который, как представляется, ближе всего к моим требованиям:

https://stackoverflow.com/a/28207652/4180447

и, наконец, нашел здесь рабочую версию jsfiddle (обновлено):

http://jsfiddle.net/tarekahf/d50tr99u/

Я могу использовать следующий простой подход:

required

Функция проверяет, найдено ли имя прошедшего поля в списке, оно вернет true.input.ng-invalid, li.ng-invalid { background:#F84072; border: 2px red solid; }

Проблема с этим подходом заключается в том, что я должен добавить эту функцию в каждое поле, которое может быть <ul class = "nav nav-pills" > <li ng-class = "{'ng-invalid': mainForm.homeForm. $ && недействительным mainPromiseResolved} " класс = "активный" > <a data-toggle = "pill" HREF = "#home"> <% = homeTabName%> </a> </ литий> <литий нг класса = " { 'нг-инвалида':. mainForm.clientForm $ недействителен && mainPromiseResolved}» > <a data-toggle = "pill" HREF = "#menu1"> <% = clientTabName%> </a> </ li> <литий нг -класс = "{ 'нг-инвалида':. mainForm.appraiserForm $ недействителен && mainPromiseResolved}" > <a data-toggle = "pill" HREF = "#menu2"> <% = appraiserTabName%> </a> </ литий> <литий нг-класс = "{ 'нг-инвалида':. mainForm.propertyForm $ недействителен && mainPromiseResolved}" > <a data-toggle = "pill" HREF = "#menu3"> <% = propertyTabName%> < / а> </ li> <литий нг-класс = "{ 'нг-инвалида':. mainForm.serviceForm $ недействителен && mainPromiseResolved}" > <a data-toggle = "pill" HREF = "#menu4"> <% = servicesTabName%> </a> </ li> <li ng-class = "{'ng-invalid': mainForm.constructionSt возраст. $ недействителен && mainPromiseResolved} " > <a data-toggle = "pill" HREF = "#menu5"> <% = constructionStageTabName%> </a> </ li> <литий нг-класс = " { 'ng- недействителен ":. mainForm.costForm $ недействителен && mainPromiseResolved}» > <a data-toggle = "pill" HREF = "#menu6"> <% = costTabName%> </a> </ li> <литий нг-класс = "{ 'нг-инвалида':. mainForm.certificationForm $ недействителен && mainPromiseResolved}" нг-нажмите = "redrawCanvas ()" > <a data-toggle = "pill" HREF = "#menu7"> <% = certificationTabName%> </a> </ li> <литий нг-класс = "{ 'нг-инвалида':. mainForm.photosForm $ недействителен && 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> .

Кроме того, каждый раз нужно передавать имя поля. Чтобы быть более эффективным, мне придется использовать директиву над родительским элементом или который позволит мне получить доступ ко всем дочерним элементам и обработать необходимые атрибуты для всех входных элементов.<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>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/..." + "&timestamp=" + 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); } }); }) }) } }; }]);

This directive need to be changed as follows:

  • To be added to the parent element of the group of fields whose el attribute will be processed and modified if needed.

  • Compare the element name against the list of fields to set as required and apply the change accordingly.

The updated code (as I am researching the solution):

STYLE

function (scope, el, attrs, ngModel)

HTML - NAVIGATION TABS:

required

HTML - Form

isFieldRequired(fieldName)

Javascrip:

ng-form

I've already made some progress. However, I still need more help. Following is the status:

  • Done: get required fields list from DB and then execute code in directive to manipulate the required attribute.
  • Done: Loop over the child input elements from a given angular element el which is passed to link function div.

  • Done: Add required attribute to each child element if div is true?

  • Done: Use promise to ensure all ajax DB calls and client updates are done before executing angular code.

  • How to recursively loop over the child elements if they are nested inside another fieldsset subform or div element?

  • How to ensure that each element has ngModel object?

  • How to restrict the directive to check-if-required-expr, required or similar elements?

Tarek

angularjs,validation,dynamic,required,subform,

0

Ответов: 1


0

The following code will satisfy the main requirement, in addition, for each element under the div block, it will allow adding attribute check-if-required-expr. This new attribute may be used to call a scope boolean expression to decide the required attribute in case the field is not found the ng-required.

I was wondering if there is a way to use the standard ng-required directive instead of the custom attribute required which basically does the same as ng-required. The only problem if I use <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> is that it might override the logic of the required field if it was specified in the list.

So the question here: is there a way to find out if the required attribute is set, and if yes, then do not check the required expression, otherwise, execute the input expression.

HTML

check-if-required-expr

Basically, in the above HTML, the input field which has list of required fields, indicates that if this field is not found in the list of required fields then execute the expression to decide if the field is required.

JavaScript

//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();
                    }
                });
            });
            //})
        }
    };
}]);
angularjs,validation,dynamic,required,subform,
Похожие вопросы