<!DOCTYPE html>
<html>

  <head>
    <title>Angular2-bound Kendo Grid Template Column</title>
    
    <script data-require="jquery@*" data-semver="2.2.0" src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script data-require="bootstrap@*" data-semver="3.3.6" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
    
    <link rel="stylesheet" href="style.css" />
    
    <link data-require="bootstrap-css@*" data-semver="3.3.6" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.css" />
    <link data-require="font-awesome@*" data-semver="4.5.0" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.css" />
    
    <link rel="stylesheet" href="http://kendo.cdn.telerik.com/2016.1.226/styles/kendo.common.min.css" />
    <link rel="stylesheet" href="http://kendo.cdn.telerik.com/2016.1.226/styles/kendo.flat.min.css" />
    <link rel="stylesheet" href="style.css" />
    
    <script src="https://code.angularjs.org/2.0.0-beta.0/angular2-polyfills.js"></script>
    <script src="https://code.angularjs.org/tools/system.js"></script>
    <script src="https://code.angularjs.org/tools/typescript.js"></script>
    <script src="config.js"></script>
    
    <script src="https://code.angularjs.org/2.0.0-beta.0/Rx.js"></script>
    <script src="https://code.angularjs.org/2.0.0-beta.0/angular2.min.js"></script>
    <script src="https://code.angularjs.org/2.0.0-beta.0/http.min.js"></script>

    <script src="http://kendo.cdn.telerik.com/2016.1.226/js/kendo.all.min.js"></script>
    
    <script>
    System.import('app')
      .catch(console.error.bind(console));
  </script>
  

  </head>

  <body>
    <my-app>
      loading...
    </my-app>
  </body>

</html>
 /* Stylings to allow for dropdown to appear outside of grid cell */
 
 /* 
   This creates column alignment issues...suggestions welcome!
 */
 .k-grid-content {
   overflow: visible;
 }
 
 .k-grid-content td {
   overflow: visible;
   text-align: center;
 }

.k-grid-header th,
.k-filter-row > th {
   border-bottom-width: 0;
   text-align: center;
   overflow: visible
 }
 
 .k-grid-header-wrap,
 .k-grid-footer-wrap {
   overflow: visible;
 }
### Kendo Grid templated column, bound by Angular 2

Click the blue action buttons in the right column to see the Kendo Grid rows' Unique Id (uid) values appear in the console

NOTICE: This is a proof of concept, and it may have performance issue or go against Angualr 2 or TypeScript best practices

If you find errors or have suggestions, please e-mail aaronjessen[at]gmail.com tweet to @aaronjessen
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
    emitDecoratorMetadata: true
  },
  //map tells the System loader where to look for things
  map: {
    app: "./src"
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    }
  }
});
//main entry point
import {DynamicComponentLoader} from 'angular2/core';
import {FORM_PROVIDERS} from 'angular2/common';
import {bootstrap} from 'angular2/platform/browser';
import {App} from './app';

bootstrap(App, [])
  .catch(err => console.error(err));
//our root app component
import {Component, DynamicComponentLoader, ElementRef, OnInit, View} from 'angular2/core'

import {KendoGridExample} from './kendo-grid-example';


@Component({
  selector: 'my-app',
  providers: [],
  template: `
    <div style="padding: 0 15px;">
      <h2>ng2-bound Kendo Grid Template Column</h2>
      <h4>Click the actions in the column dropdown and watch the row's unique id (uid) appear in the Output log</h4>
      
      <kendo-grid-example></kendo-grid-example>
      
      <div class="alert alert-info"
           style="margin: 15px 0; overflow-y: scroll; height: 200px;">
           <h4>Output (newest on top)</h4>
           <div id="log"></div>
    </div>
  `,
  directives: [KendoGridExample]
})
export class App {
  
  constructor() {
    //
  }
  
}
import {AfterViewInit, Component, DynamicComponentLoader, ElementRef} from 'angular2/core'

import {CustomActionDropdown} from './custom-action-dropdown';

/**
 * Main Kendo Grid Component
 */
