<!DOCTYPE html>
<html>

<head>
    <title>ag-Grid Rich Grid with Declarative Markup</title>

    <script src="https://unpkg.com/zone.js@0.6.23?main=browser"></script>
    <script src="https://unpkg.com/reflect-metadata@0.1.3"></script>
    <script src="https://unpkg.com/systemjs@0.19.27/dist/system.src.js"></script>

    <!-- ag-grid CSS -->
    <link href="https://unpkg.com/ag-grid/dist/styles/ag-grid.css" rel="stylesheet"/>
    <link href="https://unpkg.com/ag-grid/dist/styles/theme-fresh.css" rel="stylesheet"/>

    <!-- Example uses font awesome icons -->
    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">

    <!-- Configure SystemJS -->
    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function (err) { console.error(err); });
    </script>
</head>

<!-- 3. Display the application -->
<body>
<my-app>Loading...</my-app>
</body>

</html>
(function (global) {
    System.config({
            transpiler: 'ts',
            typescriptOptions: {
                "target": "es5",
                "module": "commonjs",
                "moduleResolution": "node",
                "sourceMap": true,
                "emitDecoratorMetadata": true,
                "experimentalDecorators": true,
                "removeComments": false,
                "noImplicitAny": true
            },
            meta: {
                'typescript': {
                    "exports": "ts"
                }
            },
            paths: {
                // paths serve as alias
                'npm:': 'https://unpkg.com/'
            },
            map: {
                'app': 'app',
                // angular bundles
                '@angular/core': 'npm:@angular/core@2.4.8/bundles/core.umd.js',
                '@angular/common': 'npm:@angular/common@2.4.8/bundles/common.umd.js',
                '@angular/compiler': 'npm:@angular/compiler@2.4.8/bundles/compiler.umd.js',
                '@angular/platform-browser': 'npm:@angular/platform-browser@2.4.8/bundles/platform-browser.umd.js',
                '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic@2.4.8/bundles/platform-browser-dynamic.umd.js',
                '@angular/router': 'npm:@angular/router@2.4.8/bundles/router.umd.js',
                '@angular/forms': 'npm:@angular/forms@2.4.8/bundles/forms.umd.js',
                // other libraries
                'rxjs':                      'npm:rxjs@5.0.0',
                'ts':                        'npm:plugin-typescript@4.0.10/lib/plugin.js',
                'typescript':                'npm:typescript@2.1.1/lib/typescript.js',
                // ag libraries
                'ag-grid-angular': 'npm:ag-grid-angular',
                'ag-grid': 'npm:ag-grid',
                'ag-grid-enterprise': 'npm:ag-grid-enterprise'
            },
            packages: {
                app: {
                    main: './boot.ts',
                    defaultExtension: 'ts'
                },
                'ag-grid': {
                    main: 'main.js'
                }
            }
        }
    );

    if (!global.noBootstrap) {
        bootstrap();
    }

    // Bootstrap the `AppModule`(skip the `app/main.ts` that normally does this)
    function bootstrap() {

        // Stub out `app/main.ts` so System.import('app') doesn't fail if called in the index.html
        System.set(System.normalizeSync('app/boot.ts'), System.newModule({}));

        // bootstrap and launch the app (equivalent to standard main.ts)
        Promise.all([
            System.import('@angular/platform-browser-dynamic'),
            System.import('app/app.module')
        ])
            .then(function (imports) {
                var platform = imports[0];
                var app = imports[1];
                platform.platformBrowserDynamic().bootstrapModule(app.AppModule);
            })
            .catch(function (err) {
                console.error(err);
            });
    }
})(this);
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);
import { Component } from '@angular/core';

@Component({
    moduleId: module.id,
    selector: 'my-app',
    templateUrl: 'app.component.html'
})

