diff --git a/README.md b/README.md index 91cdfae..b1dc410 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -##Angular Data Grid +## Angular Data Grid + Light, flexible and performant Data Grid for AngularJS apps, with built-in sorting, pagination and filtering options, unified API for client-side and server-side data fetching, -seamless synchronization with browser address bar and total freedom in mark-up and styling suitable for your application. +seamless synchronization with browser address bar and total freedom in mark-up and styling suitable for your application. Angular 1.3 - 1.6 compliant. Demo Bootstrap: http://angular-data-grid.github.io/demo/bootstrap/ @@ -8,25 +9,33 @@ Demo Material: http://angular-data-grid.github.io/demo/material/ Demo 100k: http://angular-data-grid.github.io/demo/100k/ +Demo Angular UI Router: http://angular-data-grid.github.io/demo/bootstrap/ui-router.html + ### Features - Does not have any hard-coded template so you can choose any mark-up you need, from basic `` layout to any `
` structure. - Easily switch between the most popular Bootstrap and Google Material theming, or apply your own CSS theme just by changing several CSS classes. - - Built-in sync with browser address bar (URL), so you can copy-n-paste sorting/filtering/pagination results URL and open it in other browser / send to anyone - even if pagination / filtering are done on a client-side. + - Built-in sync with browser address bar (URL), so you can copy-n-paste sorting/filtering/pagination results URL and open it in other browser / send to anyone - even if pagination / filtering are done on a client-side. [See details](#url-synchronization) + - Support of [Angular UI Router](https://github.com/angular-ui/ui-router) navigation. + - Optional support of fixed header table: [Bootstrap Demo](http://angular-data-grid.github.io/demo/fixed-header/bootstrap-grid.html) [Material Design Demo](http://angular-data-grid.github.io/demo/fixed-header/angular-md-grid.html) + - Optional support of CSV data exports: [Demo](http://angular-data-grid.github.io/demo/bootstrap/) - Unlike most part of other Angular DataGrids, we intentionally use non-isolated scope of the directive to maximize flexibility, so it can be easily synchronized with any data changes inside your controller. - NOTE! With great power comes great responsibility, so use non-isolated API carefully. + NOTE! With great power comes great responsibility, so use non-isolated API wisely. ### Installation Using Bower: `bower install angular-data-grid` +Using NPM: `npm install angular-data-grid` + Direct download: get ZIP archive [from here](https://github.com/angular-data-grid/angular-data-grid.github.io/archive/master.zip) Then use files from *dist* folder (see below). ### Setup -1. Include scripts in you application: `dataGrid.min.js` and `pagination.min.js` (include the second one only if you need pagination). +1. Include scripts in you application: `angular.min.js`, `dataGrid.min.js` and `pagination.min.js` (include the second one only if you need pagination). ```javascript + ``` @@ -80,18 +89,12 @@ angular.module('myApp', ['dataGrid', 'pagination']) sort: { predicate: 'companyName', direction: 'asc' - }, - //optional parameter - custom rules for filters (see explanation below) - customFilters: { - startFrom: function (items, value, predicate) { - return items.filter(function (item) { - return value && item[predicate] ? !item[predicate].toLowerCase().indexOf(value.toLowerCase()) : true; - }); - } } }; ``` +NOTE: `grid-item` wrapper directive used in the example above, to make code more concise, but you can always use regular `ng-repeat` instead, like: `ng-repeat="item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"` + ### Basic API 1. `grid-options`: object in your controller with start options for grid. You must create this object with at least 1 required parameter - data. @@ -109,7 +112,10 @@ Use this object for calling methods of directive: `sort()`, `filter()`, `refresh }; ``` - - For server side pagination/filtering to fetch data by page: assign ```getData``` method to some function with URL params as 1st parameter and data itself as 2d parameter: + - For server side pagination/filtering to fetch data by page: + 1. add attribute 'server-pagination'=true on element on which you applied directive 'grid-data' + 2. assign ```getData``` method to some function with URL params as 1st parameter and data itself as 2d parameter: + ```javascript $scope.gridOptions = { @@ -149,7 +155,7 @@ $scope.gridOptions = { ### Pagination -You can optionally use `pagination` directive to display paging with previous/next and first/last controls. +You can optionally use `grid-pagination` directive to display paging with previous/next and first/last controls. Directive is built on a base of excellent [Angular UI](https://angular-ui.github.io/bootstrap/) component and shares extensive API: ```HTML @@ -162,7 +168,7 @@ Directive is built on a base of excellent [Angular UI](https://angular-ui.github ``` -Settings can be provided as attributes in the or globally configured through the paginationConfig. +Settings can be provided as attributes in the or globally configured through the `paginationConfig`. ```ng-change``` : ng-change can be used together with ng-model to call a function whenever the page changes. @@ -196,8 +202,8 @@ Settings can be provided as attributes in the or globally configure ### Filters Data Grid supports 4 built-in types of filters: `text`, `select`, `dateFrom` and `dateTo`. -To use it, add attribute `filter-by` to any element and pass property name, which you want filtering. -Also you need add attribute `filter-type` with type of filter. +To use it, add attribute `filter-by` to any element and pass property name, which you want to be filtered. +Also you need add attribute `filter-type` with type of filter. After that you need call `filter()` method in `ng-change` for text or select inputs and in `ng-blur/ng-focus` for datepickers. Filters are synchronized with URL by `ng-model` value. @@ -213,7 +219,7 @@ Filters are synchronized with URL by `ng-model` value. ``` ### Custom Filters -If you need use some custom filters (f.e. filter by first letter), add `filter-by` to specify property name, which you want filtering and add `ng-model` property. +If you need to use some custom filters (e.g. filter by first letter), add `filter-by` to specify property name, which you want filtering and add `ng-model` property. Then create in `gridOptions.customFilters` variable named as `ng-model` with filtering function. Filtering function accepts items, value, predicate arguments and returns filtered array. ```javascript @@ -231,7 +237,18 @@ Then create in `gridOptions.customFilters` variable named as `ng-model` with fil ``` -### Others -All filters have optional parameter `disable-url`. If you set it to **true**, URL-synchronization for this filter will be disabled. +### URL Synchronization +You can disable/enable URL synchronization for the whole grid or on a level of particular filter. + +Global parameter `gridOptions.urlSync` (boolean, default is 'false') works for the whole grid. + +Each filter has optional parameter `disable-url` (boolean, default is 'false'). If you set it to **true**, URL-synchronization for this particular filter will be disabled. + + +### Multiple grids on page +If you need to use 2 or more grids on page, please add `id` to grids, and then use `grid-id` attribute on filters to specify their corresponding grid. [Example](http://angular-data-grid.github.io/demo/bootstrap/multiple.html) -If you need to use 2 or more grids on page, please add id to grids, and then use ```grid-id``` attribute on filters to specify their corresponding grid. +### Next / Future + - Migrate to Heroku + - Cover with unit / e2e tests + - Port to Angular2? diff --git a/demo/100k/index.html b/demo/100k/index.html index d16ea35..4ce1f28 100644 --- a/demo/100k/index.html +++ b/demo/100k/index.html @@ -207,14 +207,14 @@

Angular Data Grid 100k Example

- - - - - + + + + + - + \ No newline at end of file diff --git a/demo/100k/js/demoApp.js b/demo/100k/js/demoApp.js index 34bc386..47d6e5c 100644 --- a/demo/100k/js/demoApp.js +++ b/demo/100k/js/demoApp.js @@ -6,7 +6,10 @@ $scope.gridOptions = { data: [], - urlSync: true + urlSync: true, + pagination: { + itemsPerPage: '10' + } }; $http({ diff --git a/demo/bootstrap/css/loading-bar.min.css b/demo/bootstrap/css/loading-bar.min.css new file mode 100644 index 0000000..db06f19 --- /dev/null +++ b/demo/bootstrap/css/loading-bar.min.css @@ -0,0 +1 @@ +#loading-bar,#loading-bar-spinner{-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar .ng-enter,#loading-bar .ng-leave.ng-leave-active,#loading-bar-spinner .ng-enter,#loading-bar-spinner .ng-leave.ng-leave-active{opacity:0}#loading-bar .ng-enter.ng-enter-active,#loading-bar .ng-leave,#loading-bar-spinner .ng-enter.ng-enter-active,#loading-bar-spinner .ng-leave{opacity:1}#loading-bar{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.25);z-index:11002}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:11002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px}#loading-bar-spinner{display:block;position:fixed;z-index:11002;top:50%;left:50%;margin-left:-15px;margin-right:-15px}#loading-bar-spinner .spinner-icon{width:34px;height:34px;border:solid 2px transparent;border-top-color:#fff;border-left-color:#fff;-webkit-border-radius:17px;-moz-border-radius:17px;border-radius:17px;-webkit-animation:loading-bar-spinner .8s linear infinite;-moz-animation:loading-bar-spinner .8s linear infinite;-ms-animation:loading-bar-spinner .8s linear infinite;-o-animation:loading-bar-spinner .8s linear infinite;animation:loading-bar-spinner .8s linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0);transform:rotate(0)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}} \ No newline at end of file diff --git a/demo/bootstrap/index.html b/demo/bootstrap/index.html index c2cf1d0..ab4b483 100644 --- a/demo/bootstrap/index.html +++ b/demo/bootstrap/index.html @@ -16,7 +16,7 @@ - + @@ -93,6 +106,7 @@

First Grid

+
Date Of Birth + +
@@ -181,6 +195,18 @@

Second Grid

Date Of Birth + + + @@ -189,6 +215,7 @@

Second Grid

+ @@ -220,13 +247,13 @@

Second Grid

- - - - - + + + + + - + \ No newline at end of file diff --git a/demo/bootstrap/server-pagination.html b/demo/bootstrap/server-pagination.html new file mode 100644 index 0000000..50990cb --- /dev/null +++ b/demo/bootstrap/server-pagination.html @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + +
+
+

Angular Data Grid Server Pagination

+ Features enabled: sorting, filtering (using both in-grid and external controls), pagination and items-per-page functionality. + Angular UI Datepicker used for date controls, although you can use any other framework, plugin or styling. + Project GitHub +
+
+
+
+
+
+ + +
+
+
+
+ + +
+ + + + +
+
+
+
+
+ + +
+ + + + +
+
+ +
+
+
+ + +
+
+
{{paginationOptions.totalItems}} items total
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + +
Order # Date Placed + + Total
{{item.orderNo}}{{item.datePlaced | date:'MM/dd/yyyy'}}{{item.status}}{{item.total}}
+
+
{{paginationOptions.totalItems}} items total
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/bootstrap/ui-router.html b/demo/bootstrap/ui-router.html new file mode 100644 index 0000000..20b37ed --- /dev/null +++ b/demo/bootstrap/ui-router.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + +
+
+

Angular Data Grid - UI Router Server Pagination.

+ UI Router plus all features for server pagination.
+ UI Router can be used in both modes (hash and html5). + Project GitHub +
+
+
+
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/bootstrap/views/anotherView.html b/demo/bootstrap/views/anotherView.html new file mode 100644 index 0000000..427c2e4 --- /dev/null +++ b/demo/bootstrap/views/anotherView.html @@ -0,0 +1,4 @@ +

Another View

+
+ +
\ No newline at end of file diff --git a/demo/bootstrap/views/router-server-pagination.html b/demo/bootstrap/views/router-server-pagination.html new file mode 100644 index 0000000..1d112af --- /dev/null +++ b/demo/bootstrap/views/router-server-pagination.html @@ -0,0 +1,169 @@ +
+
+ +
+
+
+
+
+ + +
+
+
+
+ + +
+ + + + +
+
+
+
+
+ + +
+ + + + +
+
+ +
+
+
+ + +
+
+
{{paginationOptions.totalItems}} items total
+
+
+
+
+
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + + +
Order # Date Placed + + Total
{{item.orderNo}}{{item.datePlaced | date:'MM/dd/yyyy'}}{{item.status}}{{item.total}}
+
+
{{paginationOptions.totalItems}} items total
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
\ No newline at end of file diff --git a/demo/data.json b/demo/data.json index f2c15dc..edecf93 100644 --- a/demo/data.json +++ b/demo/data.json @@ -1,1157 +1,750 @@ -[{ - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 6100.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$6,100.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Valid", - "code": "3747453", - "placed": 1417402800000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1100.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,100.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747092", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1125.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,125.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747116", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1025.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,025.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747118", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1080.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,080.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747093", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1050.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,050.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747099", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1080.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,080.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747090", - "placed": 1397790000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1135.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,135.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747094", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 975.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$975.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747119", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 150.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$150.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747095", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 975.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$975.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747102", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747170", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747174", - "placed": 1401073200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 980.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$980.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747104", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1091.90, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,091.90" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747260", - "placed": 1404788400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747171", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1100.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,100.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747086", - "placed": 1397790000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1092.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,092.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747173", - "placed": 1401073200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1125.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,125.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747084", - "placed": 1397790000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 975.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$975.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747106", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 950.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$950.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747122", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1025.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,025.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747100", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 75.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$75.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3747070", - "placed": 1394766000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1122.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,122.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747164", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 975.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$975.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747107", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 50.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$50.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747083", - "placed": 1396580400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747081", - "placed": 1395975600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1075.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,075.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747088", - "placed": 1397790000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1100.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,100.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747091", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1315.45, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,315.45" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747176", - "placed": 1401073200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1050.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,050.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747103", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1136.90, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,136.90" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747087", - "placed": 1397790000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1005.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,005.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747101", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1050.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,050.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747098", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 975.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$975.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747105", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1073.80, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,073.80" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747165", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747167", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1035.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,035.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747125", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747082", - "placed": 1395975600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1125.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,125.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747085", - "placed": 1397790000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1179.75, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,179.75" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747168", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747172", - "placed": 1401073200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 950.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$950.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747123", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 950.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$950.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747108", - "placed": 1398049200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1050.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,050.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747117", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1030.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,030.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747124", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1475.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,475.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747121", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 975.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$975.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747120", - "placed": 1398135600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747169", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747175", - "placed": 1401073200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1067.85, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,067.85" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747166", - "placed": 1400727600000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 5.95, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$5.95" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3747349", - "placed": 1410145200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 59.50, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$59.50" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3747352", - "placed": 1410145200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1125.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,125.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3746995", - "placed": 1392260400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 50.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$50.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3746996", - "placed": 1392260400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747016", - "placed": 1392606000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 61.90, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$61.90" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Valid", - "code": "3746998", - "placed": 1392260400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 3125.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$3,125.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3746993", - "placed": 1391569200000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 130.95, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$130.95" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3746994", - "placed": 1392174000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747015", - "placed": 1392606000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747043", - "placed": 1392606000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3747044", - "placed": 1392692400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 0.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$0.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747014", - "placed": 1392606000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 20.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$20.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747017", - "placed": 1392606000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 50.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$50.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3747045", - "placed": 1392692400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 75.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$75.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Complete", - "code": "3747046", - "placed": 1392692400000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 50.00, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$50.00" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3747054", - "placed": 1393470000000 -}, { - "total": { - "currencyIso": "USD", - "priceType": "BUY", - "value": 1541.09, - "maxQuantity": null, - "minQuantity": null, - "formattedValue": "$1,541.09" - }, - "guid": null, - "managers": null, - "purchaseOrderNumber": null, - "status": null, - "b2bPermissionResults": null, - "statusDisplay": "Hold", - "code": "3746952", - "placed": 1384484400000 -}] \ No newline at end of file +[ + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 6100.00, + "formattedValue": "$6,100.00" + }, + "statusDisplay": "Valid", + "code": "3747453", + "placed": 1417402800000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1100.00, + "formattedValue": "$1,100.00" + }, + "statusDisplay": "Hold", + "code": "3747092", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1125.00, + "formattedValue": "$1,125.00" + }, + "statusDisplay": "Hold", + "code": "3747116", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1025.00, + "formattedValue": "$1,025.00" + }, + "statusDisplay": "Hold", + "code": "3747118", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1080.00, + "formattedValue": "$1,080.00" + }, + "statusDisplay": "Hold", + "code": "3747093", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1050.00, + "formattedValue": "$1,050.00" + }, + "statusDisplay": "Hold", + "code": "3747099", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1080.00, + "formattedValue": "$1,080.00" + }, + "statusDisplay": "Hold", + "code": "3747090", + "placed": 1397790000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1135.00, + "formattedValue": "$1,135.00" + }, + "statusDisplay": "Hold", + "code": "3747094", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 975.00, + "formattedValue": "$975.00" + }, + "statusDisplay": "Hold", + "code": "3747119", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 150.00, + "formattedValue": "$150.00" + }, + "statusDisplay": "Hold", + "code": "3747095", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 975.00, + "formattedValue": "$975.00" + }, + "statusDisplay": "Hold", + "code": "3747102", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747170", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747174", + "placed": 1401073200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 980.00, + "formattedValue": "$980.00" + }, + "statusDisplay": "Hold", + "code": "3747104", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1091.90, + "formattedValue": "$1,091.90" + }, + "statusDisplay": "Hold", + "code": "3747260", + "placed": 1404788400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747171", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1100.00, + "formattedValue": "$1,100.00" + }, + "statusDisplay": "Hold", + "code": "3747086", + "placed": 1397790000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1092.85, + "formattedValue": "$1,092.85" + }, + "statusDisplay": "Hold", + "code": "3747173", + "placed": 1401073200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1125.00, + "formattedValue": "$1,125.00" + }, + "statusDisplay": "Hold", + "code": "3747084", + "placed": 1397790000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 975.00, + "formattedValue": "$975.00" + }, + "statusDisplay": "Hold", + "code": "3747106", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 950.00, + "formattedValue": "$950.00" + }, + "statusDisplay": "Hold", + "code": "3747122", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1025.00, + "formattedValue": "$1,025.00" + }, + "statusDisplay": "Hold", + "code": "3747100", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 75.00, + "formattedValue": "$75.00" + }, + "statusDisplay": "Complete", + "code": "3747070", + "placed": 1394766000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1122.85, + "formattedValue": "$1,122.85" + }, + "statusDisplay": "Hold", + "code": "3747164", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 975.00, + "formattedValue": "$975.00" + }, + "statusDisplay": "Hold", + "code": "3747107", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 50.00, + "formattedValue": "$50.00" + }, + "statusDisplay": "Hold", + "code": "3747083", + "placed": 1396580400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Hold", + "code": "3747081", + "placed": 1395975600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1075.00, + "formattedValue": "$1,075.00" + }, + "statusDisplay": "Hold", + "code": "3747088", + "placed": 1397790000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1100.00, + "formattedValue": "$1,100.00" + }, + "statusDisplay": "Hold", + "code": "3747091", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1315.45, + "formattedValue": "$1,315.45" + }, + "statusDisplay": "Hold", + "code": "3747176", + "placed": 1401073200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1050.00, + "formattedValue": "$1,050.00" + }, + "statusDisplay": "Hold", + "code": "3747103", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1136.90, + "formattedValue": "$1,136.90" + }, + "statusDisplay": "Hold", + "code": "3747087", + "placed": 1397790000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1005.00, + "formattedValue": "$1,005.00" + }, + "statusDisplay": "Hold", + "code": "3747101", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1050.00, + "formattedValue": "$1,050.00" + }, + "statusDisplay": "Hold", + "code": "3747098", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 975.00, + "formattedValue": "$975.00" + }, + "statusDisplay": "Hold", + "code": "3747105", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1073.80, + "formattedValue": "$1,073.80" + }, + "statusDisplay": "Hold", + "code": "3747165", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747167", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1035.00, + "formattedValue": "$1,035.00" + }, + "statusDisplay": "Hold", + "code": "3747125", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Hold", + "code": "3747082", + "placed": 1395975600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1125.00, + "formattedValue": "$1,125.00" + }, + "statusDisplay": "Hold", + "code": "3747085", + "placed": 1397790000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1179.75, + "formattedValue": "$1,179.75" + }, + "statusDisplay": "Hold", + "code": "3747168", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747172", + "placed": 1401073200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 950.00, + "formattedValue": "$950.00" + }, + "statusDisplay": "Hold", + "code": "3747123", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 950.00, + "formattedValue": "$950.00" + }, + "statusDisplay": "Hold", + "code": "3747108", + "placed": 1398049200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1050.00, + "formattedValue": "$1,050.00" + }, + "statusDisplay": "Hold", + "code": "3747117", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1030.00, + "formattedValue": "$1,030.00" + }, + "statusDisplay": "Hold", + "code": "3747124", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1475.00, + "formattedValue": "$1,475.00" + }, + "statusDisplay": "Hold", + "code": "3747121", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 975.00, + "formattedValue": "$975.00" + }, + "statusDisplay": "Hold", + "code": "3747120", + "placed": 1398135600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747169", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747175", + "placed": 1401073200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1067.85, + "formattedValue": "$1,067.85" + }, + "statusDisplay": "Hold", + "code": "3747166", + "placed": 1400727600000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 5.95, + "formattedValue": "$5.95" + }, + "statusDisplay": "Complete", + "code": "3747349", + "placed": 1410145200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 59.50, + "formattedValue": "$59.50" + }, + "statusDisplay": "Complete", + "code": "3747352", + "placed": 1410145200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1125.00, + "formattedValue": "$1,125.00" + }, + "statusDisplay": "Complete", + "code": "3746995", + "placed": 1392260400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 50.00, + "formattedValue": "$50.00" + }, + "statusDisplay": "Complete", + "code": "3746996", + "placed": 1392260400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Hold", + "code": "3747016", + "placed": 1392606000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 61.90, + "formattedValue": "$61.90" + }, + "statusDisplay": "Valid", + "code": "3746998", + "placed": 1392260400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 3125.00, + "formattedValue": "$3,125.00" + }, + "statusDisplay": "Hold", + "code": "3746993", + "placed": 1391569200000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 130.95, + "formattedValue": "$130.95" + }, + "statusDisplay": "Hold", + "code": "3746994", + "placed": 1392174000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Hold", + "code": "3747015", + "placed": 1392606000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Hold", + "code": "3747043", + "placed": 1392606000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Complete", + "code": "3747044", + "placed": 1392692400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 0.00, + "formattedValue": "$0.00" + }, + "statusDisplay": "Hold", + "code": "3747014", + "placed": 1392606000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 20.00, + "formattedValue": "$20.00" + }, + "statusDisplay": "Hold", + "code": "3747017", + "placed": 1392606000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 50.00, + "formattedValue": "$50.00" + }, + "statusDisplay": "Complete", + "code": "3747045", + "placed": 1392692400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 75.00, + "formattedValue": "$75.00" + }, + "statusDisplay": "Complete", + "code": "3747046", + "placed": 1392692400000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 50.00, + "formattedValue": "$50.00" + }, + "statusDisplay": "Hold", + "code": "3747054", + "placed": 1393470000000 + }, + { + "total": { + "currencyIso": "USD", + "priceType": "BUY", + "value": 1541.09, + "formattedValue": "$1,541.09" + }, + "statusDisplay": "Hold", + "code": "3746952", + "placed": 1384484400000 + } +] \ No newline at end of file diff --git a/demo/fixed-header/angular-md-grid.html b/demo/fixed-header/angular-md-grid.html new file mode 100644 index 0000000..78f4215 --- /dev/null +++ b/demo/fixed-header/angular-md-grid.html @@ -0,0 +1,259 @@ + + + + + Angular Data Grid - Fix Header Table + + + + + + + + + +
+ +
+ Angular Data Grid — Fix Header Table + + +
+
+ +
+
+
+

