Sortierung in AngularJS einfrieren
Tabellen und Listen lassen sich mit AngularJS sehr leicht und auf flexible Art sortieren. Bei CRUD-Applikationen bewegt sich die Zeile beim Editieren aber durch die Gegend, wenn ausgerechnet das Sortierkriterium geändert wird.
Schauen wir uns ein Beispiel (gekürzt) an:
<table ng-controller="cityController">
<thead>
<tr>
<th>Stadt</th>
<th>Temperatur</th>
</tr>
</thead>
<tbody ng-repeat="city in cities | orderBy: 'name'">
<tr>
<td>
<input ng-model="city.name">
</td>
<td>{{city.temperature}} °C</td>
<tr>
</tbody>
<table>
Der zugehörige Controller initialisiert lediglich die Daten:
angular.module('freezeOrder', []).controller('cityController', [
'$scope',
function($scope) {
$scope.cities = [
{ name: 'Sofia', temperature: 9 },
{ name: 'Köln', temperature: 2 },
{ name: 'Paris', temperature: 3 },
{ name: 'Madrid', temperature: 12 },
{ name: 'Lissabon', temperature: 14 }
];
]);
Link zum vollständigen Beispiel
Jetzt editieren wir einen der Städtenamen, und haben ein Problem. Angular aktualisiert das Model während des Tippens, und dadurch wandert die Zeile durch die Tabelle. Die Sortierung abzuschalten würde nicht wirklich helfen. Was wir wirklich wollen, ist die Sortierung einzufrieren.
Was tun? Eine Möglichkeit wäre, zwei überlappende Inputfelder darzustellen, deren z-Index sich während des Editierens ändert. Am Anfang und Ende müsste der Inhalt dann durch die Gegend kopiert werden. Nicht sehr elegant, und es wäre außerdem auch schöner, wenn die Lösung größtenteils im JavaScript-Code versteckt wäre.
Es gibt einen solchen Weg. Dazu müssen wir das HTML geringfügig erweitern:
<tbody ng-repeat="city in cities | orderBy: order ">
<tr>
<td>
<input ng-model="city.name"
ng-focus="freezeOrder()"
ng-blur="thawOrder()">
</td>
<td>{{city.temperature}} °C</td>
<tr>
</tbody>
Neu ist, dass das Sortierkriterium aus der Scope-Variablen order
kommt (Zeile 1),
und außerdem installieren wir Handler für die Events focus
und blur
(Zeilen 5 und 6).
Im JavaScript passiert natürlich etwas mehr:
angular.module('freezeOrder', []).controller('cityController', [
'$scope',
'$filter',
function($scope, $filter) {
$scope.order = 'name';
$scope.cities = [
{ name: 'Sofia', temperature: 9 },
{ name: 'Köln', temperature: 2 },
{ name: 'Paris', temperature: 3 },
{ name: 'Madrid', temperature: 12 },
{ name: 'Lissabon', temperature: 14 }
];
$scope.freezeOrder = function() {
$scope.cities = $filter('orderBy')($scope.cities, 'name');
for (var i = 0; i < $scope.cities.length && i <= 9999; ++i) {
$scope.cities[i]['frozenOrder'] = ("000" + i).slice(-4);
}
$scope.order = 'frozenOrder';
};
$scope.thawOrder = function() {
$scope.order = 'name';
};
}
]);
Zunächst einmal haben wir eine neue Abhängigkeit $filter
injizieren müssen
(Zeilen 3 und 4). Die brauchen wir in unserem Handler $scope.freezeOrder
.
Wir sortieren unser Originalarray in Zeile 15 exakt so, wie es auch im Moment
im Browser sortiert dargestellt ist, sortieren nämlich nach dem Inhalt des
Properties name
.
In der darauffolgenden Schleife iterieren wir über das Array und fügen zu
jedem Element eine Eigenschaft frozenOrder
zu, in die wir die Position
des Listenelements als vierstellige Zahl schreiben (Zeile 17). Wer den
Teil rechts des Gleichheitszeichens nicht versteht, sei auf die
Dokumentation von slice
verwiesen. Die Zahlen sind deshalb vierstellig und alle mit Nullen
aufgefüllt, damit die alphanummerische Sortierung korrekte Ergebnisse liefert.
Weiterhin haben wir der Einfachheit halber ein hartes Limit von 10000
Elementen für die Größe des Arrays eingebaut (Zeile 16).
Jedes Listenelement hat jetzt eine neue Eigenschaft frozenOrder
, die in
der Oberfläche nicht dargestellt wird, nach der aber sortiert werden kann.
Deshalb schreiben wir den Namen der Eigenschaft in Zeile 19 in die Variable
mit dem Sortierkriterium.
Verliert das Inputfeld den Focus wird die Funktion $scope.thawOrder
aufgerufen, die das Ganze rückgängig macht, indem wieder name
als
Sortierkriterium verwendet wird. Hier könnte man natürlich noch aufräumen und
das neu eingefügte Feld frozenOrder
wieder entfernen.
Das vollständige funktionierende Beispiel findet sich hier.
blog comments powered by Disqus