Skip to content

Commit d2a4512

Browse files
committed
closed #11, closed #12, fixed #14
1 parent e7bbbb1 commit d2a4512

File tree

13 files changed

+191
-55
lines changed

13 files changed

+191
-55
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@ Release History
22
---------------
33

44
## [Unreleased]
5+
# [1.3.3] - 2016-08-19
6+
### Fixed
7+
- Compile error
8+
- Sorting will now be reset unless ctrl-key or meta-key (mac) is pressed while sorting
9+
10+
### Added
11+
- Excel export will by default escape unsafe methods, use new setting `exportEscapeString` to override
12+
- Support for passing scope to compile function, can be used for two way binding of table data
13+
- Support for custom search function, this function is used to set column value when searching the table
14+
515
# [1.3.2] - 2016-08-05
616
### Fixed
717
- Minor markup error

app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* # generic-table-dev (core module)
88
* The generic-table module is loaded by default.
99
*/
10-
angular.module('generic.table.dev', ['mgcrea.ngStrap','ui.router','ngAnimate','ngResource','angular.filter','angular.bind.notifier','ngSanitize', 'ngCsv','angular.generic.table']);
10+
angular.module('generic.table.dev', ['ui.router','ngAnimate','ngResource','angular.filter','angular.bind.notifier','ngSanitize', 'ngCsv','angular.generic.table']);
1111

