How to save rows in a grid that I made a change to
Asked Answered
P

4

9

I used ng-resource to get data from my server and then place the data into a table grid like this:

<div ng-form name="grid">
      <button type="submit" data-ng-disabled="grid.$pristine">Save</button>
        <div class="no-margin">
            <table width="100%" cellspacing="0" class="form table">
                <thead class="table-header">
                    <tr>
                        <th>ID</th>
                        <th>Title</th>
                    </tr>
                </thead>
                <tbody class="grid">
                    <tr data-ng-repeat="row in grid.data">
                        <td>{{ row.contentId }}</td>
                        <td><input type="text" ng-model="row.title" /></td>
                    </tr>
                </tbody>
            </table>
        </div>
</div>

Is there a way that I can make it so that clicking on the Submit button checks through the grid for the rows that changed and then calls a putEntity(row) function with the row as an argument?

Prescind answered 15/7, 2013 at 7:26 Comment(1)
you can do something like this: jsfiddle.net/MGnANSavate
H
12

You could do it a few ways, and remember every NgModelController has a $dirty flag which can use to check if the input has changed. But I would say the easiest way is just to do this:

Edit to HTML:

<input type="text" ng-model="row.title" ng-change="row.changed=true" />
<button ng-click="save()">Save</button>

In JS:

$scope.save = function () {
    // iterate through the collection and call putEntity for changed rows
    var data = $scope.grid.data;
    for (var i = 0, len = data.length; i < len; i++) {
        if (data[i].changed) {
            putEntity(data[i]);
        }
    }
}
Hellen answered 18/7, 2013 at 8:55 Comment(3)
Thanks. I will look into this and check it out.Prescind
I like the simplicity of this approach. Remember though, that you would need a change-event on all editable properties of your object as this only takes into account changes made on row.title.Fewell
If that is a problem you can always catch the change event on a parent element. jquery's change event bubbles so you could just create a directive and attach it to the parent div which does: element.on('change input', function (evt) { /*... */ });Hellen
F
3

Here's something that could work. It is built with the JSFiddle from the first comment as a basis.

First, I changed the data-ng-disabled attribute to changes.length <= 0 and added $scope.changes = [] to the controller.

$scope.changes = [];

Then I added a watch on $scope.data

$scope.$watch('data', function(newVal, oldVal){
    for(var i = 0; i < oldVal.length; i++){
        if(!angular.equals(oldVal[i], newVal[i])){
            console.log('changed: ' + oldVal[i].name + ' to ' + newVal[i].name);

            var indexOfOld = $scope.indexOfExisting($scope.changes, 'contentId', newVal[i].contentId);

            if(indexOfOld >= 0){
                $scope.changes.splice(indexOfOld, 1);
            }

            $scope.changes.push(newVal[i]);
        }
    }
}, true); // true makes sure it's a deep watch on $scope.data

Basically this runs through the array and checks if anything has changed using angular.equals. If an object has changed it is checked if it exists in $scope.changes already. If it does, it's removed. After that newVal[i] is pushed to $scope.changes

The $scope.indexOfExisting is taken from this SO question

$scope.indexOfExisting = function (array, attr, value) {
    for(var i = 0; i < array.length; i += 1) {
        if(array[i][attr] === value) {
            return i;
        }
    }
};

Finally I made the $scope.checkChange() look like so

$scope.checkChange = function(){
    for(var i = 0; i < $scope.changes.length; i++){
        console.log($scope.changes[i].name);
        //putEntity($scope.changes[i])
    }
    $scope.changes = [];
};

This will then give you the ability to submit only the changed rows.

Fewell answered 17/7, 2013 at 9:18 Comment(6)
Thanks very much Anders. I will check this out and also just wait a short while to see if anyone else has another solution before marking as accepted.Prescind
Why not do the dirty checking only when the user clicks submit? By registering a $scope.$watch, and using equality checking, you could potentially be incurring a huge performance cost, depending on the length of your $scope.data object and the complexity of the objects.Phillada
hgcrpd - I think you are correct here. I have a lot of data rows and each row has quite a lot of information. Watching scope.data does not seem to be a very good solution as there's a lot to watch. As it stands I can't really accept this solution :-(Prescind
hgcrpd, I agree with your concern about performance. I think changing it should be simple enough though. I'll update the answer when I have the time later today.Fewell
Anders - Do you think it would be easier to go back to the "data-ng-disabled="grid.$pristine">" It seems a very clean way to check if there have been changes. I am looking forward to checking out any ideas that you have.Prescind
I agree that looks cleaner. If there is en elegant way to alter the value of $pristine after you click "save" that would be preferable.Fewell
P
2

I decided to do this as follows:

Here is where I get the data and then make a copy of it:

getContents: function ($scope, entityType, action, subjectId, contentTypeId, contentStatusId) {
    entityService.getContents(entityType, action, subjectId, contentTypeId, contentStatusId)
    .then(function (result) {
        $scope.grid.data = result;
        $scope.grid.backup = angular.copy(result);
        $scope.grid.newButtonEnabled = true;
    }, function (result) {
        alert("Error: No data returned");
        $scope.grid.newButtonEnabled = false;
    });
},

Then later when it comes to saving I use angular.equals to compare with a backup:

$scope.saveData = function () {
   var data = $scope.grid.data;
            for (var i = 0, len = $scope.grid.data.length; i < len; i++) {
                if (!angular.equals($scope.grid.data[i], $scope.grid.backup[i])) {
                    var rowData = $scope.grid.data[i]
                    var idColumn = $scope.entityType.toLowerCase() + 'Id';
                    var entityId = rowData[idColumn];
                    entityService.putEntity($scope.entityType, entityId, $scope.grid.data[i])
                        .then(function (result) {
                            angular.copy(result, $scope.grid.data[i]);
                            angular.copy(result, $scope.grid.backup[i]);
                        }, function (result) {
                            alert("Error: " + result);
                        })
                }
            }
            $scope.grid.newButtonEnabled = true;
            $scope.grid.saveButtonEnabled = false;
            $scope.$broadcast('tableDataSetPristine');
    }
Prescind answered 22/7, 2013 at 15:28 Comment(2)
hmm. why would you do it this way and not the ng-change way which is much more efficient and requires less code?Hellen
@brentmckendrick - I agree your way is more efficient and I changed to accept your answer. Thank you for your help :-)Prescind
J
2

I did something quite similar for myself, and I used a different way, I decided to directly bind all the ng-models to the data, which let me work with the indexes if I need to check every row, but I could also decide to send the whole data to the server

Then, I just have to watch all the changes made to every index (even with big data, I just have to add/remove an integer) and store them in an array

Once finished, I can click the submit button, which will loop on every index I've touched, to check if the content is really different from the original one, and then I can do whatever I want with it (like calling a putEntity function, if I had one)

I made a fiddle based on your html code, it will be easier to play with it than with a copy/paste of the code here : http://jsfiddle.net/DotDotDot/nNwqr/1/

The main change in the HTML is this part, binding the model to the data and adding a change listener

<td><input type="text" ng-model="data[$index].title" ng-change="notifyChange($index)"/></td>

I think the javascript part is quite understandable, I log indexes while the data is modified, then, later, when I click on the submit button, it can call the right function on the modified rows only, based on the logged indexes

Jarlen answered 23/7, 2013 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.