<!DOCTYPE html>
 <html lang="en">
<head>
    <title>Angular 2 ag-Grid starter</title>
    <script>document.write('<base href="' + document.location + '" />');</script>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <style> html, body { margin: 0; padding: 0; height: 100%; } </style>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
    <link rel="stylesheet" href="styles.css">

    <script src="mockServerComplex.js"></script>

    <!-- Polyfills -->
    <script src="https://unpkg.com/core-js@2.6.5/client/shim.min.js"></script>
    <script src="https://unpkg.com/zone.js@0.8.17/dist/zone.js"></script>
    <script src="https://unpkg.com/systemjs@0.19.39/dist/system.src.js"></script>

    <script>
        var appLocation = '';
        var boilerplatePath = '';
        var systemJsMap = {"ag-grid-community":"https:\/\/unpkg.com\/ag-grid-community@21.0.1\/dist\/ag-grid-community.js","ag-grid-community\/main":"https:\/\/unpkg.com\/ag-grid-community@21.0.1\/dist\/ag-grid-community.js","ag-grid-enterprise":"https:\/\/unpkg.com\/ag-grid-enterprise@21.0.1\/","ag-grid-react":"npm:ag-grid-react@21.0.1\/","ag-grid-angular":"npm:ag-grid-angular@21.0.1\/","ag-grid-vue":"npm:ag-grid-vue@21.0.1\/"};
    </script>

    <script src="systemjs.config.js"></script>

    <script>
    System.import('main.ts').catch(function(err){ console.error(err); });
    </script>

</head>
<body>
    <my-app>Loading ag-Grid example&hellip;</my-app>
</body>
</html>
import { Component, ViewChild } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import "ag-grid-enterprise";

@Component({
  selector: "my-app",
  template: `
    <ag-grid-angular
      #agGrid
      style="width: 100%; height: 100%;"
      id="myGrid"
      class="ag-theme-balham-dark"
      [columnDefs]="columnDefs"
      [defaultColDef]="defaultColDef"
      [rowBuffer]="rowBuffer"
      [rowModelType]="rowModelType"
      [animateRows]="true"
      [debug]="true"
      [suppressAggFuncInHeader]="true"
      [sideBar]="sideBar"
      [maxConcurrentDatasourceRequests]="maxConcurrentDatasourceRequests"
      
      [groupUseEntireRow]="true"
      [purgeClosedRowNodes]="true"
      [getRowHeight]="getRowHeight"
      [rowData]="rowData"
      (gridReady)="onGridReady($event)"
    ></ag-grid-angular>
    <div style="position: absolute; top: 0; left: 0; margin-top: 4px;">
      <div>
        Top Level Groups: <button (click)="setGroupHeight(25)">25px</button>
        <button (click)="setGroupHeight(50)">50px</button> <button (click)="setGroupHeight(100)">100px</button>
      </div>
    </div>
  `
})
export class AppComponent {
  private gridApi;
  private gridColumnApi;

  private columnDefs;
  private defaultColDef;
  private rowBuffer;
  private rowModelType;
  private sideBar;
  private maxConcurrentDatasourceRequests;
  private cacheBlockSize;
  private getRowHeight;
  private rowData: [];
  
   setGroupHeight(height) {
    groupHeight = height;
    this.gridApi.forEachNode(function(rowNode) {
        rowNode.setRowHeight(groupHeight)
    });
      this.gridApi.resetRowHeights();
    // this.gridApi.onRowHeightChanged();
    // this.gridApi.onRowHeightChanged();
     this.gridApi.purgeServerSideCache();
    console.log(groupHeight);
  }

  constructor(private http: HttpClient) {
    this.columnDefs = [
      {
        headerName: "Athlete",
        field: "athlete",
        enableRowGroup: true,
        filter: false
      },
      {
        headerName: "Age",
        field: "age",
        enablePivot: true,
        enableRowGroup: true,
        filter: "agNumberColumnFilter",
        filterParams: {
          filterOptions: ["equals", "lessThan", "greaterThan"]
        }
      },
      {
        headerName: "Country",
        field: "country",
        enableRowGroup: true,
        enablePivot: true,
        rowGroup: true,
        hide: true,
        filter: "agSetColumnFilter",
        filterParams: { values: countries() }
      },
      {
        headerName: "Year",
        field: "year",
        enableRowGroup: true,
        enablePivot: true,
        rowGroup: true,
        hide: true,
        filter: "agSetColumnFilter",
        filterParams: {
          values: ["2000", "2004", "2008", "2012"]
        }
      },
      {
        headerName: "Sport",
        field: "sport",
        enableRowGroup: true,
        enablePivot: true,
        filter: false
      },
      {
        headerName: "Gold",
        field: "gold",
        aggFunc: "sum",
        filter: false,
        enableValue: true
      },
      {
        headerName: "Silver",
        field: "silver",
        aggFunc: "sum",
        filter: false,
        enableValue: true
      },
      {
        headerName: "Bronze",
        field: "bronze",
        aggFunc: "sum",
        filter: false,
        enableValue: true
      }
    ];
    this.defaultColDef = {
      width: 100,
      allowedAggFuncs: ["sum", "min", "max", "random"],
      sortable: true,
      resizable: true,
      filter: true
    };
    this.rowBuffer = 0;
    this.rowModelType = "serverSide";
    this.sideBar = {
      toolPanels: [
        {
          id: "columns",
          labelDefault: "Columns",
          labelKey: "columns",
          iconKey: "columns",
          toolPanel: "agColumnsToolPanel",
          toolPanelParams: {
            suppressPivots: true,
            suppressPivotMode: true,
            suppressValues: true
          }
        }
      ]
    };
    this.maxConcurrentDatasourceRequests = 2;
    // this.cacheBlockSize = 100;
    this.getRowHeight = function(params) {
     return groupHeight;
    };
  }

