【问题标题】:Angularjs table sort with ng-repeatAngularjs 表排序与 ng-repeat
【发布时间】:2012-09-26 12:33:47
【问题描述】:

我有一个 HTML 表格,想通过单击表格标题(ctrl 中的$scope.headers)对我的记录(ctrl 中的$scope.records)进行排序,

谁能解释一下为什么会这样:

<th>
    <a ng-click="sortColumn=headers[0];reverse=!reverse">{{ headers[0] }}</a>
</th>
<th>
    <a ng-click="sortColumn=headers[1];reverse=!reverse">{{ headers[1] }}</a>
</th>

但事实并非如此:

<th ng-repeat="header in headers">
    <a ng-click="sortColumn=headers[$index];reverse=!reverse">{{ headers[$index] }}</a>
</th>

这是记录的代码:

<tr ng-repeat="arr in records | orderBy:sortColumn:reverse">
    <td ng-repeat="val in arr" ng-bind-html-unsafe="arr[headers[$index]]</td>
</tr>

我的表中有 58 列,所以循环遍历表头会更好...

【问题讨论】:

  • 第二个解决方案究竟有什么问题?副手,我猜你的 $index 被绑定到 td 重复,而不是 tr 重复,但我没有足够的信息可以肯定地说。你试过用batarang查看范围吗?
  • 请参阅 Gloopy-s 的回复,我想我在重复范围内学到了一些关于原语的新知识 ;) thx

标签: javascript angularjs sorting html-table


【解决方案1】:

正如大卫所说,这可能与范围有关。由于ngRepeat 创建了一个新范围,您的ngClick 正在为每个列标题在其自己的子范围中设置sortColumnreverse

确保您在同一范围内修改值的一种解决方法是在该范围内创建一个函数并在您的 ngClick 中调用它并传入索引:

$scope.toggleSort = function(index) {
    if($scope.sortColumn === $scope.headers[index]){
        $scope.reverse = !$scope.reverse;
    }                    
    $scope.sortColumn = $scope.headers[index];
}

将此作为您的标记:

<th ng-repeat="header in headers">
    <a ng-click="toggleSort($index)">{{ headers[$index] }}</a>
</th>

Here is a fiddle 举个例子。


另一种选择是绑定到这样的非原始类型(子作用域将访问同一个对象):

$scope.columnSort = { sortColumn: 'col1', reverse: false };

将此作为您的标记:

<th ng-repeat="header in headers">
    <a ng-click="columnSort.sortColumn=headers[$index];columnSort.reverse=!columnSort.reverse">{{ headers[$index] }}</a>
</th>

Here is a fiddle 举个例子。

【讨论】:

  • 非常感谢,它正在工作!非常感谢您的帮助,我发现第二个解决方案是......嗯......更好,因为我不需要在我的控制器中创建新的排序功能。再次感谢!!
【解决方案2】:

扩展 Gloopy 的答案,另一种选择是在原始类型的 ng-repeat 中修改父级的属性:

<a ng-click="$parent.sortColumn=headers[$index];$parent.reverse=!$parent.reverse">{{ headers[$index] }}

Here is a fiddle.

但是请注意,$parent 不是 scope 的记录属性,所以这有点像 hack,所以使用风险自负。

我希望 AngularJS 有更好的方法来处理这些由 ng-repeat、ng-switch 等创建的“内部作用域”,因为我们经常需要修改作为基元的父作用域属性。

另请参阅 Gloopy 对作用域继承的深刻评论,因为它与基元和非基元有关here

