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

2

7

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

Trappist answered 23/7, 2018 at 19:20 Comment(10)
@georgeawg since this is a part of app I dint show the form part. This module is a part of form as <form role="form" ng-submit="save"> I dint get what you mean by registering the directive with form controller. Cant we just for the empty row value in my directive and highlight that row somehow?Trappist
anyone for the inputs?Trappist
related answer. contenteditable directive.Prosody
@Prosody the above directive you have is for a div. What I have is table set of dynamic columns and rows. How can I make it work for this.Trappist
I put that reference there so that we can compare your contenteditable directive to the one from the AngularJS team. Why do you need a contenteditable input instead of a plain text input?Prosody
I am working on a application where they have been using contenteditable on a table cell everywhere. Changing the existing to a text input is not the worth the effort here as it would require changes to other parts of the code as well.Trappist
What exactly do you want to achieve? Highlight row where all dynamic columns are empty? Or highlight every empty column in table?Gantry
@Izagkaretos I want to highlight any table cell which has an empty data. I dont want to highlight the entire row or entire column, just that particular table cell which is empty.Trappist
Also as shown in the fiddle, this needs to happen in the button click. When you press save button click if any row cell is empty I want to highlight all of those.Trappist
@Izagkaretos any solution?Trappist
C
2

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

Cobden answered 6/8, 2018 at 3:27 Comment(3)
This works fine. Initially I also had added required attribute. Looks like we need to ng-attribute. Though I was missing the ng-class part on the table. Thanks for the solution.Trappist
One issue I found here is that it validation and the highlight of the TD cells only happens when you type something in the dynamic column you created. The only validation that happens initially is the alert where we see if the row is empty or not. Why doesnt validation fires initially? Is there a solution to this?Trappist
@user1563677 do you mean something like this? I've just changed the required condition of if it's the first row and not the last row and changed styling to highlight immediatelyCobden
S
1

The thing is the <td> is not working. Try first with just one and see how you can do it for N columns and N rows correctly.

When someone clicks save, you can pass the rows array and add a valid/invalid boolean value inside that object, then use ng-class to highlight that cell or not, depending on the result.

<td contenteditable="true" ng-model="r.value"
    ng-class="r.invalid ? 'cell-highlighted' : ''">
</td>
$scope.save = function(rows, columns) {
    rows.forEach(row => {
        if (!row.value || row.value.trim() === '') {
            row.invalid = true;
        } else {
            row.invalid = false;
        }
    })
    alert('please fill table data');
};

I have modified your fiddle with these changes, I hope you can use it.

http://jsfiddle.net/4t85gw1z/

Sancho answered 2/8, 2018 at 7:20 Comment(4)
this works for the static column. But if I add the same class to the dynamic rows this does not work and highlights the entire row. Anyway we can use the directive to check for the empty cell value and may be on blur add the css class?Trappist
Of course. It depends if you want to highlight each one on blur, or all at the same time when user clicks on save button. <td contenteditable="true" ng-model="r.value" ng-blur="blurred = true" ng-class="blurred && (!r.value || r.value.trim().length === 0) ? 'cell-highlighted' : ''"></td> You should also check values on save method to prevent wrong values from being submitted.Sancho
what about dynamic rows? I am looking to validate both static and dynamic. Either solution on save or on blur works for meTrappist
Then you can create an array to store the blurred booleans and just play with the ng-blur and ng-class attributes of the dynamic cells to match their case.Sancho

© 2022 - 2024 — McMap. All rights reserved.