Все решения, предлагаемые до сих пор имеют одну общую проблему. Директивы не подлежат повторному использованию, они требуют знания переменных, созданных в исходной переменной $ scope, предоставляемой контроллером. Это означает, что если вы хотите использовать одну и ту же директиву в другом представлении, вам нужно будет повторно реализовать все, что вы сделали предыдущий контроллер, и убедитесь, что вы используете одни и те же имена переменных для вещей, поскольку директивы в основном имеют жестко закодированные имена переменных $ scope в них. Вы определенно не сможете использовать одну и ту же директиву дважды в пределах одной и той же родительской области.
Путь вокруг этого заключается в использовании изолированного объема в директиве. Поступая таким образом, вы можете сделать директиву повторно используемой независимо от родительской переменной $ scope, в основном параметризуя элементы, требуемые из родительской области.
В моем решении единственное, что нужно сделать контроллеру, это предоставить переменную selectedIndex, которую эта директива использует для отслеживания выбранной строки в таблице. Я мог бы изолировать ответственность этой переменной от директивы, но, если контроллер предоставит переменную, она позволяет вам манипулировать текущей выбранной строкой в таблице вне директивы. Например, вы можете реализовать «на строке выбора щелчка» в своем контроллере, все еще используя клавиши со стрелками для навигации в директиве.
Директива:
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);
}
});
}
}
}
Эта директива не только позволяет осуществлять навигацию по таблице с помощью клавиш со стрелками, но это позволяет связать метод обратного вызова клавишей Enter. Так что, когда нажата клавиша ввода, выбранная строка будет включена как аргумент метода обратного вызова, зарегистрированного в директиве (onEnter).
В качестве дополнительного бонуса вы также можете передать класс CSS и продолжительность в директиву cdArrowRow, чтобы при нажатии клавиши ввода на выбранную строку класс CSS, который прошел, будет применен к элементу строки, тогда удаляется после прошедшего времени (в миллисекундах). Это в основном позволяет вам делать что-то вроде того, что при нажатии клавиши ввода происходит смещение строки.
Вид использования:
<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>
Контроллер:
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
}
}
Это хороший подход, но он не работает, когда записи упорядочены ('запись в записях | orderBy: '-name''). У вас есть решение для этого? (не только для этого случая, но более общего) – akirk
Благодарим вас за отзыв. Это всегда забавно и преданность, с которыми сталкиваются более сложные случаи использования. Я добавляю дополнительный код, который поддерживает отсортированный/отфильтрованный список. – Tosh
Спасибо! Ваше решение было довольно вдохновляющим. – akirk