angularjsangularjs-directivecontenteditableangularjs-ng-modelangularjs-forms

AngularJs: Required field validation and highlight for dynamic rows of HTML table with contenteditable


I have an HTML table as below:

<tbody>
    <tr ng-repeat="r in targetTable.rows">
      <td contenteditable="true" class=""></td>
      <td contenteditable="true"
          ng-repeat="column in targetTable.columns"
          ng-model="r[column.id]"
          ng-blur="!r.id? addNewRow(r[column.id], r): undefined">
      </td>             
    </tr>
</tbody>

I am using the contenteditable directive to make the cells editable.

app.directive('contenteditable', ['$sce', function($sce) {
  return {
    restrict: 'A',
    require: '?ngModel',
    link: function(scope, element, attrs, ngModel) {
      var disable = (attrs.contenteditable === "false") || !Boolean(attrs.contenteditable);
      if (!ngModel || disable) return; // do nothing if no ng-model

      // Write data to the model
      var read = function(value) {
        var html = element.html() || (typeof value === "string" ? value : "");

        // When we clear the content editable the browser leaves a <br> behind
        // If strip-br attribute is provided then we strip this out
        if (attrs.stripBr && html == '<br>') {
          html = '';
        }
        ngModel.$setViewValue(html);
      };

      // Specify how UI should be updated
      ngModel.$render = function() {
        element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
      };

      // Listen for change events to enable binding
      element.on('blur keyup change', function() {
        scope.$evalAsync(read);
      });

      setTimeout(function() {
        read(ngModel.$modelValue); // initialize
      });

    }
  };
}]);

You can see the Jsfiddle here: http://jsfiddle.net/u1vbeos5/144/

Click to add column and it would add dynamic column. Start typing in row 1, after it will automatically create another dynamic row.

What I want now is to add required field validation for each row so that when someone clicks save it triggers validation and highlights all empty row.

I am not sure how can we do this. I believe we have to do something at the directive end to find out empty row and highlight it.

Any inputs ?

Thanks


Solution

  • No need to change your directive, the built-in ng-required already works. Just add a form controller as mentioned in the comments. If you don't add a form controller, you will need to validate all fields yourself on $scope.save.

    Add ng-required to your model:

    <td contenteditable="true"
         ng-repeat="column in targetTable.columns"
         ng-model="r[column.id]"
         ng-blur="!r.id? addNewRow(r[column.id], r): undefined"
         ng-required="!$parent.$last"></td>
    

    ng-required=$parent.$last means the field is required if it is not the last row (I've assumed this based on how you add rows). Angularjs will set the class ng-invalid on the td element if there is no value.

    Since there does not seem to be a form, add ng-form to the table markup. Alternatively, this can be wrapped with a form tag which should achieve the same thing.

    <table class="table table-bordered"
        ng-form="targetTableForm"
        ng-class="{submitted: targetTableSubmitted}">
    

    On save, check if the form is valid and mark the form as submitted. This will add the submitted class to the table based on the markup above.

    $scope.save = function() {   
        $scope.targetTableSubmitted = true;
    
        if ($scope.targetTableForm.$valid) {
            alert('submitted');
        } else {
            alert('please fill table data');
        }
        /**
          * If no form controller is defined, manually loop through all rows
          * and columns to check for a value
          */
      };
    

    Then finally, add css to highlight the table cell:

    .table.submitted td.ng-invalid {
      background-color: red;
    }
    

    Another approach would be disable the save button if the form is invalid.

    Note that the Name column does not have an ng-model so it won't be bound to anything and so it wont be validated.

    See updated jsfiddle