export class AppComponent { }
<ag-rich-grid-declarative></ag-rich-grid-declarative>
import {NgModule} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {FormsModule} from "@angular/forms";
// ag-grid
import {AgGridModule} from "ag-grid-angular/main";
// application
import {AppComponent} from "./app.component";
// rich grid declarative
import {RichGridDeclarativeComponent} from "./rich-grid-declarative-example/rich-grid-declarative.component";
import {DateComponent} from "./date-component/date.component";
import {HeaderComponent} from "./header-component/header.component";
import {HeaderGroupComponent} from "./header-group-component/header-group.component";

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        AgGridModule.withComponents(
            [
                DateComponent,
                HeaderComponent,
                HeaderGroupComponent
            ])
    ],
    declarations: [
        AppComponent,
        RichGridDeclarativeComponent,
        DateComponent,
        HeaderComponent,
        HeaderGroupComponent
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}
<div style="width: 800px;">
    <h1>Rich Grid with Declarative Markup</h1>
    <div style="padding: 4px;">
        <div style="float: right;">
            <input (keyup)="onQuickFilterChanged($event)" type="text" id="quickFilterInput" placeholder="Type text to filter..."/>
            <button [disabled]="!showGrid" (click)="showGrid=false">Destroy Grid</button>
            <button [disabled]="showGrid" (click)="showGrid=true">Create Grid</button>
        </div>
        <div>
            <b>Employees Skills and Contact Details</b>
            {{rowCount}}
        </div>
    </div>
    <div style="clear: both;"></div>

    <div *ngIf="showGrid">

        <!-- Because we are using the Angular ID (ie #ag-grid marker), we have to have all the items that use
             that marker inside the same ng-if as the grid -->

        <div style="padding: 4px;" class="toolbar">
            <span>
                Grid API:
                <button (click)="agGrid.api.selectAll()">Select All</button>
                <button (click)="agGrid.api.deselectAll()">Clear Selection</button>
            </span>
            <span style="margin-left: 20px;">
                Column API:
                <button (click)="agGrid.columnApi.setColumnVisible('country', false)">Hide Country Column</button>
                <button (click)="agGrid.columnApi.setColumnVisible('country', true)">Show Country Column</button>
            </span>
        </div>
        <div style="clear: both;"></div>
        <div style="padding: 4px;" class="toolbar">
            <label>
                <input type="checkbox" (change)="showToolPanel=$event.target.checked"/>
                Show Tool Panel
            </label>
            <button (click)="createRowData()">Refresh Data</button>
        </div>
        <div style="clear: both;"></div>

        <ag-grid-angular #agGrid style="width: 100%; height: 350px;" class="ag-fresh"
                     [gridOptions]="gridOptions"
                     [showToolPanel]="showToolPanel"
                     [rowData]="rowData"

                     enableColResize
                     enableSorting
                     enableFilter
                     groupHeaders
                     suppressRowClickSelection
                     toolPanelSuppressGroups
                     toolPanelSuppressValues
                     rowHeight="22"
                     rowSelection="multiple"
                    >
            <ag-grid-column headerName="Employee" [headerGroupComponentFramework]="components.headerGroupComponent">
                <ag-grid-column headerName="#" [width]="30" [checkboxSelection]="true" [suppressSorting]="true" [suppressMenu]="true" [pinned]="true"></ag-grid-column>
                <ag-grid-column headerName="Name" field="name" [width]="150" [pinned]="true" ></ag-grid-column>
                <ag-grid-column headerName="Country" field="country" [width]="150" [cellRenderer]="countryCellRenderer" [pinned]="true" [filterParams]="getCountryFilterParams()" [columnGroupShow]="'open'"></ag-grid-column>
                <ag-grid-column headerName="DOB" field="dob" [width]="120" [pinned]="true" [columnGroupShow]="'open'" [cellRenderer]="parseDate" [filter]="'date'"></ag-grid-column>
            </ag-grid-column>
            <ag-grid-column headerName="IT Skills">
                <ag-grid-column headerName="Skills" [width]="125" [suppressSorting]="true" [cellRenderer]="skillsCellRenderer" [filter]="getSkillFilter()"></ag-grid-column>
                <ag-grid-column headerName="Proficiency" field="proficiency" [width]="120" [cellRenderer]="percentCellRenderer" [filter]="getProficiencyFilter()"></ag-grid-column>
            </ag-grid-column>
            <ag-grid-column headerName="Contact">
                <ag-grid-column headerName="Mobile" field="mobile" [width]="150" filter="text"></ag-grid-column>
                <ag-grid-column headerName="Land-line" field="landline" [width]="150" filter="text"></ag-grid-column>
                <ag-grid-column headerName="Address" field="address" [width]="500" filter="text"></ag-grid-column>
            </ag-grid-column>
        </ag-grid-angular>
    </div>
</div>
import {Component,ViewEncapsulation} from '@angular/core';

import {GridOptions} from 'ag-grid/main';

import ProficiencyFilter from '../filters/proficiencyFilter';
import SkillFilter from '../filters/skillFilter';
import RefData from '../data/refData';

// only import this if you are using the ag-Grid-Enterprise
import 'ag-grid-enterprise/main';
import {HeaderGroupComponent} from "../header-group-component/header-group.component";
import {DateComponent} from "../date-component/date.component";
import {HeaderComponent} from "../header-component/header.component";

@Component({
    moduleId: module.id,
    selector: 'ag-rich-grid-declarative',
    templateUrl: 'rich-grid-declarative.component.html',
    styleUrls: ['rich-grid.css', 'proficiency-renderer.css'],
    encapsulation: ViewEncapsulation.None
})
export class RichGridDeclarativeComponent {

    public gridOptions:GridOptions;
    public showGrid:boolean;
    private rowData:any[];
    public rowCount:string;
    public components = {
        headerGroupComponent:HeaderGroupComponent
    }

    constructor() {
        // we pass an empty gridOptions in, so we can grab the api out
        this.gridOptions = <GridOptions>{};
        this.createRowData();
        this.showGrid = true;
        this.gridOptions.dateComponentFramework = DateComponent;
        this.gridOptions.defaultColDef = {
            headerComponentFramework : <{new():HeaderComponent}>HeaderComponent,
            headerComponentParams : {
                menuIcon: 'fa-bars'
            }
        }
    }

    private createRowData() {
        var rowData:any[] = [];

        for (var i = 0; i < 10000; i++) {
            var countryData = RefData.countries[i % RefData.countries.length];
            rowData.push({
                name: RefData.firstNames[i % RefData.firstNames.length] + ' ' + RefData.lastNames[i % RefData.lastNames.length],
                skills: {
                    android: Math.random() < 0.4,
                    html5: Math.random() < 0.4,
                    mac: Math.random() < 0.4,
                    windows: Math.random() < 0.4,
                    css: Math.random() < 0.4
                },
                dob: RefData.DOBs[i % RefData.DOBs.length],
                address: RefData.addresses[i % RefData.addresses.length],
                years: Math.round(Math.random() * 100),
                proficiency: Math.round(Math.random() * 100),
                country: countryData.country,
                continent: countryData.continent,
                language: countryData.language,
                mobile: this.createRandomPhoneNumber(),
                landline: this.createRandomPhoneNumber()
            });
        }

        this.rowData = rowData;
    }

    private calculateRowCount() {
        if (this.gridOptions.api && this.rowData) {
            var model = this.gridOptions.api.getModel();
            var totalRows = this.rowData.length;
            var processedRows = model.getRowCount();
            this.rowCount = processedRows.toLocaleString() + ' / ' + totalRows.toLocaleString();
        }
    }

    private onModelUpdated() {
        console.log('onModelUpdated');
        this.calculateRowCount();
    }

    private onReady() {
        console.log('onReady');
        this.calculateRowCount();
    }

    public onQuickFilterChanged($event) {
        this.gridOptions.api.setQuickFilter($event.target.value);
    }

    private countryCellRenderer(params) {
        var flag = "<img border='0' width='15' height='10' style='margin-bottom: 2px' src='http://www.ag-grid.com/images/flags/" + RefData.COUNTRY_CODES[params.value] + ".png'>";
        return flag + " " + params.value;
    }

    //noinspection JSUnusedLocalSymbols
    private skillsCellRenderer(params) {
        var data = params.data;
        var skills = [];
        RefData.IT_SKILLS.forEach(function (skill) {
            if (data && data.skills && data.skills[skill]) {
                skills.push('<img src="http://www.ag-grid.com/images/skills/' + skill + '.png" width="16px" title="' + skill + '" />');
            }
        });
        return skills.join(' ');
    }

    //noinspection JSUnusedLocalSymbols
    private percentCellRenderer(params) {
        var value = params.value;

        var eDivPercentBar = document.createElement('div');
        eDivPercentBar.className = 'div-percent-bar';
        eDivPercentBar.style.width = value + '%';
        if (value < 20) {
            eDivPercentBar.style.backgroundColor = 'red';
        } else if (value < 60) {
            eDivPercentBar.style.backgroundColor = '#ff9900';
        } else {
            eDivPercentBar.style.backgroundColor = '#00A000';
        }

        var eValue = document.createElement('div');
        eValue.className = 'div-percent-value';
        eValue.innerHTML = value + '%';

        var eOuterDiv = document.createElement('div');
        eOuterDiv.className = 'div-outer-div';
        eOuterDiv.appendChild(eValue);
        eOuterDiv.appendChild(eDivPercentBar);

        return eOuterDiv;
    }

    //noinspection JSUnusedLocalSymbols
    private getSkillFilter():any {
        return SkillFilter;
    }

    //noinspection JSUnusedLocalSymbols
    private getProficiencyFilter():any {
        return ProficiencyFilter;
    }

    //noinspection JSUnusedLocalSymbols
    private getCountryFilterParams():any {
        return {
            cellRenderer: this.countryCellRenderer,
            cellHeight: 20
        }
    }

    private createRandomPhoneNumber() {
        var result = '+';
        for (var i = 0; i < 12; i++) {
            result += Math.round(Math.random() * 10);
            if (i === 2 || i === 5 || i === 8) {
                result += ' ';
            }
        }
        return result;
    }

    public parseDate (params) {
        return  pad(params.value.getDate(), 2) + '/' +
            pad(params.value.getMonth() + 1, 2)+ '/' +
            params.value.getFullYear();
    }
}

//Utility function used to pad the date formatting.
function pad(num, totalStringSize) {
    let asString = num + "";
    while (asString.length < totalStringSize) asString = "0" + asString;
    return asString;
}
import {Component} from "@angular/core";
import {IDateParams} from "ag-grid/main";
import {IDateAngularComp} from "ag-grid-angular/main";


@Component({
    moduleId: module.id,
    selector: 'ag-full-width-grid',
    templateUrl: 'date.component.html',
    styleUrls: ['date.component.css'],
})
export class DateComponent implements IDateAngularComp {
    private date: Date;
    private params: IDateParams;
    public dd: string = '';
    public mm: string = '';
    public yyyy: string = '';

    agInit(params: IDateParams): void {
        this.params = params;
    }

    ngOnDestroy() {
        console.log(`Destroying DateComponent`);
    }

    onResetDate() {
        this.dd = '';
        this.mm = '';
        this.yyyy = '';
        this.date = null;
        this.params.onDateChanged();
    }

    onDateChanged(on: string, newValue: string) {
        this.date = this.parseDate(
            on === 'dd' ? newValue : this.dd,
            on === 'mm' ? newValue : this.mm,
            on === 'yyyy' ? newValue : this.yyyy
        );
        this.params.onDateChanged();
    }

    getDate(): Date {
        return this.date;
    }

    setDate(date: Date): void {
        this.dd = date.getDate() + '';
        this.mm = (date.getMonth() + 1) + '';
        this.yyyy = date.getFullYear() + '';
        this.date = date;
        this.params.onDateChanged();
    }

    //*********************************************************************************
    //          INTERNAL LOGIC
    //*********************************************************************************

    parseDate(dd, mm, yyyy) {
        //If any of the three input date fields are empty, stop and return null
        if (dd.trim() === '' || mm.trim() === '' || yyyy.trim() === '') {
            return null;
        }

        let day = Number(dd);
        let month = Number(mm);
        let year = Number(yyyy);

        let date = new Date(year, month - 1, day);

        //If the date is not valid
        if (isNaN(date.getTime())) {
            return null;
        }

        //Given that new Date takes any garbage in, it is possible for the user to specify a new Date
        //like this (-1, 35, 1) and it will return a valid javascript date. In this example, it will
        //return Sat Dec 01    1 00:00:00 GMT+0000 (GMT) - Go figure...
        //To ensure that we are not letting non sensical dates to go through we check that the resultant
        //javascript date parts (month, year and day) match the given date fields provided as parameters.
        //If the javascript date parts don't match the provided fields, we assume that the input is non
        //sensical... ie: Day=-1 or month=14, if this is the case, we return null
        //This also protects us from non sensical dates like dd=31, mm=2 of any year
        if (date.getDate() != day || date.getMonth() + 1 != month || date.getFullYear() != year) {
            return null;
        }

        return date;
    }
}
import {Component} from "@angular/core";
import {IHeaderGroupParams} from "ag-grid/main";
import {IHeaderGroupAngularComp} from "ag-grid-angular/main";

@Component({
    moduleId: module.id,
    templateUrl: 'header-group.component.html',
    styleUrls: ['header-group.component.css']
})
export class HeaderGroupComponent implements IHeaderGroupAngularComp {
    public params: IHeaderGroupParams;
    public expanded: boolean;

    agInit(params: IHeaderGroupParams): void {
        this.params = params;
        this.params.columnGroup.getOriginalColumnGroup().addEventListener('expandedChanged', this.onExpandChanged.bind(this));
    }

    ngOnDestroy() {
        console.log(`Destroying HeaderComponent`);
    }


    expandOrCollapse() {
        this.params.setExpanded(!this.expanded);
    };

    onExpandChanged() {
        this.expanded = this.params.columnGroup.getOriginalColumnGroup().isExpanded()
    }
}
import {Component, ElementRef} from "@angular/core";
import {IHeaderParams} from "ag-grid/main";
import {IHeaderAngularComp} from "ag-grid-angular/main";

interface MyParams extends IHeaderParams {
    menuIcon: string;
}

@Component({
    moduleId: module.id,
    templateUrl: 'header.component.html',
    styleUrls: ['header.component.css']
})
export class HeaderComponent implements IHeaderAngularComp {
    public params: MyParams;
    public sorted: string;
    private elementRef: ElementRef;

    constructor(elementRef: ElementRef) {
        this.elementRef = elementRef;
    }

    agInit(params: MyParams): void {
        this.params = params;
        this.params.column.addEventListener('sortChanged', this.onSortChanged.bind(this));
        this.onSortChanged();
    }

    ngOnDestroy() {
        console.log(`Destroying HeaderComponent`);
    }

    onMenuClick() {
        this.params.showColumnMenu(this.querySelector('.customHeaderMenuButton'));
    }

    onSortRequested(order, event) {
        this.params.setSort(order, event.shiftKey);
    };

    onSortChanged() {
        if (this.params.column.isSortAscending()) {
            this.sorted = 'asc'
        } else if (this.params.column.isSortDescending()) {
            this.sorted = 'desc'
        } else {
            this.sorted = ''
        }
    };


    private querySelector(selector: string) {
        return <HTMLElement>this.elementRef.nativeElement.querySelector(
            '.customHeaderMenuButton', selector);
    }
}
<div>
    <div [hidden]="!params.enableMenu" class="customHeaderMenuButton" (click)="onMenuClick()"><i class="{{'fa ' + params.menuIcon}}"></i></div>
    <div class="customHeaderLabel">{{params.displayName}}</div>
    <div [hidden]="!params.enableSorting" class="{{'customSortDownLabel'+ (this.sorted === 'desc' ? ' active' : '') }}" (click)="onSortRequested('desc', $event)">
        <i class="fa fa-long-arrow-down"></i>
    </div>
    <div [hidden]="!params.enableSorting" class="{{'customSortUpLabel'+ (this.sorted === 'asc' ? ' active' : '') }}" (click)="onSortRequested('asc', $event)">
        <i class="fa fa-long-arrow-up"></i>
    </div>
    <div [hidden]="!params.enableSorting" class="{{'customSortRemoveLabel'+ (this.sorted === '' ? ' active' : '') }}" (click)="onSortRequested('', $event)">
        <i class="fa fa-times"></i>
    </div>
</div>
.customHeaderMenuButton{
    margin-top: 5px;
    margin-left: 4px;
    float: left;
}

.customHeaderLabel{
    margin-left: 5px;
    margin-top: 3px;
    float: left;
}

.customSortDownLabel{
    float: left;
    margin-left: 10px;
    margin-top: 5px;
}

.customSortUpLabel{
    float: left;
    margin-left: 3px;
    margin-top: 4px;
}

.customSortRemoveLabel{
    float: left;
    font-size: 11px;
    margin-left: 3px;
    margin-top: 6px;
}

.active {
    color: cornflowerblue;
}

.hidden { display:none; }
<div class="filter">
    <span class="reset" (click)="onResetDate()">x</span>
    <input class="dd" (ngModelChange)="onDateChanged('dd', $event)" placeholder="dd" [(ngModel)]="dd" maxLength="2"/>/
    <input class="mm" (ngModelChange)="onDateChanged('mm', $event)" placeholder="mm" [(ngModel)]="mm" maxLength="2"/>/
    <input class="yyyy" (ngModelChange)="onDateChanged('yyyy', $event)" placeholder="yyyy" [(ngModel)]="yyyy" maxLength="4"/>
</div>
.filter {
    margin:2px
}

.dd {
    width:30px
}

.mm {
    width:30px
}

.yyyy {
    width:60px
}

.reset {
    padding: 2px;
    background-color: red;
    border-radius: 3px;
    font-size: 10px;
    margin-right: 5px;
    color: white
}
<div>
    <div class="customHeaderLabel"> {{this.params.displayName}}</div>
    <div (click)="expandOrCollapse()" class="{{'customExpandButton' + (this.expanded ?  ' expanded': ' collapsed')}}"><i class="fa fa-arrow-right" ></i></div>
</div>
.customHeaderLabel{
    margin-left: 5px;
    margin-top: 3px;
    float: left;
}

.customExpandButton{
    float:right;
    margin-top: 5px;
    margin-left: 3px;
}

.expanded {
    animation-name: toExpanded;
    animation-duration: 1s;
    -ms-transform: rotate(180deg); /* IE 9 */
    -webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
    transform: rotate(180deg);
}

.collapsed {
    color: cornflowerblue;
    animation-name: toCollapsed;
    animation-duration: 1s;
    -ms-transform: rotate(0deg); /* IE 9 */
    -webkit-transform: rotate(0deg); /* Chrome, Safari, Opera */
    transform: rotate(0deg);
}



@keyframes  toExpanded{
    from {
        color: cornflowerblue;
        -ms-transform: rotate(0deg); /* IE 9 */
        -webkit-transform: rotate(0deg); /* Chrome, Safari, Opera */
        transform: rotate(0deg);
    }
    to {
        color: black;
        -ms-transform: rotate(180deg); /* IE 9 */
        -webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
        transform: rotate(180deg);
    }
}

@keyframes toCollapsed{
    from {
        color: black;
        -ms-transform: rotate(180deg); /* IE 9 */
        -webkit-transform: rotate(180deg); /* Chrome, Safari, Opera */
        transform: rotate(180deg);
    }
    to {
        color: cornflowerblue;
        -ms-transform: rotate(0deg); /* IE 9 */
        -webkit-transform: rotate(0deg); /* Chrome, Safari, Opera */
        transform: rotate(0deg);
    }
}
export default class RefData {

    static IT_SKILLS = ['android', 'css', 'html5', 'mac', 'windows'];
    static IT_SKILLS_NAMES = ['Android', 'CSS', 'HTML 5', 'Mac', 'Windows'];


    static firstNames = ["Sophie", "Isabelle", "Emily", "Olivia", "Lily", "Chloe", "Isabella",
        "Amelia", "Jessica", "Sophia", "Ava", "Charlotte", "Mia", "Lucy", "Grace", "Ruby",
        "Ella", "Evie", "Freya", "Isla", "Poppy", "Daisy", "Layla"];
    static lastNames = ["Beckham", "Black", "Braxton", "Brennan", "Brock", "Bryson", "Cadwell",
        "Cage", "Carson", "Chandler", "Cohen", "Cole", "Corbin", "Dallas", "Dalton", "Dane",
        "Donovan", "Easton", "Fisher", "Fletcher", "Grady", "Greyson", "Griffin", "Gunner",
        "Hayden", "Hudson", "Hunter", "Jacoby", "Jagger", "Jaxon", "Jett", "Kade", "Kane",
        "Keating", "Keegan", "Kingston", "Kobe"];

    static DOBs = [
        new Date(2000, 0, 1 ),
        new Date(2001, 1, 2 ),
        new Date(2002, 2, 3 ),
        new Date(2003, 3, 4 ),
        new Date(2004, 4, 5 ),
        new Date(2005, 5, 6 ),
        new Date(2006, 6, 7 ),
        new Date(2007, 7, 8 ),
        new Date(2008, 8, 9 ),
        new Date(2009, 9, 10 ),
        new Date(2010, 10, 11 ),
        new Date(2011, 11, 12 )
    ];

    static COUNTRY_CODES = {
        Ireland: "ie",
        Spain: "es",
        "United Kingdom": "gb",
        France: "fr",
        Germany: "de",
        Sweden: "se",
        Italy: "it",
        Greece: "gr",
        Iceland: "is",
        Portugal: "pt",
        Malta: "mt",
        Norway: "no",
        Brazil: "br",
        Argentina: "ar",
        Colombia: "co",
        Peru: "pe",
        Venezuela: "ve",
        Uruguay: "uy"
    };

    static countries = [
        {country: "Ireland", continent: "Europe", language: "English"},
        {country: "Spain", continent: "Europe", language: "Spanish"},
        {country: "United Kingdom", continent: "Europe", language: "English"},
        {country: "France", continent: "Europe", language: "French"},
        {country: "Germany", continent: "Europe", language: "(other)"},
        {country: "Sweden", continent: "Europe", language: "(other)"},
        {country: "Norway", continent: "Europe", language: "(other)"},
        {country: "Italy", continent: "Europe", language: "(other)"},
        {country: "Greece", continent: "Europe", language: "(other)"},
        {country: "Iceland", continent: "Europe", language: "(other)"},
        {country: "Portugal", continent: "Europe", language: "Portuguese"},
        {country: "Malta", continent: "Europe", language: "(other)"},
        {country: "Brazil", continent: "South America", language: "Portuguese"},
        {country: "Argentina", continent: "South America", language: "Spanish"},
        {country: "Colombia", continent: "South America", language: "Spanish"},
        {country: "Peru", continent: "South America", language: "Spanish"},
        {country: "Venezuela", continent: "South America", language: "Spanish"},
        {country: "Uruguay", continent: "South America", language: "Spanish"}
    ];

    static addresses = [
        '1197 Thunder Wagon Common, Cataract, RI, 02987-1016, US, (401) 747-0763',
        '3685 Rocky Glade, Showtucket, NU, X1E-9I0, CA, (867) 371-4215',
        '3235 High Forest, Glen Campbell, MS, 39035-6845, US, (601) 638-8186',
        '2234 Sleepy Pony Mall , Drain, DC, 20078-4243, US, (202) 948-3634',
        '2722 Hazy Turnabout, Burnt Cabins, NY, 14120-5642, US, (917) 604-6597',
        '6686 Lazy Ledge, Two Rock, CA, 92639-3020, US, (619) 901-9911',
        '2000 Dewy Limits, Wacahoota, NF, A4L-2V9, CA, (709) 065-3959',
        '7710 Noble Pond Avenue, Bolivia, RI, 02931-1842, US, (401) 865-2160',
        '3452 Sunny Vale, Pyro, ON, M8V-4Z0, CA, (519) 072-8609',
        '4402 Dusty Cove, Many Farms, UT, 84853-8223, US, (435) 518-0673',
        '5198 Silent Parade, Round Bottom, MD, 21542-9798, US, (301) 060-7245',
        '8550 Shady Moor, Kitty Fork, CO, 80941-6207, US, (303) 502-3767',
        '2131 Old Dell, Merry Midnight, AK, 99906-8842, US, (907) 369-2206',
        '7390 Harvest Crest, Mosquito Crossing, RI, 02957-6116, US, (401) 463-6348',
        '874 Little Point, Hot Coffee, BC, V3U-2P6, CA, (250) 706-9207',
        '8834 Stony Pioneer Heights, Newlove, OR, 97419-8670, US, (541) 408-2213',
        '9829 Grand Beach, Flint, UT, 84965-9900, US, (435) 700-5161',
        '3799 Cozy Blossom Ramp, Ptarmigan, MS, 38715-0313, US, (769) 740-1526',
        '3254 Silver Island Loop, Maunaloa, DE, 19869-3169, US, (302) 667-7671',
        '1081 Middle Wood, Taylors Gut Landing, OR, 97266-2873, US, (541) 357-6310',
        '1137 Umber Trail, Shacktown, NW, X3U-5Y8, CA, (867) 702-6883',
        '9914 Hidden Bank, Wyoming, MO, 64635-9665, US, (636) 280-4192',
        '7080 Misty Nectar Townline, Coward, AB, T9U-3N4, CA, (403) 623-2838',
        '1184 Wishing Grounds, Vibank, NW, X7D-0V9, CA, (867) 531-2730',
        '126 Easy Pointe, Grandview Beach, KY, 40928-9539, US, (502) 548-0956',
        '6683 Colonial Street, Swan River, BC, V1A-9I8, CA, (778) 014-4257',
        '960 Gentle Oak Lane, Shakopee, ND, 58618-6277, US, (701) 327-1219',
        '6918 Cotton Pine Corner, Kenaston, IA, 52165-3975, US, (515) 906-7427',
        '2368 Burning Woods, Ernfold, NY, 11879-9186, US, (646) 819-0355',
        '5646 Quiet Shadow Chase, Tiger Tail, IA, 52283-5537, US, (712) 375-9225',
        '5466 Foggy Mountain Dale, Sweet Home, MT, 59738-0251, US, (406) 881-1706',
        '5313 Clear Willow Route, Amazon, BC, V0S-2S6, CA, (604) 340-7596',
        '7000 Pleasant Autoroute, Spaceport City, UT, 84749-2448, US, (435) 154-3360',
        '8359 Quaking Anchor Road, Gross, BC, V9O-0H5, CA, (250) 985-3859',
        '5143 Amber Deer Hollow, New Deal, ND, 58446-0853, US, (701) 927-0322',
        '6230 Jagged Bear Key, Young, AR, 72337-3811, US, (501) 805-7239',
        '7207 Heather Vista, Devon, WY, 82520-1771, US, (307) 358-7092',
        '9416 Red Rise Place, Spraytown, OK, 73809-4766, US, (580) 867-1973',
        '3770 Golden Horse Diversion, Yelland, IL, 60471-1487, US, (224) 717-9349',
        '4819 Honey Treasure Park, Alaska, NB, E1U-3I0, CA, (506) 656-9138',
        '6187 Round Front, Land O Lakes, AK, 99873-6403, US, (907) 853-9063',
        '9218 Crystal Highway, Pickelville, MT, 59847-9299, US, (406) 076-0024',
        '6737 Bright Quay, Lazy Mountain, KY, 42390-4772, US, (606) 256-7288',
        '237 Merry Campus, Twentysix, SC, 29330-4909, US, (864) 945-0157',
        '446 Fallen Gate Rise, Petrolia, SC, 29959-9527, US, (864) 826-0553',
        '2347 Indian Boulevard, Frisbee, VA, 23797-6458, US, (703) 656-8445',
        '365 Emerald Grove Line, Level, NC, 28381-1514, US, (919) 976-7958',
        '1207 Iron Extension, Klickitat, SC, 29197-8571, US, (803) 535-7888',
        '6770 Cinder Glen, Caronport, OH, 45053-5002, US, (440) 369-4018',
        '7619 Tawny Carrefour, Senlac, NV, 89529-9876, US, (775) 901-6433'];
}
import RefData from '../data/refData';
import {IFilter,IFilterParams} from "ag-grid/main";

var SKILL_TEMPLATE =
    '<label style="border: 1px solid lightgrey; margin: 4px; padding: 4px; display: inline-block;">' +
    '  <span>' +
    '    <div style="text-align: center;">SKILL_NAME</div>' +
    '    <div>' +
    '      <input type="checkbox"/>' +
    '      <img src="http://www.ag-grid.com/images/skills/SKILL.png" width="30px"/>' +
    '    </div>' +
    '  </span>' +
    '</label>';

var FILTER_TITLE =
    '<div style="text-align: center; background: lightgray; width: 100%; display: block; border-bottom: 1px solid grey;">' +
    '<b>TITLE_NAME</b>' +
    '</div>';

export default class SkillFilter implements IFilter {
    private filterChangedCallback:Function;
    private model:any;

    public init(params: IFilterParams) : void {
        this.filterChangedCallback = params.filterChangedCallback;
        this.model = {
            android: false,
            css: false,
            html5: false,
            mac: false,
            windows: false
        };
    };

    public getGui() {
        var eGui = document.createElement('div');
        eGui.style.width = '380px';
        var eInstructions = document.createElement('div');
        eInstructions.innerHTML = FILTER_TITLE.replace('TITLE_NAME', 'Custom Skills Filter');
        eGui.appendChild(eInstructions);

        var that = this;

        RefData.IT_SKILLS.forEach(function (skill, index) {
            var skillName = RefData.IT_SKILLS_NAMES[index];
            var eSpan = document.createElement('span');
            var html = SKILL_TEMPLATE.replace("SKILL_NAME", skillName).replace("SKILL", skill);
            eSpan.innerHTML = html;

            var eCheckbox = <HTMLInputElement> eSpan.querySelector('input');
            eCheckbox.addEventListener('click', function () {
                that.model[skill] = eCheckbox.checked;
                that.filterChangedCallback();
            });

            eGui.appendChild(eSpan);
        });

        return eGui;
    };

    public doesFilterPass(params) {

        var rowSkills = params.data.skills;
        var model = this.model;
        var passed = true;

        RefData.IT_SKILLS.forEach(function (skill) {
            if (model[skill]) {
                if (!rowSkills[skill]) {
                    passed = false;
                }
            }
        });

        return passed;
    };

    public isFilterActive() {
        var model = this.model;
        var somethingSelected = model.android || model.css || model.html5 || model.mac || model.windows;
        return somethingSelected;
    };

    public getModel():any {
        return undefined;
    }

    public setModel(model:any):void {
    }
}

import {IFilter,IFilterParams} from "ag-grid/main";

var FILTER_TITLE =
    '<div style="text-align: center; background: lightgray; width: 100%; display: block; border-bottom: 1px solid grey;">' +
    '<b>TITLE_NAME</b>' +
    '</div>';

var PROFICIENCY_TEMPLATE =
    '<label style="padding-left: 4px;">' +
    '<input type="radio" name="RANDOM"/>' +
    'PROFICIENCY_NAME' +
    '</label>';

var PROFICIENCY_NONE = 'none';
var PROFICIENCY_ABOVE40 = 'above40';
var PROFICIENCY_ABOVE60 = 'above60';
var PROFICIENCY_ABOVE80 = 'above80';

var PROFICIENCY_NAMES = ['No Filter', 'Above 40%', 'Above 60%', 'Above 80%'];
var PROFICIENCY_VALUES = [PROFICIENCY_NONE, PROFICIENCY_ABOVE40, PROFICIENCY_ABOVE60, PROFICIENCY_ABOVE80];

export default class ProficiencyFilter implements IFilter {
    private filterChangedCallback:Function;
    private selected:string;
    private valueGetter:Function;

    public init(params: IFilterParams) : void {
        this.filterChangedCallback = params.filterChangedCallback;
        this.selected = PROFICIENCY_NONE;
        this.valueGetter = params.valueGetter;
    }

    public getGui() {
        var eGui = document.createElement('div');
        var eInstructions = document.createElement('div');
        eInstructions.innerHTML = FILTER_TITLE.replace('TITLE_NAME', 'Custom Proficiency Filter');
        eGui.appendChild(eInstructions);

        var random = '' + Math.random();

        var that = this;
        PROFICIENCY_NAMES.forEach(function (name, index) {
            var eFilter = document.createElement('div');
            var html = PROFICIENCY_TEMPLATE.replace('PROFICIENCY_NAME', name).replace('RANDOM', random);
            eFilter.innerHTML = html;
            var eRadio = <HTMLInputElement> eFilter.querySelector('input');
            if (index === 0) {
                eRadio.checked = true;
            }
            eGui.appendChild(eFilter);

            eRadio.addEventListener('click', function () {
                that.selected = PROFICIENCY_VALUES[index];
                that.filterChangedCallback();
            });
        });

        return eGui;
    }

    public doesFilterPass(params) {

        var value = this.valueGetter(params);
        var valueAsNumber = parseFloat(value);

        switch (this.selected) {
            case PROFICIENCY_ABOVE40 :
                return valueAsNumber >= 40;
            case PROFICIENCY_ABOVE60 :
                return valueAsNumber >= 60;
            case PROFICIENCY_ABOVE80 :
                return valueAsNumber >= 80;
            default :
                return true;
        }

    }

    public isFilterActive() {
        return this.selected !== PROFICIENCY_NONE;
    }

    public getModel():any {
        return undefined;
    }

    public setModel(model:any):void {
    }
}
.toolbar button {
    margin: 2px; padding: 0;
}
.ag-cell {
    padding-top: 2px !important;
    padding-bottom: 2px !important;
}

label {
    font-weight: normal !important;
}

.div-percent-bar {
    display: inline-block;
    height: 100%;
    position: relative;
    z-index: 0;
}

.div-percent-value {
    position: absolute;
    padding-left: 4px;
    font-weight: bold;
    font-size: 13px;
    z-index: 100;
}

.div-outer-div {
    display: inline-block;
    height: 100%;
    width: 100%;
}