1/* 2angular-tablesort v1.2.0 3(c) 2013-2016 Mattias Holmlund, http://mattiash.github.io/angular-tablesort 4License: MIT 5*/ 6 7var tableSortModule = angular.module( 'tableSort', [] ); 8 9tableSortModule.provider('tableSortConfig', function () { 10 this.filterTemplate = ""; //no filtering by default unless a template is provided 11 this.filterFunction = null; //empty by default - use the built in filter function when left blank 12 this.paginationTemplate = ""; //no pagination by default unless a template is provided 13 this.perPageOptions = [10, 25, 50, 100]; 14 this.perPageDefault = this.perPageOptions[0]; //first option by default 15 this.itemNameSingular = "item"; 16 this.itemNamePlural = this.itemNameSingular + "s"; 17 this.noDataText = "No " + this.itemNamePlural; 18 19 if(!isNaN(this.perPageDefault) && this.perPageOptions.indexOf(this.perPageDefault) === -1){ 20 //If a default per-page option was added that isn't in the array, add it and sort the array 21 this.perPageOptions.push(this.perPageDefault); 22 } 23 24 //Sort the array 25 this.perPageOptions.sort(function (a,b) {return a - b;}); 26 27 this.$get = function () { 28 return this; 29 }; 30 31}); 32 33tableSortModule.directive('tsWrapper', ['$parse', '$compile', function( $parse, $compile ) { 34 'use strict'; 35 36 function replaceTemplateTokens($scope, templateString){ 37 //Replace some strings with the proper expressions to be compiled 38 return templateString 39 .replace(/FILTER_STRING/g, "filtering.filterString") 40 .replace(/CURRENT_PAGE_RANGE/g, "pagination.getPageRangeString(TOTAL_COUNT)") 41 .replace(/TOTAL_COUNT/g, $scope.pagination.itemsArrayExpression + ".length") 42 .replace(/PER_PAGE_OPTIONS/g, 'pagination.perPageOptions') 43 .replace(/ITEMS_PER_PAGE/g, 'pagination.perPage') 44 .replace(/ITEM_NAME_SINGULAR/g, 'itemNameSingular') 45 .replace(/ITEM_NAME_PLURAL/g, 'itemNamePlural') 46 .replace(/FILTERED_COUNT/g, "filtering.filteredCount") 47 .replace(/CURRENT_PAGE_NUMBER/g, "pagination.currentPage"); 48 } 49 50 return { 51 scope: true, 52 controller: ['$scope', 'tableSortConfig', function($scope, tableSortConfig ) { 53 //local scope vars for this directive 54 $scope.pagination = { 55 template: tableSortConfig.paginationTemplate, 56 perPageOptions: tableSortConfig.perPageOptions.concat(), //copy the array, not a reference 57 perPage: tableSortConfig.perPageDefault, 58 itemsArrayExpression: "", //this will contain the string expression for the array of items in the table 59 currentPage: 1, 60 getPageRangeString: function(total) { 61 //TODO: Format these numbers, perhaps optionally 62 var maxOnPage = total !== $scope.filtering.filteredCount ? $scope.filtering.filteredCount : total; 63 var startPage = ($scope.pagination.currentPage - 1) * ($scope.pagination.perPage + 1); 64 var endPage = Math.min($scope.pagination.currentPage * $scope.pagination.perPage, maxOnPage); 65 return $scope.filtering.filteredCount === 0 ? "" : startPage + "-" + endPage; 66 } 67 }; 68 69 $scope.filtering = { 70 template: tableSortConfig.filterTemplate, 71 filterString: "", 72 filterFunction: tableSortConfig.filterFunction, 73 filteredCount: 0, 74 filterFields: [] 75 }; 76 77 $scope.itemNameSingular = tableSortConfig.itemNameSingular; 78 $scope.itemNamePlural = tableSortConfig.itemNamePlural; 79 $scope.noDataText = tableSortConfig.noDataText; 80 $scope.sortExpression = []; 81 $scope.headings = []; 82 83 //Private vars 84 var parse_sortexpr = function( expr ) { 85 return [$parse( expr ), null, false]; 86 }; 87 88 //Public directive vars for the other directives that depend on this 89 this.setSortField = function( sortexpr, element ) { 90 var i; 91 var expr = parse_sortexpr( sortexpr ); 92 if( $scope.sortExpression.length === 1 93 && $scope.sortExpression[0][0] === expr[0] ) { 94 if( $scope.sortExpression[0][2] ) { 95 element.removeClass( "tablesort-desc" ); 96 element.addClass( "tablesort-asc" ); 97 $scope.sortExpression[0][2] = false;
98 } 99 else { 100 element.removeClass( "tablesort-asc" ); 101 element.addClass( "tablesort-desc" ); 102 $scope.sortExpression[0][2] = true; 103 } 104 } 105 else { 106 for( i=0; i<$scope.headings.length; i=i+1 ) { 107 $scope.headings[i] 108 .removeClass( "tablesort-desc" ) 109 .removeClass( "tablesort-asc" ); 110 } 111 element.addClass( "tablesort-asc" ); 112 $scope.sortExpression = [expr]; 113 } 114 }; 115 116 this.addSortField = function( sortexpr, element ) { 117 var i; 118 var toggle_order = false; 119 var expr = parse_sortexpr( sortexpr ); 120 for( i=0; i<$scope.sortExpression.length; i=i+1 ) { 121 if( $scope.sortExpression[i][0] === expr[0] ) { 122 if( $scope.sortExpression[i][2] ) { 123 element.removeClass( "tablesort-desc" ); 124 element.addClass( "tablesort-asc" ); 125 $scope.sortExpression[i][2] = false; 126 } 127 else { 128 element.removeClass( "tablesort-asc" ); 129 element.addClass( "tablesort-desc" ); 130 $scope.sortExpression[i][2] = true; 131 } 132 toggle_order = true; 133 } 134 } 135 if( !toggle_order ) { 136 element.addClass( "tablesort-asc" ); 137 $scope.sortExpression.push( expr ); 138 } 139 }; 140 141 this.setTrackBy = function( trackBy ) { 142 $scope.trackBy = trackBy; 143 }; 144 145 this.registerHeading = function( headingelement ) { 146 $scope.headings.push( headingelement ); 147 }; 148 149 this.addFilterField = function( sortexpr, element ) { 150 var expr = parse_sortexpr( sortexpr ); 151 $scope.filtering.filterFields.push( expr ); 152 }; 153 154 this.setDataForPager = function( dataArrayExp ) { 155 $scope.pagination.itemsArrayExpression = dataArrayExp; 156 }; 157 }], 158 link: function($scope, $element, $attrs, tsWrapperCtrl){ 159 160 if($attrs.tsItemName){ 161 var originalNoDataText = "No " + $scope.itemNamePlural; 162 163 //if the table attributes has an item name on it, this takes priority 164 $scope.itemNameSingular = $attrs.tsItemName; 165 166 if($attrs.tsItemNamePlural){ 167 //if a plural name was specified, use that 168 $scope.itemNamePlural = $attrs.tsItemNamePlural; 169 }else{ 170 //otherwise just add "s" to the singular name 171 $scope.itemNamePlural = $attrs.tsItemName + "s"; 172 } 173 174 if(!$attrs.tsNoDataText && $scope.noDataText === originalNoDataText){ 175 //If the noDataText was NOT specified AND it's in the same "No ITEMS" format as the default , update it to contain the new item name 176 $scope.noDataText = "No " + $scope.itemNamePlural; 177 } 178 } 179 180 if($attrs.tsNoDataText){ 181 //If the noDataText was specified, update it 182 $scope.noDataText = $attrs.tsNoDataText; 183 } 184 185 //local attribute usages of the pagination/filtering options will override the global config 186 if($attrs.tsPerPageOptions){ 187 $scope.pagination.perPageOptions = $scope.$eval($attrs.tsPerPageOptions); 188 } 189 190 if($attrs.tsPerPageDefault){ 191 var defaultPerPage = $scope.$eval($attrs.tsPerPageDefault); 192 if(!isNaN(defaultPerPage)){ 193 $scope.pagination.perPage = defaultPerPage; 194 if($scope.pagination.perPageOptions.indexOf($scope.pagination.perPage) === -1){ 195 //If a default per-page option was added that isn't in the array, add it and sort the array 196 $scope.pagination.perPageOptions.push($scope.pagination.perPage); 197 $scope.pagination.perPageOptions.sort(function (a,b) {return a - b;}); 198 } 199 } 200 } 201 202 if($attrs.tsFilterFields){ 203 var filterFields = $attrs.tsFilterFields.split(",") 204 .filter(function(item){ 205 return item && item.trim() !== ""; 206 }); 207 for( var i=0; i<filterFields.length; i=i+1 ){ 208 tsWrapperCtrl.addFilterField(filterFields[i]); 209 } 210 } 211 212 var $filterHtml;
213 if($attrs.tsDisplayFiltering !== "false" && $scope.filtering.template !== "" && $scope.filtering.filterFields.length>0){ 214 var filterString = replaceTemplateTokens($scope, $scope.filtering.template); 215 $filterHtml = $compile(filterString)($scope); 216 //Add filtering HTML BEFORE the table 217 $element.parent()[0].insertBefore($filterHtml[0], $element[0]); 218 } 219 220 if($attrs.tsFilterFunction){ 221 //if the table attributes has a filter function on it, this takes priority 222 $scope.filtering.filterFunction = $scope.$eval($attrs.tsFilterFunction); 223 } 224 225 if(!angular.isFunction($scope.filtering.filterFunction)){ 226 //No custom filter was provided... 227 if($scope.filtering.filterFields.length===0){ 228 //There are no filter fields, so always return everything 229 $scope.filtering.filterFunction = function(item){ 230 return true; 231 }; 232 }else{ 233 //This is the default filter function. It does a lowercase string match 234 $scope.filtering.filterFunction = function(item) { 235 var shouldInclude = false; 236 for( var i=0; i<$scope.filtering.filterFields.length; i=i+1 ) { 237 if(!shouldInclude){ 238 var str = ($scope.filtering.filterFields[i][0](item) || "").toString().toLowerCase(); //parse the item's property using the `ts-criteria` value & filter 239 shouldInclude = str.indexOf($scope.filtering.filterString.toLowerCase()) > -1; 240 } 241 } 242 return shouldInclude; 243 }; 244 } 245 } 246 247 $scope.filterLimitFun = function(array){ 248 if(!$attrs.tsFilterFunction && $scope.filtering.filterString === ""){ 249 //Return unfiltered when NOT using a custom filter function and when nothing is being searched 250 $scope.filtering.filteredCount = array.length; 251 return array; 252 } 253 var filteredArr = array.filter($scope.filtering.filterFunction); 254 $scope.filtering.filteredCount = filteredArr.length; 255 return filteredArr; 256 }; 257 258 $scope.sortFun = function( a, b ) { 259 var i, aval, bval, descending, filterFun; 260 for( i=0; i<$scope.sortExpression.length; i=i+1 ){ 261 aval = $scope.sortExpression[i][0](a); 262 bval = $scope.sortExpression[i][0](b); 263 filterFun = b[$scope.sortExpression[i][1]]; 264 if( filterFun ) { 265 aval = filterFun( aval ); 266 bval = filterFun( bval ); 267 } 268 if( aval === undefined || aval === null ) { 269 aval = ""; 270 } 271 if( bval === undefined || bval === null ) { 272 bval = ""; 273 } 274 descending = $scope.sortExpression[i][2]; 275 if( aval > bval ) { 276 return descending ? -1 : 1; 277 } 278 else if( aval < bval ) { 279 return descending ? 1 : -1; 280 } 281 } 282 283 // All the sort fields were equal. If there is a "track by" expression, 284 // use that as a tiebreaker to make the sort result stable. 285 if( $scope.trackBy ) { 286 aval = a[$scope.trackBy]; 287 bval = b[$scope.trackBy]; 288 if( aval === undefined || aval === null ) { 289 aval = ""; 290 } 291 if( bval === undefined || bval === null ) { 292 bval = ""; 293 } 294 if( aval > bval ) { 295 return descending ? -1 : 1; 296 } 297 else if( aval < bval ) { 298 return descending ? 1 : -1; 299 } 300 } 301 return 0; 302 }; 303 304 $scope.pageLimitFun = function(array){
305 if($attrs.tsDisplayPagination === "false"){ 306 //pagination is disabled, so return everything 307 return array; 308 } 309 //Only return the items that are in the correct index range for the currently selected page 310 var begin = ($scope.pagination.currentPage-1) * $scope.pagination.perPage; 311 var end = $scope.pagination.currentPage * $scope.pagination.perPage; 312 var final=[]; 313 for(var i=0; i < array.length; i++){ 314 if(i >= begin && i < end){ 315 final.push(array[i]); 316 } 317 } 318 return final; 319 }; 320 321 var $paginationHtml; 322 if($attrs.tsDisplayPagination !== "false" && $scope.pagination.template !== ""){ 323 var pagerString = replaceTemplateTokens($scope, $scope.pagination.template); 324 $paginationHtml = $compile(pagerString)($scope); 325 //Add pagination HTML AFTER the table 326 $element.after($paginationHtml); 327 } 328 329 $scope.$on("$destroy", function(){ 330 //When the directive is destroyed, also remove the filter & pagination HTML 331 if($filterHtml){ 332 $filterHtml.remove(); 333 } 334 if($paginationHtml){ 335 $paginationHtml.remove(); 336 } 337 }); 338 } 339 }; 340}]); 341 342tableSortModule.directive('tsCriteria', function() { 343 return { 344 require: "^tsWrapper", 345 link: function(scope, element, attrs, tsWrapperCtrl) { 346 var clickingCallback = function(event) { 347 scope.$apply( function() { 348 if( event.shiftKey ) { 349 tsWrapperCtrl.addSortField(attrs.tsCriteria, element); 350 } 351 else { 352 tsWrapperCtrl.setSortField(attrs.tsCriteria, element); 353 } 354 } ); 355 }; 356 element.bind('click', clickingCallback); 357 element.addClass('tablesort-sortable'); 358 if( "tsDefault" in attrs && attrs.tsDefault !== "0" ) { 359 tsWrapperCtrl.addSortField( attrs.tsCriteria, element ); 360 if( attrs.tsDefault === "descending" ) { 361 tsWrapperCtrl.addSortField( attrs.tsCriteria, element ); 362 } 363 } 364 if( "tsFilter" in attrs) { 365 tsWrapperCtrl.addFilterField( attrs.tsCriteria, element ); 366 } 367 tsWrapperCtrl.registerHeading( element ); 368 } 369 }; 370}); 371 372tableSortModule.directive("tsRepeat", ['$compile', function($compile) { 373 return { 374 terminal: true, 375 multiElement: true, 376 require: "^tsWrapper", 377 priority: 1000000, 378 link: function(scope, element, attrs, tsWrapperCtrl) { 379 var repeatAttrs = ["ng-repeat", "data-ng-repeat", "ng-repeat-start", "data-ng-repeat-start"]; 380 var ngRepeatDirective = repeatAttrs[0]; 381 var tsRepeatDirective = "ts-repeat"; 382 for (var i = 0; i < repeatAttrs.length; i++) { 383 if (angular.isDefined(element.attr(repeatAttrs[i]))) { 384 ngRepeatDirective = repeatAttrs[i]; 385 tsRepeatDirective = ngRepeatDirective.replace(/^(data-)?ng/, '$1ts'); 386 break; 387 } 388 } 389 390 var tsExpr = "tablesortOrderBy:sortFun | tablesortLimit:filterLimitFun | tablesortLimit:pageLimitFun"; 391 var repeatExpr = element.attr(ngRepeatDirective); 392 var repeatExprRegex = /^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(\s+track\s+by\s+[\s\S]+?)?\s*$/; 393 var trackByMatch = repeatExpr.match(/\s+track\s+by\s+\S+?\.(\S+)/); 394 var repeatInMatch = repeatExpr.match(repeatExprRegex); 395 if (trackByMatch) { 396 tsWrapperCtrl.setTrackBy(trackByMatch[1]); 397 } 398 399 //Limit Sort the results, then limit them to only include what matches the filter, then only what's on the current page 400 if (repeatExpr.search(/tablesort/) !== -1) { 401 repeatExpr = repeatExpr.replace(/tablesort/, tsExpr); 402 if (trackByMatch) { 403 //Move the "track by" statement to the end 404 repeatExpr = repeatExpr.replace(trackByMatch[0], "") + trackByMatch[0]; 405 } 406 } else { 407 repeatExpr = repeatExpr.replace(repeatExprRegex, "$1 in $2 | " + tsExpr + "$3"); 408 } 409 410 if (angular.isUndefined(attrs.tsHideNoData)) {
411 var noDataRow = angular.element(element[0]).clone(); 412 noDataRow.removeAttr(ngRepeatDirective); 413 noDataRow.removeAttr(tsRepeatDirective); 414 noDataRow.addClass("showIfLast"); 415 noDataRow.children().remove(); 416 noDataRow.append('<td colspan="' + element[0].childElementCount + '">{{noDataText}}</td>'); 417 noDataRow = $compile(noDataRow)(scope); 418 element.parent().prepend(noDataRow); 419 } 420 421 //pass the `itemsList` from `item in itemsList` to the master directive as a string so it can be used in expressions 422 tsWrapperCtrl.setDataForPager(repeatInMatch[2]); 423 424 angular.element(element[0]).attr(ngRepeatDirective, repeatExpr); 425 $compile(element, null, 1000000)(scope); 426 } 427 }; 428}]); 429 430tableSortModule.filter( 'tablesortLimit', function(){ 431 return function(array, limitFun) { 432 if(!array) return; 433 return limitFun(array); 434 }; 435} ); 436 437tableSortModule.filter( 'tablesortOrderBy', function(){ 438 return function(array, sortfun ) { 439 if(!array) return; 440 var arrayCopy = array.concat(); 441 return arrayCopy.sort( sortfun ); 442 }; 443} ); 444 445tableSortModule.filter( 'parseInt', function(){ 446 return function(input) { 447 return parseInt( input ) || null; 448 }; 449} ); 450 451tableSortModule.filter( 'parseFloat', function(){ 452 return function(input) { 453 return parseFloat( input ) || null; 454 }; 455} ); 456 457tableSortModule.filter('parseDate', function () { 458 return function (input) { 459 var timestamp = Date.parse(input); 460 return isNaN(timestamp) ? null : timestamp; 461 }; 462} );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.