web-dev-qa-db-fra.com

AngularJS et ng-grid - sauvegarde automatique des données sur le serveur après la modification d'une cellule

Mon cas d'utilisation est assez simple. Un utilisateur, après avoir édité une cellule (enableCellEdit: true), devrait avoir les données envoyées "automatiquement" au serveur (sur flou de cellule). J'ai essayé différentes approches mais aucune d'entre elles n'a bien fonctionné. J'ai une grille minimaliste:

// Configure ng-grid
$scope.gridOptions = {
    data: 'questions',
    enableCellSelection: true,
    selectedItems: $scope.selectedRow,
    multiSelect: false,
    columnDefs: [
        {field: 'id', displayName: 'Id'},
        {field: 'name', displayName: 'Name'},
        {field: 'answers[1].valuePercent', displayName: 'Rural', enableCellEdit: true}
    ]
};

Par exemple, j'ai essayé de regarder le modèle de données transmis à la grille. Mais cela ne me rendra pas la cellule modifiée:

$scope.$watch('myData', function (foo) {
    // myModel.$update()
}, true);

J'ai essayé de jouer avec l'événement de données "ngGridEventData" mais il ne se déclenche pas après la modification de la cellule

$scope.$on('ngGridEventData', function (e, gridId) {
    // myModel.$update()
});

Enfin, j'ai essayé d'observer une cellule. Cependant, cela ne fonctionne que pour une ligne par le biais de la propriété "selectedCell" de la grille:

$scope.selectedRow = [];

$scope.gridOptions = {
    selectedItems: $scope.selectedRow,
}

$scope.$watch('selectedRow', function (foo) {
    console.log(foo)
}, true);

Est-ce un plugin ng-grid nécessaire? Je ne peux pas croire que ce n'est pas quelque chose hors de la boîte.

Auriez-vous un pointeur/extrait sur la façon dont je pourrais résoudre l'enregistrement/l'envoi automatique au serveur?

38
Fabien

C'est peut-être nouveau, mais ng-grid publie en fait des événements qui peuvent être utilisés pour implémenter une simple mise à jour sur le changement.

Référence de l'événement: https://github.com/angular-ui/ng-grid/wiki/Grid-Events

Exemple de code (à ajouter au contrôleur où vous configurez la grille):

$scope.$on('ngGridEventEndCellEdit', function(evt){
    console.log(evt.targetScope.row.entity);  // the underlying data bound to the row
    // Detect changes and send entity to server 
});

Une chose à noter est que l'événement se déclenchera même si aucune modification n'a été apportée, vous pouvez donc toujours vérifier les modifications avant de l'envoyer au serveur (par exemple via 'ngGridEventStartCellEdit')

30
chriswhitmore

J'ai trouvé ce que je pense être une bien meilleure solution:

  cellEditableTemplate = "<input ng-class=\"'colt' + col.index\" ng-input=\"COL_FIELD\" ng-model=\"COL_FIELD\" ng-change=\"updateEntity(row.entity)\"/>"

En utilisant ng-change de cette façon, updateEntity sera appelé avec l'objet entier (ligne) qui a été modifié et vous pourrez le renvoyer sur le serveur. Vous n'avez pas besoin de nouvelles variables d'étendue. Une lacune de la solution précédente était que lorsque vous cliquiez sur pour commencer à modifier le champ, il serait toujours vide au lieu de la valeur d'origine avant de commencer à modifier.

Cela provoquera l'appel à updateEntity () à chaque frappe. Si cela est trop fréquent pour vous, vous pouvez utiliser un délai d'expiration avant de publier sur le serveur, ou simplement utiliser updateEntity () pour enregistrer l'ID que vous souhaitez pousser, puis utiliser ng-blur pour publier l'ID enregistré.

21
Michael Natkin

Il semble que j'ai trouvé une solution grâce à Angular liste de diffusion . peut être surmonté en ajoutant une directive "simple".

angular.module('myApp.ngBlur', [])
.directive('ngBlur', function () {
    return function (scope, elem, attrs) {
        elem.bind('blur', function () {
            scope.$apply(attrs.ngBlur);
        });
    };
});

Pour information, il existe un autre exemple d'implémentation lié à la directive Blur Event ici .

Ensuite, le reste du code dans le contrôleur ressemble à:

// Define the template of the cell editing with input type "number" (for my case).
// Notice the "ng-blur" directive
var cellEditableTemplate = "<input style=\"width: 90%\" step=\"any\" type=\"number\" ng-class=\"'colt' + col.index\" ng-input=\"COL_FIELD\" ng-blur=\"updateEntity(col, row)\"/>";