@Component({
    directives: [ CustomActionDropdown /* templated column dropdown */ ],
    selector: 'kendo-grid-example',
    template: ``
})
export class KendoGridExample implements AfterViewInit {

  _that = this;

  /**
   * Static array of items for the grid
   * TODO: Declare dynamic Kendo DataSource inside of an Angular 2 Service
   */
  customers:Array<any> = [
    {
        _id: 1,
        firstName: 'Tom',
        lastName: 'Williams'
    },
    {
        _id: 2,
        firstName: 'Sally',
        lastName: 'Smith'
    },
    {
        _id: 3,
        firstName: 'Joe',
        lastName: 'Johnson'
    }
  ];
  
  /**
   * Here's an extensive collection of grid settings, if you're interested
   */
  gridOptions = {
        autoBind: true,
        columns: [
            {
                field: '_id',
                filterable: false,
                hidden: false,
                title: 'ID',
                width: '10%'
            },
            {
                field: 'firstName',
                filterable: {
                    cell: {
                        dataTextField: 'year',
                        delay: 0,
                        enabled: true,
                        minLength: 0,
                        operator: 'contains',
                        showOperators: true,
                        suggestionOperator: 'contains'
                    },
                    extra: true,
                    mode: 'menu, row'
                },
                title: 'First Name',
                width: '25%'
            },
            {
                field: 'lastName',
                filterable: {
                    cell: {
                        delay: 0,
                        enabled: true,
                        minLength: 0,
                        operator: 'contains',
                        showOperators: true,
                        suggestionOperator: 'contains'
                    }
                },
                title: 'Last Name',
                width: '45%'
            },
            {
                attributes: {
                    class: 'grid-action-column'
                },
                template: ``,
                title: 'Actions',
                width: '10%'
            }
        ],
        columnMenu: {
            columns: false,
            filterable: true,
            messages: {
                columns: 'Choose Columns',
                filter: 'Apply Filter',
                sortAscending: 'Sort Ascending',
                sortDescending: 'Sort Descending'
            },
            sortable: true
        },
        dataSource: {
          data: this.customers,
          pageSize: 5 // setting pageSize inside the dataSource prevents "NaNn-NaN of 3" for paging
        },
        dataBound: this.gridDataBound.bind(this._that), // bind the local 'this' scope (is this kosher?)
        editable: {
            confirmation: 'Are you sure you want to drop this prospect?',
            createAt: 'top',
            destroy: true,
            mode: 'popup', // inline | incell | popup
            update: true
        },
        filterable: {
            //extra: true,
            messages: {
                and: 'and',
                or: 'or',
                filter: 'Apply Filter',
                clear: 'Clear Filter',
                isFalse: 'False',
                isTrue: 'True',
                selectValue: 'Select'
            },
            mode: 'row'
        },
        groupable: true,
        messages: {
            filterempty: 'The applied filter has returned 0 results.'
        },
        mobile: {
            return false;
        },
        navigatable: false,
        pageable: {
            buttonCount: 5,
            info: true,
            input: true,
            messages: {
                display: 'Showing {0}-{1} of {2} prospects',
                empty: 'No listings available for display',
                first: 'First page',
                itemsPerPage: 'prospects per page',
                last: 'Last page',
                next: 'Next page',
                of: 'of {0}',
                previous: 'Previous Page',
                page: 'Go to',
                refresh: 'Refresh the grid'
            },
            pageSizes: [1, 5, 10, 15, 20],
            previousNext: true,
            numeric: false,
            refresh: true
        },
        pageSize: 5,
        remove: function (e) {
            //console.log('removing', e.model.firstName);
        },
        save: function (e) {
            //
        },
        saveChanges: function (e) {
            if (!confirm('Are you sure you want to save all changes?')) {
                e.preventDefault();
            }
        },
        scrollable: {
            virtual: false,
        },
        selectable: 'multiple, row',
        sortable: {
            allowUnsort: true,
            mode: 'multiple'
        }
    };
  
  /**
   * Pass in the elementRef and dynamic component loader for binding the column template
   */
  constructor(public elementRef:ElementRef, public loader:DynamicComponentLoader) {

  }
  