  onGridReady(params) {
    this.gridApi = params.api;
    this.gridColumnApi = params.columnApi;

    this.http
      .get("https://raw.githubusercontent.com/ag-grid/ag-grid/master/packages/ag-grid-docs/src/olympicWinners.json")
      .subscribe(data => {
        var fakeServer = createFakeServer(data);
        var datasource = createServerSideDatasource(fakeServer);
        params.api.setServerSideDatasource(datasource);
      });
  }
}

function countries() {
  return [
    "United States",
    "Russia",
    "Australia",
    "Canada",
    "Norway",
    "China",
    "Zimbabwe",
    "Netherlands",
    "South Korea",
    "Croatia",
    "France",
    "Japan",
    "Hungary",
    "Germany",
    "Poland",
    "South Africa",
    "Sweden",
    "Ukraine",
    "Italy",
    "Czech Republic",
    "Austria",
    "Finland",
    "Romania",
    "Great Britain",
    "Jamaica",
    "Singapore",
    "Belarus",
    "Chile",
    "Spain",
    "Tunisia",
    "Brazil",
    "Slovakia",
    "Costa Rica",
    "Bulgaria",
    "Switzerland",
    "New Zealand",
    "Estonia",
    "Kenya",
    "Ethiopia",
    "Trinidad and Tobago",
    "Turkey",
    "Morocco",
    "Bahamas",
    "Slovenia",
    "Armenia",
    "Azerbaijan",
    "India",
    "Puerto Rico",
    "Egypt",
    "Kazakhstan",
    "Iran",
    "Georgia",
    "Lithuania",
    "Cuba",
    "Colombia",
    "Mongolia",
    "Uzbekistan",
    "North Korea",
    "Tajikistan",
    "Kyrgyzstan",
    "Greece",
    "Macedonia",
    "Moldova",
    "Chinese Taipei",
    "Indonesia",
    "Thailand",
    "Vietnam",
    "Latvia",
    "Venezuela",
    "Mexico",
    "Nigeria",
    "Qatar",
    "Serbia",
    "Serbia and Montenegro",
    "Hong Kong",
    "Denmark",
    "Portugal",
    "Argentina",
    "Afghanistan",
    "Gabon",
    "Dominican Republic",
    "Belgium",
    "Kuwait",
    "United Arab Emirates",
    "Cyprus",
    "Israel",
    "Algeria",
    "Montenegro",
    "Iceland",
    "Paraguay",
    "Cameroon",
    "Saudi Arabia",
    "Ireland",
    "Malaysia",
    "Uruguay",
    "Togo",
    "Mauritius",
    "Syria",
    "Botswana",
    "Guatemala",
    "Bahrain",
    "Grenada",
    "Uganda",
    "Sudan",
    "Ecuador",
    "Panama",
    "Eritrea",
    "Sri Lanka",
    "Mozambique",
    "Barbados"
  ];
}
function GroupInnerRenderer() {}
function loadTemplate(template) {
  var tempDiv = document.createElement("div");
  tempDiv.innerHTML = template;
  return tempDiv.firstChild;
}
function createServerSideDatasource(fakeServer) {
  function ServerSideDatasource(fakeServer, gridInstance) {
    this.fakeServer = fakeServer;
    this.gridInstance = gridInstance;
  }
  ServerSideDatasource.prototype.getRows = function(params) {
    var that = this;
    this.fakeServer.getData(params.request, function successCallback(resultForGrid, lastRow, secondaryColDefs) {
      params.successCallback(resultForGrid, lastRow);
    });
  };
  ServerSideDatasource.prototype.setSecondaryColsIntoGrid = function(secondaryColDefs) {
    var colDefHash = this.createColsHash(secondaryColDefs);
    if (this.colDefHash !== colDefHash) {
      this.gridInstance.columnApi.setSecondaryColumns(secondaryColDefs);
      this.colDefHash = colDefHash;
    }
  };
  ServerSideDatasource.prototype.createColsHash = function(colDefs) {
    if (!colDefs) {
      return null;
    }
    var parts = [];
    var that = this;
    colDefs.forEach(function(colDef) {
      if (colDef.children) {
        parts.push(colDef.groupId);
        parts.push("[" + that.createColsHash(colDef.children) + "]");
      } else {
        parts.push(colDef.colId);
        if (colDef.headerName) {
          parts.push(colDef.headerName);
        }
      }
    });
    return parts.join(",");
  };
  return new ServerSideDatasource(fakeServer);
}
function createFakeServer(data) {
  function FakeServer(allData) {
    this.allData = allData;
  }
  FakeServer.prototype.getData = function(request, callback) {
    var rowGroupCols = request.rowGroupCols;
    var groupKeys = request.groupKeys;
    var valueCols = request.valueCols;
    var pivotCols = request.pivotCols;
    var pivotMode = request.pivotMode;
    var pivotActive = pivotMode && pivotCols.length > 0 && valueCols.length > 0;
    var filterModel = request.filterModel;
    var sortModel = request.sortModel;
    var rowData = this.allData;
    var secondaryColDefs = null;
    rowData = this.filterList(rowData, filterModel);
    if (pivotActive) {
      var pivotResult = this.pivot(pivotCols, rowGroupCols, valueCols, rowData);
      rowData = pivotResult.data;
      valueCols = pivotResult.aggCols;
      secondaryColDefs = pivotResult.secondaryColDefs;
    }
    if (rowGroupCols.length > 0) {
      rowData = this.filterOutOtherGroups(rowData, groupKeys, rowGroupCols);
      var showingGroupLevel = rowGroupCols.length > groupKeys.length;
      if (showingGroupLevel) {
        rowData = this.buildGroupsFromData(rowData, rowGroupCols, groupKeys, valueCols);
      }
    } else if (pivotMode) {
      var rootGroup = this.aggregateList(rowData, valueCols);
      rowData = [rootGroup];
    }
    rowData = this.sortList(rowData, sortModel);
    var lastRowFound = rowData.length <= request.endRow;
    var lastRow = lastRowFound ? rowData.length : null;
    rowData = rowData.slice(request.startRow, request.endRow);
    setTimeout(function() {
      callback(rowData, lastRow, secondaryColDefs);
    }, 1000);
  };
  FakeServer.prototype.sortList = function(data, sortModel) {
    var sortPresent = sortModel && sortModel.length > 0;
    if (!sortPresent) {
      return data;
    }
    var resultOfSort = data.slice();
    resultOfSort.sort(function(a, b) {
      for (var k = 0; k < sortModel.length; k++) {
        var sortColModel = sortModel[k];
        var valueA = a[sortColModel.colId];
        var valueB = b[sortColModel.colId];
        if (valueA == valueB) {
          continue;
        }
        var sortDirection = sortColModel.sort === "asc" ? 1 : -1;
        if (valueA > valueB) {
          return sortDirection;
        } else {
          return sortDirection * -1;
        }
      }
      return 0;
    });
    return resultOfSort;
  };
  FakeServer.prototype.filterList = function(data, filterModel) {
    var filterPresent = filterModel && Object.keys(filterModel).length > 0;
    if (!filterPresent) {
      return data;
    }
    var resultOfFilter = [];
    for (var i = 0; i < data.length; i++) {
      var item = data[i];
      if (filterModel.age) {
        var age = item.age;
        var allowedAge = parseInt(filterModel.age.filter);
        if (filterModel.age.type == "equals") {
          if (age !== allowedAge) {
            continue;
          }
        } else if (filterModel.age.type == "lessThan") {
          if (age >= allowedAge) {
            continue;
          }
        } else {
          if (age <= allowedAge) {
            continue;
          }
        }
      }
      if (filterModel.year) {
        if (filterModel.year.indexOf(item.year.toString()) < 0) {
          continue;
        }
      }
      if (filterModel.country) {
        if (filterModel.country.indexOf(item.country) < 0) {
          continue;
        }
      }
      resultOfFilter.push(item);
    }
    return resultOfFilter;
  };
  FakeServer.prototype.iterateObject = function(object, callback) {
    if (!object) {
      return;
    }
    let keys = Object.keys(object);
    for (let i = 0; i < keys.length; i++) {
      let key = keys[i];
      let value = object[key];
      callback(key, value);
    }
  };
  FakeServer.prototype.pivot = function(pivotCols, rowGroupCols, valueCols, data) {
    var pivotData = [];
    var aggColsList = [];
    var colKeyExistsMap = {};
    var secondaryColDefs = [];
    var secondaryColDefsMap = {};
    data.forEach(function(item) {
      var pivotValues = [];
      pivotCols.forEach(function(pivotCol) {
        var pivotField = pivotCol.id;
        var pivotValue = item[pivotField];
        if (pivotValue !== null && pivotValue !== undefined && pivotValue.toString) {
          pivotValues.push(pivotValue.toString());
        } else {
          pivotValues.push("-");
        }
      });
      var pivotItem = {};
      valueCols.forEach(function(valueCol) {
        var valField = valueCol.id;
        var colKey = createColKey(pivotValues, valField);
        var value = item[valField];
        pivotItem[colKey] = value;
        if (!colKeyExistsMap[colKey]) {
          addNewAggCol(colKey, valueCol);
          addNewSecondaryColDef(colKey, pivotValues, valueCol);
          colKeyExistsMap[colKey] = true;
        }
      });
      rowGroupCols.forEach(function(rowGroupCol) {
        var rowGroupField = rowGroupCol.id;
        pivotItem[rowGroupField] = item[rowGroupField];
      });
      pivotData.push(pivotItem);
    });
    function addNewAggCol(colKey, valueCol) {
      var newCol = {
        id: colKey,
        field: colKey,
        aggFunc: valueCol.aggFunc
      };
      aggColsList.push(newCol);
    }
    function addNewSecondaryColDef(colKey, pivotValues, valueCol) {
      var parentGroup = null;
      var keyParts = [];
      pivotValues.forEach(function(pivotValue) {
        keyParts.push(pivotValue);
        var colKey = createColKey(keyParts);
        var groupColDef = secondaryColDefsMap[colKey];
        if (!groupColDef) {
          groupColDef = {
            groupId: colKey,
            headerName: pivotValue,
            children: []
          };
          secondaryColDefsMap[colKey] = groupColDef;
          if (parentGroup) {
            parentGroup.children.push(groupColDef);
          } else {
            secondaryColDefs.push(groupColDef);
          }
        }
        parentGroup = groupColDef;
      });
      parentGroup.children.push({
        colId: colKey,
        headerName: valueCol.aggFunc + "(" + valueCol.displayName + ")",
        field: colKey
      });
    }
    function createColKey(pivotValues, valueField) {
      var result = pivotValues.join("|");
      if (valueField !== undefined) {
        result += "|" + valueField;
      }
      return result;
    }
    return {
      data: pivotData,
      aggCols: aggColsList,
      secondaryColDefs: secondaryColDefs
    };
  };
  FakeServer.prototype.buildGroupsFromData = function(rowData, rowGroupCols, groupKeys, valueCols) {
    var rowGroupCol = rowGroupCols[groupKeys.length];
    var field = rowGroupCol.id;
    var mappedRowData = this.groupBy(rowData, field);
    var groups = [];
    var that = this;
    this.iterateObject(mappedRowData, function(key, rowData) {
      var groupItem = that.aggregateList(rowData, valueCols);
      groupItem[field] = key;
      groups.push(groupItem);
    });
    return groups;
  };
  FakeServer.prototype.aggregateList = function(rowData, valueCols) {
    var result = {};
    valueCols.forEach(function(valueCol) {
      var field = valueCol.id;
      var values = [];
      rowData.forEach(function(childItem) {
        var value = childItem[field];
        if (value !== undefined) {
          values.push(value);
        }
      });
      switch (valueCol.aggFunc) {
        case "sum":
          var sum = 0;
          values.forEach(function(value) {
            sum += value;
          });
          result[field] = sum;
          break;
        case "min":
          var min = null;
          values.forEach(function(value) {
            if (min === null || min > value) {
              min = value;
            }
          });
          result[field] = min;
          break;
        case "max":
          var max = null;
          values.forEach(function(value) {
            if (max === null || max < value) {
              max = value;
            }
          });
          result[field] = max;
          break;
        case "random":
          result[field] = Math.random();
          break;
        default:
          console.warn("unrecognised aggregation function: " + valueCol.aggFunc);
          break;
      }
    });
    return result;
  };
  FakeServer.prototype.filterOutOtherGroups = function(originalData, groupKeys, rowGroupCols) {
    var filteredData = originalData;
    var that = this;
    groupKeys.forEach(function(groupKey, index) {
      var rowGroupCol = rowGroupCols[index];
      var field = rowGroupCol.id;
      filteredData = that.filter(filteredData, function(item) {
        return item[field] == groupKey;
      });
    });
    return filteredData;
  };
  FakeServer.prototype.groupBy = function(data, field) {
    var result = {};
    data.forEach(function(item) {
      var key = item[field];
      var listForThisKey = result[key];
      if (!listForThisKey) {
        listForThisKey = [];
        result[key] = listForThisKey;
      }
      listForThisKey.push(item);
    });
    return result;
  };
  FakeServer.prototype.filter = function(data, callback) {
    var result = [];
    data.forEach(function(item) {
      if (callback(item)) {
        result.push(item);
      }
    });
    return result;
  };
  return new FakeServer(data);
}
var groupHeight = 25;
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms"; // <-- NgModel lives here
// HttpClient
import { HttpClientModule } from "@angular/common/http";