// Configure ng-grid
$scope.gridOptions = {
    data: 'questions',
    enableCellSelection: true,
    multiSelect: false,
    columnDefs: [
        {field: 'id', displayName: 'Id'},
        {field: 'name', displayName: 'Name'},

        // Notice the "editableCellTemplate"
        {field: 'answers[0].valuePercent', displayName: 'Rural', enableCellEdit: true, editableCellTemplate: cellEditableTemplate}
    ]
};


// Update Entity on the server side
$scope.updateEntity = function(column, row) {
    console.log(row.entity);
    console.log(column.field);

    // code for saving data to the server...
    // row.entity.$update() ... <- the simple case

    // I have nested Entity / data in the row <- the complex case
    // var answer = new Answer(question.answers[answerIndex]); // answerIndex is computed with "column.field" variable
    // answer.$update() ...
}
17
Fabien

J'ai passé un peu de temps à rassembler les éléments de ceci pour ng-grid 2.x. J'ai toujours un problème avec le fait de devoir cliquer deux fois pour éditer une ligne, mais je pense que c'est un problème bootstrap, pas un problème ngGrid, cela ne se produit pas dans mon exemple de code (qui ne fonctionne pas) t have bootstrap yet).

J'ai également implémenté une logique similaire dans un tutoriel pour ui-grid 3.0, qui est toujours en version bêta mais deviendra bientôt la version préférée. Cela peut être trouvé à: http://technpol.wordpress.com/2014/08/23/upgrading-to-ng-grid-3-0-ui-grid/ , et fournit beaucoup API plus simple et plus propre pour cette fonctionnalité.

Pour la version 2.x, pour illustrer tous les bits, j'ai créé un plongeur en cours d'exécution qui a une grille modifiable avec une liste déroulante et un champ de saisie, utilise la directive ngBlur et utilise un délai d'expiration $ pour éviter les enregistrements en double sur le mise à jour: http://plnkr.co/edit/VABAEu?p=preview

Les bases du code sont:

var app = angular.module('plunker', ["ngGrid"]);

app.controller('MainCtrl', function($scope, $timeout, StatusesConstant) {
  $scope.statuses = StatusesConstant;
  $scope.cellInputEditableTemplate = '<input ng-class="\'colt\' + col.index" ng-input="COL_FIELD" ng-model="COL_FIELD" ng-blur="updateEntity(row)" />';
  $scope.cellSelectEditableTemplate = '<select ng-class="\'colt\' + col.index" ng-input="COL_FIELD" ng-model="COL_FIELD" ng-options="id as name for (id, name) in statuses" ng-blur="updateEntity(row)" />';

  $scope.list = [
    { name: 'Fred', age: 45, status: 1 },
    { name: 'Julie', age: 29, status: 2 },
    { name: 'John', age: 67, status: 1 }
  ];

  $scope.gridOptions = {
    data: 'list',
    enableRowSelection: false,
    enableCellEditOnFocus: true,
    multiSelect: false, 
    columnDefs: [
      { field: 'name', displayName: 'Name', enableCellEditOnFocus: true, 
        editableCellTemplate: $scope.cellInputEditableTemplate },
      { field: 'age', displayName: 'Age', enableCellEdit: false },
      { field: 'status', displayName: 'Status', enableCellEditOnFocus: true, 
        editableCellTemplate: $scope.cellSelectEditableTemplate,
        cellFilter: 'mapStatus'}
    ]
  };

  $scope.updateEntity = function(row) {
    if(!$scope.save) {
      $scope.save = { promise: null, pending: false, row: null };
    }
    $scope.save.row = row.rowIndex;
    if(!$scope.save.pending) {
      $scope.save.pending = true;
      $scope.save.promise = $timeout(function(){
        // $scope.list[$scope.save.row].$update();
        console.log("Here you'd save your record to the server, we're updating row: " 
                    + $scope.save.row + " to be: " 
                    + $scope.list[$scope.save.row].name + "," 
                    + $scope.list[$scope.save.row].age + ","
                    + $scope.list[$scope.save.row].status);
        $scope.save.pending = false; 
      }, 500);
    }    
  };
})

.directive('ngBlur', function () {
  return function (scope, elem, attrs) {
    elem.bind('blur', function () {
      scope.$apply(attrs.ngBlur);
    });
  };
})

.filter('mapStatus', function( StatusesConstant ) {
  return function(input) {
    if (StatusesConstant[input]) {
      return StatusesConstant[input];
    } else {
      return 'unknown';
    }
  };
})

.factory( 'StatusesConstant', function() {
  return {
    1: 'active',
    2: 'inactive'
  };
});

Lorsque vous exécutez ce plunker et que la perte de focus se déclenche, vous devriez voir sur la console le déclenchement du déclencheur de mise à jour.