  /**
   * Instantite the grid using jQuery
   * Note: This also works appears to work inside ngOnInit()
   */
  ngAfterViewInit() {
      // use jQuery to create the Kendo Grid as usual
      jQuery(this.elementRef.nativeElement).kendoGrid(this.gridOptions);
  }
  
  /**
  * Kendo-UI type definitions for GridDataBoundEvent available from
  * https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/kendo-ui
  */
  gridDataBound(e: GridDataBoundEvent) {
        //var columns = e.sender.columns; // showing one way to access columns
        
        /* 
          Search for the index of the template column by class name
          (which is assigned inside this.gridOptions' columns declaration)
        */
        var actionColumnIndex = e.sender.wrapper.find('.grid-action-column').index();
        var rows = e.sender.tbody.children();
      
        /*
          Iterate through the rows to bind the template column with a dynamic loader
          (is this a performance issue for large datasets?)
        */
        $.each(rows, (index, item) => {
            var row = jQuery(item);
            var dataItem = e.sender.dataItem(row);
            var cell = row.children('td').eq(actionColumnIndex);

            /*
              Use the DynamicComponentLoader to load the CustomActionDropdown Component
              inside of the grid cells
            */
            this.loader.loadNextToLocation(CustomActionDropdown, this.elementRef)
                .then((result) => {
                    /*  
                      Example showing how to access methods on the templated Component.
                      For this example, it's passing in the row's uid to the setUid()
                      method of the CustomActionDropdown Component
                     */
                    result.instance.setUid(dataItem.uid);

                    // append the action button to the grid cell
                    cell.append(result.location.nativeElement);
                });
        });
    }
}
import {Component, OnInit, View} from 'angular2/core'

/**
 * Example of a templated grid column with access to Component event functions
 **/
@Component({
    selector: 'action-bar'
})
@View({
    /**
     * Templated dropown with click events tied to Component functions
     */
    template: `
      <div class="dropdown">
        <button class="btn btn-default dropdown-toggle"
                type="button"
                data-toggle="dropdown"
                aria-haspopup="true"
                aria-expanded="true">
            <i class="fa fa-cog"></i>
          <span class="caret"></span>
        </button>
        <ul class="dropdown-menu dropdown-menu-right"
            aria-labelledby="dropdownMenu1">
          <li class="dropdown-header">Customer Actions</li>
          <li>
            <a href="#"
               (click)="contactCustomer($event)">
                <i class="fa fa-user"></i> Contact
            </a>
          </li>
          <li>
            <a href="#"
                (click)="editCustomer($event)">
                  <i class="fa fa-pencil"></i> Edit
            </a>
          </li>
          <li role="separator" class="divider"></li>
          <li>
              <a href="#"
                 (click)="removeCustomer($event)">
                    <i class="fa fa-times"></i> Remove
              </a>
            </li>
        </ul>
      </div>
   `
})
export class CustomActionDropdown implements OnInit {

    public uid:string;

    constructor() {
      //
    }

    // receives and stores the grid row's unique id (uid) for later use
    setUid(uid) {
        this.uid = uid;
    }

    ngOnInit() {
      //
    }

    /**
     * Contact Customer
     * Event triggered by the button in the grid's template column
     */
    contactCustomer(e) {
        console.log('Contact Customer UID:', this.uid);
        $('#log').html(`<i class="fa fa-user"></i> Contact Customer UID: ${this.uid} </br/>` + $('#log').html());
    }
    
    /**
     * Edit Customer
     */
    editCustomer(e) {
        console.log('Edit Customer UID:', this.uid);
        $('#log').html(`<i class="fa fa-pencil"></i> Edit Customer UID: ${this.uid} </br/>` + $('#log').html());
    }
    
    /**
     * Remove Customer
     */
    removeCustomer(e) {
        console.log('Remove Customer UID:', this.uid);
        $('#log').html(`<i class="fa fa-times"></i> Remove Customer UID: ${this.uid} </br/>` + $('#log').html());
    }

}