1

I have a form. In order to validate the fields (on the client side) I'm using a bit of HTML5 and ng-message to display the error.

Two of the fields behave differently from the rest of the fields.

If one of these 2 fields is filled, the other should be filled as well.

On the other side, if none of the two is filled, the form should accept these 2 fields as valid.

I want a direct feedback in the form, as soon as the user enter something in one of the two field, the other should display the corresponding message.

The 2 fields in the code bellow are fWebsiteURLand fWebsiteName.

What is the best approach to implement such logic in my directive/form ?

Here is portion of my partial (using jade and bootstrap 2.x.x).

At the moment, only a basic required validation is made for each field, making them mandatory for the all form.

Thanks for your help.

    form.form-horizontal(name="scheduleForm")
    .control-group(ng-class="{error: scheduleForm.fProjectName.$invalid}")
        label.control-label.muted(for='fProjectName') Project
        .controls
            input.input-xxlarge(name="fProjectName", type='text', placeholder='Project name', ng-model="schedule.projectName", required)
            small.help-inline(ng-messages="scheduleForm.fProjectName.$error")
                div(ng-message="required")
                    | The project name is required.
    .control-group(ng-class="{error: scheduleForm.fWebsiteName.$invalid}")
        label.control-label.muted(for='fWebsiteName') Website name
        .controls
            input.input-xlarge(name="fWebsiteName", type='text', placeholder='Website name', ng-model="schedule.website.name", required)
            small.help-inline(ng-messages="scheduleForm.fWebsiteName.$error")
                div(ng-message="required")
                    | The website name is required if you entered a website link.
    .control-group(ng-class="{error: scheduleForm.fWebsiteURL.$invalid}")
        label.control-label.muted(for='fWebsiteURL') Website URL
        .controls
            input.input-xlarge(name="fWebsiteURL", type='text', placeholder='Website link', ng-model="schedule.website.url", required)
            small.help-inline(ng-messages="scheduleForm.fWebsiteURL.$error")
                div(ng-message="required")
                    | The website link is required if you entered a website name.
    .control-group
        .controls
            button.btn.btn-primary(type='button', name='submit', ng-disabled="scheduleForm.$invalid", ng-click="update()") Save
            button.btn(type='button', name='Cancel', ng-click="cancel()") Cancel
BENARD Patrick
  • 30,363
  • 16
  • 99
  • 105
Michael
  • 2,436
  • 1
  • 36
  • 57

2 Answers2

0

Here is a plunk that illustrates a simple example when whether one field is valid or not depends on the presence of the other:

http://plnkr.co/edit/sr3qDWLk7jpEc520qiKs?p=preview

Basically you create a validate function:

var checkAndSet = function(first, second, scope){
      if ( !_.isUndefined(first) && first !== ''){
        var firstFilledSecondNot =  (_.isUndefined(second) || second === '');
        scope.firstFilledSecondNot = firstFilledSecondNot;
      } else {
        scope.firstFilledSecondNot = false;
      }
   };

This function just straightforwardly checks if when the first input is filled in, the second one is not. The tricky part is you put the validation result into your scope to be used by the hidden input:

<input name="fSecondInput" data-ng-model="firstFilledSecondNot" type="hidden" data-ui-validate="{firstInputFilledInButSecondNot: 'isFirstInputFilledInButSecondNot($value)'}">

As you can see, this input has the same name as your "real" second input, and has as model the scope

firstFilledSecondNot

variable you exposed in the function in step 1. Then you have a

data-ui-validate="{firstInputFilledInButSecondNot: 'isFirstInputFilledInButSecondNot($value)'}"

block which just takes this value and returns it. After the validation cycle has passed, you'd have

someForm.fSecondInput.$error.firstInputFilledInButSecondNot

object in your scope, which is what you wanted in the first place.

You could have just used the two watches and then put the result of each in your scope.someForm.fSecondInputObject, but I find it uglier:

var checkAndSet = function(first, second, scope){
      if ( !_.isUndefined(first) && first !== ''){
        var firstFilledSecondNot =  (_.isUndefined(second) || second === '');
        scope.someForm.fSecondInput.$error.firstFilledSecondNot = firstFilledSecondNot;
      } else {
        scope.someForm.fSecondInput.$error.firstFilledSecondNot = false;
      }
   };