Fix Header Table

+ +

Features enabled: sorting, filtering, sync with browser URLs, pagination, item-per-page and fixed-header functionality. + Out-of-box Angular Material layout and + input controls used, + along with Material Design Light default CSS for + grid styling. + Project GitHub

+ +
+

How To Freeze Table Header

+

Using HTML Layout

+
+

The first option is to split table header and table body in two tables. + One way to do this is to follow the next steps:

+
    +
  • Use the next styles (with any fixed height) applied to table body container to make it scrollable:
    + .div-table-body { height: 600px; overflow-x: auto; overflow-y: scroll; }
    +
  • +
  • Make sure that th elements have the same padding as td elements have.
  • +
  • Use padding-right with the value of scroll bar width to make an offset for the table contains header.
  • +
  • Use width attribute for columns to sync widths.
  • +
+

Code Sample: +

+<table>
+    <thead>
+        ...
+    </thead>
+</table>
+<table>
+    <tbody class="div-table-body">
+        ...
+    </tbody>
+</table>
+        
+

+
+

Using Stand-alone Directive

+
+

Another option is to use the directive fixed-header that can be injected to the Data Grid like a separate module dataGridUtils.

+

To make it work it is needed to perform next steps:

+
    +
  • Include script to your application:
    + <script src="bower_components/angular-data-grid/dist/dataGridUtils.min.js"></script> +
  • +
  • Include css stylesheets to your application:
    + <link rel="stylesheet" href="bower_components/angular-data-grid/css/fixedHeader/fixed-header.css"> +
  • +
  • Inject dataGridUtils dependency in your module:
    + angular.module('myApp', ['dataGrid', 'dataGridUtils.fixedHeader']) +
  • +
  • Apply the directive fixed-header to the grid table:
    + <table class="table" fixed-header> +
  • +
+

The directive uses z-index: 99 if your page uses the same value or higher please make appropriate changes to fixed-header.scss file.

+

The directive also has additional attribute offset-from-element. + It is needed if you already have some other elements with fixed position above the table. + In this case you need to pass a class or id of the very last element (if there are several) to this attribute + to make the directive take + into consideration that header needs to be fixed with offset from above elements.

+

Example:
+

<table class="table" fixed-header offset-from-element=".the-class-on-above-fixed-element">
+ or +
<table class="table" fixed-header offset-from-element="#the-id-on-above-fixed-element”>
+

+
+

The directive subscribes on scroll event, but the event is not fired when directive is used inside <md-content>, so to make it work please use the directive + outside the <md-content> container. + For more information about this problem please review this issue. +

+
+
+
+
+
+ + + +
+
+ + +
+
+ + +
+
+ Clear Dates + +
+
+
+
+
+ {{filtered.length}} items total +
+
+
+ + + + 10 + 25 + 50 + 75 + + +
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Order # + + + + All Statuses + + {{option.text}} + + + + Date Placed + + + Total + +
+ +
+
+
+ + + + 10 + 25 + 50 + 75 + + +
+
+
+ CodePen +
+

See the Pen eJWWpM by AngularDataGrid (@AngularDataGrid) on CodePen. +

+ +
+
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/fixed-header/bootstrap-grid.html b/demo/fixed-header/bootstrap-grid.html new file mode 100644 index 0000000..b1e7991 --- /dev/null +++ b/demo/fixed-header/bootstrap-grid.html @@ -0,0 +1,279 @@ + + + + + Angular Data Grid - Fix Header Table + + + + + + + + + +
+
+

Fix Header Table

+ Features enabled: sorting, filtering (using both in-grid and external controls), sync with browser URLs, pagination, items-per-page and fixed-header functionality. + Angular UI Datepicker used for date controls, although you can use any other framework, plugin or styling. + Project GitHub +
+
+

How To Freeze Table Header

+

Using HTML Layout

+
+

The first option is to split table header and table body in two tables. + One way to do this is to follow the next steps:

+ +

Code Sample: +

+<table>
+    <thead>
+        ...
+    </thead>
+</table>
+<table>
+    <tbody class="div-table-body">
+        ...
+    </tbody>
+</table>
+        
+

+
+

Using Stand-alone Directive

+
+

Another option is to use the directive fixed-header that can be injected to the Data Grid like a separate module dataGridUtils.

+

To make it work it is needed to perform next steps:

+ +

The directive uses z-index: 99 if your page uses the same value or higher please make appropriate changes to fixed-header.scss file.

+

The directive also has additional attribute offset-from-element. + It is needed if you already have some other elements with fixed position above the table. + In this case you need to pass a class or id of the very last element (if there are several) to this attribute + to make the directive take + into consideration that header needs to be fixed with offset from above elements.

+

Example:
+

<table class="table" fixed-header offset-from-element=".the-class-on-above-fixed-element">
+ or +
<table class="table" fixed-header offset-from-element="#the-id-on-above-fixed-element">
+

+
+
+
+
+
+
+ + +
+
+
+
+ + +
+ + + + +
+
+
+
+
+ + +
+ + + + +
+
+ +
+
+
+
+
+
+
+
+
+ {{filtered.length}} items total +
+
+
+
+ +
+
+ + +
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Order # + + Date Placed + + + + Total +
+
+
+ +
+
+ + +
+
+
+
+
+
+
+ +
+

See the Pen xZddZm by AngularDataGrid (@AngularDataGrid) on CodePen.

