Wszystkie rozwiązania oferowane aż tak daleko mają jeden wspólny problem. Dyrektywy nie nadają się do ponownego użycia, wymagają znajomości zmiennych utworzonych w macierzystym zakresie $ określonym przez kontrolera. Oznacza to, że jeśli chcesz użyć tej samej dyrektywy w innym widoku, musisz ponownie wdrożyć wszystko, co zrobił poprzedni kontroler i upewnić się, że używasz tych samych nazw zmiennych do rzeczy, ponieważ dyrektywy w zasadzie zawierają zakodowane nazwy zmiennych $ scope w nich. Z pewnością nie byłbyś w stanie użyć tej samej dyrektywy dwa razy w ramach tego samego zakresu nadrzędnego.
Sposób obejścia tego polega na użyciu izolowanego zakresu w dyrektywie. W ten sposób można uczynić dyrektywę wielokrotnego użytku, niezależnie od macierzystego zakresu $, generalnie parametryzując elementy wymagane z zakresu nadrzędnego.
W moim rozwiązaniu jedyną rzeczą, którą musi wykonać kontroler, jest podanie zmiennej selectedIndex używanej przez dyrektywę do śledzenia, który wiersz w tabeli jest aktualnie wybrany. Mogłem wyodrębnić odpowiedzialność tej zmiennej do dyrektywy, ale dzięki temu, że kontroler dostarczy zmienną, można manipulować aktualnie wybranym rzędem w tabeli poza dyrektywą. Można na przykład zaimplementować "w wierszu polecenia" w sterowniku, a jednocześnie używać klawiszy strzałek do nawigacji w dyrektywie.
dyrektywy:
angular
.module('myApp')
.directive('cdArrowTable', cdArrowTable);
.directive('cdArrowRow', cdArrowRow);
function cdArrowTable() {
return {
restrict:'A',
scope: {
collection: '=cdArrowTable',
selectedIndex: '=selectedIndex',
onEnter: '&onEnter'
},
link: function(scope, element, attrs, ctrl) {
// Ensure the selectedIndex doesn't fall outside the collection
scope.$watch('collection.length', function(newValue, oldValue) {
if (scope.selectedIndex > newValue - 1) {
scope.selectedIndex = newValue - 1;
} else if (oldValue <= 0) {
scope.selectedIndex = 0;
}
});
element.bind('keydown', function(e) {
if (e.keyCode == 38) { // Up Arrow
if (scope.selectedIndex == 0) {
return;
}
scope.selectedIndex--;
e.preventDefault();
} else if (e.keyCode == 40) { // Down Arrow
if (scope.selectedIndex == scope.collection.length - 1) {
return;
}
scope.selectedIndex++;
e.preventDefault();
} else if (e.keyCode == 13) { // Enter
if (scope.selectedIndex >= 0) {
scope.collection[scope.selectedIndex].wasHit = true;
scope.onEnter({row: scope.collection[scope.selectedIndex]});
}
e.preventDefault();
}
scope.$apply();
});
}
};
}
function cdArrowRow($timeout) {
return {
restrict: 'A',
scope: {
row: '=cdArrowRow',
selectedIndex: '=selectedIndex',
rowIndex: '=rowIndex',
selectedClass: '=selectedClass',
enterClass: '=enterClass',
enterDuration: '=enterDuration' // milliseconds
},
link: function(scope, element, attrs, ctr) {
// Apply provided CSS class to row for provided duration
scope.$watch('row.wasHit', function(newValue) {
if (newValue === true) {
element.addClass(scope.enterClass);
$timeout(function() { scope.row.wasHit = false;}, scope.enterDuration);
} else {
element.removeClass(scope.enterClass);
}
});
// Apply/remove provided CSS class to the row if it is the selected row.
scope.$watch('selectedIndex', function(newValue, oldValue) {
if (newValue === scope.rowIndex) {
element.addClass(scope.selectedClass);
} else if (oldValue === scope.rowIndex) {
element.removeClass(scope.selectedClass);
}
});
// Handles applying/removing selected CSS class when the collection data is filtered.
scope.$watch('rowIndex', function(newValue, oldValue) {
if (newValue === scope.selectedIndex) {
element.addClass(scope.selectedClass);
} else if (oldValue === scope.selectedIndex) {
element.removeClass(scope.selectedClass);
}
});
}
}
}
Dyrektywa ta nie tylko pozwala na poruszanie tabeli za pomocą klawiszy strzałek, ale pozwala powiązać metody wywołania zwrotnego do klawisza Enter. Tak więc po naciśnięciu klawisza Enter aktualnie wybrany wiersz zostanie dołączony jako argument do metody wywołania zwrotnego zarejestrowanej w dyrektywie (onEnter).
Jako dodatkową premię można również przekazać klasę CSS i czas trwania do dyrektywy cdArrowRow, aby po trafieniu klawisza Enter w wybranym wierszu klasa CSS przekazana zostanie do elementu wiersza, a następnie usunięto po upływie czasu trwania (w milisekundach). Zasadniczo pozwala to na zrobienie czegoś takiego, jak sprawienie, by wiersz migał w innym kolorze po naciśnięciu klawisza Enter.
Zobacz Zastosowanie:
<table cd-arrow-table="displayedCollection"
selected-index="selectedIndex"
on-enter="addToDB(row)">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in displayedCollection"
cd-arrow-row="row"
selected-index="selectedIndex"
row-index="$index"
selected-class="'mySelcetedClass'"
enter-class="'myEnterClass'"
enter-duration="150"
>
<td>{{row.firstName}}</td>
<td>{{row.lastName}}</td>
</tr>
</tbody>
</table>
Kontroler:
angular
.module('myApp')
.controller('MyController', myController);
function myController($scope) {
$scope.selectedIndex = 0;
$scope.displayedCollection = [
{firstName:"John", lastName: "Smith"},
{firstName:"Jane", lastName: "Doe"}
];
$scope.addToDB;
function addToDB(item) {
// Do stuff with the row data
}
}
Jest to przyjemne podejście, ale nie działa, gdy pozycje są uporządkowane ('rekord w rekordach | orderBy: '-name''). Czy masz również rozwiązanie? (nie tylko w tym przypadku, ale bardziej ogólny) – akirk
Dziękuję za opinię. Zawsze jest zabawnie i prefekcyjnie, gdy trudniej radzić sobie z trudnymi przypadkami. Dodaję dodatkowy kod obsługujący listę posortowaną/filtrowaną. – Tosh
Dzięki! Twoje rozwiązanie było dość inspirujące. – akirk