Second plunkr here:

http://plnkr.co/edit/W1UW7E2D9DDuOJtL6Jea?p=preview

Nikola Yovchev
  • 9,498
  • 4
  • 46
  • 72
  • Thanks @baba, I think i got the logic of your code, i still have 2 concerns 1/ I'm using a directive for the form and trying to implement your code inside the directive makes the validation not working (could you make a plunkr with a directive using the **link function** instead of a regular controller ?) 2/ If the first field is filled, the second is made mandatory; This doesn't work the other way around, if second field is filled, the first one is not mandatory - thanks for your time, it is really appreciated ;-) – Michael Aug 09 '14 at 08:10
  • BTW, with your code i discovered `lodash` which is a very nice alternative to underscore ! Thx. – Michael Aug 09 '14 at 08:14
  • @Michael Can you show me a plunkr of the directive you are using? As for lodash, you can read this: http://kitcambridge.be/blog/say-hello-to-lo-dash/ and this http://stackoverflow.com/questions/13789618/differences-between-lodash-and-underscore. All in all, I really like the library. And as for both fields to be mandatory, it's fairly simple to implement it. – Nikola Yovchev Aug 09 '14 at 09:39
0

Based on the comment from @baba i think i found a solution to my problem, here is the logic i implemented.

I created a custom directive responsible to do the validation. This validator is called coBothOrNoneValidator. It require 2 parameters, the formand the other-field. This last parameter is the field being compare to.

Here is my jade partial:

form.form-horizontal(name="scheduleForm")
    .control-group(ng-class="{error: scheduleForm.fProjectName.$invalid}")
        label.control-label.muted(for='fProjectName') Project
        .controls
            input.input-xxlarge(name="fProjectName", type='text', placeholder='Project name', ng-model="schedule.projectName", required)
            small.help-inline(ng-messages="scheduleForm.fProjectName.$error")
                div(ng-message="required")
                    | The project name is required.
    .control-group(ng-class="{error: scheduleForm.fWebsiteName.$invalid}")
        label.control-label.muted(for='fWebsiteName') Website name
        .controls
            input.input-xlarge(name="fWebsiteName", type='text', placeholder='Website name', ng-model="item.website.name", co-both-or-none-validator, form="scheduleForm", other-field="fWebsiteURL")
            small.help-inline(ng-messages="scheduleForm.fWebsiteName.$error")
                div(ng-message="bothOrNone")
                    | The website name is required if you entered a website link.
    .control-group(ng-class="{error: scheduleForm.fWebsiteURL.$invalid}")
        label.control-label.muted(for='fWebsiteURL') Website URL
        .controls
            input.input-xlarge(name="fWebsiteURL", type='text', placeholder='Website link', ng-model="item.website.url", co-both-or-none-validator, form="scheduleForm", other-field="fWebsiteName")
            small.help-inline(ng-messages="scheduleForm.fWebsiteURL.$error")
                div(ng-message="bothOrNone")
                    | The website link is required if you entered a website name.
    .control-group
        .controls
            button.btn.btn-primary(type='button', name='submit', ng-disabled="scheduleForm.$invalid", ng-click="update()") Save
            button.btn(type='button', name='Cancel', ng-click="cancel()") Cancel

Finally, here is my directive co-both-or-none-validator:

angular.module('app').directive("coBothOrNoneValidator", function() {
    return {
        restrict: 'A',
        require: 'ngModel',
        link: function (scope, el, attrs, ctrl) {
            var validateBothOrNone = function(value){
                var form = scope[attrs.form];
                var theOther = form[attrs.otherField];
                var isNotValid = (value && (_.isUndefined(theOther.$modelValue) || theOther.$modelValue === ''))
                ctrl.$setValidity('bothOrNone', !isNotValid);
                theOther.$setValidity('bothOrNone', !isNotValid);
                return value;
            };

            ctrl.$parsers.unshift(validateBothOrNone);
        }
    }
})

Here is a very basic demo

Any idea to improve this code is welcome

Michael
  • 2,436
  • 1
  • 36
  • 57