jwashin / angular2-rbi-dart

Material Design Lite (MDL) working with Angular2-Dart
Other
12 stars 1 forks source link

class="mdl-textfield__error" prevents my error message from being displayed #2

Closed st-clair-clarke closed 8 years ago

st-clair-clarke commented 8 years ago

Hi Jim, I have the following Dart angular2 files:

name_component.dart

import 'package:angular2/angular2.dart';
import 'package:angular2_rbi/directives.dart';
import 'package:dev_string_converter/dev_string_converter.dart';
import 'package:epimss_ng2_reg/models.dart' show Name;
import 'package:jsonx/jsonx.dart';
//import 'package:epimss_ng2_shared/validators.dart';

@Component(
    selector: 'name-component' )
@View(
    templateUrl: '../views/name_view.html',
    directives: const [
      FORM_DIRECTIVES,
      CORE_DIRECTIVES,
      MaterialTextfield,
      MaterialButton
    ] )
class NameComponent
    implements AfterViewInit {

  Name name = new Name( );

  @Input( )
  String topic = '';

  ControlGroup nameForm;
  Control firstCtrl = new Control( '', Validators.minLength( 2 ) );
  Control middleCtrl = new Control( '', Validators.minLength( 2 ) );
  Control lastCtrl = new Control( '', Validators.minLength( 2 ) );

  String firstCtrlErrorMsg;
  String middleCtrlErrorMsg;
  String lastCtrlErrorMsg;
  String titleCtrlErrorMsg;
  String suffixCtrlErrorMsg;

  bool isSubmitted = false;
  bool isDevelopment = true;

  NameComponent( FormBuilder fb ) {
    nameForm = fb.group( {
      "firstCtrl": firstCtrl,
      'middleCtrl': middleCtrl,
      'lastCtrl': lastCtrl
    } );
  }

  String doGetControlErrorMessage( Control ctrl, String validationType ) {
    bool isValid = false;
    String errorMsg;

    if ( !ctrl.valid ) {
      switch ( validationType ) {
        case 'length':
          var errors = ctrl.errors;
          errorMsg = "REQUIRED ${errors['minlength']['requiredLength']} "
              "VALID characters, ACTUAL ${errors['minlength']['actualLength']} character";
          break;
      }
    }
    return errorMsg;
  }

  // TODO: Make diagnostic global
  String get diagnostic {
    if ( isDevelopment ) {
      return 'DIAGNOSTIC: ${encode( name, indent: ' ' )}';
    }
    return '';
  }

  void onInputHandler( event ) {
    String property = toLowerCamelCase( event.target.id );

    switch ( property ) {
      case 'first':
        firstCtrlErrorMsg = doGetControlErrorMessage(firstCtrl, 'length');
        break;

      case 'middle':
        middleCtrlErrorMsg = doGetControlErrorMessage(middleCtrl, 'length');
        break;

      case 'last':
        lastCtrlErrorMsg = doGetControlErrorMessage(lastCtrl, 'length');
         break;
    }
  }

  onSubmit( ) {
    isSubmitted = true;
  }
}

name_view.html