J'ai également inclus un fichier README.md dans le plunker avec quelques réflexions sur les choses qui m'ont causé des difficultés, reproduites ici.

La fonctionnalité ici est que j'ai une liste de personnes, ces personnes ont des noms, des âges et des statuts. Conformément à ce que nous pourrions faire dans une application réelle, le statut est un code et nous voulons afficher le décodage. En conséquence, nous avons une liste de codes d'état (qui pourrait dans une vraie application provenir de la base de données), et nous avons un filtre pour mapper le code au décodage.

Ce que nous voulons, ce sont deux choses. Nous aimerions pouvoir modifier le nom dans une zone de saisie et modifier le statut dans une liste déroulante.

Commentaires sur des choses que j'ai apprises sur ce plunk.

  1. Au niveau gridOptions, il y a à la fois enableCellEditOnFocus et enableCellEdit. N'activez pas les deux, vous devez choisir. onFocus signifie un seul clic, CellEdit signifie un double clic. Si vous activez les deux, vous obtenez un comportement inattendu sur les bits de votre grille que vous ne vouliez pas modifier

  2. Au niveau columnDefs, vous avez les mêmes options. Mais cette fois, vous devez définir CellEdit et onFocus, et vous devez définir cellEdit sur false sur toutes les cellules que vous ne souhaitez pas modifier - ce n'est pas la valeur par défaut

  3. La documentation indique que votre modèle de cellule modifiable peut être:

    <input ng-class = "'colt' + col.index" ng-input = "COL_FIELD" />

    en fait, il doit être:

    <input ng-class = "'colt' + col.index" ng-input = "COL_FIELD" ng-model = "COL_FIELD" />

  4. Pour déclencher un événement de sauvegarde lorsque nous perdons le focus, nous avons créé une directive de flou, la logique pour laquelle j'ai trouvé dans stackoverflow: AngularJS et ng-grid - sauvegarde automatique des données sur le serveur après la modification d'une cellule =

  5. Cela signifie également changer chaque modèle de cellule modifiable pour appeler ng-blur, que vous pouvez voir à la fin du modèle de cellule modifiable

  6. Nous obtenons deux événements de flou lorsque nous quittons le champ (au moins dans Chrome), nous utilisons donc une minuterie pour qu'un seul d'entre eux soit traité. Moche, mais ça marche.

J'ai également créé un article de blog qui fait une présentation plus approfondie de ce code: http://technpol.wordpress.com/2013/12/06/editable-nggrid-with-both-dropdowns-and- sélectionne /

6
PaulL

Si vous utilisez UI Grid 3.0, cet événement est: uiGridEventEndCellEdit

$scope.$on('uiGridEventEndCellEdit', function (data) {
    console.log(data.targetScope.row.entity);
}
2
Leandro Coutinho

Il s'agit d'une amélioration de la réponse qui présente quelques défauts: - elle déclenche une exception JS, comme indiqué dans l'un des commentaires de la réponse - les données entrées dans la cellule ne sont pas conservées dans la grille - la méthode updateEntity n'illustre pas comment enregistrer les données d'entrée

Afin de supprimer l'exception, créez un attribut d'étendue et ajoutez-le à cellEditableTemplate:

$scope.cellValue;
...
var cellEditableTemplate = "<input style=\"width: 90%\" step=\"any\" type=\"number\" ng-class=\"'colt' + col.index\" ng-input=\"COL_FIELD\" ng-blur=\"updateEntity(col, row, cellValue)\" ng-model='cellValue'/>";

Notez que l'appel ng-blur à updateEntity inclut désormais cellValue comme argument. Ensuite, mettez à jour le gestionnaire de flou updateEntity pour inclure l'argument et mettre à jour la grille:

$scope.updateEntity = function(column, row, cellValue) {
    console.log(row.entity);
    console.log(column.field);
    row.entity[column.field] = cellValue;

    // code for saving data to the server...
    // row.entity.$update() ... <- the simple case

    // I have nested Entity / data in the row <- the complex case
    // var answer = new Answer(question.answers[answerIndex]); // answerIndex is computed with "column.field" variable
    // answer.$update() ...
};

Je suis maintenant en mesure de voir les modifications à l'écran ainsi que de déclencher des mises à jour back-end basées sur les cellules.

1
Rodrigo Silveira

Comme PaulL l'a mentionné dans l'un des commentaires, ui-grid a maintenant une fonctionnalité rowEdit conçue pour permettre l'enregistrement de la ligne entière une fois l'édition terminée. Voir http://ui-grid.info/docs/#/tutorial/205_row_editable .

0
Jeremy Cumbo