1212
angular.module('generic.table.dev').config(function($stateProvider, $urlRouterProvider) {
1313

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "angular-generic-table",
33
"description": "Generic Table - A generic table for Angular that leverages one time binding for fast rendering. Generic table uses standard markup for tables ie. table, tr and td elements etc. and has support for expanding rows, search, filters, sorting, pagination, export to CSV, column clicks, custom column rendering, custom export values.",
4-
"version": "1.3.2",
4+
"version": "1.3.3",
55
"keywords": [
66
"angular",
77
"generic-table",

dist/js/angular-generic-table.js

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* Description of generic-table module, lorem ipsum dolar sit amet
77
*/
88
angular.module('angular.generic.table', ['ngAnimate','angular.filter','angular.bind.notifier','ngCsv']);
9-
!function(e){try{e=angular.module("angular.generic.table")}catch(t){e=angular.module("angular.generic.table",[])}e.run(["$templateCache",function(e){e.put("generic-table/directive/generic-table/generic-table.html",'<div class="generic-table"><div class="gt-wrapper"><table class="table table-sortable" ng-if="gtHasData" ng-class=":gtRefresh:gtClasses"><thead><tr ng-class="::gtRowTransition ? \'fade-in animate\':\'\'"><th ng-repeat="field in ::gtFields | orderBy:\'columnOrder\' track by field.objectKey" ng-show=":gtRefresh:gtSettings | getProperty:field.objectKey:\'visible\'" ng-class=":gtRefresh:[field.classNames, (field.objectKey | camelToDash) + \'-column\', \'sort-\'+(gtSettings | getProperty:field.objectKey:\'sort\')]"><span ng-click=":gtRefresh:(gtSettings | getProperty:field.objectKey:\'sort\') === \'enable\' ? sort(field.objectKey):(gtSettings | getProperty:field.objectKey:\'sort\') === \'asc\' ? sort(field.objectKey):(gtSettings | getProperty:field.objectKey:\'sort\') === \'desc\' ? sort(field.objectKey):\'\'">{{::field.name}}</span></th></tr><tr ng-if=":gtRefresh:gtTotals" ng-class="::gtRowTransition ? \'fade-in animate\':\'\'"><td ng-repeat="field in ::gtFields | orderBy:\'columnOrder\' track by field.objectKey" class="total-column" ng-show=":gtRefresh:gtSettings | getProperty:field.objectKey:\'visible\'" ng-class="::[(gtFields | getProperty:field.objectKey:\'classNames\'), (field.objectKey | camelToDash) + \'-column\']" field-settings="::field" gt-render="" row-data="::gtTotals" gt-compile="::field.compile"></td></tr></thead><tbody><tr gt-row="" ng-repeat="row in :gtRefresh:gtDisplayData | limitTo: displayRows" gt-event="" ng-class=":gtRefresh:[gtRowTransition ? \'fade-in animate\':\'\',row.isOpen ? \'row-open\':\'\', $index % 2 == 0 ? \'row-odd\':\'row-even\']"><td ng-repeat="field in :gtRefresh:gtFields | orderBy:\'columnOrder\' track by field.objectKey" ng-show=":gtRefresh:gtSettings | getProperty:field.objectKey:\'visible\'" ng-class="::[(gtFields | getProperty:field.objectKey:\'classNames\'), (field.objectKey | camelToDash) + \'-column\']"><span ng-class="::field.click ? \'gt-click-enabled\':\'\'" field-settings="::field" gt-render="" row-data="::row" gt-compile="::field.compile" ng-click=":gtRefresh:!field.click || field.click(row);!field.expand || toggleRow(field.expand,(gtSettings | filter:{\'visible\':true}:true).length,row,field.objectKey);"></span></td></tr></tbody><tr ng-if=":gtRefresh:pagination === false"><td class="gt-no-data" colspan="{{:gtRefresh:(gtSettings | filter:{\'visible\':true}:true).length}}">{{::gtNoDataTxt}}</td></tr></table></div><div class="gt-pagination text-center" ng-if=":gtRefresh:gtPagination === true && pagination !== false"><ul class="pagination"><li ng-class=":gtRefresh:{disabled: currentPage === 0}" ng-show="currentPage !== 0"><button class="btn-link link" ng-click="previousPage()" translate="ALL.GENERAL#PAGINATION_PREVIOUS#BUTTON" ng-disabled=":gtRefresh:currentPage === 0">&laquo; Prev</button></li><li ng-show=":gtRefresh:currentPage > 3"><button class="btn-link link" ng-click="setPage(0)">1</button><small>&hellip;</small></li><li style="display: inline;padding: 0 5px;" ng-repeat="page in :gtRefresh:pagination" ng-class=":gtRefresh:page === currentPage ? \'active\':\'\'"><button class="btn-link link" ng-click="setPage(page)">{{page+1}}</button></li><li ng-show=":gtRefresh:currentPage +1 < pages.length-1 && pages.length > 4"><small ng-show=":gtRefresh:currentPage + 3 < pages.length">&hellip;</small><button class="btn-link link" ng-click="setPage(pages.length-1)">{{pages.length}}</button></li><li ng-class=":gtRefresh:{disabled: currentPage == pages.length}" ng-show=":gtRefresh:currentPage+1 !== pages.length"><button class="btn-link link" ng-click="nextPage()" translate="ALL.GENERAL#PAGINATION_NEXT#BUTTON" ng-disabled=":gtRefresh:currentPage+1 === pages.length">Next &raquo;</button></li></ul></div></div>')}])}();
9+
!function(e){try{e=angular.module("angular.generic.table")}catch(t){e=angular.module("angular.generic.table",[])}e.run(["$templateCache",function(e){e.put("generic-table/directive/generic-table/generic-table.html",'<div class="generic-table"><div class="gt-wrapper"><table class="table table-sortable" ng-if="gtHasData" ng-class=":gtRefresh:gtClasses"><thead><tr ng-class="::gtRowTransition ? \'fade-in animate\':\'\'"><th ng-repeat="field in ::gtFields | orderBy:\'columnOrder\' track by field.objectKey" ng-show=":gtRefresh:gtSettings | getProperty:field.objectKey:\'visible\'" ng-class=":gtRefresh:[field.classNames, (field.objectKey | camelToDash) + \'-column\', \'sort-\'+(gtSettings | getProperty:field.objectKey:\'sort\')]"><span ng-click=":gtRefresh:(gtSettings | getProperty:field.objectKey:\'sort\') === \'enable\' ? sort($event,field.objectKey):(gtSettings | getProperty:field.objectKey:\'sort\') === \'asc\' ? sort($event,field.objectKey):(gtSettings | getProperty:field.objectKey:\'sort\') === \'desc\' ? sort($event,field.objectKey):\'\'">{{::field.name}}</span></th></tr><tr ng-if=":gtRefresh:gtTotals" ng-class="::gtRowTransition ? \'fade-in animate\':\'\'"><td ng-repeat="field in ::gtFields | orderBy:\'columnOrder\' track by field.objectKey" class="total-column" ng-show=":gtRefresh:gtSettings | getProperty:field.objectKey:\'visible\'" ng-class="::[(gtFields | getProperty:field.objectKey:\'classNames\'), (field.objectKey | camelToDash) + \'-column\']" field-settings="::field" gt-render="" row-data="::gtTotals" gt-compile="::field.compile"></td></tr></thead><tbody><tr gt-row="" ng-repeat="row in :gtRefresh:gtDisplayData | limitTo: displayRows" gt-event="" ng-class=":gtRefresh:[gtRowTransition ? \'fade-in animate\':\'\',row.isOpen ? \'row-open\':\'\', $index % 2 == 0 ? \'row-odd\':\'row-even\']"><td ng-repeat="field in :gtRefresh:gtFields | orderBy:\'columnOrder\' track by field.objectKey" ng-show=":gtRefresh:gtSettings | getProperty:field.objectKey:\'visible\'" ng-class="::[(gtFields | getProperty:field.objectKey:\'classNames\'), (field.objectKey | camelToDash) + \'-column\']"><span ng-class="::field.click ? \'gt-click-enabled\':\'\'" field-settings="::field" gt-render="" row-data="::row" gt-compile="::field.compile" ng-click=":gtRefresh:!field.click || field.click(row);!field.expand || toggleRow(field.expand,(gtSettings | filter:{\'visible\':true}:true).length,row,field.objectKey);"></span></td></tr></tbody><tr ng-if=":gtRefresh:pagination === false"><td class="gt-no-data" colspan="{{:gtRefresh:(gtSettings | filter:{\'visible\':true}:true).length}}">{{::gtNoDataTxt}}</td></tr></table></div><div class="gt-pagination text-center" ng-if=":gtRefresh:gtPagination === true && pagination !== false"><ul class="pagination"><li ng-class=":gtRefresh:{disabled: currentPage === 0}" ng-show="currentPage !== 0"><button class="btn-link link" ng-click="previousPage()" translate="ALL.GENERAL#PAGINATION_PREVIOUS#BUTTON" ng-disabled=":gtRefresh:currentPage === 0">&laquo; Prev</button></li><li ng-show=":gtRefresh:currentPage > 3"><button class="btn-link link" ng-click="setPage(0)">1</button><small>&hellip;</small></li><li style="display: inline;padding: 0 5px;" ng-repeat="page in :gtRefresh:pagination" ng-class=":gtRefresh:page === currentPage ? \'active\':\'\'"><button class="btn-link link" ng-click="setPage(page)">{{page+1}}</button></li><li ng-show=":gtRefresh:currentPage +1 < pages.length-1 && pages.length > 4"><small ng-show=":gtRefresh:currentPage + 3 < pages.length">&hellip;</small><button class="btn-link link" ng-click="setPage(pages.length-1)">{{pages.length}}</button></li><li ng-class=":gtRefresh:{disabled: currentPage == pages.length}" ng-show=":gtRefresh:currentPage+1 !== pages.length"><button class="btn-link link" ng-click="nextPage()" translate="ALL.GENERAL#PAGINATION_NEXT#BUTTON" ng-disabled=":gtRefresh:currentPage+1 === pages.length">Next &raquo;</button></li></ul></div></div>')}])}();
1010
/**
1111
* @ngdoc directive
1212
* @name angular.generic.table.directive:genericTable
@@ -146,7 +146,7 @@ angular.module('angular.generic.table').directive('genericTable', function() {
146146
},true).slice(0);
147147

148148
// return rows where columns match entered search terms
149-
filteredData = $filter('searchRow')(filtered,searchColumns, search);
149+
filteredData = $filter('searchRow')(filtered,searchColumns, search, $scope.gtFields);
150150
applySort();
151151
};
152152

@@ -341,7 +341,19 @@ angular.module('angular.generic.table').directive('genericTable', function() {
341341
};
342342

343343
// sort function
344-
$scope.sort = function(property){
344+
$scope.sort = function($event,property){
345+
var ctrlKey = $event.ctrlKey || $event.metaKey;
346+
347+
// reset sorting unless ctrl key or meta key (mac) is pressed while sorting
348+
if(!ctrlKey && property) {
349+
for (var i = 0; $scope.gtSettings.length > i; i++){
350+
var setting = $scope.gtSettings[i];
351+
if(setting.objectKey !== property && setting.sort === 'asc' || setting.objectKey !== property && setting.sort === 'desc'){
352+
$scope.gtSettings[i].sort = 'enable';
353+
354+
}
355+
}
356+
}
345357
if (property){
346358
for (var i = 0; $scope.gtSettings.length > i; i++){
347359
var setting = $scope.gtSettings[i];
@@ -369,7 +381,7 @@ angular.module('angular.generic.table').directive('genericTable', function() {
369381
sorting = $filter('map')($filter('filter')($scope.gtSettings,{sort:"asc desc"},function(expected, actual){
370382
return actual.indexOf(expected) > -1;
371383
}),function(sort){
372-
return (sort.sort === 'desc' ? '-':'') + sort.objectKey
384+
return (sort.sort === 'desc' ? '-':'') + sort.objectKey;
373385
});
374386

375387
applySort();
@@ -400,7 +412,10 @@ angular.module('angular.generic.table').directive('genericTable', function() {
400412
// if export method is declared and is a function...
401413
if (exportMethod && angular.isFunction(exportMethod)) {
402414
// ...replace export data row value with value returned by function
403-
row[key] = exportMethod(row, key);
415+
var exportValue = exportMethod(row, key);
416+
row[key] = fieldSetting.exportEscapeString === false ? exportValue : $filter('escapeCsvString')(exportValue);
417+
} else {
418+
row[key] = fieldSetting.exportEscapeString === false ? row[key] : $filter('escapeCsvString')(row[key]);
404419
}
405420

406421
// if exportColumns are passed with options...
@@ -424,7 +439,7 @@ angular.module('angular.generic.table').directive('genericTable', function() {
424439

425440
// if exportColumns are passed with options...
426441
if (typeof options.exportColumns !== 'undefined') {
427-
442+
428443
// ...get field settings for all objectKeys in exportColumns array
429444
var exportFields = $filter('map')(options.exportColumns, function(objectKey){
430445
return $filter('filter')($scope.gtFields.slice(0), {objectKey:objectKey}, true)[0];
@@ -442,7 +457,8 @@ angular.module('angular.generic.table').directive('genericTable', function() {
442457
columnOrder:$filter('map')(exportFields,"objectKey"), // get column order
443458
decimalSep:typeof options.decimalSep === 'undefined' ? ',':options.decimalSep,
444459
addByteOrderMarker:typeof options.addBom === 'undefined',
445-
charset:typeof options.charset === 'undefined' ? 'utf-8':options.charset
460+
charset:typeof options.charset === 'undefined' ? 'utf-8':options.charset,
461+
quoteStrings: typeof options.quoteStrings === 'undefined' ? false : options.quoteStrings
446462
};
447463

448464
CSV.stringify(data, headers).then(function(result){
@@ -544,11 +560,14 @@ angular.module('angular.generic.table').directive('genericTable', function() {
544560
} else {
545561
output = row[key];
546562
}
547-
if(scope.compile === true){
548-
output = $compile(output)(scope.$parent); // use same scope as row
563+
if(scope.compile && scope.compile !== false){
564+
// declare element scope
565+
var elementScope = scope.compile !== true && scope.compile.$watch ? scope.compile:scope.$parent; // use same scope as row unless a valid scope is passed
566+
output = $compile(output)(elementScope); // compile
567+
element.append(output); // add html
568+
} else {
569+
element[0].innerHTML = output; // add html
549570
}
550-
551-
element[0].innerHTML = output; // add html
552571
}
553572
};
554573
}).filter('getProperty',function($filter){
@@ -561,23 +580,39 @@ angular.module('angular.generic.table').directive('genericTable', function() {
561580
}
562581
return output;
563582
}
564-
}).filter('searchRow',function($filter){
565-
return function(array,fields,searchTerms){
583+
}).filter('searchRow',function($filter,$interpolate){
584+
return function(array,fields,searchTerms,fieldsSettings){
585+
//console.log(array,fields,fieldsSettings);
586+
var search = {};
587+
for(var k = 0; k < fieldsSettings.length;k++){
588+
//console.log(array[k]);
589+
var fieldSetting = fieldsSettings[k];
590+
if(fieldSetting.search && angular.isFunction(fieldSetting.search)){
591+
var objectKey = fieldSetting.objectKey;
592+
search[objectKey] = fieldSetting.search;
593+
}
594+
}
566595

596+
if(!searchTerms || searchTerms.replace(/"/g,"").length === 0){
597+
return array;
598+
}
567599
var filteredArray = [];
568-
var searchTerms = typeof searchTerms === 'undefined' ? '':searchTerms;
569-
var searchTermsArray = searchTerms.toLowerCase().split(' ');
600+
searchTerms = typeof searchTerms === 'undefined' ? '':searchTerms;
601+
var searchTermsArray = searchTerms.toLowerCase().match(/"[^"]+"|[\w]+/g);
570602

571603
for (var i = 0; i < array.length; i++){
572604
var row = array[i];
573605
var string = '';
606+
var j = 0;
574607
$filter('map')(fields,function(field){
575-
string += row[field];
608+
var separator = j === 0 ? '':' & ';
609+
string += search[field] ? separator+search[field](row,field):separator+row[field];
610+
j++;
576611
});
577612
string = string.toLowerCase();
578613
var match = true;
579614
for (var k = 0; k < searchTermsArray.length;k++){
580-
var term = searchTermsArray[k];
615+
var term = searchTermsArray[k].replace(/"/g,'');
581616
match = string.indexOf(term) !== -1;
582617

583618
if(!match){
@@ -632,4 +667,8 @@ angular.module('angular.generic.table').directive('genericTable', function() {
632667
}
633668
return array.sort(dynamicSortMultiple(propertyArray));
634669
}
635-
});
670+
}).filter('escapeCsvString', function() {
671+
return function(input){
672+
return /^(\=|\@|\+,\-).*/g.test(input) ? '\''+input : input;
673+
}
674+
});

0 commit comments

Comments
 (0)