<div [hidden] = "isSubmitted">
  <form
      novalidate
      (ngSubmit) = "onSubmit()"
      [ngFormModel] = "nameForm">
    {{diagnostic}}
    <div class = "mdl-card mdl-shadow--3dp layout horizontal wrap">
      <div class = "mdl-card__title">
        <h2 class = "mdl-card__title-text">Name</h2>
      </div>

      <div
          class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
        <label
            class = "mdl-textfield__label"
            for = "first">First</label>
        <input
            type = "text"
            ngControl = "firstCtrl"
            ([ngModel]) = "name.first"
            (keyup) = "onInputHandler($event)"
            class = "mdl-textfield__input"
            id = "first">

        <span class="mdl-textfield__error"
            *ngIf = "!firstCtrl.valid">{{firstCtrlErrorMsg}}</span>
      </div>

      <div
          class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label ">
        <input
            type = "text"
            ngControl = "middleCtrl"
            ([ngModel]) = "name.middle"
            class = "mdl-textfield__input"
            id = "middle">
        <label
            class = "mdl-textfield__label"
            for = "middle">Middle</label>
         <span
             *ngIf = "!middleCtrl.valid">{{middleCtrlErrorMsg}}</span>
      </div>

      <div
          class = "mdl-textfield mdl-js-textfield mdl-textfield--floating-label ">
        <input
            type = "text"
            ngControl = "lastCtrl"
            ([ngModel]) = "name.last"
            (keyup) = "onInputHandler($event)"
            class = "mdl-textfield__input"
            id = "last">
        <label
            class = "mdl-textfield__label"
            for = "last">Last</label>
        <span
            *ngIf = "!lastCtrl.valid">{{lastCtrlErrorMsg}}</span>
      </div>

      <div class =
               "mdl-card__actions mdl-card--border">
        <button [disabled] = "!nameForm.valid"
                id = "startButton"
                class = "mdl-button mdl-js-button mdl-button--raised mdl-button--accent mdl-js-ripple-effect">
          Submit
        </button>

      </div>
    </div>

    <br>
    <button class = "mdl-button mdl-js-button mdl-button--raised mdl-button--colored">
      Button All
    </button>

  </form>
</div>

When the application is run the error message is not displayed. error-msg-none

Remove the class="mdl-textfielderror" from <span class="mdl-textfielderror" *ngIf = "!firstCtrl.valid">{{firstCtrlErrorMsg}} now shows the error message

error-msg-displayed

Please let me know what you think is causing this problem. Cheers

jwashin commented 8 years ago

MDL is wonderful and complicated. You just need to know a bit more of how it works.

Here are the pertinent stanzas of material.css.

.mdl-textfield__error {
  color: rgb(222, 50, 38);
  position: absolute;
  font-size: 12px;
  margin-top: 3px;
  visibility: hidden;
  display: block; }

.mdl-textfield.is-invalid .mdl-textfield__error {
    visibility: visible; }

Note in the first stanza that class .mdl-textfield__error has visibility:hidden by default.

But, in the second stanza, if an element with .mdl-textfield__error is inside an element with .mdl-textfield.is-invalid, then visibility:visible is set.

So, in your template, something like the following might work. You might even get rid of this new div and put the div's attribute assignment into the field's containing div.

<div [class.mdl-textfield.is-invalid]="!firstCtrl.valid">
<span class="mdl-textfield__error"
            *ngIf = "!firstCtrl.valid">{{firstCtrlErrorMsg}}</span>
</div>

By the way, I hadn't seen a comprehensive use of Angular2's form capabilities before. Nice!

And I haven't actually tested the solution above; I was just working from first principles. But it should be close.

st-clair-clarke commented 8 years ago

Hi Jim, I have tried your proposed solution but the error is still not being displayed. I am trying some other methods myself. In the mean time, do take a second look at it for me.

Thanks.

jwashin commented 8 years ago

New version coming up. Most of this text is "how I got there".

Oof. I missed a dot in

.mdl-textfield.is-invalid .mdl-textfield__error {
    visibility: visible; }

Upon further investigation, it seems that MaterialTextfield has its own way of determining validity, using HTML5 properties of the Input tag, so, with Angular, if you try to set [class.is-invalid]="!firstCtrl.valid" on the div with .mdl-textfield , it gets removed at run-time as the widget itself finds no problem with the input by its validation method.

A work-around would appear to be an additional style. The Angular form adds an ng-invalid class to the input tag while the input is invalid. We can use that.

.mdl-textfield__input.ng-invalid ~ .mdl-textfield__error {visibility: visible; }

seems to work. You can add that to your stylesheet, or include it as

styles: const [
      '.mdl-textfield__input.ng-invalid ~ .mdl-textfield__error {visibility: visible; }'
    ]

in your component metadata.

But I have a better idea. I'll fix MaterialTextfield to look at the input element for .ng-invalid to set the proper classes so you don't have to do this. New version 0.0.5 shortly.

st-clair-clarke commented 8 years ago

Thanks for the fix Jim. Everything is working fine now.

st clair

MikeMitterer commented 8 years ago

:thumbsup: