johannesjo / ng-fab-form

Convenient forms for Angular with no extra markup? Fabulous!
https://johannesjo.github.io/ng-fab-form
MIT License
172 stars 25 forks source link

Add a way to copy the error classes to parent element. #81

Closed vincentpalita closed 9 years ago

vincentpalita commented 9 years ago

Hi ! I am using ng-fab-form for a new project and by far I am very happy with it! I use Foundation for Apps for this project and their forms are made with some options such as prefix & suffix elements for inputs:

<span class="inline-label">
    <span class="form-label icon icon-user"></span>
    <input type="text" name="username" ng-model="login.username" validation-msg-required="{{'USER_LOGIN_INCORRECT' | translate}}" required>
</span>

Actually ng-fab-form adds the validations on the input and insert the messages after it. I would like to have a way to toggle the same errors/success classes on the parent (span.inline-label) without having to write a ng-class myself.

I tried with a directive but it doesn't work. If I write myself the ng-class in the html template then it works:

<span class="inline-label" ng-class="{'has-error': loginForm.username.$touched && loginForm.username.$invalid }">

Is there a way to do it with ng-fab-form?

Thank you for your help! Vincent

johannesjo commented 9 years ago

Hello there. Thanks for posting!

I think this has some complexity to it that makes it a little too specific to add it to ng-fab-form (what about cases when you don't want it, which classes do you want to transfer, etc.).

Writing your own directive should be easy though: For monitoring the class name see: http://stackoverflow.com/questions/21693064/monitor-for-class-changing-on-element-in-angularjs-directive You could then use el.parent().addClass(...) and el.parent().removeClass(...);

vincentpalita commented 9 years ago

Hi,

thank you for replying to me that fast!!

Well I really think as a front designer that this would be a great feature to add to the module. Every html framework out there uses prefix & suffix (bootstrap, foundation, etc...) and they all wrap the inputs and other elements of design inside a container. This container could receive the same classes as the input and this would give the power to style the way you want your errors/successes in forms. And to disable this option, this could be done through a boolean option.

I did find a way to do it with a directive, thanks to: http://www.kvetis.com/2014/01/angularjs-and-bootstrap-form-group.html

app.directive("metaValidate", function () {
      return {
        restrict: 'A',
        require:  'metaValidate',
        link: function ($scope, element, attributes, controller) {
          var errorList = {};
          controller.inputError = function () {
            element.addClass("has-error");
          };
          controller.inputValid = function () {
            element.removeClass("has-error");
          };
        },
        //the controller is initialized in link function
        controller: function() {return {};}
      }
}
app.directive("input", function () {
      return {
        restrict: "A",
        //require controlllers of ngModel and parent directive
        //formGroup
        //they're injected as array parameter of link function
        require: ["?ngModel", "^?metaValidate"],
        link: function ($scope, element, attributes, controllers) {
          var modelController = controllers[0];
          var metaValidateController = controllers[1];
          if (!modelController || !metaValidateController) return;
          var hasBeenVisited = false;
          // check if user has left the field
          element.on("blur", function () {
            $scope.$apply(function () {
              hasBeenVisited = true;
            });
          });
          // Watch the validity of the input
          $scope.$watch(function () {
            return modelController.$invalid && hasBeenVisited;
          }, function () {
            // $emit messages to the control group
            if (modelController.$invalid && hasBeenVisited) {
              metaValidateController.inputError();
            } else {
              metaValidateController.inputValid();
            }
          });
        }
      };
}

I just put the meta-validate attribute on the container and the required attribute on the input (which is needed to be validated by ng-fab-form by the way!) This works just fine but I find it a bit complicated to maintain.

Other solution with pure CSS is to put the input first in the html then the other elements and use the flex model and the order property. For example with foundation they use the following way to build the input wrapper:

<span class="inline-label">
    <span class="form-label icon icon-lock"></span>
    <input type="password" name="password" ng-model="login.password" validation-msg-required="Incorrect password" required>
    <span class="form-label"><a>Forgot your password?</a></span>
</span>

The order is: span input span.

The way to do it in css3 (hopefully with css4 you won't need to do that!) is to just put the input as the first element so you can use the sibling classes:

<span class="inline-label">
    <input type="password" name="password" ng-model="login.password" validation-msg-required="Incorrect password" required>
    <span class="form-label icon icon-lock"></span>
    <span class="form-label"><a>Forgot your password?</a></span>
</span>

The order is now: input span span

input, span {
    display: flex;
}
input {
    order: 2
}
span:first-of-type {
    order: 1
}

input.ng-invalid.ng-touched {
    color: red;
}

input.ng-invalid.ng-touched ~ span {
    color: red
}

I am telling you, for real if this is added to your module I really think this would suit a lot of designer out there. Otherwise people will need to write specific directives that add watches that angular-messages handle just fine or find css tricks :)

Hope you see my point! Well thank you again for the module!

Have a great weekend. Cheers, Vincent

johannesjo commented 9 years ago

Hello Vincent. I see your point. It might be definately useful. Point is that this hard to do in a way that it would fit everyones use case and there are lots of unwanted side-effects to consider. If you just add all classes to the parent than you might also end up with classes you don't want to be there.

Consider this:

<div class="my-wrapper">
  <input type="text" class"yellow-text">
</div>

You probably wouldn't want to copy yellow-text as well.

A solution to this would be to track all error classes like ng-invalid, .ng-touched etc. but this is tedious and would require you to also look out for new validators being added. I don't know. If you have a good idea how to go about it, I'm all ears.

johannesjo commented 9 years ago

I'm closing this for now. Feel free to reopen, if you have a good suggestion how to go about it.