+ +
+
+
+
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/demo/fixed-header/css/fixed-header.bootstrap.css b/demo/fixed-header/css/fixed-header.bootstrap.css new file mode 100644 index 0000000..421dc34 --- /dev/null +++ b/demo/fixed-header/css/fixed-header.bootstrap.css @@ -0,0 +1,16 @@ +.fixed-header { + border: none; } + .fixed-header tr { + background-color: #fff; } + +.tbody-offset:before { + color: white; + height: 60px; } + +.fixed-nav { + position: fixed; + width: 100%; + z-index: 5; } + +.padding-top-50 { + padding-top: 50px; } diff --git a/demo/fixed-header/css/fixed-header.material.css b/demo/fixed-header/css/fixed-header.material.css new file mode 100644 index 0000000..d1ce846 --- /dev/null +++ b/demo/fixed-header/css/fixed-header.material.css @@ -0,0 +1,8 @@ +.fixed-header { + border: 1px solid rgba(0, 0, 0, 0.12); } + .fixed-header tr { + background-color: #fff; } + +.tbody-offset:before { + color: white; + height: 60px; } diff --git a/demo/fixed-header/js/bootstrap/demoApp.js b/demo/fixed-header/js/bootstrap/demoApp.js new file mode 100644 index 0000000..8ec93b1 --- /dev/null +++ b/demo/fixed-header/js/bootstrap/demoApp.js @@ -0,0 +1,28 @@ +angular.module('myApp', ['ui.bootstrap', 'dataGrid', 'pagination', 'dataGridUtils.fixedHeader']) + .controller('myAppController', ['$scope', 'myAppFactory', function ($scope, myAppFactory) { + + $scope.gridOptions = { + data: [], + urlSync: true, + pagination: { + itemsPerPage: '75', + currentPage: 1 + } + }; + + myAppFactory.getData().then(function (responseData) { + $scope.gridOptions.data = responseData.data; + }); + + }]) + .factory('myAppFactory', function ($http) { + return { + getData: function () { + return $http({ + method: 'GET', + url: 'https://angular-data-grid.github.io/demo/data.json' + }); + } + } + }); + diff --git a/demo/fixed-header/js/material-design/demoApp.js b/demo/fixed-header/js/material-design/demoApp.js new file mode 100644 index 0000000..0532f3d --- /dev/null +++ b/demo/fixed-header/js/material-design/demoApp.js @@ -0,0 +1,27 @@ +angular.module('myApp', ['dataGrid', 'pagination', 'ngMaterial', 'dataGridUtils.fixedHeader']) + .controller('myAppController', ['$scope', 'myAppFactory', function ($scope, myAppFactory) { + + $scope.gridOptions = { + data: [], + urlSync: true, + pagination: { + itemsPerPage: '75', + currentPage: 1 + } + }; + myAppFactory.getData().then(function (responseData) { + $scope.gridOptions.data = responseData.data; + }); + + }]) + .factory('myAppFactory', function ($http) { + return { + getData: function () { + return $http({ + method: 'GET', + url: 'https://angular-data-grid.github.io/demo/data.json' + }); + } + } + }); + diff --git a/demo/fixed-header/scss/fixed-header.bootstrap.scss b/demo/fixed-header/scss/fixed-header.bootstrap.scss new file mode 100644 index 0000000..8c6ff62 --- /dev/null +++ b/demo/fixed-header/scss/fixed-header.bootstrap.scss @@ -0,0 +1,25 @@ +//styles specific to bootstrap sample (not required) +.fixed-header { + border: none; + tr { + background-color: #fff; + } +} + +.tbody-offset { + &:before { + color: white; + height: 60px; + } +} + +.fixed-nav { + position: fixed; + width: 100%; + z-index: 5; +} + +.padding-top-50 { + padding-top: 50px; +} + diff --git a/demo/fixed-header/scss/fixed-header.material.scss b/demo/fixed-header/scss/fixed-header.material.scss new file mode 100644 index 0000000..b1dfee6 --- /dev/null +++ b/demo/fixed-header/scss/fixed-header.material.scss @@ -0,0 +1,14 @@ +//styles specific to material design sample (not required) +.fixed-header { + border: 1px solid rgba(0,0,0,.12); + tr { + background-color: #fff; + } +} + +.tbody-offset { + &:before { + color: white; + height: 60px; + } +} \ No newline at end of file diff --git a/demo/material/css/angular-data-grid.material.css b/demo/material/css/angular-data-grid.material.css index 7e6dc66..7edbd95 100644 --- a/demo/material/css/angular-data-grid.material.css +++ b/demo/material/css/angular-data-grid.material.css @@ -51,33 +51,19 @@ th md-select { .pagination-page.active a { color: #fff; } -.material-icons, .sortable span:before, .sortable span:after { - font-family: 'Material Icons'; - font-size: 18px; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; } +.sortable .arrow { + position: relative; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; } -.sortable span { - position: relative; } - .sortable span:before { - content: 'arrow_drop_down'; - position: absolute; - right: -18px; - top: -2px; } - .sortable span:after { - content: 'arrow_drop_up'; - position: absolute; - right: -18px; - top: -9px; } +.sortable.sort-ascent .arrow { + top: -10px; + left: 5px; + border-bottom: 4px solid #757575; } -.sortable.sort-ascent span:before { - display: none; } - -.sortable.sort-ascent span:after { - top: -5px; } - -.sortable.sort-descent span:before { - top: -5px; } - -.sortable.sort-descent span:after { - display: none; } +.sortable.sort-descent .arrow { + top: 10px; + left: 5px; + border-top: 4px solid #757575; } diff --git a/demo/material/css/loading-bar.min.css b/demo/material/css/loading-bar.min.css new file mode 100644 index 0000000..db06f19 --- /dev/null +++ b/demo/material/css/loading-bar.min.css @@ -0,0 +1 @@ +#loading-bar,#loading-bar-spinner{-webkit-transition:350ms linear all;-moz-transition:350ms linear all;-o-transition:350ms linear all;transition:350ms linear all}#loading-bar .ng-enter,#loading-bar .ng-leave.ng-leave-active,#loading-bar-spinner .ng-enter,#loading-bar-spinner .ng-leave.ng-leave-active{opacity:0}#loading-bar .ng-enter.ng-enter-active,#loading-bar .ng-leave,#loading-bar-spinner .ng-enter.ng-enter-active,#loading-bar-spinner .ng-leave{opacity:1}#loading-bar{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.25);z-index:11002}#loading-bar .bar{-webkit-transition:width 350ms;-moz-transition:width 350ms;-o-transition:width 350ms;transition:width 350ms;background:#29d;position:fixed;z-index:11002;top:0;left:0;width:100%;height:2px;border-bottom-right-radius:1px;border-top-right-radius:1px}#loading-bar .peg{position:absolute;width:70px;right:0;top:0;height:2px}#loading-bar-spinner{display:block;position:fixed;z-index:11002;top:50%;left:50%;margin-left:-15px;margin-right:-15px}#loading-bar-spinner .spinner-icon{width:34px;height:34px;border:solid 2px transparent;border-top-color:#fff;border-left-color:#fff;-webkit-border-radius:17px;-moz-border-radius:17px;border-radius:17px;-webkit-animation:loading-bar-spinner .8s linear infinite;-moz-animation:loading-bar-spinner .8s linear infinite;-ms-animation:loading-bar-spinner .8s linear infinite;-o-animation:loading-bar-spinner .8s linear infinite;animation:loading-bar-spinner .8s linear infinite}@-webkit-keyframes loading-bar-spinner{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes loading-bar-spinner{0%{-moz-transform:rotate(0);transform:rotate(0)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes loading-bar-spinner{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes loading-bar-spinner{0%{-ms-transform:rotate(0);transform:rotate(0)}100%{-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes loading-bar-spinner{0%{transform:rotate(0)}100%{transform:rotate(360deg)}} \ No newline at end of file diff --git a/demo/material/index.html b/demo/material/index.html index cf9ec30..76acecd 100644 --- a/demo/material/index.html +++ b/demo/material/index.html @@ -5,8 +5,8 @@ Angular Data Grid - Material Design - - + + @@ -14,7 +14,6 @@
Angular Data Grid — Material Design - @@ -33,21 +32,26 @@
- +
-
-

Angular Data Grid sample using Material Design styling

+

Angular Data Grid sample using Material Design styling

-

Features enabled: sorting, filtering, sync with browser URLs, pagination and item-per-page functionality. - Out-of-box Angular Material layout and input controls used, - along with Material Design Light default CSS for grid styling. - Project GitHub

-
+

Features enabled: sorting, filtering, sync with browser URLs, pagination and item-per-page functionality. + Out-of-box Angular Material layout and input controls used, + along with Material Design Light default CSS for grid styling. + Project GitHub

+

Additional Demos

+
+
-
- +
+ Angular Data Grid sample using Material Design styling aria-invalid="false">
-
+
+ ng-change="gridActions.filter()"> +
-
+
+ ng-change="gridActions.filter()"> +
-
- Clear Dates +
+ Clear Dates +
@@ -99,10 +106,10 @@

Angular Data Grid sample using Material Design styling

items-per-page="paginationOptions.itemsPerPage"> - 10 - 25 - 50 - 75 + 10 + 25 + 50 + 75
@@ -114,6 +121,7 @@

Angular Data Grid sample using Material Design styling

Order # + Angular Data Grid sample using Material Design styling Date Placed + Total + @@ -159,10 +169,10 @@

Angular Data Grid sample using Material Design styling

items-per-page="paginationOptions.itemsPerPage"> - 10 - 25 - 50 - 75 + 10 + 25 + 50 + 75
@@ -177,15 +187,15 @@

Angular Data Grid sample using Material Design styling


- +
- - - - - + + + + + diff --git a/demo/material/scss/angular-data-grid.material.scss b/demo/material/scss/angular-data-grid.material.scss index ff688a7..fdb8d81 100644 --- a/demo/material/scss/angular-data-grid.material.scss +++ b/demo/material/scss/angular-data-grid.material.scss @@ -64,44 +64,26 @@ th md-select { } } } -.material-icons { - font-family: 'Material Icons'; - font-size: 18px; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} .sortable { - span { + .arrow { position: relative; - &:before { - @extend .material-icons; - content: 'arrow_drop_down'; - position: absolute; - right: -18px; - top: -2px; - } - &:after { - @extend .material-icons; - content: 'arrow_drop_up'; - position: absolute; - right: -18px; - top: -9px; - } + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; } &.sort-ascent { - span:before { - display: none; - } - span:after { - top: -5px + .arrow { + top: -10px; + left: 5px; + border-bottom: 4px solid $backgroundArrows; } } &.sort-descent { - span:before { - top: -5px - } - span:after { - display: none; + .arrow { + top: 10px; + left: 5px; + border-top: 4px solid $backgroundArrows; } } } \ No newline at end of file diff --git a/demo/material/scss/angular-data-grid.variables.scss b/demo/material/scss/angular-data-grid.variables.scss index 60b1dda..2c7053a 100644 --- a/demo/material/scss/angular-data-grid.variables.scss +++ b/demo/material/scss/angular-data-grid.variables.scss @@ -2,4 +2,5 @@ $backgroundActive: rgb(63, 81, 181); $foregroundActive: #fff; $foregroundPagination: #ddd; $foregroundDefault: #444; -$backgroundIcons: #333; \ No newline at end of file +$backgroundIcons: #333; +$backgroundArrows: #757575; \ No newline at end of file diff --git a/dist/JSONToCSVConvertor.js b/dist/JSONToCSVConvertor.js new file mode 100644 index 0000000..3fce1b6 --- /dev/null +++ b/dist/JSONToCSVConvertor.js @@ -0,0 +1,84 @@ +function JSONToCSVConvertor(JSONData, ReportTitle, ShowLabel) { + //If JSONData is not an object then JSON.parse will parse the JSON string in an Object + var arrData = typeof JSONData != 'object' ? JSON.parse(JSONData) : JSONData; + + var CSV = ''; + //Set Report title in first row or line + + CSV += ReportTitle + '\r\n\n'; + + var currentDate = new Date(); + + CSV += currentDate + '\r\n\n'; + + //This condition will generate the Label/Header + if (ShowLabel) { + var row = ""; + + //This loop will extract the label from 1st index of on array + for (var index in arrData[0]) { + + //Now convert each value to string and comma-seprated + row += index + ','; + } + + row = row.slice(0, -1); + + //append Label row with line break + CSV += row + '\r\n'; + } + + //1st loop is to extract each row + for (var i = 0; i < arrData.length; i++) { + var row = ""; + + //2nd loop will extract each column and convert it in string comma-seprated + for (var index in arrData[i]) { + row += '"' + arrData[i][index] + '",'; + } + + row.slice(0, row.length - 1); + + //add a line break after each row + CSV += row + '\r\n'; + } + + if (CSV == '') { + alert("Invalid data"); + return; + } + + //Generate a file name + var fileName = ""; + //this will remove the blank-spaces from the title and replace it with an underscore + fileName += ReportTitle.replace(/ /g, "_"); + + //Initialize file format you want csv or xls + var uri = 'data:text/csv;charset=utf-8,' + escape(CSV); + + // Now the little tricky part. + // you can use either>> window.open(uri); + // but this will not work in some browsers + // or you will not get the correct file extension + + //this trick will generate a temp tag + + if (navigator.msSaveBlob) { + uri = 'data:text/csv;charset=utf-8,' + CSV; + navigator.msSaveBlob(new Blob([uri], {type: 'text/csv;charset=utf-8;'}), fileName + ".csv"); + } + + else { + var link = document.createElement("a"); + link.href = uri; + + //set the visibility hidden so it will not effect on your web-layout + link.style = "visibility:hidden"; + link.download = fileName + ".csv"; + + //this part will append the anchor tag and remove it after automatic click + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } +} \ No newline at end of file diff --git a/dist/css/fixedHeader/fixed-header.css b/dist/css/fixedHeader/fixed-header.css new file mode 100644 index 0000000..29c52fc --- /dev/null +++ b/dist/css/fixedHeader/fixed-header.css @@ -0,0 +1,10 @@ +.fixed-header { + top: 0; + position: fixed; + width: auto; + display: table; + z-index: 99; } + +.tbody-offset:before { + content: " "; + display: block; } diff --git a/dist/dataGrid.js b/dist/dataGrid.js index 94666e8..d7ddb3f 100644 --- a/dist/dataGrid.js +++ b/dist/dataGrid.js @@ -12,7 +12,7 @@ return []; } }) - .controller('gridController', ['$scope', '$element', '$filter', '$location', 'filtersFactory', function ($scope, $element, $filter, $location, filtersFactory) { + .controller('gridController', ['$scope', '$rootScope', '$element', '$filter', '$location', 'filtersFactory', function ($scope, $rootScope, $element, $filter, $location, filtersFactory) { // values by default $scope._gridOptions = $scope.$eval($element.attr('grid-options')); $scope._gridActions = $scope.$eval($element.attr('grid-actions')); @@ -29,7 +29,7 @@ $scope.filtered = $scope._gridOptions.data.slice(); $scope.paginationOptions = $scope._gridOptions.pagination ? angular.copy($scope._gridOptions.pagination) : {}; $scope.defaultsPaginationOptions = { - itemsPerPage: $scope.paginationOptions.itemsPerPage || '10', + itemsPerPage: $scope.paginationOptions.itemsPerPage, currentPage: $scope.paginationOptions.currentPage || 1 }; $scope.paginationOptions = angular.copy($scope.defaultsPaginationOptions); @@ -37,8 +37,8 @@ $scope.customFilters = $scope._gridOptions.customFilters ? angular.copy($scope._gridOptions.customFilters) : {}; $scope.urlSync = $scope._gridOptions.urlSync; - $scope.$watch('_gridOptions.data', function (newValue) { - if (newValue && newValue.length) { + $scope.$watchCollection('_gridOptions.data', function (newValue) { + if (newValue && newValue.length > -1) { $scope.sortCache = {}; $scope.filtered = $scope._gridOptions.data.slice(); $scope.filters.forEach(function (filter) { @@ -48,19 +48,21 @@ }); if ($scope.urlSync) { - parseUrl($location.path()); + parseUrl(); } else { applyFilters(); } } }); - $scope.sort = function (predicate) { - var direction = $scope.sortOptions.predicate === predicate && $scope.sortOptions.direction === 'desc' ? 'asc' : 'desc'; - $scope.sortOptions.predicate = predicate; - $scope.sortOptions.direction = direction; + $scope.sort = function (predicate, isDefaultSort) { + if (!isDefaultSort) { + var direction = $scope.sortOptions.predicate === predicate && $scope.sortOptions.direction === 'desc' ? 'asc' : 'desc'; + $scope.sortOptions.direction = direction; + $scope.sortOptions.predicate = predicate; + } $scope.paginationOptions.currentPage = 1; - $scope.reloadGrid(); + $scope.reloadGrid(isDefaultSort); }; $scope.filter = function () { @@ -69,30 +71,39 @@ }; $scope.$on('$locationChangeSuccess', function () { - if ($scope.urlSync || $scope.serverPagination) { - if ($scope.serverPagination) { - clearTimeout($scope.getDataTimeout); - $scope.getDataTimeout = setTimeout(getData, $scope.getDataDelay); - } - if ($scope.filtered) { - parseUrl($location.path()); - } - } + onChangeStateOrLocation() }); - $scope.reloadGrid = function () { + $scope.$on("$stateChangeSuccess", function (event, toState) { + onChangeStateOrLocation() + }); + + $scope.reloadGrid = function (isDefaultSort) { if ($scope.urlSync || $scope.serverPagination) { - changePath(); + changePath(isDefaultSort); } else { applyFilters(); } + $rootScope.$broadcast('gridReloaded'); }; $scope._gridActions.refresh = $scope.reloadGrid; $scope._gridActions.filter = $scope.filter; $scope._gridActions.sort = $scope.sort; - function changePath() { + function onChangeStateOrLocation(){ + if ($scope.urlSync || $scope.serverPagination) { + if ($scope.serverPagination) { + clearTimeout($scope.getDataTimeout); + $scope.getDataTimeout = setTimeout(getData, $scope.getDataDelay); + } + if ($scope.filtered) { + parseUrl(); + } + } + } + + function changePath(isDefaultSort) { var path, needApplyFilters = false; path = 'page=' + $scope.paginationOptions.currentPage; @@ -107,7 +118,7 @@ //custom filters $scope.filters.forEach(function (filter) { var urlName = filter.model, - value = $scope.$eval(urlName); + value = filter.isInScope ? $scope.$eval(urlName) : $scope.$parent.$eval(urlName); if (filter.disableUrl) { needApplyFilters = true; @@ -132,24 +143,21 @@ if (needApplyFilters) { applyFilters(); } - $location.path(path); + $location.search(path); + if (isDefaultSort) { + $scope.$apply(); + } } function parseUrl() { - var url = $location.path().slice(1), - params = {}, + var params = $location.search(), customParams = {}; - $scope.params = params; - - url.split('&').forEach(function (urlParam) { - var param = urlParam.split('='); - params[param[0]] = param[1]; - if (param[0] !== 'page' && param[0] !== 'sort' && param[0] !== 'itemsPerPage') { - customParams[decodeURIComponent(param[0])] = decodeURIComponent(param[1]); - } + Object.keys(params).forEach(function(key) { + if (key !== 'page' && key !== 'sort' && key !== 'itemsPerPage') { + customParams[key] = params[key]; } - ); + }); //custom filters $scope.filters.forEach(function (filter) { @@ -173,16 +181,16 @@ } if (value) { - $scope.__evaltmp = value; - $scope.$eval(urlName + '=__evaltmp'); + if (filter.isInScope) { + $scope.__evaltmp = value; + $scope.$eval(urlName + '=__evaltmp'); + } else { + $scope.$parent.__evaltmp = value; + $scope.$parent.$eval(urlName + '=__evaltmp'); + } } }); - if (!$scope.serverPagination) { - applyCustomFilters(); - } - - //pagination options $scope.paginationOptions.itemsPerPage = $scope.defaultsPaginationOptions.itemsPerPage; $scope.paginationOptions.currentPage = $scope.defaultsPaginationOptions.currentPage; @@ -211,11 +219,20 @@ } function getData() { - var url = $location.path().slice(1); - $scope._gridOptions.getData('?' + url, function (data, totalItems) { - $scope.filtered = data; - $scope.paginationOptions.totalItems = totalItems; + var url = ''; + var params = $location.search(); + Object.keys(params).forEach(function(key) { + url += key + '=' + params[key] + '&'; }); + url = url.slice(0, -1); + if (!url && $scope.sortOptions.predicate) { + $scope.sort($scope.sortOptions.predicate, true); + } else { + $scope._gridOptions.getData('?' + url, function (data, totalItems) { + $scope.filtered = data; + $scope.paginationOptions.totalItems = totalItems; + }); + } // -> to promise //$scope._gridOptions.getData('?' + url).then(function (data, totalItems) { // $scope.filtered = data; @@ -260,7 +277,7 @@ $scope.filters.forEach(function (filter) { var predicate = filter.filterBy, urlName = filter.model, - value = $scope.$eval(urlName), + value = filter.isInScope ? $scope.$eval(urlName) : $scope.$parent.$eval(urlName), type = filter.filterType; if ($scope.customFilters[urlName]) { $scope.filtered = $scope.customFilters[urlName]($scope.filtered, value, predicate); @@ -273,35 +290,52 @@ }); } }]) + .directive('gridItem', ['$compile', function ($compile) { + return { + restrict: 'EA', + terminal:true, + scope: false, + link: function ($scope, element, attrs, ctrl, transclude) { + if ($scope.serverPagination) { + element.attr('ng-repeat', "item in filtered"); + } else { + element.attr('ng-repeat', "item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"); + } + element.removeAttr('grid-item'); + var html = element[0].outerHTML; + element.replaceWith($compile(html)($scope)); + } + } + }]) .directive('gridData', ['$compile', '$animate', function ($compile) { return { restrict: 'EA', + //transclude: true, + //replace: true, scope: true, controller: 'gridController', link: function ($scope, $element, attrs) { - var sorting = [], - filters = [], - rows = [], + var filters = [], directiveElement = $element.parent(), gridId = attrs.id, serverPagination = attrs.serverPagination === 'true'; + $scope.serverPagination = serverPagination; angular.forEach(angular.element(directiveElement[0].querySelectorAll('[sortable]')), function (sortable) { var element = angular.element(sortable), predicate = element.attr('sortable'); - sorting.push(element); element.attr('ng-class', "{'sort-ascent' : sortOptions.predicate ==='" + predicate + "' && sortOptions.direction === 'asc', 'sort-descent' : sortOptions.predicate === '" + predicate + "' && sortOptions.direction === 'desc'}"); element.attr('ng-click', "sort('" + predicate + "')"); $compile(element)($scope); }); - - angular.forEach(angular.element(document.querySelectorAll('[filter-by]')), function (filter) { + angular.forEach(document.querySelectorAll('[filter-by]'), function (filter) { var element = angular.element(filter), - isInScope = directiveElement.find(element).length > 0, predicate = element.attr('filter-by'), + dataGridElement = document.querySelectorAll('[grid-data]')[0], + isInScope = dataGridElement.querySelectorAll('[filter-by="'+ predicate+'"]').length > 0, filterType = element.attr('filter-type') || '', urlName = element.attr('ng-model'), disableUrl = element.attr('disable-url'); @@ -319,13 +353,15 @@ && !element.attr('ng-blur')) { element.attr('ng-focus', "filter('{" + urlName + " : " + "this." + urlName + "}')"); element.attr('ng-blur', "filter('{" + urlName + " : " + "this." + urlName + "}')"); + //$compile(element)($scope); } if (!urlName) { urlName = predicate; element.attr('ng-model', predicate); element.attr('ng-change', 'filter()'); + //$compile(element)($scope); } - + //$compile(element)($scope); filters.push({ model: urlName, isInScope: isInScope, @@ -333,23 +369,8 @@ filterType: filterType, disableUrl: disableUrl }); - - $compile(element)($scope); - }); - - angular.forEach(angular.element(directiveElement[0].querySelectorAll('[grid-item]')), function (row) { - var element = angular.element(row); - rows.push(element); - if (serverPagination) { - element.attr('ng-repeat', "item in filtered"); - } else { - element.attr('ng-repeat', "item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"); - } - $compile(element)($scope); }); - $scope.sorting = sorting; - $scope.rows = rows; $scope.filters = filters; } } @@ -363,7 +384,7 @@ function textFilter(items, value, predicate) { return items.filter(function (item) { - return value && item[predicate] ? ~(item[predicate] + '').toLowerCase().indexOf((value + '').toLowerCase()) : true; + return value && item[predicate] ? ~(item[predicate] + '').toLowerCase().indexOf((value + '').toLowerCase()) : !!item[predicate]; }); } diff --git a/dist/dataGrid.min.js b/dist/dataGrid.min.js index f745d92..a3a012d 100644 --- a/dist/dataGrid.min.js +++ b/dist/dataGrid.min.js @@ -1 +1 @@ -!function(){"use strict";function t(t,e){var i=[];return t?(t.forEach(function(t){~i.indexOf(t[e])||i.push(t[e])}),i.map(function(t){return{text:t,value:t}})):void 0}angular.module("dataGrid",[]).filter("startFrom",function(){return function(t,e){return t?(e=+e,t.slice(e)):[]}}).controller("gridController",["$scope","$element","$filter","$location","filtersFactory",function(e,i,r,n,a){function o(){var t,i=!1;t="page="+e.paginationOptions.currentPage,e.paginationOptions.itemsPerPage!==e.defaultsPaginationOptions.itemsPerPage&&(t+="&itemsPerPage="+e.paginationOptions.itemsPerPage),e.sortOptions.predicate&&(t+="&sort="+encodeURIComponent(e.sortOptions.predicate+"-"+e.sortOptions.direction)),e.filters.forEach(function(r){var n=r.model,a=e.$eval(n);if(r.disableUrl)return void(i=!0);if(a){var o;if(a instanceof Date){if(isNaN(a.getTime()))return;o=a.getFullYear()+"-",o+=a.getMonth()<9?"0"+(a.getMonth()+1)+"-":a.getMonth()+1+"-",o+=a.getDate()<10?"0"+a.getDate():a.getDate(),a=o}t+="&"+encodeURIComponent(n)+"="+encodeURIComponent(a)}}),i&&c(),n.path(t)}function s(){var t=n.path().slice(1),i={},r={};if(e.params=i,t.split("&").forEach(function(t){var e=t.split("=");i[e[0]]=e[1],"page"!==e[0]&&"sort"!==e[0]&&"itemsPerPage"!==e[0]&&(r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]))}),e.filters.forEach(function(t){var i=t.model,n=r[i];if(!t.disableUrl){if(~t.filterType.toLowerCase().indexOf("date"))return e.$parent.__evaltmp=n?new Date(n):null,void e.$parent.$eval(i+"=__evaltmp");"select"!==t.filterType||n||(n=""),n&&(e.__evaltmp=n,e.$eval(i+"=__evaltmp"))}}),e.serverPagination||p(),e.paginationOptions.itemsPerPage=e.defaultsPaginationOptions.itemsPerPage,e.paginationOptions.currentPage=e.defaultsPaginationOptions.currentPage,i.itemsPerPage&&(e.paginationOptions.itemsPerPage=i.itemsPerPage),i.page&&(!e.serverPagination&&(i.page-1)*e.paginationOptions.itemsPerPage>e.filtered.length?e.paginationOptions.currentPage=1:e.paginationOptions.currentPage=i.page),i.sort){var a=i.sort.split("-");e.sortOptions.predicate=decodeURIComponent(a[0]),e.sortOptions.direction=decodeURIComponent(a[1])}e.serverPagination||c()}function l(){var t=n.path().slice(1);e._gridOptions.getData("?"+t,function(t,i){e.filtered=t,e.paginationOptions.totalItems=i})}function c(){var t=Date.now(),i=!1;e._time={},e.sortOptions.predicate&&e.sortCache&&e.sortCache.predicate===e.sortOptions.predicate&&e.sortCache.direction===e.sortOptions.direction?(e.filtered=e.sortCache.data.slice(),i=!0):e.filtered=e._gridOptions.data.slice(),e._time.copy=Date.now()-t;var n=Date.now();p(),e._time.filters=Date.now()-n;var a=Date.now();e.sortOptions.predicate&&!i&&(e.filtered=r("orderBy")(e.filtered,e.sortOptions.predicate,"desc"===e.sortOptions.direction),e.sortCache={data:e.filtered.slice(),predicate:e.sortOptions.predicate,direction:e.sortOptions.direction}),e._time.sort=Date.now()-a,e._time.all=Date.now()-t,e.paginationOptions.totalItems=e.filtered.length}function p(){e.filters.forEach(function(t){var i=t.filterBy,r=t.model,n=e.$eval(r),o=t.filterType;if(e.customFilters[r])e.filtered=e.customFilters[r](e.filtered,n,i);else if(n&&o){var s=a.getFilterByType(o);s&&(e.filtered=s(e.filtered,n,i))}})}e._gridOptions=e.$eval(i.attr("grid-options")),e._gridActions=e.$eval(i.attr("grid-actions")),e.serverPagination="true"===i.attr("server-pagination"),e.getDataDelay=i.attr("get-delay")||350,e._gridActions||(e.$parent.$eval(i.attr("grid-actions")+"= {}"),e._gridActions=e.$parent.$eval(i.attr("grid-actions"))),e._gridOptions.grid=e,e.filtered=e._gridOptions.data.slice(),e.paginationOptions=e._gridOptions.pagination?angular.copy(e._gridOptions.pagination):{},e.defaultsPaginationOptions={itemsPerPage:e.paginationOptions.itemsPerPage||"10",currentPage:e.paginationOptions.currentPage||1},e.paginationOptions=angular.copy(e.defaultsPaginationOptions),e.sortOptions=e._gridOptions.sort?angular.copy(e._gridOptions.sort):{},e.customFilters=e._gridOptions.customFilters?angular.copy(e._gridOptions.customFilters):{},e.urlSync=e._gridOptions.urlSync,e.$watch("_gridOptions.data",function(i){i&&i.length&&(e.sortCache={},e.filtered=e._gridOptions.data.slice(),e.filters.forEach(function(i){"select"===i.filterType&&(e[i.model+"Options"]=t(e.filtered,i.filterBy))}),e.urlSync?s(n.path()):c())}),e.sort=function(t){var i=e.sortOptions.predicate===t&&"desc"===e.sortOptions.direction?"asc":"desc";e.sortOptions.predicate=t,e.sortOptions.direction=i,e.paginationOptions.currentPage=1,e.reloadGrid()},e.filter=function(){e.paginationOptions.currentPage=1,e.reloadGrid()},e.$on("$locationChangeSuccess",function(){(e.urlSync||e.serverPagination)&&(e.serverPagination&&(clearTimeout(e.getDataTimeout),e.getDataTimeout=setTimeout(l,e.getDataDelay)),e.filtered&&s(n.path()))}),e.reloadGrid=function(){e.urlSync||e.serverPagination?o():c()},e._gridActions.refresh=e.reloadGrid,e._gridActions.filter=e.filter,e._gridActions.sort=e.sort}]).directive("gridData",["$compile","$animate",function(e){return{restrict:"EA",scope:!0,controller:"gridController",link:function(i,r,n){var a=[],o=[],s=[],l=r.parent(),c=n.id,p="true"===n.serverPagination;angular.forEach(angular.element(l[0].querySelectorAll("[sortable]")),function(t){var r=angular.element(t),n=r.attr("sortable");a.push(r),r.attr("ng-class","{'sort-ascent' : sortOptions.predicate ==='"+n+"' && sortOptions.direction === 'asc', 'sort-descent' : sortOptions.predicate === '"+n+"' && sortOptions.direction === 'desc'}"),r.attr("ng-click","sort('"+n+"')"),e(r)(i)}),angular.forEach(angular.element(document.querySelectorAll("[filter-by]")),function(n){var a=angular.element(n),s=l.find(a).length>0,p=a.attr("filter-by"),g=a.attr("filter-type")||"",d=a.attr("ng-model"),u=a.attr("disable-url");c&&a.attr("grid-id")&&c!=a.attr("grid-id")||("select"!==g||(i[d+"Options"]=t(i.$eval(r.attr("grid-options")+".data"),p)),!~g.indexOf("date")||a.attr("ng-focus")||a.attr("ng-blur")||(a.attr("ng-focus","filter('{"+d+" : this."+d+"}')"),a.attr("ng-blur","filter('{"+d+" : this."+d+"}')")),d||(d=p,a.attr("ng-model",p),a.attr("ng-change","filter()")),o.push({model:d,isInScope:s,filterBy:p,filterType:g,disableUrl:u}),e(a)(i))}),angular.forEach(angular.element(l[0].querySelectorAll("[grid-item]")),function(t){var r=angular.element(t);s.push(r),p?r.attr("ng-repeat","item in filtered"):r.attr("ng-repeat","item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"),e(r)(i)}),i.sorting=a,i.rows=s,i.filters=o}}}]).factory("filtersFactory",function(){function t(t,e,i){return t.filter(function(t){return e&&t[i]?t[i]===e:!0})}function e(t,e,i){return t.filter(function(t){return e&&t[i]?~(t[i]+"").toLowerCase().indexOf((e+"").toLowerCase()):!0})}function i(t,e,i){return e=new Date(e).getTime(),t.filter(function(t){return e&&t[i]?t[i]<=e+86399999:!0})}function r(t,e,i){return e=new Date(e).getTime(),t.filter(function(t){return e&&t[i]?t[i]>=e:!0})}return{getFilterByType:function(n){switch(n){case"select":return t;case"text":return e;case"dateTo":return i;case"dateFrom":return r;default:return null}}}})}(); \ No newline at end of file +!function(){"use strict";function t(t,e){var i=[];if(t)return t.forEach(function(t){~i.indexOf(t[e])||i.push(t[e])}),i.map(function(t){return{text:t,value:t}})}angular.module("dataGrid",[]).filter("startFrom",function(){return function(t,e){return t?(e=+e,t.slice(e)):[]}}).controller("gridController",["$scope","$rootScope","$element","$filter","$location","filtersFactory",function(e,i,r,n,a,o){function s(){(e.urlSync||e.serverPagination)&&(e.serverPagination&&(clearTimeout(e.getDataTimeout),e.getDataTimeout=setTimeout(p,e.getDataDelay)),e.filtered&&l())}function c(t){var i,r=!1;i="page="+e.paginationOptions.currentPage,e.paginationOptions.itemsPerPage!==e.defaultsPaginationOptions.itemsPerPage&&(i+="&itemsPerPage="+e.paginationOptions.itemsPerPage),e.sortOptions.predicate&&(i+="&sort="+encodeURIComponent(e.sortOptions.predicate+"-"+e.sortOptions.direction)),e.filters.forEach(function(t){var n=t.model,a=t.isInScope?e.$eval(n):e.$parent.$eval(n);if(t.disableUrl)return void(r=!0);if(a){var o;if(a instanceof Date){if(isNaN(a.getTime()))return;o=a.getFullYear()+"-",o+=a.getMonth()<9?"0"+(a.getMonth()+1)+"-":a.getMonth()+1+"-",o+=a.getDate()<10?"0"+a.getDate():a.getDate(),a=o}i+="&"+encodeURIComponent(n)+"="+encodeURIComponent(a)}}),r&&d(),a.search(i),t&&e.$apply()}function l(){var t=a.search(),i={};if(Object.keys(t).forEach(function(e){"page"!==e&&"sort"!==e&&"itemsPerPage"!==e&&(i[e]=t[e])}),e.filters.forEach(function(t){var r=t.model,n=i[r];if(!t.disableUrl){if(~t.filterType.toLowerCase().indexOf("date"))return e.$parent.__evaltmp=n?new Date(n):null,void e.$parent.$eval(r+"=__evaltmp");"select"!==t.filterType||n||(n=""),n&&(t.isInScope?(e.__evaltmp=n,e.$eval(r+"=__evaltmp")):(e.$parent.__evaltmp=n,e.$parent.$eval(r+"=__evaltmp")))}}),e.paginationOptions.itemsPerPage=e.defaultsPaginationOptions.itemsPerPage,e.paginationOptions.currentPage=e.defaultsPaginationOptions.currentPage,t.itemsPerPage&&(e.paginationOptions.itemsPerPage=t.itemsPerPage),t.page&&(!e.serverPagination&&(t.page-1)*e.paginationOptions.itemsPerPage>e.filtered.length?e.paginationOptions.currentPage=1:e.paginationOptions.currentPage=t.page),t.sort){var r=t.sort.split("-");e.sortOptions.predicate=decodeURIComponent(r[0]),e.sortOptions.direction=decodeURIComponent(r[1])}e.serverPagination||d()}function p(){var t="",i=a.search();Object.keys(i).forEach(function(e){t+=e+"="+i[e]+"&"}),t=t.slice(0,-1),!t&&e.sortOptions.predicate?e.sort(e.sortOptions.predicate,!0):e._gridOptions.getData("?"+t,function(t,i){e.filtered=t,e.paginationOptions.totalItems=i})}function d(){var t=Date.now(),i=!1;e._time={},e.sortOptions.predicate&&e.sortCache&&e.sortCache.predicate===e.sortOptions.predicate&&e.sortCache.direction===e.sortOptions.direction?(e.filtered=e.sortCache.data.slice(),i=!0):e.filtered=e._gridOptions.data.slice(),e._time.copy=Date.now()-t;var r=Date.now();g(),e._time.filters=Date.now()-r;var a=Date.now();e.sortOptions.predicate&&!i&&(e.filtered=n("orderBy")(e.filtered,e.sortOptions.predicate,"desc"===e.sortOptions.direction),e.sortCache={data:e.filtered.slice(),predicate:e.sortOptions.predicate,direction:e.sortOptions.direction}),e._time.sort=Date.now()-a,e._time.all=Date.now()-t,e.paginationOptions.totalItems=e.filtered.length}function g(){e.filters.forEach(function(t){var i=t.filterBy,r=t.model,n=t.isInScope?e.$eval(r):e.$parent.$eval(r),a=t.filterType;if(e.customFilters[r])e.filtered=e.customFilters[r](e.filtered,n,i);else if(n&&a){var s=o.getFilterByType(a);s&&(e.filtered=s(e.filtered,n,i))}})}e._gridOptions=e.$eval(r.attr("grid-options")),e._gridActions=e.$eval(r.attr("grid-actions")),e.serverPagination="true"===r.attr("server-pagination"),e.getDataDelay=r.attr("get-delay")||350,e._gridActions||(e.$parent.$eval(r.attr("grid-actions")+"= {}"),e._gridActions=e.$parent.$eval(r.attr("grid-actions"))),e._gridOptions.grid=e,e.filtered=e._gridOptions.data.slice(),e.paginationOptions=e._gridOptions.pagination?angular.copy(e._gridOptions.pagination):{},e.defaultsPaginationOptions={itemsPerPage:e.paginationOptions.itemsPerPage,currentPage:e.paginationOptions.currentPage||1},e.paginationOptions=angular.copy(e.defaultsPaginationOptions),e.sortOptions=e._gridOptions.sort?angular.copy(e._gridOptions.sort):{},e.customFilters=e._gridOptions.customFilters?angular.copy(e._gridOptions.customFilters):{},e.urlSync=e._gridOptions.urlSync,e.$watchCollection("_gridOptions.data",function(i){i&&i.length>-1&&(e.sortCache={},e.filtered=e._gridOptions.data.slice(),e.filters.forEach(function(i){"select"===i.filterType&&(e[i.model+"Options"]=t(e.filtered,i.filterBy))}),e.urlSync?l():d())}),e.sort=function(t,i){if(!i){var r=e.sortOptions.predicate===t&&"desc"===e.sortOptions.direction?"asc":"desc";e.sortOptions.direction=r,e.sortOptions.predicate=t}e.paginationOptions.currentPage=1,e.reloadGrid(i)},e.filter=function(){e.paginationOptions.currentPage=1,e.reloadGrid()},e.$on("$locationChangeSuccess",function(){s()}),e.$on("$stateChangeSuccess",function(t,e){s()}),e.reloadGrid=function(t){e.urlSync||e.serverPagination?c(t):d(),i.$broadcast("gridReloaded")},e._gridActions.refresh=e.reloadGrid,e._gridActions.filter=e.filter,e._gridActions.sort=e.sort}]).directive("gridItem",["$compile",function(t){return{restrict:"EA",terminal:!0,scope:!1,link:function(e,i,r,n,a){e.serverPagination?i.attr("ng-repeat","item in filtered"):i.attr("ng-repeat","item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"),i.removeAttr("grid-item");var o=i[0].outerHTML;i.replaceWith(t(o)(e))}}}]).directive("gridData",["$compile","$animate",function(e){return{restrict:"EA",scope:!0,controller:"gridController",link:function(i,r,n){var a=[],o=r.parent(),s=n.id,c="true"===n.serverPagination;i.serverPagination=c,angular.forEach(angular.element(o[0].querySelectorAll("[sortable]")),function(t){var r=angular.element(t),n=r.attr("sortable");r.attr("ng-class","{'sort-ascent' : sortOptions.predicate ==='"+n+"' && sortOptions.direction === 'asc', 'sort-descent' : sortOptions.predicate === '"+n+"' && sortOptions.direction === 'desc'}"),r.attr("ng-click","sort('"+n+"')"),e(r)(i)}),angular.forEach(document.querySelectorAll("[filter-by]"),function(e){var n=angular.element(e),o=n.attr("filter-by"),c=document.querySelectorAll("[grid-data]")[0],l=c.querySelectorAll('[filter-by="'+o+'"]').length>0,p=n.attr("filter-type")||"",d=n.attr("ng-model"),g=n.attr("disable-url");s&&n.attr("grid-id")&&s!=n.attr("grid-id")||("select"!==p||(i[d+"Options"]=t(i.$eval(r.attr("grid-options")+".data"),o)),!~p.indexOf("date")||n.attr("ng-focus")||n.attr("ng-blur")||(n.attr("ng-focus","filter('{"+d+" : this."+d+"}')"),n.attr("ng-blur","filter('{"+d+" : this."+d+"}')")),d||(d=o,n.attr("ng-model",o),n.attr("ng-change","filter()")),a.push({model:d,isInScope:l,filterBy:o,filterType:p,disableUrl:g}))}),i.filters=a}}}]).factory("filtersFactory",function(){function t(t,e,i){return t.filter(function(t){return!e||!t[i]||t[i]===e})}function e(t,e,i){return t.filter(function(t){return e&&t[i]?~(t[i]+"").toLowerCase().indexOf((e+"").toLowerCase()):!!t[i]})}function i(t,e,i){return e=new Date(e).getTime(),t.filter(function(t){return!e||!t[i]||t[i]<=e+86399999})}function r(t,e,i){return e=new Date(e).getTime(),t.filter(function(t){return!e||!t[i]||t[i]>=e})}return{getFilterByType:function(n){switch(n){case"select":return t;case"text":return e;case"dateTo":return i;case"dateFrom":return r;default:return null}}}})}(); \ No newline at end of file diff --git a/dist/dataGridUtils.js b/dist/dataGridUtils.js new file mode 100644 index 0000000..06f4061 --- /dev/null +++ b/dist/dataGridUtils.js @@ -0,0 +1,89 @@ +(function () { + 'use strict'; + angular.module('dataGridUtils', []); +})(); +(function () { + 'use strict'; + angular.module('dataGridUtils.fixedHeader', []) + .directive('fixedHeader', FixedHeader); + + FixedHeader.$inject = ['$window', '$timeout']; + + function FixedHeader($window, $timeout) { + var window = angular.element($window); + + return { + restrict: 'A', + link: link + }; + + function link(scope, element, attrs) { + var elementOffsetFrom = attrs.offsetFromElement ? + document.querySelector(attrs.offsetFromElement) : + window; + + function onResize() { + var thElements = element.find("th"); + for (var i = 0; i < thElements.length; i++) { + var tdElement = element.find("td").eq(i)[0]; + if (!tdElement) { + return; + } + var tdElementWidth = tdElement.offsetWidth; + angular.element(thElements[i]).css({'width': tdElementWidth + 'px'}); + } + } + + function bindFixedToHeader() { + var thead = element.find("thead"), + tbody = element.find("tbody"), + tbodyLeftPos = tbody[0].getBoundingClientRect().left; + thead.addClass('fixed-header'); + if (attrs.offsetFromElement) { + var topElement = document.querySelector(attrs.offsetFromElement); + var offset = topElement.getBoundingClientRect().top + topElement.offsetHeight; + thead.css({"top": offset}); + } + thead.css({"left": tbodyLeftPos}); + tbody.addClass("tbody-offset"); + } + + function unBindFixedToHeader() { + var thead = element.find("thead"), + tbody = element.find("tbody"); + thead.removeClass('fixed-header'); + thead.css({"left": ""}); + thead.css({"top": ""}); + tbody.removeClass("tbody-offset"); + } + + function onScroll() { + var offset = attrs.offsetFromElement ? + elementOffsetFrom.getBoundingClientRect().top + elementOffsetFrom.offsetHeight : + $window.pageYOffset, + tableOffsetTop = attrs.offsetFromElement ? + element[0].getBoundingClientRect().top : + element[0].getBoundingClientRect().top + offset, + tableOffsetBottom = tableOffsetTop + element[0].offsetHeight - element.find("thead")[0].offsetHeight; + + if (offset < tableOffsetTop || offset > tableOffsetBottom) { + unBindFixedToHeader(); + } + else if (offset >= tableOffsetTop && offset <= tableOffsetBottom) { + bindFixedToHeader(); + } + onResize(); + } + + scope.$on('gridReloaded', function () { + $timeout(function () { + onResize(); + onScroll(); + }, 0); + }); + window.on('resize', onResize); + window.on('scroll', onScroll); + } + } + +})(); \ No newline at end of file diff --git a/dist/dataGridUtils.min.js b/dist/dataGridUtils.min.js new file mode 100644 index 0000000..fa1410f --- /dev/null +++ b/dist/dataGridUtils.min.js @@ -0,0 +1 @@ +!function(){"use strict";angular.module("dataGridUtils",[])}(),function(){"use strict";function e(e,t){function n(n,f,i){function d(){for(var e=f.find("th"),t=0;to?s():t>=n&&t<=o&&r(),d()}var l=i.offsetFromElement?document.querySelector(i.offsetFromElement):o;n.$on("gridReloaded",function(){t(function(){d(),a()},0)}),o.on("resize",d),o.on("scroll",a)}var o=angular.element(e);return{restrict:"A",link:n}}angular.module("dataGridUtils.fixedHeader",[]).directive("fixedHeader",e),e.$inject=["$window","$timeout"]}(); \ No newline at end of file diff --git a/dist/loading-bar.js b/dist/loading-bar.js new file mode 100644 index 0000000..d12341f --- /dev/null +++ b/dist/loading-bar.js @@ -0,0 +1,324 @@ +/*! + * angular-loading-bar v0.7.1 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2015 Wes Cruver + * License: MIT + */ +/* + * angular-loading-bar + * + * intercepts XHR requests and creates a loading bar. + * Based on the excellent nprogress work by rstacruz (more info in readme) + * + * (c) 2013 Wes Cruver + * License: MIT + */ + + +(function() { + + 'use strict'; + +// Alias the loading bar for various backwards compatibilities since the project has matured: + angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); + angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); + + + /** + * loadingBarInterceptor service + * + * Registers itself as an Angular interceptor and listens for XHR requests. + */ + angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) + .config(['$httpProvider', function ($httpProvider) { + + var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', '$log', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, $log, cfpLoadingBar) { + + /** + * The total number of requests made + */ + var reqsTotal = 0; + + /** + * The number of requests completed (either successfully or not) + */ + var reqsCompleted = 0; + + /** + * The amount of time spent fetching before showing the loading bar + */ + var latencyThreshold = cfpLoadingBar.latencyThreshold; + + /** + * $timeout handle for latencyThreshold + */ + var startTimeout; + + + /** + * calls cfpLoadingBar.complete() which removes the + * loading bar from the DOM. + */ + function setComplete() { + $timeout.cancel(startTimeout); + cfpLoadingBar.complete(); + reqsCompleted = 0; + reqsTotal = 0; + } + + /** + * Determine if the response has already been cached + * @param {Object} config the config option from the request + * @return {Boolean} retrns true if cached, otherwise false + */ + function isCached(config) { + var cache; + var defaultCache = $cacheFactory.get('$http'); + var defaults = $httpProvider.defaults; + + // Choose the proper cache source. Borrowed from angular: $http service + if ((config.cache || defaults.cache) && config.cache !== false && + (config.method === 'GET' || config.method === 'JSONP')) { + cache = angular.isObject(config.cache) ? config.cache + : angular.isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + var cached = cache !== undefined ? + cache.get(config.url) !== undefined : false; + + if (config.cached !== undefined && cached !== config.cached) { + return config.cached; + } + config.cached = cached; + return cached; + } + + + return { + 'request': function(config) { + // Check to make sure this request hasn't already been cached and that + // the requester didn't explicitly ask us to ignore this request: + if (!config.ignoreLoadingBar && !isCached(config)) { + $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); + if (reqsTotal === 0) { + startTimeout = $timeout(function() { + cfpLoadingBar.start(); + }, latencyThreshold); + } + reqsTotal++; + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + return config; + }, + + 'response': function(response) { + if (!response || !response.config) { + $log.error('Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); + return response; + } + + if (!response.config.ignoreLoadingBar && !isCached(response.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return response; + }, + + 'responseError': function(rejection) { + if (!rejection || !rejection.config) { + $log.error('Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); + return $q.reject(rejection); + } + + if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return $q.reject(rejection); + } + }; + }]; + + $httpProvider.interceptors.push(interceptor); + }]); + + + /** + * Loading Bar + * + * This service handles adding and removing the actual element in the DOM. + * Generally, best practices for DOM manipulation is to take place in a + * directive, but because the element itself is injected in the DOM only upon + * XHR requests, and it's likely needed on every view, the best option is to + * use a service. + */ + angular.module('cfp.loadingBar', []) + .provider('cfpLoadingBar', function() { + + this.includeSpinner = true; + this.includeBar = true; + this.latencyThreshold = 100; + this.startSize = 0.02; + this.parentSelector = 'body'; + this.spinnerTemplate = '
'; + this.loadingBarTemplate = '
'; + + this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { + var $animate; + var $parentSelector = this.parentSelector, + loadingBarContainer = angular.element(this.loadingBarTemplate), + loadingBar = loadingBarContainer.find('div').eq(0), + spinner = angular.element(this.spinnerTemplate); + + var incTimeout, + completeTimeout, + started = false, + status = 0; + + var includeSpinner = this.includeSpinner; + var includeBar = this.includeBar; + var startSize = this.startSize; + + /** + * Inserts the loading bar element into the dom, and sets it to 2% + */ + function _start() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + var $parent = $document.find($parentSelector).eq(0); + $timeout.cancel(completeTimeout); + + // do not continually broadcast the started event: + if (started) { + return; + } + + $rootScope.$broadcast('cfpLoadingBar:started'); + started = true; + + if (includeBar) { + $animate.enter(loadingBarContainer, $parent, angular.element($parent[0].lastChild)); + } + + if (includeSpinner) { + $animate.enter(spinner, $parent, angular.element($parent[0].lastChild)); + } + + _set(startSize); + } + + /** + * Set the loading bar's width to a certain percent. + * + * @param n any value between 0 and 1 + */ + function _set(n) { + if (!started) { + return; + } + var pct = (n * 100) + '%'; + loadingBar.css('width', pct); + status = n; + + // increment loadingbar to give the illusion that there is always + // progress but make sure to cancel the previous timeouts so we don't + // have multiple incs running at the same time. + $timeout.cancel(incTimeout); + incTimeout = $timeout(function() { + _inc(); + }, 250); + } + + /** + * Increments the loading bar by a random amount + * but slows down as it progresses + */ + function _inc() { + if (_status() >= 1) { + return; + } + + var rnd = 0; + + // TODO: do this mathmatically instead of through conditions + + var stat = _status(); + if (stat >= 0 && stat < 0.25) { + // Start out between 3 - 6% increments + rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; + } else if (stat >= 0.25 && stat < 0.65) { + // increment between 0 - 3% + rnd = (Math.random() * 3) / 100; + } else if (stat >= 0.65 && stat < 0.9) { + // increment between 0 - 2% + rnd = (Math.random() * 2) / 100; + } else if (stat >= 0.9 && stat < 0.99) { + // finally, increment it .5 % + rnd = 0.005; + } else { + // after 99%, don't increment: + rnd = 0; + } + + var pct = _status() + rnd; + _set(pct); + } + + function _status() { + return status; + } + + function _completeAnimation() { + status = 0; + started = false; + } + + function _complete() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + $rootScope.$broadcast('cfpLoadingBar:completed'); + _set(1); + + $timeout.cancel(completeTimeout); + + // Attempt to aggregate any start/complete calls within 500ms: + completeTimeout = $timeout(function() { + var promise = $animate.leave(loadingBarContainer, _completeAnimation); + if (promise && promise.then) { + promise.then(_completeAnimation); + } + $animate.leave(spinner); + }, 500); + } + + return { + start : _start, + set : _set, + status : _status, + inc : _inc, + complete : _complete, + includeSpinner : this.includeSpinner, + latencyThreshold : this.latencyThreshold, + parentSelector : this.parentSelector, + startSize : this.startSize + }; + + + }]; // + }); // wtf javascript. srsly +})(); // \ No newline at end of file diff --git a/dist/loading-bar.min.js b/dist/loading-bar.min.js new file mode 100644 index 0000000..2d76439 --- /dev/null +++ b/dist/loading-bar.min.js @@ -0,0 +1 @@ +!function(){"use strict";angular.module("angular-loading-bar",["cfp.loadingBarInterceptor"]),angular.module("chieffancypants.loadingBar",["cfp.loadingBarInterceptor"]),angular.module("cfp.loadingBarInterceptor",["cfp.loadingBar"]).config(["$httpProvider",function(e){var n=["$q","$cacheFactory","$timeout","$rootScope","$log","cfpLoadingBar",function(n,t,a,r,i,c){function o(){a.cancel(d),c.complete(),u=0,s=0}function l(n){var a,r=t.get("$http"),i=e.defaults;!n.cache&&!i.cache||n.cache===!1||"GET"!==n.method&&"JSONP"!==n.method||(a=angular.isObject(n.cache)?n.cache:angular.isObject(i.cache)?i.cache:r);var c=void 0!==a&&void 0!==a.get(n.url);return void 0!==n.cached&&c!==n.cached?n.cached:(n.cached=c,c)}var d,s=0,u=0,g=c.latencyThreshold;return{request:function(e){return e.ignoreLoadingBar||l(e)||(r.$broadcast("cfpLoadingBar:loading",{url:e.url}),0===s&&(d=a(function(){c.start()},g)),s++,c.set(u/s)),e},response:function(e){return e&&e.config?(e.config.ignoreLoadingBar||l(e.config)||(u++,r.$broadcast("cfpLoadingBar:loaded",{url:e.config.url,result:e}),u>=s?o():c.set(u/s)),e):(i.error("Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50"),e)},responseError:function(e){return e&&e.config?(e.config.ignoreLoadingBar||l(e.config)||(u++,r.$broadcast("cfpLoadingBar:loaded",{url:e.config.url,result:e}),u>=s?o():c.set(u/s)),n.reject(e)):(i.error("Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50"),n.reject(e))}}}];e.interceptors.push(n)}]),angular.module("cfp.loadingBar",[]).provider("cfpLoadingBar",function(){this.includeSpinner=!0,this.includeBar=!0,this.latencyThreshold=100,this.startSize=.02,this.parentSelector="body",this.spinnerTemplate='
',this.loadingBarTemplate='
',this.$get=["$injector","$document","$timeout","$rootScope",function(e,n,t,a){function r(){s||(s=e.get("$animate"));var r=n.find(h).eq(0);t.cancel(g),m||(a.$broadcast("cfpLoadingBar:started"),m=!0,b&&s.enter(p,r,angular.element(r[0].lastChild)),$&&s.enter(v,r,angular.element(r[0].lastChild)),i(S))}function i(e){if(m){var n=100*e+"%";f.css("width",n),B=e,t.cancel(u),u=t(function(){c()},250)}}function c(){if(!(o()>=1)){var e=0,n=o();e=n>=0&&n<.25?(3*Math.random()+3)/100:n>=.25&&n<.65?3*Math.random()/100:n>=.65&&n<.9?2*Math.random()/100:n>=.9&&n<.99?.005:0;var t=o()+e;i(t)}}function o(){return B}function l(){B=0,m=!1}function d(){s||(s=e.get("$animate")),a.$broadcast("cfpLoadingBar:completed"),i(1),t.cancel(g),g=t(function(){var e=s.leave(p,l);e&&e.then&&e.then(l),s.leave(v)},500)}var s,u,g,h=this.parentSelector,p=angular.element(this.loadingBarTemplate),f=p.find("div").eq(0),v=angular.element(this.spinnerTemplate),m=!1,B=0,$=this.includeSpinner,b=this.includeBar,S=this.startSize;return{start:r,set:i,status:o,inc:c,complete:d,includeSpinner:this.includeSpinner,latencyThreshold:this.latencyThreshold,parentSelector:this.parentSelector,startSize:this.startSize}}]})}(); \ No newline at end of file diff --git a/dist/pagination.min.js b/dist/pagination.min.js index 3dcecb4..eea082a 100644 --- a/dist/pagination.min.js +++ b/dist/pagination.min.js @@ -1 +1 @@ -!function(){"use strict";angular.module("paging",[]).factory("paging",["$parse",function(e){return{create:function(a,n,t){a.setNumPages=t.numPages?e(t.numPages).assign:angular.noop,a.ngModelCtrl={$setViewValue:angular.noop},a.init=function(i,r){a.ngModelCtrl=i,a.config=r,i.$render=function(){a.render()},t.itemsPerPage?n.$parent.$watch(e(t.itemsPerPage),function(e){a.itemsPerPage=parseInt(e,10),n.totalPages=a.calculateTotalPages(),a.updatePage()}):a.itemsPerPage=r.itemsPerPage,n.$watch("totalItems",function(e,t){(angular.isDefined(e)||e!==t)&&(n.totalPages=a.calculateTotalPages(),a.updatePage())})},a.calculateTotalPages=function(){var e=a.itemsPerPage<1?1:Math.ceil(n.totalItems/a.itemsPerPage);return Math.max(e||0,1)},a.render=function(){n.page=parseInt(a.ngModelCtrl.$viewValue,10)||1},n.selectPage=function(e,t){t&&t.preventDefault();var i=!n.ngDisabled||!t;i&&n.page!==e&&e>0&&e<=n.totalPages&&(t&&t.target&&t.target.blur(),a.ngModelCtrl.$setViewValue(e),a.ngModelCtrl.$render())},n.getText=function(e){return n[e+"Text"]||a.config[e+"Text"]},n.noPrevious=function(){return 1===n.page},n.noNext=function(){return n.page===n.totalPages},a.updatePage=function(){a.setNumPages(n.$parent,n.totalPages),n.page>n.totalPages?n.selectPage(n.totalPages):a.ngModelCtrl.$render()}}}}]),angular.module("pagination",["paging"]).controller("PaginationController",["$scope","$attrs","$parse","paging","paginationConfig",function(e,a,n,t,i){function r(e,a,n){return{number:e,text:a,active:n}}function s(e,a){var n=[],t=1,i=a,s=angular.isDefined(g)&&a>g;s&&(o?(t=Math.max(e-Math.floor(g/2),1),i=t+g-1,i>a&&(i=a,t=i-g+1)):(t=(Math.ceil(e/g)-1)*g+1,i=Math.min(t+g-1,a)));for(var l=t;i>=l;l++){var p=r(l,l,l===e);n.push(p)}if(s&&g>0&&(!o||u||c)){if(t>1){if(!c||t>3){var f=r(t-1,"...",!1);n.unshift(f)}if(c){if(3===t){var d=r(2,"2",!1);n.unshift(d)}var P=r(1,"1",!1);n.unshift(P)}}if(a>i){if(!c||a-2>i){var v=r(i+1,"...",!1);n.push(v)}if(c){if(i===a-2){var m=r(a-1,a-1,!1);n.push(m)}var $=r(a,a,!1);n.push($)}}}return n}var l=this,g=angular.isDefined(a.maxSize)?e.$parent.$eval(a.maxSize):i.maxSize,o=angular.isDefined(a.rotate)?e.$parent.$eval(a.rotate):i.rotate,u=angular.isDefined(a.forceEllipses)?e.$parent.$eval(a.forceEllipses):i.forceEllipses,c=angular.isDefined(a.boundaryLinkNumbers)?e.$parent.$eval(a.boundaryLinkNumbers):i.boundaryLinkNumbers;e.boundaryLinks=angular.isDefined(a.boundaryLinks)?e.$parent.$eval(a.boundaryLinks):i.boundaryLinks,e.directionLinks=angular.isDefined(a.directionLinks)?e.$parent.$eval(a.directionLinks):i.directionLinks,t.create(this,e,a),a.maxSize&&e.$parent.$watch(n(a.maxSize),function(e){g=parseInt(e,10),l.render()});var p=this.render;this.render=function(){p(),e.page>0&&e.page<=e.totalPages&&(e.pages=s(e.page,e.totalPages))}}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,boundaryLinkNumbers:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0,forceEllipses:!1}).directive("gridPagination",["$parse","paginationConfig",function(e,a){return{scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["gridPagination","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(e,a){return a.templateUrl||"src/template/pagination/pagination.html"},replace:!0,link:function(e,n,t,i){var r=i[0],s=i[1];s&&r.init(s,a)}}}]).run(["$templateCache",function(e){e.put("src/template/pagination/pagination.html","
")}])}(); \ No newline at end of file +!function(){"use strict";angular.module("paging",[]).factory("paging",["$parse",function(e){return{create:function(a,n,t){a.setNumPages=t.numPages?e(t.numPages).assign:angular.noop,a.ngModelCtrl={$setViewValue:angular.noop},a.init=function(i,r){a.ngModelCtrl=i,a.config=r,i.$render=function(){a.render()},t.itemsPerPage?n.$parent.$watch(e(t.itemsPerPage),function(e){a.itemsPerPage=parseInt(e,10),n.totalPages=a.calculateTotalPages(),a.updatePage()}):a.itemsPerPage=r.itemsPerPage,n.$watch("totalItems",function(e,t){(angular.isDefined(e)||e!==t)&&(n.totalPages=a.calculateTotalPages(),a.updatePage())})},a.calculateTotalPages=function(){var e=a.itemsPerPage<1?1:Math.ceil(n.totalItems/a.itemsPerPage);return Math.max(e||0,1)},a.render=function(){n.page=parseInt(a.ngModelCtrl.$viewValue,10)||1},n.selectPage=function(e,t){t&&t.preventDefault();var i=!n.ngDisabled||!t;i&&n.page!==e&&e>0&&e<=n.totalPages&&(t&&t.target&&t.target.blur(),a.ngModelCtrl.$setViewValue(e),a.ngModelCtrl.$render())},n.getText=function(e){return n[e+"Text"]||a.config[e+"Text"]},n.noPrevious=function(){return 1===n.page},n.noNext=function(){return n.page===n.totalPages},a.updatePage=function(){a.setNumPages(n.$parent,n.totalPages),n.page>n.totalPages?n.selectPage(n.totalPages):a.ngModelCtrl.$render()}}}}]),angular.module("pagination",["paging"]).controller("PaginationController",["$scope","$attrs","$parse","paging","paginationConfig",function(e,a,n,t,i){function r(e,a,n){return{number:e,text:a,active:n}}function s(e,a){var n=[],t=1,i=a,s=angular.isDefined(g)&&ga&&(i=a,t=i-g+1)):(t=(Math.ceil(e/g)-1)*g+1,i=Math.min(t+g-1,a)));for(var l=t;l<=i;l++){var p=r(l,l,l===e);n.push(p)}if(s&&g>0&&(!o||u||c)){if(t>1){if(!c||t>3){var f=r(t-1,"...",!1);n.unshift(f)}if(c){if(3===t){var d=r(2,"2",!1);n.unshift(d)}var P=r(1,"1",!1);n.unshift(P)}}if(i0&&e.page<=e.totalPages&&(e.pages=s(e.page,e.totalPages))}}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,boundaryLinkNumbers:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0,forceEllipses:!1}).directive("gridPagination",["$parse","paginationConfig",function(e,a){return{scope:{totalItems:"=",firstText:"@",previousText:"@",nextText:"@",lastText:"@",ngDisabled:"="},require:["gridPagination","?ngModel"],controller:"PaginationController",controllerAs:"pagination",templateUrl:function(e,a){return a.templateUrl||"src/template/pagination/pagination.html"},replace:!0,link:function(e,n,t,i){var r=i[0],s=i[1];s&&r.init(s,a)}}}]).run(["$templateCache",function(e){e.put("src/template/pagination/pagination.html","")}])}(); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index d3a2af8..cea1115 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,7 +21,7 @@ } gulp.task('js', function () { - gulp.src(['./src/**/*.js']) + gulp.src(['./src/js/vendors/*.js', './src/js/*.js']) .pipe(rename({dirname: ''})) .pipe(gulp.dest('./dist')) .pipe(uglify()) @@ -29,6 +29,15 @@ suffix: ".min" })) .pipe(gulp.dest('./dist')); + + gulp.src(['./src/js/directives/**/*.js']) + .pipe(concat('dataGridUtils.js')) + .pipe(gulp.dest('./dist')) + .pipe(uglify()) + .pipe(rename({ + suffix: ".min" + })) + .pipe(gulp.dest('./dist')); }); gulp.task('sass', function () { @@ -41,6 +50,15 @@ gulp.src('./demo/100k/scss/angular-data-grid.bootstrap.scss') .pipe(sass().on('error', sass.logError)) .pipe(gulp.dest('./demo/100k/css/')); + gulp.src('./demo/fixed-header/scss/fixed-header.bootstrap.scss') + .pipe(sass().on('error', sass.logError)) + .pipe(gulp.dest('./demo/fixed-header/css/')); + gulp.src('./demo/fixed-header/scss/fixed-header.material.scss') + .pipe(sass().on('error', sass.logError)) + .pipe(gulp.dest('./demo/fixed-header/css/')); + gulp.src('./src/js/directives/fixedHeader/fixed-header.scss') + .pipe(sass().on('error', sass.logError)) + .pipe(gulp.dest('./dist/css/fixedHeader/')); }); gulp.task('build', ['js', 'sass']); diff --git a/index.js b/index.js new file mode 100644 index 0000000..6b85ad2 --- /dev/null +++ b/index.js @@ -0,0 +1,7 @@ +require('./dist/dataGrid'); +require('./dist/pagination'); + +module.exports = { + dataGrid: 'dataGrid', + pagination: 'pagination', +}; diff --git a/package.json b/package.json index e91cc83..6c7b4de 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { - "name": "data-grid", - "version": "0.1.0", + "name": "angular-data-grid", + "version": "1.0.0", "scripts": { "start": "gulp serve", "prebuild": "npm install" }, - "dependencies": { + "devDependencies": { "browser-sync": "^2.10.0", "gulp": "^3.9.0", "gulp-concat": "^2.6.0", @@ -13,5 +13,32 @@ "gulp-rimraf": "*", "gulp-sass": "^2.1.0", "gulp-uglify": "^1.4.2" - } + }, + "description": "Light, flexible and performant Data Grid for AngularJS apps, with built-in sorting, pagination and filtering options, unified API for client-side and server-side data fetching, \r seamless synchronization with browser address bar and total freedom in mark-up and styling suitable for your application. Angular 1.3 - 1.6 compliant.", + "main": "index.js", + "dependencies": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/angular-data-grid/angular-data-grid.github.io.git" + }, + "keywords": [ + "angular data grid", + "angular-data-grid", + "angular grid", + "angular-grid", + "data grid", + "data grid for angular", + "ng-data-grid", + "ng-table", + "angular table", + "bootstrap data grid", + "material design grid", + "data table" + ], + "author": "Alexander Zhuk (https://github.com/angular-data-grid/angular-data-grid.github.io)", + "license": "ISC", + "bugs": { + "url": "https://github.com/angular-data-grid/angular-data-grid.github.io/issues" + }, + "homepage": "https://github.com/angular-data-grid/angular-data-grid.github.io#readme" } diff --git a/src/js/dataGrid.js b/src/js/dataGrid.js index 89bccc4..d7ddb3f 100644 --- a/src/js/dataGrid.js +++ b/src/js/dataGrid.js @@ -12,7 +12,7 @@ return []; } }) - .controller('gridController', ['$scope', '$element', '$filter', '$location', 'filtersFactory', function ($scope, $element, $filter, $location, filtersFactory) { + .controller('gridController', ['$scope', '$rootScope', '$element', '$filter', '$location', 'filtersFactory', function ($scope, $rootScope, $element, $filter, $location, filtersFactory) { // values by default $scope._gridOptions = $scope.$eval($element.attr('grid-options')); $scope._gridActions = $scope.$eval($element.attr('grid-actions')); @@ -29,7 +29,7 @@ $scope.filtered = $scope._gridOptions.data.slice(); $scope.paginationOptions = $scope._gridOptions.pagination ? angular.copy($scope._gridOptions.pagination) : {}; $scope.defaultsPaginationOptions = { - itemsPerPage: $scope.paginationOptions.itemsPerPage || '10', + itemsPerPage: $scope.paginationOptions.itemsPerPage, currentPage: $scope.paginationOptions.currentPage || 1 }; $scope.paginationOptions = angular.copy($scope.defaultsPaginationOptions); @@ -37,8 +37,8 @@ $scope.customFilters = $scope._gridOptions.customFilters ? angular.copy($scope._gridOptions.customFilters) : {}; $scope.urlSync = $scope._gridOptions.urlSync; - $scope.$watch('_gridOptions.data', function (newValue) { - if (newValue && newValue.length) { + $scope.$watchCollection('_gridOptions.data', function (newValue) { + if (newValue && newValue.length > -1) { $scope.sortCache = {}; $scope.filtered = $scope._gridOptions.data.slice(); $scope.filters.forEach(function (filter) { @@ -48,19 +48,21 @@ }); if ($scope.urlSync) { - parseUrl($location.path()); + parseUrl(); } else { applyFilters(); } } }); - $scope.sort = function (predicate) { - var direction = $scope.sortOptions.predicate === predicate && $scope.sortOptions.direction === 'desc' ? 'asc' : 'desc'; - $scope.sortOptions.predicate = predicate; - $scope.sortOptions.direction = direction; + $scope.sort = function (predicate, isDefaultSort) { + if (!isDefaultSort) { + var direction = $scope.sortOptions.predicate === predicate && $scope.sortOptions.direction === 'desc' ? 'asc' : 'desc'; + $scope.sortOptions.direction = direction; + $scope.sortOptions.predicate = predicate; + } $scope.paginationOptions.currentPage = 1; - $scope.reloadGrid(); + $scope.reloadGrid(isDefaultSort); }; $scope.filter = function () { @@ -69,30 +71,39 @@ }; $scope.$on('$locationChangeSuccess', function () { - if ($scope.urlSync || $scope.serverPagination) { - if ($scope.serverPagination) { - clearTimeout($scope.getDataTimeout); - $scope.getDataTimeout = setTimeout(getData, $scope.getDataDelay); - } - if ($scope.filtered) { - parseUrl($location.path()); - } - } + onChangeStateOrLocation() }); - $scope.reloadGrid = function () { + $scope.$on("$stateChangeSuccess", function (event, toState) { + onChangeStateOrLocation() + }); + + $scope.reloadGrid = function (isDefaultSort) { if ($scope.urlSync || $scope.serverPagination) { - changePath(); + changePath(isDefaultSort); } else { applyFilters(); } + $rootScope.$broadcast('gridReloaded'); }; $scope._gridActions.refresh = $scope.reloadGrid; $scope._gridActions.filter = $scope.filter; $scope._gridActions.sort = $scope.sort; - function changePath() { + function onChangeStateOrLocation(){ + if ($scope.urlSync || $scope.serverPagination) { + if ($scope.serverPagination) { + clearTimeout($scope.getDataTimeout); + $scope.getDataTimeout = setTimeout(getData, $scope.getDataDelay); + } + if ($scope.filtered) { + parseUrl(); + } + } + } + + function changePath(isDefaultSort) { var path, needApplyFilters = false; path = 'page=' + $scope.paginationOptions.currentPage; @@ -107,7 +118,7 @@ //custom filters $scope.filters.forEach(function (filter) { var urlName = filter.model, - value = $scope.$eval(urlName); + value = filter.isInScope ? $scope.$eval(urlName) : $scope.$parent.$eval(urlName); if (filter.disableUrl) { needApplyFilters = true; @@ -132,24 +143,21 @@ if (needApplyFilters) { applyFilters(); } - $location.path(path); + $location.search(path); + if (isDefaultSort) { + $scope.$apply(); + } } function parseUrl() { - var url = $location.path().slice(1), - params = {}, + var params = $location.search(), customParams = {}; - $scope.params = params; - - url.split('&').forEach(function (urlParam) { - var param = urlParam.split('='); - params[param[0]] = param[1]; - if (param[0] !== 'page' && param[0] !== 'sort' && param[0] !== 'itemsPerPage') { - customParams[decodeURIComponent(param[0])] = decodeURIComponent(param[1]); - } + Object.keys(params).forEach(function(key) { + if (key !== 'page' && key !== 'sort' && key !== 'itemsPerPage') { + customParams[key] = params[key]; } - ); + }); //custom filters $scope.filters.forEach(function (filter) { @@ -173,16 +181,16 @@ } if (value) { - $scope.__evaltmp = value; - $scope.$eval(urlName + '=__evaltmp'); + if (filter.isInScope) { + $scope.__evaltmp = value; + $scope.$eval(urlName + '=__evaltmp'); + } else { + $scope.$parent.__evaltmp = value; + $scope.$parent.$eval(urlName + '=__evaltmp'); + } } }); - if (!$scope.serverPagination) { - applyCustomFilters(); - } - - //pagination options $scope.paginationOptions.itemsPerPage = $scope.defaultsPaginationOptions.itemsPerPage; $scope.paginationOptions.currentPage = $scope.defaultsPaginationOptions.currentPage; @@ -211,11 +219,20 @@ } function getData() { - var url = $location.path().slice(1); - $scope._gridOptions.getData('?' + url, function (data, totalItems) { - $scope.filtered = data; - $scope.paginationOptions.totalItems = totalItems; + var url = ''; + var params = $location.search(); + Object.keys(params).forEach(function(key) { + url += key + '=' + params[key] + '&'; }); + url = url.slice(0, -1); + if (!url && $scope.sortOptions.predicate) { + $scope.sort($scope.sortOptions.predicate, true); + } else { + $scope._gridOptions.getData('?' + url, function (data, totalItems) { + $scope.filtered = data; + $scope.paginationOptions.totalItems = totalItems; + }); + } // -> to promise //$scope._gridOptions.getData('?' + url).then(function (data, totalItems) { // $scope.filtered = data; @@ -260,7 +277,7 @@ $scope.filters.forEach(function (filter) { var predicate = filter.filterBy, urlName = filter.model, - value = $scope.$eval(urlName), + value = filter.isInScope ? $scope.$eval(urlName) : $scope.$parent.$eval(urlName), type = filter.filterType; if ($scope.customFilters[urlName]) { $scope.filtered = $scope.customFilters[urlName]($scope.filtered, value, predicate); @@ -273,6 +290,23 @@ }); } }]) + .directive('gridItem', ['$compile', function ($compile) { + return { + restrict: 'EA', + terminal:true, + scope: false, + link: function ($scope, element, attrs, ctrl, transclude) { + if ($scope.serverPagination) { + element.attr('ng-repeat', "item in filtered"); + } else { + element.attr('ng-repeat', "item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"); + } + element.removeAttr('grid-item'); + var html = element[0].outerHTML; + element.replaceWith($compile(html)($scope)); + } + } + }]) .directive('gridData', ['$compile', '$animate', function ($compile) { return { restrict: 'EA', @@ -281,29 +315,27 @@ scope: true, controller: 'gridController', link: function ($scope, $element, attrs) { - var sorting = [], - filters = [], - rows = [], + var filters = [], directiveElement = $element.parent(), gridId = attrs.id, serverPagination = attrs.serverPagination === 'true'; + $scope.serverPagination = serverPagination; angular.forEach(angular.element(directiveElement[0].querySelectorAll('[sortable]')), function (sortable) { var element = angular.element(sortable), predicate = element.attr('sortable'); - sorting.push(element); element.attr('ng-class', "{'sort-ascent' : sortOptions.predicate ==='" + predicate + "' && sortOptions.direction === 'asc', 'sort-descent' : sortOptions.predicate === '" + predicate + "' && sortOptions.direction === 'desc'}"); element.attr('ng-click', "sort('" + predicate + "')"); $compile(element)($scope); }); - - angular.forEach(angular.element(document.querySelectorAll('[filter-by]')), function (filter) { + angular.forEach(document.querySelectorAll('[filter-by]'), function (filter) { var element = angular.element(filter), - isInScope = directiveElement.find(element).length > 0, predicate = element.attr('filter-by'), + dataGridElement = document.querySelectorAll('[grid-data]')[0], + isInScope = dataGridElement.querySelectorAll('[filter-by="'+ predicate+'"]').length > 0, filterType = element.attr('filter-type') || '', urlName = element.attr('ng-model'), disableUrl = element.attr('disable-url'); @@ -329,7 +361,7 @@ element.attr('ng-change', 'filter()'); //$compile(element)($scope); } - + //$compile(element)($scope); filters.push({ model: urlName, isInScope: isInScope, @@ -337,23 +369,8 @@ filterType: filterType, disableUrl: disableUrl }); - - $compile(element)($scope); - }); - - angular.forEach(angular.element(directiveElement[0].querySelectorAll('[grid-item]')), function (row) { - var element = angular.element(row); - rows.push(element); - if (serverPagination) { - element.attr('ng-repeat', "item in filtered"); - } else { - element.attr('ng-repeat', "item in filtered | startFrom:(paginationOptions.currentPage-1)*paginationOptions.itemsPerPage | limitTo:paginationOptions.itemsPerPage track by $index"); - } - $compile(element)($scope); }); - $scope.sorting = sorting; - $scope.rows = rows; $scope.filters = filters; } } @@ -367,7 +384,7 @@ function textFilter(items, value, predicate) { return items.filter(function (item) { - return value && item[predicate] ? ~(item[predicate] + '').toLowerCase().indexOf((value + '').toLowerCase()) : true; + return value && item[predicate] ? ~(item[predicate] + '').toLowerCase().indexOf((value + '').toLowerCase()) : !!item[predicate]; }); } diff --git a/src/js/directives/dataGridUtils.js b/src/js/directives/dataGridUtils.js new file mode 100644 index 0000000..11f7813 --- /dev/null +++ b/src/js/directives/dataGridUtils.js @@ -0,0 +1,4 @@ +(function () { + 'use strict'; + angular.module('dataGridUtils', []); +})(); \ No newline at end of file diff --git a/src/js/directives/fixedHeader/fixed-header.scss b/src/js/directives/fixedHeader/fixed-header.scss new file mode 100644 index 0000000..eeea5eb --- /dev/null +++ b/src/js/directives/fixedHeader/fixed-header.scss @@ -0,0 +1,15 @@ +//Here are described styles that are used bu fixed-header directive +.fixed-header { + top: 0; + position: fixed; + width: auto; + display: table; + z-index: 99; +} + +.tbody-offset { + &:before { + content: " "; + display: block; + } +} \ No newline at end of file diff --git a/src/js/directives/fixedHeader/fixedHeader.js b/src/js/directives/fixedHeader/fixedHeader.js new file mode 100644 index 0000000..8242357 --- /dev/null +++ b/src/js/directives/fixedHeader/fixedHeader.js @@ -0,0 +1,85 @@ +(function () { + 'use strict'; + angular.module('dataGridUtils.fixedHeader', []) + .directive('fixedHeader', FixedHeader); + + FixedHeader.$inject = ['$window', '$timeout']; + + function FixedHeader($window, $timeout) { + var window = angular.element($window); + + return { + restrict: 'A', + link: link + }; + + function link(scope, element, attrs) { + var elementOffsetFrom = attrs.offsetFromElement ? + document.querySelector(attrs.offsetFromElement) : + window; + + function onResize() { + var thElements = element.find("th"); + for (var i = 0; i < thElements.length; i++) { + var tdElement = element.find("td").eq(i)[0]; + if (!tdElement) { + return; + } + var tdElementWidth = tdElement.offsetWidth; + angular.element(thElements[i]).css({'width': tdElementWidth + 'px'}); + } + } + + function bindFixedToHeader() { + var thead = element.find("thead"), + tbody = element.find("tbody"), + tbodyLeftPos = tbody[0].getBoundingClientRect().left; + thead.addClass('fixed-header'); + if (attrs.offsetFromElement) { + var topElement = document.querySelector(attrs.offsetFromElement); + var offset = topElement.getBoundingClientRect().top + topElement.offsetHeight; + thead.css({"top": offset}); + } + thead.css({"left": tbodyLeftPos}); + tbody.addClass("tbody-offset"); + } + + function unBindFixedToHeader() { + var thead = element.find("thead"), + tbody = element.find("tbody"); + thead.removeClass('fixed-header'); + thead.css({"left": ""}); + thead.css({"top": ""}); + tbody.removeClass("tbody-offset"); + } + + function onScroll() { + var offset = attrs.offsetFromElement ? + elementOffsetFrom.getBoundingClientRect().top + elementOffsetFrom.offsetHeight : + $window.pageYOffset, + tableOffsetTop = attrs.offsetFromElement ? + element[0].getBoundingClientRect().top : + element[0].getBoundingClientRect().top + offset, + tableOffsetBottom = tableOffsetTop + element[0].offsetHeight - element.find("thead")[0].offsetHeight; + + if (offset < tableOffsetTop || offset > tableOffsetBottom) { + unBindFixedToHeader(); + } + else if (offset >= tableOffsetTop && offset <= tableOffsetBottom) { + bindFixedToHeader(); + } + onResize(); + } + + scope.$on('gridReloaded', function () { + $timeout(function () { + onResize(); + onScroll(); + }, 0); + }); + window.on('resize', onResize); + window.on('scroll', onScroll); + } + } + +})(); \ No newline at end of file diff --git a/src/js/vendors/JSONToCSVConvertor.js b/src/js/vendors/JSONToCSVConvertor.js new file mode 100644 index 0000000..3fce1b6 --- /dev/null +++ b/src/js/vendors/JSONToCSVConvertor.js @@ -0,0 +1,84 @@ +function JSONToCSVConvertor(JSONData, ReportTitle, ShowLabel) { + //If JSONData is not an object then JSON.parse will parse the JSON string in an Object + var arrData = typeof JSONData != 'object' ? JSON.parse(JSONData) : JSONData; + + var CSV = ''; + //Set Report title in first row or line + + CSV += ReportTitle + '\r\n\n'; + + var currentDate = new Date(); + + CSV += currentDate + '\r\n\n'; + + //This condition will generate the Label/Header + if (ShowLabel) { + var row = ""; + + //This loop will extract the label from 1st index of on array + for (var index in arrData[0]) { + + //Now convert each value to string and comma-seprated + row += index + ','; + } + + row = row.slice(0, -1); + + //append Label row with line break + CSV += row + '\r\n'; + } + + //1st loop is to extract each row + for (var i = 0; i < arrData.length; i++) { + var row = ""; + + //2nd loop will extract each column and convert it in string comma-seprated + for (var index in arrData[i]) { + row += '"' + arrData[i][index] + '",'; + } + + row.slice(0, row.length - 1); + + //add a line break after each row + CSV += row + '\r\n'; + } + + if (CSV == '') { + alert("Invalid data"); + return; + } + + //Generate a file name + var fileName = ""; + //this will remove the blank-spaces from the title and replace it with an underscore + fileName += ReportTitle.replace(/ /g, "_"); + + //Initialize file format you want csv or xls + var uri = 'data:text/csv;charset=utf-8,' + escape(CSV); + + // Now the little tricky part. + // you can use either>> window.open(uri); + // but this will not work in some browsers + // or you will not get the correct file extension + + //this trick will generate a temp tag + + if (navigator.msSaveBlob) { + uri = 'data:text/csv;charset=utf-8,' + CSV; + navigator.msSaveBlob(new Blob([uri], {type: 'text/csv;charset=utf-8;'}), fileName + ".csv"); + } + + else { + var link = document.createElement("a"); + link.href = uri; + + //set the visibility hidden so it will not effect on your web-layout + link.style = "visibility:hidden"; + link.download = fileName + ".csv"; + + //this part will append the anchor tag and remove it after automatic click + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } +} \ No newline at end of file diff --git a/src/js/vendors/loading-bar.js b/src/js/vendors/loading-bar.js new file mode 100644 index 0000000..d12341f --- /dev/null +++ b/src/js/vendors/loading-bar.js @@ -0,0 +1,324 @@ +/*! + * angular-loading-bar v0.7.1 + * https://chieffancypants.github.io/angular-loading-bar + * Copyright (c) 2015 Wes Cruver + * License: MIT + */ +/* + * angular-loading-bar + * + * intercepts XHR requests and creates a loading bar. + * Based on the excellent nprogress work by rstacruz (more info in readme) + * + * (c) 2013 Wes Cruver + * License: MIT + */ + + +(function() { + + 'use strict'; + +// Alias the loading bar for various backwards compatibilities since the project has matured: + angular.module('angular-loading-bar', ['cfp.loadingBarInterceptor']); + angular.module('chieffancypants.loadingBar', ['cfp.loadingBarInterceptor']); + + + /** + * loadingBarInterceptor service + * + * Registers itself as an Angular interceptor and listens for XHR requests. + */ + angular.module('cfp.loadingBarInterceptor', ['cfp.loadingBar']) + .config(['$httpProvider', function ($httpProvider) { + + var interceptor = ['$q', '$cacheFactory', '$timeout', '$rootScope', '$log', 'cfpLoadingBar', function ($q, $cacheFactory, $timeout, $rootScope, $log, cfpLoadingBar) { + + /** + * The total number of requests made + */ + var reqsTotal = 0; + + /** + * The number of requests completed (either successfully or not) + */ + var reqsCompleted = 0; + + /** + * The amount of time spent fetching before showing the loading bar + */ + var latencyThreshold = cfpLoadingBar.latencyThreshold; + + /** + * $timeout handle for latencyThreshold + */ + var startTimeout; + + + /** + * calls cfpLoadingBar.complete() which removes the + * loading bar from the DOM. + */ + function setComplete() { + $timeout.cancel(startTimeout); + cfpLoadingBar.complete(); + reqsCompleted = 0; + reqsTotal = 0; + } + + /** + * Determine if the response has already been cached + * @param {Object} config the config option from the request + * @return {Boolean} retrns true if cached, otherwise false + */ + function isCached(config) { + var cache; + var defaultCache = $cacheFactory.get('$http'); + var defaults = $httpProvider.defaults; + + // Choose the proper cache source. Borrowed from angular: $http service + if ((config.cache || defaults.cache) && config.cache !== false && + (config.method === 'GET' || config.method === 'JSONP')) { + cache = angular.isObject(config.cache) ? config.cache + : angular.isObject(defaults.cache) ? defaults.cache + : defaultCache; + } + + var cached = cache !== undefined ? + cache.get(config.url) !== undefined : false; + + if (config.cached !== undefined && cached !== config.cached) { + return config.cached; + } + config.cached = cached; + return cached; + } + + + return { + 'request': function(config) { + // Check to make sure this request hasn't already been cached and that + // the requester didn't explicitly ask us to ignore this request: + if (!config.ignoreLoadingBar && !isCached(config)) { + $rootScope.$broadcast('cfpLoadingBar:loading', {url: config.url}); + if (reqsTotal === 0) { + startTimeout = $timeout(function() { + cfpLoadingBar.start(); + }, latencyThreshold); + } + reqsTotal++; + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + return config; + }, + + 'response': function(response) { + if (!response || !response.config) { + $log.error('Broken interceptor detected: Config object not supplied in response:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); + return response; + } + + if (!response.config.ignoreLoadingBar && !isCached(response.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: response.config.url, result: response}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return response; + }, + + 'responseError': function(rejection) { + if (!rejection || !rejection.config) { + $log.error('Broken interceptor detected: Config object not supplied in rejection:\n https://github.com/chieffancypants/angular-loading-bar/pull/50'); + return $q.reject(rejection); + } + + if (!rejection.config.ignoreLoadingBar && !isCached(rejection.config)) { + reqsCompleted++; + $rootScope.$broadcast('cfpLoadingBar:loaded', {url: rejection.config.url, result: rejection}); + if (reqsCompleted >= reqsTotal) { + setComplete(); + } else { + cfpLoadingBar.set(reqsCompleted / reqsTotal); + } + } + return $q.reject(rejection); + } + }; + }]; + + $httpProvider.interceptors.push(interceptor); + }]); + + + /** + * Loading Bar + * + * This service handles adding and removing the actual element in the DOM. + * Generally, best practices for DOM manipulation is to take place in a + * directive, but because the element itself is injected in the DOM only upon + * XHR requests, and it's likely needed on every view, the best option is to + * use a service. + */ + angular.module('cfp.loadingBar', []) + .provider('cfpLoadingBar', function() { + + this.includeSpinner = true; + this.includeBar = true; + this.latencyThreshold = 100; + this.startSize = 0.02; + this.parentSelector = 'body'; + this.spinnerTemplate = '
'; + this.loadingBarTemplate = '
'; + + this.$get = ['$injector', '$document', '$timeout', '$rootScope', function ($injector, $document, $timeout, $rootScope) { + var $animate; + var $parentSelector = this.parentSelector, + loadingBarContainer = angular.element(this.loadingBarTemplate), + loadingBar = loadingBarContainer.find('div').eq(0), + spinner = angular.element(this.spinnerTemplate); + + var incTimeout, + completeTimeout, + started = false, + status = 0; + + var includeSpinner = this.includeSpinner; + var includeBar = this.includeBar; + var startSize = this.startSize; + + /** + * Inserts the loading bar element into the dom, and sets it to 2% + */ + function _start() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + var $parent = $document.find($parentSelector).eq(0); + $timeout.cancel(completeTimeout); + + // do not continually broadcast the started event: + if (started) { + return; + } + + $rootScope.$broadcast('cfpLoadingBar:started'); + started = true; + + if (includeBar) { + $animate.enter(loadingBarContainer, $parent, angular.element($parent[0].lastChild)); + } + + if (includeSpinner) { + $animate.enter(spinner, $parent, angular.element($parent[0].lastChild)); + } + + _set(startSize); + } + + /** + * Set the loading bar's width to a certain percent. + * + * @param n any value between 0 and 1 + */ + function _set(n) { + if (!started) { + return; + } + var pct = (n * 100) + '%'; + loadingBar.css('width', pct); + status = n; + + // increment loadingbar to give the illusion that there is always + // progress but make sure to cancel the previous timeouts so we don't + // have multiple incs running at the same time. + $timeout.cancel(incTimeout); + incTimeout = $timeout(function() { + _inc(); + }, 250); + } + + /** + * Increments the loading bar by a random amount + * but slows down as it progresses + */ + function _inc() { + if (_status() >= 1) { + return; + } + + var rnd = 0; + + // TODO: do this mathmatically instead of through conditions + + var stat = _status(); + if (stat >= 0 && stat < 0.25) { + // Start out between 3 - 6% increments + rnd = (Math.random() * (5 - 3 + 1) + 3) / 100; + } else if (stat >= 0.25 && stat < 0.65) { + // increment between 0 - 3% + rnd = (Math.random() * 3) / 100; + } else if (stat >= 0.65 && stat < 0.9) { + // increment between 0 - 2% + rnd = (Math.random() * 2) / 100; + } else if (stat >= 0.9 && stat < 0.99) { + // finally, increment it .5 % + rnd = 0.005; + } else { + // after 99%, don't increment: + rnd = 0; + } + + var pct = _status() + rnd; + _set(pct); + } + + function _status() { + return status; + } + + function _completeAnimation() { + status = 0; + started = false; + } + + function _complete() { + if (!$animate) { + $animate = $injector.get('$animate'); + } + + $rootScope.$broadcast('cfpLoadingBar:completed'); + _set(1); + + $timeout.cancel(completeTimeout); + + // Attempt to aggregate any start/complete calls within 500ms: + completeTimeout = $timeout(function() { + var promise = $animate.leave(loadingBarContainer, _completeAnimation); + if (promise && promise.then) { + promise.then(_completeAnimation); + } + $animate.leave(spinner); + }, 500); + } + + return { + start : _start, + set : _set, + status : _status, + inc : _inc, + complete : _complete, + includeSpinner : this.includeSpinner, + latencyThreshold : this.latencyThreshold, + parentSelector : this.parentSelector, + startSize : this.startSize + }; + + + }]; // + }); // wtf javascript. srsly +})(); // \ No newline at end of file