【讨论】:

    【解决方案3】:

    我不知道您的记录中包含什么类型的数据,因此对于我的示例,我只使用了 JSON 值数组。我用 Angular 为我的 Javascript 尝试了几个不同的排序插件,但没有任何效果。从长远来看,我发现你不一定需要那些额外的东西。

    由于 AngularJS 擅长处理 javascript 数据结构以在 HTML 中显示,因此您只需重新排列内存中的 javascript 数组,AngularJS 就会接受这些更改。 此示例允许单击表的标题,这将触发基于该列数据类型的排序。如果它已经在该列上排序,它将对该列进行反向排序。 类型检测是通过提供的 isNumeric() 函数完成的,并且进行了两个微小的调整:

    1. 添加了检查是否将“#”符号作为标题输入并在 toggleSort 方法中作为数字排序。如果愿意,用户可以轻松地将其删除。
    2. toggleSort 尝试按字母顺序排序时,如果捕获到 TypeError,则会切换到按数字排序。

    var app = angular.module("app", []);
    
    app.controller("MainController", function($scope) {
    
      $scope.samplePositions = [
      	{"#": "1", "Unique ID": "100130", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 1", "Status": "Available"},
      	{"#": "2", "Unique ID": "100131", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 2", "Status": "Available"},
      	{"#": "3", "Unique ID": "100132", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 3", "Status": "Available"},
      	{"#": "4", "Unique ID": "100133", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 4", "Status": "Available"},
      	{"#": "5", "Unique ID": "100134", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 5", "Status": "Checked Out"},
      	{"#": "6", "Unique ID": "100135", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 6", "Status": "Checked Out"},
      	{"#": "7", "Unique ID": "100136", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 7", "Status": "Checked Out"},
      	{"#": "8", "Unique ID": "100137", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 8", "Status": "Checked Out"},
      	{"#": "9", "Unique ID": "100138", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 1 - Box 1 - Position 1", "Status": "Available"},
      	{"#": "10", "Unique ID": "100139", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 2 - Box 1 - Position 1", "Status": "Available"},
      	{"#": "11", "Unique ID": "100140", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 2 - Box 1 - Position 2", "Status": "Available"},
      	{"#": "12", "Unique ID": "100141", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 2 - Box 1 - Position 3", "Status": "Lost"},
      	{"#": "13", "Unique ID": "100142", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 2 - Box 1 - Position 4", "Status": "Lost"},
      	{"#": "14", "Unique ID": "100143", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 3 - Box 1 - Position 1", "Status": "Available"},
      	{"#": "15", "Unique ID": "100144", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 3 - Box 1 - Position 2", "Status": "Available"},
      	{"#": "16", "Unique ID": "100145", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 4 - Box 1 - Position 1", "Status": "Checked Out"},
      	{"#": "17", "Unique ID": "100146", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 4 - Box 1 - Position 2", "Status": "Available"},
      	{"#": "18", "Unique ID": "100147", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 4 - Box 1 - Position 3", "Status": "Available"},
      	{"#": "19", "Unique ID": "100148", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 4 - Box 1 - Position 4", "Status": "Checked Out"},
      	{"#": "20", "Unique ID": "100149", "Name": "Book", "Section": "Paraguay", "Position": "Shelf 1 - Rack 5 - Box 1 - Position 1", "Status": "Available"}		
      ]
      
      // Dynamically get the entry headers to use with displaying the nested data via header-key lookups
      // Assumes all lines contain same key-text data
    	$scope.samplePositionsHeaderKeys = []; // Contains only the key-data, not the values
    	for (var key in $scope.samplePositions[0]) {
    		if ($scope.samplePositions[0].hasOwnProperty(key)) {
    			$scope.samplePositionsHeaderKeys.push(key);
    		}
    	}
      
    		/**
    		 * Determine if the input value is a number or not.
    	   * @param n The input value to be checked for numeric status.
    		 * @returns true if parameter is numeric, or false otherwise.
    		 * 
    		 * This method uses the following evaluations to determine if input is a numeric:
    		 * 
    		 * 		(5); // true  
    		 * 		('123'); // true  
    		 * 		('123abc'); // false  
    		 * 		('q345'); // false
    		 * 		(null); // false
    		 * 		(""); // false
    		 *		([]); // false
    		 * 		('   '); // false
    		 * 		(true); // false
    		 * 		(false); // false
    		 * 		(undefined); // false
    		 * 		(new String('')); // false
    		 * 
    		 * @author C.D. (modified by)
    		 * @original https://stackoverflow.com/a/1421988/10930451
    		 * 
    		 */
    		function isNumeric(n) {
    			if (!isNaN(parseFloat(n)) && !isNaN(n - 0) && n !== null && n !== "") {
    				return true;
    			}
    			return false;
    		}
    
    		/**
    		 * Column Sort Method (generic). Sort based on target column header or reverse sort if already selected on that.
    		 * @param dataSource The array of JSON data to be sorted
    		 * @param headers The array of JSON object-keys (table column headers) to be referenced
    		 * @param index The target JSON object-key to sort the table columns based upon
    		 * 
    		 * @author C.D.
    		 */
    		$scope.lastSortIndex = 0;
    		$scope.toggleSort = function (dataSource, headers, index) {
    			if ($scope.lastSortIndex === index) {
    				dataSource.reverse();
    			}
    			else {
    				var key = headers[index];
    				if (key === "#" || isNumeric(dataSource[key])) { // Compare as numeric or on '#' sign
    					dataSource.sort((a, b) => parseFloat(a[key]) - parseFloat(b[key]));
    				}
    				else // Compare as Strings
    				{
    					try { // Attempt to sort as Strings
    						dataSource.sort((a, b) => a[key].localeCompare(b[key]));
    					} catch (error) {
    						if (error.name === 'TypeError') { // Catch type error, actually sort as Numeric
    							dataSource.sort((a, b) => parseFloat(a[key]) - parseFloat(b[key]));
    						}
    					}
    				}
    				$scope.lastSortIndex = index;
    			}
    		}
    
    });
    <html ng-app="app">
    
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
      <title>AngularJS - Hello World</title>
      <script data-require="jquery@*" data-semver="3.1.1" src="https://cdn.jsdelivr.net/npm/jquery@3.1.1/dist/jquery.min.js"></script>
      <script data-require="angular.js@1.3.13" data-semver="1.3.13" src="https://code.angularjs.org/1.3.13/angular.js"></script>
      <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
      <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
    
      <link rel="stylesheet" href="style.css" />
      <script src="script.js"></script>
    </head>
    
    <body ng-controller="MainController">
      <div class="container">
        <table class="table table-hover	table-sm">
      		<thead>
      			<tr>
      				<th ng-repeat="header in samplePositionsHeaderKeys">
      					<a ng-click="toggleSort(samplePositions, samplePositionsHeaderKeys, $index)">{{ header }}</a>
      				</th>
      			</tr>
      		</thead>
      
      		<tbody>
      			<!-- Data is nested, so double-repeat to extract and display -->
      			<tr ng-repeat="row in samplePositions" >
      				<td ng-repeat="key in samplePositionsHeaderKeys">
      					{{row[key]}}
      				</td>
      			</tr>
      		</tbody>
      	</table>
      </div>
    </body>
    
    </html>

    我已经整理了一个有效的Plunker example 来演示。只需单击标题,它们就会在内存中对数组进行排序,AngularJS 将在其中获取更改并刷新 DOM 的该部分。

    【讨论】:

      猜你喜欢
      相关资源
      最近更新 更多
      热门标签