// ag-grid
import { AgGridModule } from "ag-grid-angular";
import { AppComponent } from "./app.component";

@NgModule({
  imports: [
    BrowserModule,
    FormsModule, // <-- import the FormsModule before binding with [(ngModel)]
    HttpClientModule,
    AgGridModule.withComponents([])
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}
function ServerSideDatasource(fakeServer, gridOptions) {
    this.fakeServer = fakeServer;
    this.gridOptions = gridOptions;
}

ServerSideDatasource.prototype.getRows = function(params) {
    // console.log('ServerSideDatasource.getRows: params = ', params);
    var that = this;
    this.fakeServer.getData(params.request,
        function successCallback(resultForGrid, lastRow, secondaryColDefs) {
            params.successCallback(resultForGrid, lastRow);
            // that.setSecondaryColsIntoGrid(secondaryColDefs);
        });
};

// we only set the secondary cols if they have changed since the last time. otherwise
// the cols would reset every time data comes back from the server (which means col
// width, positioning etc would be lost every time we eg expand a group, or load another
// block by scrolling down).
ServerSideDatasource.prototype.setSecondaryColsIntoGrid = function(secondaryColDefs) {
    var colDefHash = this.createColsHash(secondaryColDefs);
    if (this.colDefHash !== colDefHash) {
        this.gridOptions.columnApi.setSecondaryColumns(secondaryColDefs);
        this.colDefHash = colDefHash;
    }
};

ServerSideDatasource.prototype.createColsHash = function(colDefs) {
    if (!colDefs) { return null; }
    var parts = [];
    var that = this;
    colDefs.forEach( function(colDef) {
        if (colDef.children) {
            parts.push(colDef.groupId);
            parts.push('[' + that.createColsHash(colDef.children) + ']');
        } else {
            parts.push(colDef.colId);
            // headerName can change if the aggFunc was changed in a value col. if we didn't
            // do this, then the grid would not pick up on new header names as we move from
            // eg min to max.
            if (colDef.headerName) {
                parts.push(colDef.headerName);
            }
        }
    });
    return parts.join(',');
};

// THIS IS NOT PRODUCTION CODE
// in your application, you should be implementing the server logic in your server, maybe in JavaScript, but
// also maybe in Java, C# or another server side language. The server side would then typically query a database
// or another data store to get the data, and the grouping, aggregation and pivoting would be done by the data store.
// This fake server is only intended to demonstrate the interface between ag-Grid and the server side. The
// implementation details are not intended to be and example of how your server side should create results.
function FakeServer(allData) {
    this.allData = allData;
}

FakeServer.prototype.getData = function(request, callback) {

    // the row group cols, ie teh cols that the user has dragged into the
    // 'group by' zone, eg 'Country' and 'Year'
    var rowGroupCols = request.rowGroupCols;
    // the keys we are looking at. will be empty if looking at top level (either
    // no groups, or looking at top level groups). eg ['United States','2002']
    var groupKeys = request.groupKeys;
    // if going aggregation, contains the value columns, eg ['gold','silver','bronze']
    var valueCols = request.valueCols;
    // if pivoting, contains the columns we are pivoting by
    var pivotCols = request.pivotCols;

    var pivotMode = request.pivotMode;
    var pivotActive = pivotMode && pivotCols.length > 0 && valueCols.length > 0;

    // we are not doing sorting and filtering in this example, but if you did
    // want to sort or filter using your implementation, you would do it here.
    var filterModel = request.filterModel;
    var sortModel = request.sortModel;

    var rowData = this.allData;

    // if pivoting, this gets set
    var secondaryColDefs = null;

    rowData = this.filterList(rowData, filterModel);

    if (pivotActive) {
        var pivotResult = this.pivot(pivotCols, rowGroupCols, valueCols, rowData);
        rowData = pivotResult.data;
        valueCols = pivotResult.aggCols;
        secondaryColDefs = pivotResult.secondaryColDefs;
    }

    // if not grouping, just return the full set
    if (rowGroupCols.length>0) {
        // otherwise if grouping, a few steps...

        // first, if not the top level, take out everything that is not under the group
        // we are looking at.
        rowData = this.filterOutOtherGroups(rowData, groupKeys, rowGroupCols);

        // if we are showing a group level, we need to group, otherwise we are showing
        // a leaf level.
        var showingGroupLevel = rowGroupCols.length > groupKeys.length;

        if (showingGroupLevel) {
            rowData = this.buildGroupsFromData(rowData, rowGroupCols, groupKeys, valueCols);
        }
    } else if (pivotMode) {
        // if pivot mode active, but no grouping, then we aggregate everything in to one group
        var rootGroup = this.aggregateList(rowData, valueCols);
        rowData = [rootGroup];
    }

    // sort data if needed
    rowData = this.sortList(rowData, sortModel);

    // we mimic finding the last row. if the request exceeds the length of the
    // list, then we assume the last row is found. this would be similar to hitting
    // a database, where we have gone past the last row.
    var lastRowFound = (rowData.length <= request.endRow);
    var lastRow = lastRowFound ? rowData.length : null;

    // only return back the rows that the user asked for
    rowData = rowData.slice(request.startRow, request.endRow);

    // so that the example behaves like a server side call, we put
    // it in a timeout to a) give a delay and b) make it asynchronous
    setTimeout( function() {
        callback(rowData, lastRow, secondaryColDefs);
    }, 1000);
};

FakeServer.prototype.sortList = function(data, sortModel) {
    var sortPresent = sortModel && sortModel.length > 0;
    if (!sortPresent) {
        return data;
    }
    // do an in memory sort of the data, across all the fields
    var resultOfSort = data.slice();
    resultOfSort.sort(function(a,b) {
        for (var k = 0; k<sortModel.length; k++) {
            var sortColModel = sortModel[k];
            var valueA = a[sortColModel.colId];
            var valueB = b[sortColModel.colId];
            // this filter didn't find a difference, move onto the next one
            if (valueA==valueB) {
                continue;
            }
            var sortDirection = sortColModel.sort === 'asc' ? 1 : -1;
            if (valueA > valueB) {
                return sortDirection;
            } else {
                return sortDirection * -1;
            }
        }
        // no filters found a difference
        return 0;
    });
    return resultOfSort;
};

FakeServer.prototype.filterList = function(data, filterModel) {
    var filterPresent = filterModel && Object.keys(filterModel).length > 0;
    if (!filterPresent) {
        return data;
    }

    var resultOfFilter = [];
    for (var i = 0; i<data.length; i++) {
        var item = data[i];

        if (filterModel.age) {
            var age = item.age;
            var allowedAge = parseInt(filterModel.age.filter);
            if (filterModel.age.type == 'equals') {
                if (age !== allowedAge) {
                    continue;
                }
            } else if (filterModel.age.type == 'lessThan') {
                if (age >= allowedAge) {
                    continue;
                }
            } else {
                if (age <= allowedAge) {
                    continue;
                }
            }
        }

        if (filterModel.year) {
            if (filterModel.year.indexOf(item.year.toString()) < 0) {
                // year didn't match, so skip this record
                continue;
            }
        }

        if (filterModel.country) {
            if (filterModel.country.indexOf(item.country)<0) {
                continue;
            }
        }

        resultOfFilter.push(item);
    }

    return resultOfFilter;
};

FakeServer.prototype.iterateObject = function(object, callback) {
    if (!object) {
        return;
    }
    let keys = Object.keys(object);
    for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        let value = object[key];
        callback(key, value);
    }
};

// function does pivoting. this is very funky logic, doing pivoting and creating secondary columns on the fly.
// if you are using the ag-Grid Enterprise Row Model, remember this would all be done on your server side with a
// database or something that does pivoting for you - this messy code is just for demo purposes on how to use
// ag-Gird, it's not supposed to be beautiful production quality code.
FakeServer.prototype.pivot = function(pivotCols, rowGroupCols, valueCols, data) {
    // assume 1 pivot col and 1 value col for this example

    var pivotData = [];
    var aggColsList = [];

    var colKeyExistsMap = {};

    var secondaryColDefs = [];
    var secondaryColDefsMap = {};

    data.forEach( function(item) {

        var pivotValues = [];
        pivotCols.forEach( function(pivotCol) {
            var pivotField = pivotCol.id;
            var pivotValue = item[pivotField];
            if (pivotValue!==null && pivotValue!==undefined && pivotValue.toString) {
                pivotValues.push(pivotValue.toString());
            } else {
                pivotValues.push('-');
            }
        });

        // var pivotValue = item[pivotField].toString();
        var pivotItem = {};

        valueCols.forEach( function(valueCol) {
            var valField = valueCol.id;
            var colKey = createColKey(pivotValues, valField);

            var value = item[valField];
            pivotItem[colKey] = value;

            if (!colKeyExistsMap[colKey]) {
                addNewAggCol(colKey, valueCol);
                addNewSecondaryColDef(colKey, pivotValues, valueCol);
                colKeyExistsMap[colKey] = true;
            }
        });

        rowGroupCols.forEach( function(rowGroupCol) {
            var rowGroupField = rowGroupCol.id;
            pivotItem[rowGroupField] = item[rowGroupField];
        });

        pivotData.push(pivotItem);
    });

    function addNewAggCol(colKey, valueCol) {
        var newCol = {
            id: colKey,
            field: colKey,
            aggFunc: valueCol.aggFunc
        };
        aggColsList.push(newCol);
    }

    function addNewSecondaryColDef(colKey, pivotValues, valueCol) {

        var parentGroup = null;

        var keyParts = [];

        pivotValues.forEach( function(pivotValue) {
            keyParts.push(pivotValue);
            var colKey = createColKey(keyParts);
            var groupColDef = secondaryColDefsMap[colKey];
            if (!groupColDef) {
                groupColDef = {
                    groupId: colKey,
                    headerName: pivotValue,
                    children: []
                };
                secondaryColDefsMap[colKey] = groupColDef;
                if (parentGroup) {
                    parentGroup.children.push(groupColDef);
                } else {
                    secondaryColDefs.push(groupColDef);
                }
            }
            parentGroup = groupColDef;
        });

        parentGroup.children.push({
            colId: colKey,
            headerName: valueCol.aggFunc + '(' + valueCol.displayName + ')',
            field: colKey
        });
    }

    function createColKey(pivotValues, valueField) {
        var result = pivotValues.join('|');
        if (valueField!==undefined) {
            result += '|' + valueField;
        }
        return result;
    }

    return {
        data: pivotData,
        aggCols: aggColsList,
        secondaryColDefs: secondaryColDefs
    };
};

FakeServer.prototype.buildGroupsFromData = function(rowData, rowGroupCols, groupKeys, valueCols) {
    var rowGroupCol = rowGroupCols[groupKeys.length];
    var field = rowGroupCol.id;
    var mappedRowData = this.groupBy(rowData, field);
    var groups = [];
    var that = this;

    this.iterateObject(mappedRowData, function(key, rowData) {
        var groupItem = that.aggregateList(rowData, valueCols);
        groupItem[field] = key;
        groups.push(groupItem);
    });
    return groups;
};

FakeServer.prototype.aggregateList = function(rowData, valueCols) {

    var result = {};

    valueCols.forEach(function(valueCol) {
        var field = valueCol.id;

        var values = [];
        rowData.forEach( function(childItem) {
            var value = childItem[field];
            // if pivoting, value will be undefined if this row data has no value for the column
            if (value!==undefined) {
                values.push(value);
            }
        });

        // the aggregation we do depends on which agg func the user picked
        switch (valueCol.aggFunc) {
            case 'sum':
                var sum = 0;
                values.forEach( function(value) {
                    sum += value;
                });
                result[field] = sum;
                break;
            case 'min':
                var min = null;
                values.forEach( function(value) {
                    if (min===null || min > value) {
                        min = value;
                    }
                });
                result[field] = min;
                break;
            case 'max':
                var max = null;
                values.forEach( function(value) {
                    if (max===null || max < value) {
                        max = value;
                    }
                });
                result[field] = max;
                break;
            case 'random':
                result[field] = Math.random(); // just make up a number
                break;
            default:
                console.warn('unrecognised aggregation function: ' + valueCol.aggFunc);
                break;
        }

    });

    return result;
};

// if user is down some group levels, we take everything else out. eg
// if user has opened the two groups United States and 2002, we filter
// out everything that is not equal to United States and 2002.
FakeServer.prototype.filterOutOtherGroups = function(originalData, groupKeys, rowGroupCols) {
    var filteredData = originalData;
    var that = this;

    // if we are inside a group, then filter out everything that is not
    // part of this group
    groupKeys.forEach(function(groupKey, index) {
        var rowGroupCol = rowGroupCols[index];
        var field = rowGroupCol.id;

        filteredData = that.filter(filteredData, function(item) {
            return item[field] == groupKey;
        });
    });

    return filteredData;
};

// simple implementation of lodash groupBy
FakeServer.prototype.groupBy = function(data, field) {
    var result = {};
    data.forEach( function(item) {
        var key = item[field];
        var listForThisKey = result[key];
        if (!listForThisKey) {
            listForThisKey = [];
            result[key] = listForThisKey;
        }
        listForThisKey.push(item);
    });
    return result;
};

// simple implementation of lodash filter
FakeServer.prototype.filter = function(data, callback) {
    var result = [];
    data.forEach( function(item) {
        if (callback(item)) {
            result.push(item);
        }
    });
    return result;
};
.group-inner-renderer-country {
  font-size: 20px;
  position: relative;
}

.group-inner-renderer-year {
  font-size: 15px;
  font-weight: bold;
}

.ag-theme-balham-dark .ag-row-level-0 {
  padding-left: 10px;
  background-color: #002b2b;
  border-top: 1px solid #4a4a4a;
}

.ag-theme-balham-dark .ag-row-level-1 {
  padding-left: 20px;
  background-color: #1c3b3b;
}
// Angular entry point file
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from 'app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);
/**
 * WEB ANGULAR VERSION
 * (based on systemjs.config.js from the angular tutorial - https://angular.io/tutorial)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;

module.exports.translate = function(load){
  if (load.source.indexOf('moduleId') != -1) return load;

  var url = document.createElement('a');
  url.href = load.address;

  var basePathParts = url.pathname.split('/');

  basePathParts.pop();
  var basePath = basePathParts.join('/');

  var baseHref = document.createElement('a');
  baseHref.href = this.baseURL;
  baseHref = baseHref.pathname;

  if (!baseHref.startsWith('/base/')) { // it is not karma
    basePath = basePath.replace(baseHref, '');
  }

  load.source = load.source
    .replace(templateUrlRegex, function(match, quote, url){
      var resolvedUrl = url;

      if (url.startsWith('.')) {
        resolvedUrl = basePath + url.substr(1);
      }

      return 'templateUrl: "' + resolvedUrl + '"';
    })
    .replace(stylesRegex, function(match, relativeUrls) {
      var urls = [];

      while ((match = stringRegex.exec(relativeUrls)) !== null) {
        if (match[2].startsWith('.')) {
          urls.push('"' + basePath + match[2].substr(1) + '"');
        } else {
          urls.push('"' + match[2] + '"');
        }
      }

      return "styleUrls: [" + urls.join(', ') + "]";
    });

  return load;
};
/**
 * WEB ANGULAR VERSION
 * (based on systemjs.config.js from the angular tutorial - https://angular.io/tutorial)
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
    var ANGULAR_VERSION = "5.1.3";
    var ANGULAR_CDK_VERSION = "5.2.5";
    var ANGULAR_MATERIAL_VERSION = "5.2.5";

    System.config({
        // DEMO ONLY! REAL CODE SHOULD NOT TRANSPILE IN THE BROWSER
        transpiler: "ts",
        typescriptOptions: {
            // Copy of compiler options in standard tsconfig.json
            target: "es5",
            module: "system", //gets rid of console warning
            moduleResolution: "node",
            sourceMap: true,
            emitDecoratorMetadata: true,
            experimentalDecorators: true,
            lib: ["es2015", "dom"],
            noImplicitAny: true,
            suppressImplicitAnyIndexErrors: true
        },
        meta: {
            typescript: {
                exports: "ts"
            }
        },
        paths: {
            // paths serve as alias
            "npm:": "https://unpkg.com/"
        },
        // RxJS makes a lot of requests to unpkg. This guy addressed it:
        // https://github.com/OasisDigital/rxjs-system-bundle.
        bundles: {
            "npm:rxjs-system-bundle@5.5.5/Rx.system.js": [
                "rxjs",
                "rxjs/*",
                "rxjs/operator/*",
                "rxjs/operators/*",
                "rxjs/observable/*",
                "rxjs/scheduler/*",
                "rxjs/symbol/*",
                "rxjs/add/operator/*",
                "rxjs/add/observable/*",
                "rxjs/util/*"
            ]
        },
        // map tells the System loader where to look for things
        map: Object.assign(
            {
                // angular bundles
                "@angular/animations": "npm:@angular/animations@" + ANGULAR_VERSION + "/bundles/animations.umd.js",
                "@angular/animations/browser": "npm:@angular/animations@" + ANGULAR_VERSION + "/bundles/animations-browser.umd.js",
                "@angular/core": "npm:@angular/core@" + ANGULAR_VERSION + "/bundles/core.umd.js",
                "@angular/common": "npm:@angular/common@" + ANGULAR_VERSION + "/bundles/common.umd.js",
                "@angular/common/http": "npm:@angular/common@" + ANGULAR_VERSION + "/bundles/common-http.umd.js",
                "@angular/compiler": "npm:@angular/compiler@" + ANGULAR_VERSION + "/bundles/compiler.umd.js",
                "@angular/platform-browser": "npm:@angular/platform-browser@" + ANGULAR_VERSION + "/bundles/platform-browser.umd.js",
                "@angular/platform-browser/animations": "npm:@angular/platform-browser@" + ANGULAR_VERSION + "/bundles/platform-browser-animations.umd.js",
                "@angular/platform-browser-dynamic": "npm:@angular/platform-browser-dynamic@" + ANGULAR_VERSION + "/bundles/platform-browser-dynamic.umd.js",
                "@angular/http": "npm:@angular/http@" + ANGULAR_VERSION + "/bundles/http.umd.js",
                "@angular/router": "npm:@angular/router@" + ANGULAR_VERSION + "/bundles/router.umd.js",
                "@angular/router/upgrade": "npm:@angular/router@" + ANGULAR_VERSION + "/bundles/router-upgrade.umd.js",
                "@angular/forms": "npm:@angular/forms@" + ANGULAR_VERSION + "/bundles/forms.umd.js",
                "@angular/upgrade": "npm:@angular/upgrade@" + ANGULAR_VERSION + "/bundles/upgrade.umd.js",
                "@angular/upgrade/static": "npm:@angular/upgrade@" + ANGULAR_VERSION + "/bundles/upgrade-static.umd.js",
                "angular-in-memory-web-api": "npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js",
                // material design
                "@angular/material": "npm:@angular/material@" + ANGULAR_MATERIAL_VERSION + "/bundles/material.umd.js",
                "@angular/cdk/platform": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-platform.umd.js",
                "@angular/cdk/bidi": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-bidi.umd.js",
                "@angular/cdk/coercion": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-coercion.umd.js",
                "@angular/cdk/keycodes": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-keycodes.umd.js",
                "@angular/cdk/a11y": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-a11y.umd.js",
                "@angular/cdk/overlay": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-overlay.umd.js",
                "@angular/cdk/portal": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-portal.umd.js",
                "@angular/cdk/observers": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-observers.umd.js",
                "@angular/cdk/collections": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-collections.umd.js",
                "@angular/cdk/accordion": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-accordion.umd.js",
                "@angular/cdk/scrolling": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-scrolling.umd.js",
                "@angular/cdk/layout": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-layout.umd.js",
                "@angular/cdk/table": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-table.umd.js",
                "@angular/cdk/text-field": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-text-field.umd.js",
                "@angular/cdk/tree": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-tree.umd.js",
                "@angular/cdk/stepper": "npm:@angular/cdk@" + ANGULAR_CDK_VERSION + "/bundles/cdk-stepper.umd.js",
                // ngx bootstrap
                "ngx-bootstrap": "npm:ngx-bootstrap@2.0.0-rc.0",
                // ng2 typeahead
                "ng2-typeahead": "npm:ng2-typeahead@1.2.0",

                ts: "npm:plugin-typescript@5.2.7/lib/plugin.js",
                tslib: "npm:tslib@1.7.1/tslib.js",
                typescript: "npm:typescript@2.3.2/lib/typescript.js",

                // for some of the examples
                lodash: "npm:lodash@4.17.4/lodash.js",

                // our app is within the app folder, appLocation comes from index.html
                app: appLocation + "app",

                rxjs: "npm:rxjs@6.1.0/bundles/rxjs.umd.min.js"
            },
            systemJsMap
        ),
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            app: {
                main: "./main.ts",
                defaultExtension: "ts",
                meta: {
                    "./*.ts": {
                        loader: boilerplatePath + "systemjs-angular-loader.js"
                    }
                }
            },
            "ag-grid-angular": {
                main: "./main.js",
                defaultExtension: "js"
            },
            "ag-grid-enterprise": {
                main: "./main.js",
                defaultExtension: "js"
            },
            rxjs: {
                defaultExtension: false
            }
        }
    });
})(this);