var app = angular.module('plunker', []);

app.controller('MainCtrl', ['$scope', '$rootScope', '$sce',
  function($scope, $rootScope, $sce) {
    $scope.name = 'World';

    $scope.Appcontrols = [{
      defvalue: 222,
      "label": "Username:",
      "element": 'inputtag',
      "id": "uname"
    }, {
      defvalue: 222,
      "label": "Password:",
      "element": 'inputtag',
      "id": "upwd"
    }, {
      defvalue: 222,
      "label": "Email:",
      "element": 'inputtag',
      "id": "email"
    }, {
      defvalue: 222,
      "label": "email",
      "element": 'savetag',
      "id": "submit"
    }, {
      defvalue: 222,
      "label": "email",
      "element": 'cleartag',
      "id": "submit"
    }];

    $scope.Appcontrols.forEach(function(entry) {
      html = "";
      html = "<" + entry.element + " ng-model='$parent.form." + entry.id + "' id='" + entry.id + "' label='"+entry.label+"' >" + "</" + entry.element + ">";
      entry.element = $sce.trustAsHtml(html);
    });



    $scope.save = function() {
      alert('Saved ' + $scope.form.uname);
    }

    $scope.clear = function() {
      $scope.form = {};
    }


    $scope.counter = 0;

var dataSource = $scope.Appcontrols;


        //fake database
        var dataSource1 = [
            { id : 0, engine: "Trident", browser: "Internet Explorer 4.0", platform: "Win 95+", version: 4, grade: "X" },
            { id : 1, engine: "Trident", browser: "Internet Explorer 5.0", platform: "Win 95+", version: 5, grade: "C" },
            { id : 2, engine: "Trident", browser: "Internet Explorer 5.5", platform: "Win 95+", version: 5.5, grade: "A" },
            { id : 3, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
            { id : 4, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
            { id : 5, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
            { id : 6, engine: "Gecko", browser: "Firefox 2", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
            { id : 7, engine: "Gecko", browser: "Firefox 3", platform: "Win 2k+ / OSX.3+", version: 1.9, grade: "A" },
            { id : 8, engine: "Webkit", browser: "Safari 1.2", platform: "OSX.3", version: 125.5, grade: "A" },
            { id : 9, engine: "Webkit", browser: "Safari 1.3", platform: "OSX.3", version: 312.8, grade: "A" },
            { id : 10, engine: "Webkit", browser: "Safari 2.0", platform: "OSX.4+", version: 419.3, grade: "A" },
            { id : 11, engine: "Webkit", browser: "Safari 3.0", platform: "OSX.4+", version: 522.1, grade: "A" },
            { id : 12, engine: "Trident", browser: "Internet Explorer 4.0", platform: "Win 95+", version: 4, grade: "X" },
            { id : 13, engine: "Trident", browser: "Internet Explorer 5.0", platform: "Win 95+", version: 5, grade: "C" },
            { id : 14, engine: "Trident", browser: "Internet Explorer 5.5", platform: "Win 95+", version: 5.5, grade: "A" },
            { id : 15, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
            { id : 16, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
            { id : 17, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
            { id : 18, engine: "Gecko", browser: "Firefox 2", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
            { id : 19, engine: "Gecko", browser: "Firefox 3", platform: "Win 2k+ / OSX.3+", version: 1.9, grade: "A" },
            { id : 20, engine: "Webkit", browser: "Safari 1.2", platform: "OSX.3", version: 125.5, grade: "A" },
            { id : 21, engine: "Webkit", browser: "Safari 1.3", platform: "OSX.3", version: 312.8, grade: "A" },
            { id : 22, engine: "Webkit", browser: "Safari 2.0", platform: "OSX.4+", version: 419.3, grade: "A" },
            { id : 23, engine: "Webkit", browser: "Safari 3.0", platform: "OSX.4+", version: 522.1, grade: "A" },
            { id : 24, engine: "Trident", browser: "Internet Explorer 4.0", platform: "Win 95+", version: 4, grade: "X" },
            { id : 25, engine: "Trident", browser: "Internet Explorer 5.0", platform: "Win 95+", version: 5, grade: "C" },
            { id : 26, engine: "Trident", browser: "Internet Explorer 5.5", platform: "Win 95+", version: 5.5, grade: "A" },
            { id : 27, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
            { id : 28, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
            { id : 29, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" }
        ];

        //<editor-fold desc='CONFIGURE DATA TABLE SECTION'>

        //define custom headers for columns
        var customTitles = [
            {propertyName : 'defvalue', title : '1. defvalue'},
            {propertyName : 'label', title : '2. label'},
            {propertyName : 'element', title : '3. element'},
            {propertyName : 'id', title : '4. id'}
        ];

        $scope.customTitles = customTitles;

        //custom header controls of the data table
        var selector = {
            type: DataTableExtensions.headerControls.select,
            properties: {
                label: "Version:",
                options: ["0-10", "10-100", "100-1000"],
                filterName: "version",
                trigger: {mode: DataTableExtensions.triggerModes.explicit, starter: "buttonFilter"}
            }
        };

        var textBox = {
            type: DataTableExtensions.headerControls.textBox,
            properties: {
                label: "Search:",
                filterName: "search",
                trigger: {mode: DataTableExtensions.triggerModes.explicit, starter: "buttonFilter"}
            }
        };

        var button1 = {
            type: DataTableExtensions.headerControls.button,
            properties: {
                name: "buttonFilter",
                text: "Search"
                //callback: function(){console.log("fucking awesome")} //option to bind controller function to button
            }
        };

        var button2 = {
            type: DataTableExtensions.headerControls.button,
            properties: {
                name: "buttonDoNotClick",
                text: "DO NOT CLICK",
                callback: function(){alert("I said do not click it!")} //option to bind controller function to button
            }
        };

        //array of controls inside the array makes the controls into one logical group
        $scope.headerControls = [[selector, textBox, button1], button2];

        //paging - page sizes
        $scope.pageSizes = [10,50,100];

        //sorting allowed only by this properties
        $scope.sortingProperties = ["engine", "grade"];

        //hide the following properties
        $scope.hidingProperties = ["id"];

        //data source of the data table's first page
        /******************************IMPLICIT PAGINATION*************************************/
        /* Remove slice and add the whole collection as source if you use implicit pagination */
        $scope.dataSource = dataSource.slice(0, 10);
        /**************************************************************************************/

        //need to store the last used settings to reuse it after delete/edit
        var lastUsedData;
        //callback function of the data table
        $scope.refresh = function (data) {

            lastUsedData = data;
            var tempDataSource = dataSource;

            //filter options
            if(data.filterOptions){

                for(var i = 0; i < data.filterOptions.length; i++){

                    //filter names are from the custom header controls defined earlier
                    if(data.filterOptions[i].filterName == "version"){

                        tempDataSource = sortDataByVersion(tempDataSource, data.filterOptions[i].filterValue);
                    }

                    if(data.filterOptions[i].filterName == "search"){

                        tempDataSource = sortDataBySearch(tempDataSource, data.filterOptions[i].filterValue);
                    }
                }
            }

            //now it use only the paging changes
            /***************IMPLICIT PAGINATION*****************/
            /* Comment this row out if you use implicit paging */
            $scope.dataSource = tempDataSource.slice(data.pageIndex * data.pageSize, (data.pageIndex + 1) * data.pageSize);
            /***************************************************/
        };

        var sortDataByVersion = function(source, versionRangeString){

            if(versionRangeString){

                var versions = versionRangeString.split("-");
                var result = [];

                if(versions){

                    var start = parseInt(versions[0]);
                    var end = parseInt(versions[1]);

                    for(var i = 0; i < source.length; i++){
                        if(source[i].version >= start && source[i].version <= end){
                            result.push(source[i]);
                        }
                    }
                }

                return result;
            }

            return source;
        };

        var sortDataBySearch = function(source, searchCondition){

            if(searchCondition){

                var result = [];

                for(var i = 0; i < source.length; i++){
                    if(source[i].platform.indexOf(searchCondition) != -1){
                        result.push(source[i]);
                    }
                }

                return result;
            }

            return source;
        };

        //fake function on select
        $scope.select = function(item){

            console.log("SELECTED");
            console.log(item);
        };

        //fake function on edit
        $scope.editRow = function(item){

            console.log("EDIT");
            console.log(item);
        };

        //fake function on select
        $scope.deleteRow = function(item){

            dataSource.deleteById(item);

            var tmpData = {pageIndex : 0, pageSize : 10};

            $scope.refresh(lastUsedData ? lastUsedData : tmpData);
        };

        $scope.gradeConverter = function (value) {

            if (value === 'A') {
                return '5';
            } else if(value === 'C'){
                return '3';
            }else{
                return '1';
            }
        };

        $scope.converters = [
            { property: 'grade', converterCallback: 'gradeConverter(value)'}
        ];
        //</editor-fold>

//helper function to delete item by id
Array.prototype.deleteById = function (obj) {

    for (var i = 0; i < this.length; i++) {

        if (this[i].id === obj.id) {
            this.splice(i, 1);
        }
    }
}



}]);//controller

app.directive('inputtag', function($compile) {
  return {
    restrict: 'E',
    scope: {
      ngModel: '=',
      label: '@'
    },
    template: '<div><input type="text" ng-model="ngModel" ></div>',
    link: function(scope, element, attrs) {
       var html = '<div class="span12 field-box"><label>'+attrs.label+'</label><input class="inputbox_custom" type="text" ng-model="ngModel" id="'+attrs.id+'"/></div>';
       var e2 = $compile(html)(scope);
       element.replaceWith(e2);
    }

  };
});

app.directive('savetag', function() {
  return {
    restrict: 'E',
    scope: {
      ngModel: '='
    },
    template: '<div><button ng-click="$parent.save()" href="#">Save</button></div>',
    link: function(scope, elem, attrs) {}

  };
});

app.directive('cleartag', function() {
  return {
    restrict: 'E',
    scope: {
      ngModel: '='
    },
    template: '<div><button ng-click="$parent.clear()" href="#">Clear</button></div>',
    link: function(scope, elem, attrs) {}

  };
});



/*This compiles dynamically created html (inputbox directive)*/
app.directive('compile', ['$compile',
  function($compile) {
    return function(scope, element, attrs) {
      scope.$watch(
        function(scope) {
          // watch the 'compile' expression for changes
          return scope.$eval(attrs.compile);
        },
        function(value) {
          element.html(value);
          $compile(element.contents())(scope);
        }
      );
    };
  }
]);


<!DOCTYPE html>
<html ng-app="plunker">

<head>
  <meta charset="utf-8" />
  <title>AngularJS Plunker</title>
  <script>
    document.write('<base href="' + document.location + '" />');
  </script>
  <link rel="stylesheet" href="style.css" />
  <link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/themes/redmond/jquery-ui.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.8.2/css/jquery.dataTables.css">
  <script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.5/angular.js" data-semver="1.2.5"></script>
  <script data-require="angular-sanitize@*" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular-sanitize.min.js"></script>

<!-- DataTables CSS -->
<link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css">
 
<!-- jQuery -->
<script type="text/javascript" charset="utf8" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"></script>
 
<!-- DataTables -->
<script type="text/javascript" charset="utf8" src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>



  <script src="app.js"></script>
   <script src="dataTableExtensions.js"></script>
    <script src="dataTable.js"></script>
</head>

<body ng-controller="MainCtrl">

  <b>Custom Dynamic Form</b>
  <div ng-repeat="ctrls in Appcontrols">
    <div ng-bind-html="ctrls.element" compile></div>
  </div>



<div my-data-table
     my-source="dataSource"
     refresh="refresh(data)"
     header="headerControls"
     page-sizes="pageSizes"
     default-page-size="10"
     pagination-type="explicit"
     header-titles="customTitles"
     sorting-properties="sortingProperties"
     hiding-properties="hidingProperties"
     delete-row="deleteRow(item)"
     edit-row="editRow(item)"
     converters="converters"
     select-row="select(item)">
</div>

</body>

</html>
table.dataTable thead th {
    text-transform: uppercase;
}

table.dataTable td {
    vertical-align: middle;
}

.dataTables_paginate {
    margin-left: auto;
    margin-right: auto;
}

.paginate_disabled_previous:active,
.paginate_enabled_previous:active,
.paginate_disabled_next:active,
.paginate_enabled_next:active {
    outline: none;
}

.paginate_disabled_previous,
.paginate_disabled_next {
    display: none !important;
}

.paginate_disabled_previous,
.paginate_enabled_previous {
    margin-left: 23px;
}

.paginate_disabled_next,
.paginate_enabled_next {
    padding-right: 23px;
    margin-left: 10px;
}

/*own css classes*/
#customHeader {
    position: relative;
    top: 28px;
    margin-left: -5px;
    margin-bottom: 30px;
    z-index: 500;
}

.header-selector{
    display: inline;
    margin: 10px;
}

select.header-selector {
    width:auto;
}

.header-button{
    display: inline;
    margin: 10px;
}

.header-textbox{
    display: inline;
    margin: 10px;
}

.header-label{
    display: inline;
    margin: 10px -5px 10px 10px;
}

.control-group{
    margin-left: 50px;
    margin-right: 50px;
    display: inline;
}

.header-controls{
    display: inline;
}

.dataTables_empty{
    margin-left: auto;
    margin-right: auto;
    text-align: center !important;
}

angular.module('plunker')
    .directive('myDataTable', function ($parse) {
        'use strict';

        return {
            template: '<table class="display table table-striped table-hover" id="my-data-table"><div id="customHeader"></div></table>',

            link: function postLink(scope, element, attrs) {

                //<editor-fold desc="region: constants">
                var tableRowId = '#my-data-table tbody tr';
                var tableId = '#my-data-table';
                var twoButtonsPagination = 'my_two_buttons';
                var customButtonsPagination = 'my_custom_buttons';
                //</editor-fold>

                //<editor-fold desc="region: variables">
                //store the original data source to get element later by row index
                var originalSource;
                var actualPageIndex = 0;
                var orderBy;
                var filterOptions = [];
                var headerControls = [];
                var headers = [];
                var headerTitles = [];
                var elementsMaxReached = false;
                var lastIndex = -1;
                //first element is true because the datatable initialization calls the decorate function on the first page
                var pageDecorationStatus = [true];
                //new option to use custom header titles
                var haveCustomHeaderTitles = false;
                var table;
                var isInitTime = true;

                var sortables = [];
                var toHide = [];
                //</editor-fold>

                //<editor-fold desc="region: handler parsers">
                //ANGULAR - parse callable functions from attributes
                var refreshHandler = $parse(attrs.refresh);

                var onRowSelectedHandler = $parse(attrs.selectRow);

                var onEditRow = $parse(attrs.editRow);

                var onDeleteRow = $parse(attrs.deleteRow);
                //</editor-fold>

                //<editor-fold desc="region: parsed attrs">

                //new option to define one default page size
                var pageSize = scope[attrs.defaultPageSize] ? parseInt(scope[attrs.defaultPageSize]) : 10;
                //optional page sizes for page size selector
                var pageSizesAttr = scope[attrs.pageSizes];
                var headerControlsAttr = scope[attrs.header];
                var convertersAttr = scope[attrs.converters];
                //support case-insensitivity
                var sortingProperties = scope[attrs.sortingProperties];
                for (var i = 0; i < sortingProperties.length; i++) {
                    sortables.push(sortingProperties[i].toLowerCase())
                }
                var hidingProperties = scope[attrs.hidingProperties];
                for (var i = 0; i < hidingProperties.length; i++) {
                    toHide.push(hidingProperties[i].toLowerCase())
                }

                var headerTitlesAttr = scope[attrs.headerTitles];
                var paginationTypeAttr = attrs.paginationType;
                //</editor-fold>

                //<editor-fold desc="region: callbacks called from outside">

                //dataTableExtensions
                //get previous page - callback of dataTables's pagination
                function previousCallback() {

                    if (actualPageIndex > 0) {

                        actualPageIndex--;
                        refreshCallback();
                    }
                }

                //dataTableExtensions
                //get next page - callback of dataTables's pagination
                function nextCallback() {

                    if (!elementsMaxReached) {

                        actualPageIndex++;
                        refreshCallback();
                    }
                }

                //inner use
                //set the filter conditions - callback of the custom header controls
                function setFilterOptions(options) {

                    for (var i = 0; i < options.length; i++) {

                        var filter = filterOptions.getElementByFilterName(options[i]);
                        if (filter) {

                            filter.filterValue = options[i].filterValue;
                        } else {
                            filterOptions.push(options[i]);
                        }
                    }

                    //after filter we want to see the results started from the first page
                    actualPageIndex = 0;
                    refreshCallback();
                }

                //inner use
                //set the actual page size - callback of the custom header controls
                function setPageSize(size) {

                    pageSize = size;

                    //after changed page size we want to see the results started from the first page
                    actualPageIndex = 0;
                    refreshCallback();
                }

                //inner use
                //check if it has a next page or not - callback of dataTables's pagination
                function hasNextPage() {

                    return !elementsMaxReached;
                }

                //inner use
                //check if it has a previous page or not - callback of dataTables's pagination
                function hasPreviousPage() {

                    return actualPageIndex !== 0;
                }

                //</editor-fold>

                //<editor-fold desc="region: table decorators">

                //calls the refresh method of the controller, sends the new filter/order/paging options to the controller
                function refreshCallback() {

                    //collect the filter options
                    var conditions = {
                        pageIndex: actualPageIndex,
                        pageSize: pageSize,
                        orderBy: orderBy,
                        filterOptions: filterOptions
                    };

                    scope.$apply(function () {

                        refreshHandler({data: conditions}, scope, { $event: event });
                    });
                }

                //calls decoration on new page if the data tables uses the my-two-buttons pagination mode
                //it stores if a page was already decorated or not
                function decorateCallback(startIndex) {

                    var pageIndex = startIndex / pageSize;

                    if (!pageDecorationStatus[pageIndex]) {

                        decorateDataTableOnChange();
                        pageDecorationStatus[pageIndex] = true;
                    }
                }

                //callback on row selection
                function setRowSelectionCallback() {

                    $(tableRowId).click(function (event) {

                        //TWITTER BOOTSTRAP - color the row
                        $(tableRowId).removeClass('info');
                        $(this).addClass('info');

                        var index = $(this).context._DT_RowIndex;

                        //set lastIndex to stay consistent with row change on arrow key pressed
                        lastIndex = $(this).context.rowIndex - 1;

                        var itemOnIndex = originalSource[index];

                        scope.$apply(function () {

                            onRowSelectedHandler({item: itemOnIndex}, scope, {$event: event});
                        });
                    });
                }

                //function to add extra buttons to rows
                function addExtraButtonToRow(button) {

                    //create empty header (to extend the style)
                    if ($('#' + button.name).length === 0) {

                        var nCloneTh = $('<th>');
                        nCloneTh.attr("id", button.name);
                        nCloneTh.appendTo($(tableRowId));
                    }

                    var nCloneTd = $('<td>');

                    var btn = $('<button class="header-button"/>');

                    //add class by style set on button param
                    switch (button.style) {
                        case DataTableExtensions.buttonStyle.info:
                            btn.addClass("btn btn-info");
                        case DataTableExtensions.buttonStyle.warning:
                            btn.addClass("btn btn-warning");
                        case DataTableExtensions.buttonStyle.success:
                            btn.addClass("btn btn-success");
                        case DataTableExtensions.buttonStyle.error:
                            btn.addClass("btn btn-danger");
                        default:
                            btn.addClass("btn");
                    }

                    btn.text(button.text);

                    btn.appendTo(nCloneTd);

                    $(tableRowId).each(function (i) {

                        var tdButton = nCloneTd.clone(true);

                        tdButton.click(function (event) {

                            event.stopPropagation();
                            button.callback(event, originalSource[i]);
                        });

                        tdButton.appendTo(this);
                    });
                }

                //function to call on data table change
                function decorateDataTableOnChange() {

                    setRowSelectionCallback();

                    //if the function for editing a row parsed from an attribute exists add edit button
                    if (onEditRow != angular.noop) {

                        var btnEdit = {
                            name: "edit",
                            style: DataTableExtensions.buttonStyle.warning,
                            text: "Edit",
                            callback: function (event, obj) {
                                event.preventDefault();
                                scope.$apply(function () {

                                    onEditRow({item: obj}, scope, {$event: event});
                                });
                            }};

                        addExtraButtonToRow(btnEdit);
                    }

                    //if the function for deleting a row parsed from an attribute exists add delete button
                    if (onDeleteRow != angular.noop) {

                        var btnDelete = {
                            name: "delete",
                            style: DataTableExtensions.buttonStyle.error,
                            text: "Delete",
                            callback: function (event, obj) {
                                event.preventDefault();
                                scope.$apply(function () {

                                    onDeleteRow({item: obj}, scope, {$event: event});
                                });
                            }};

                        addExtraButtonToRow(btnDelete);
                    }
                }

                //navigation allowed by arrow keys
                function setNavigationOnArrows() {

                    $(document).keydown(function (event) {

                        var currentRow;
                        var rowsOnPageCount = $(tableRowId).length;

                        //!!!!!nice from dataTables
                        //$(currentRow).next().context._DT_RowIndex -- gives the original index of the row back (after ordering it remains the same)
                        //$(currentRow).context.rowIndex -- gives the actual index back

                        //change row selection and/or page on key down
                        switch (event.keyCode) {
                            //arrow down
                            case 40:

                                if (lastIndex < rowsOnPageCount - 1) {
                                    lastIndex++;
                                    currentRow = $(tableRowId).get(lastIndex);
                                    $(currentRow).prev().removeClass("info");
                                    $(currentRow).addClass("info");
                                } else {
                                    if (hasNextPage()) {
                                        var infoId = tableRowId + ".info";
                                        $(infoId).removeClass("info");
                                        lastIndex = -1;
                                        nextCallback();
                                    } else {
                                        lastIndex = rowsOnPageCount - 1;
                                    }
                                }
                                break;
                            //arrow up
                            case 38:
                                if (lastIndex > 0) {
                                    lastIndex--;
                                    currentRow = $(tableRowId).get(lastIndex);
                                    $(currentRow).next().removeClass("info");
                                    $(currentRow).addClass("info");
                                } else {
                                    if (hasPreviousPage()) {
                                        lastIndex = pageSize;
                                        previousCallback();
                                    } else {
                                        lastIndex = 0;
                                    }
                                }
                                break;
                        }
                    });
                }

                //</editor-fold>

                //<editor-fold desc="region: draw and redraw table">

                //create header of the table - set sorting and hide if needed
                function prepareHeader(header) {

                    //get the property names for header titles
                    for (var i = 0; i < header.length; i++) {
                        var head = header[i];
                        var item = (haveCustomHeaderTitles === true) ? head.propertyName : head;
                        var title = (haveCustomHeaderTitles === true) ? head.title : head;

                        if (!toHide || toHide.indexOf(item.toLowerCase()) == -1) {

                            var sortable = attrs.allSortable === "";

                            if (sortables && sortables.indexOf(item.toLowerCase()) != -1) {
                                sortable = true;
                            }

                            headers.push(item);
                            headerTitles.push({ "sTitle": title, "bSortable": sortable });
                        }
                    }
                }

                //create rows of the table - hide if needed
                function prepareRows(source) {

                    var rows = [];

                    for (var i = 0; i < source.length; i++) {

                        var row = [];

                        //use header to iterate through the elements to keep the property order
                        //'hide items' happens in prepareHeaders
                        for (var j = 0; j < headers.length; j++) {
                            var item = headers[j];//.sTitle;

                            var valueToAdd = source[i][item];

                            //use converters
                            if (convertersAttr) {
                                var converterString = convertersAttr.getConverterStringTo(item);
                                if (converterString) {
                                    var converter = $parse(converterString);
                                    valueToAdd = converter({value: valueToAdd}, scope, { $event: event });
                                }
                                row.push(valueToAdd);
                            }
                        }

                        rows.push(row);
                    }

                    return rows;
                }

                //prepare the header controls to add them to the header
                function prepareHeaderControls() {

                    //if the pageSizes attr is defined add it to the header controls
                    if (pageSizesAttr) {

                        var pageSizeChanger = {
                            type: DataTableExtensions.headerControls.select,
                            properties: {
                                options: pageSizesAttr,
                                label: "Show",
                                trigger: {mode: DataTableExtensions.triggerModes.onChange, callback: setPageSize} //calls the data table filters changed event
                            }
                        };

                        headerControls.push(pageSizeChanger);
                    }

                    //collect all of the header controls
                    if (headerControlsAttr) {
                        for (var i = 0; i < headerControlsAttr.length; i++) {

                            var control = headerControlsAttr[i];

                            //check if trigger mode is 'onchange' but there is no callback set by user
                            if (control.trigger && control.trigger.mode === DataTableExtensions.triggerModes.onChange
                                && !control.trigger.callback) {

                                control.trigger.callback = refreshCallback;
                            }

                            headerControls.push(control);
                        }
                    }

                    createCustomHeader(headerControls, setFilterOptions);
                }

                //call the create the header controls
                prepareHeaderControls();

                //call the create and set own paginations
                createPagination(previousCallback, nextCallback, hasNextPage, hasPreviousPage, decorateCallback);

                //function to draw the table
                function drawTable(source) {

                    originalSource = source;

                    //get the property names for header titles or the user defined header titles
                    if (headerTitlesAttr) {
                        haveCustomHeaderTitles = true;
                        prepareHeader(headerTitlesAttr);
                    } else {
                        prepareHeader(source[0]);
                    }

                    //get the property values for rows
                    var rows = prepareRows(source);

                    //set pagination
                    var pagination = twoButtonsPagination;

                    if (paginationTypeAttr) {
                        switch (paginationTypeAttr) {
                            case 'explicit':
                                pagination = customButtonsPagination;
                                break;
                            case 'implicit':
                                pagination = twoButtonsPagination;
                                break;
                        }
                    }

                    table = $(tableId).dataTable({
                        "aoColumns": headerTitles,
                        "aaData": rows,
                        "iDisplayLength": pageSize,
                        "fnInfoCallback": onInfoChanged,
                        "sPaginationType": pagination, //my implementation in dataTablesExtensions
                        "sDom": 't<"bottom"pi>',
                        "bFilter": false, //hides the search field
                        "bLengthChange": false, //hides the page size change option
                        "bSort": true //allows ordering
                    });

                    decorateDataTableOnChange();

                    setNavigationOnArrows();
                }

                //function called on the source change
                function refreshData(source) {

                    originalSource = source;

                    //check if it is the end of the elements or probably it is more
                    elementsMaxReached = source.length < pageSize;

                    table.fnClearTable();

                    var rows = prepareRows(source);

                    table.fnAddData(rows);

                    decorateDataTableOnChange();
                }

                //after adding the custom header it is not triggered by the default controls
                //checked why? ----------> 'i' was missing from sDom...
                function onInfoChanged(oSettings) { //other available params: iStart, iEnd, iMax, iTotal, sPre

                    //reading out the ordering info from oSettings
                    if (oSettings) {
                        var headerIndex = oSettings.aaSorting[0][0];
                        var direction = oSettings.aaSorting[0][1];
                        var header = headers[headerIndex];
                        var newOrder = { property: header.sTitle, direction: direction};

                        //need to check because at the first creation time it will be called to and at this time we are already in digest phase
                        if (!scope.$$phase) {
                            if (orderBy !== newOrder) {

                                orderBy = newOrder;
                                actualPageIndex = 0;
                                refreshCallback();
                            }
                        }
                    }
                }

                //set watch on the scope element
                function setWatchOnTheSource() {
                    var mySourceId = attrs.mySource;
                    var mySource = scope[mySourceId];

                    if (mySource) {
                        scope.$watch(mySourceId, function (source) {

                            //the first time set of the collection is a watch triggering action too
                            if (isInitTime && source && source.length > 0) {

                                //initialize the table
                                drawTable(mySource);
                                isInitTime = false;
                                return;
                            } else if (!isInitTime) {

                                if (source && source.length > 0) {

                                    //page size needs to be set explicitly on the table options too
                                    var oSettings = table.fnSettings();
                                    oSettings._iDisplayLength = pageSize;

                                    //redraw after resize
                                    table.fnDraw();

                                    refreshData(source);
                                } else {
                                    //if empty source comes back
                                    elementsMaxReached = true;
                                    table.fnClearTable();
                                }
                            }
                        });
                    }
                }

                //call the set watch
                setWatchOnTheSource();
                //</editor-fold>
            }
        }
    });


//<editor-fold desc="array extensions">
Array.prototype.getElementByFilterName = getElementByFilterName;
function getElementByFilterName(obj) {
    if (obj) {
        for (var i = 0; i < this.length; i++) {
            if (this[i].filterName === obj.filterName) {
                return this[i];
            }
        }
    }
}


Array.prototype.getConverterStringTo = getConverterStringTo;
function getConverterStringTo(property) {
    if (property) {
        for (var i = 0; i < this.length; i++) {
            var element = this[i];
            if (element && element.property && element.property.toLowerCase() === property.toLowerCase()) {

                return element.converterCallback;
            }
        }
    }
}
var DataTableExtensions = DataTableExtensions || {};

DataTableExtensions.headerControls = {
    select: 0,
    button: 1,
    textBox: 2
};

DataTableExtensions.triggerModes = {
    onChange: 0,
    explicit: 1
};

DataTableExtensions.buttonStyle = {
    info: 0,
    warning: 1,
    error: 2,
    success: 3
};

//function to overwrite the pagination system of dataTables
function createPagination(previousCallback, nextCallback, hasNextPageCallback, hasPreviousPageCallback, decorateCallback) {

    $.fn.dataTableExt.oPagination.my_custom_buttons = {

        fnInit: function (oSettings, nPaging) {

            var pager = $('<ul class="pager">').appendTo(nPaging);

            console.log(pager);

            var nPreviousWrapper = $('<li id="previousPager">').appendTo(pager);//.addClass("paginate_enabled_previous");
            var nNextWrapper = $('<li id="nextPager">').appendTo(pager);//.addClass("paginate_enabled_next");

            var nPrevious = $('<a>').text("Previous").appendTo(nPreviousWrapper);
            var nNext = $('<a>').text("Next").appendTo(nNextWrapper);

            //click event subscribe on previous
            nPrevious.click(previousCallback);

            //click event subscribe on next
            nNext.click(nextCallback);


            //disallow text selection
            nPrevious.bind('selectstart', function () {
                return false;
            });
            nNext.bind('selectstart', function () {
                return false;
            });
        },

        fnUpdate: function (oSettings) {

            //check if there is the paging control allowed or not
            if (!oSettings.aanFeatures.p) {
                return;
            }

            $('#previousPager').switchClass(hasPreviousPageCallback, "paginate_enabled_previous", "paginate_disabled_previous");

            $('#nextPager').switchClass(hasNextPageCallback, "paginate_enabled_next", "paginate_disabled_next")

        }
    };


    $.fn.dataTableExt.oPagination.my_two_buttons = {
        /*
         * Function: oPagination.two_button.fnInit
         * Purpose:  Initialise dom elements required for pagination with forward/back buttons only
         * Returns:  -
         * Inputs:   object:oSettings - dataTables settings object
         *           node:nPaging - the DIV which contains this pagination control
         *           function:fnCallbackDraw - draw function which must be called on update
         */
        "fnInit": function ( oSettings, nPaging, fnCallbackDraw )
        {
            //var oLang = oSettings.oLanguage.oPaginate;
            //var oClasses = oSettings.oClasses;
            var fnClickHandler = function ( e ) {
                if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
                {
                    fnCallbackDraw( oSettings );
                }
            };

            var pager = $('<ul class="pager">').appendTo(nPaging);

            var nPreviousWrapper = $('<li id="previousPager">').appendTo(pager);//.addClass("paginate_enabled_previous");
            var nNextWrapper = $('<li id="nextPager">').appendTo(pager);//.addClass("paginate_enabled_next");

            var nPrevious = $('<a>').text("Previous").appendTo(nPreviousWrapper);
            var nNext = $('<a>').text("Next").appendTo(nNextWrapper);

            /*var els = $('a', nPaging);
            var nPrevious = els[0],
                nNext = els[1];*/

            oSettings.oApi._fnBindAction( nPrevious, {action: "previous"}, fnClickHandler );
            oSettings.oApi._fnBindAction( nNext,     {action: "next"},     fnClickHandler );

            /* ID the first elements only */
            if ( !oSettings.aanFeatures.p )
            {
                nPaging.id = oSettings.sTableId+'_paginate';
                nPrevious.attr('id', oSettings.sTableId+'_previous');
                nNext.attr('id', oSettings.sTableId+'_next');

                nPrevious.attr('aria-controls', oSettings.sTableId);
                nNext.attr('aria-controls', oSettings.sTableId);
            }
        },

        /*
         * Function: oPagination.two_button.fnUpdate
         * Purpose:  Update the two button pagination at the end of the draw
         * Returns:  -
         * Inputs:   object:oSettings - dataTables settings object
         *           function:fnCallbackDraw - draw function to call on page change
         */
        "fnUpdate": function ( oSettings )
        {
            if (!oSettings.aanFeatures.p) {
                return;
            }

            $('#previousPager').switchClass(function (){ return oSettings._iDisplayStart === 0 }, "paginate_disabled_previous", "paginate_enabled_previous");

            $('#nextPager').switchClass(function(){ return oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay()}, "paginate_disabled_next", "paginate_enabled_next");

            decorateCallback(oSettings._iDisplayStart);
        }
    };
}

//function to create own header for the data table
function createCustomHeader(controls, filterOptionsCallback) {

    //get the placeholder div for the custom header
    var customHeader = $("#customHeader");

    //array of own filter options defined by new controls
    var filtersToTrigger = [];

    //walk through the custom controls that the user want
    for (var i = 0; i < controls.length; i++) {

        var control = controls[i];

        //if array element is not a single element
        if (Object.prototype.toString.call(control) === '[object Array]') {
            createControlGroup(control);
        } else {

            var createFunction;
            switch (control.type) {

                case DataTableExtensions.headerControls.button:
                    createFunction = createButton;
                    break;
                case DataTableExtensions.headerControls.textBox:
                    createFunction = createTextBox;
                    break;
                case DataTableExtensions.headerControls.select:
                    createFunction = createSelector;
                    break;
            }

            createAndAppendControl(createFunction, control, customHeader);
        }
    }

    function createControlGroup(controls) {

        var controlGroup = $('<div></div>');
        controlGroup.addClass("control-group");

        //walk through the custom controls that the user want
        for (var i = 0; i < controls.length; i++) {

            var control = controls[i];

            var createFunction;
            switch (control.type) {

                case DataTableExtensions.headerControls.button:
                    createFunction = createButton;
                    break;
                case DataTableExtensions.headerControls.textBox:
                    createFunction = createTextBox;
                    break;
                case DataTableExtensions.headerControls.select:
                    createFunction = createSelector;
                    break;
            }

            createAndAppendControl(createFunction, control, controlGroup);
        }

        controlGroup.appendTo(customHeader);
    }

    function createAndAppendControl(createFunction, control, addToThis) {

        var newControl = createFunction(control.properties);
        newControl.appendTo(addToThis);
    }

    //create and append selector
    function createSelector(properties) {

        var selectorDiv = $('<div class="header-controls"></div>');

        //create the select tag with css class
        var select = $('<select class="header-selector"/>');

        //create label if the text box have label text
        if (properties.label) {

            var label = $('<label class="header-label">').text(properties.label);
            label.appendTo(selectorDiv);
        }

        select.appendTo(selectorDiv);

        //set the options for select
        for (var i = 0; i < properties.options.length; i++) {

            //$('<option />', {value: item, text: properties.options[item]}).appendTo(select);
            $('<option />', { value: properties.options[i], text: properties.options[i] }).appendTo(select);
        }

        //set trigger on sending filter infos of the selector
        if (properties.trigger) {

            var mode = properties.trigger.mode;

            if (mode === DataTableExtensions.triggerModes.explicit) {

                filtersToTrigger.push({
                    starter: properties.trigger.starter,
                    name: properties.filterName,
                    value: properties.options[0]
                });

                select.change(function () {

                    filtersToTrigger.push({
                        starter: properties.trigger.starter,
                        name: properties.filterName,
                        value: $(this).val()
                    });
                });
            }
            else if (mode === DataTableExtensions.triggerModes.onChange) {

                select.change(function () {
                    properties.trigger.callback($(this).val());
                });
            }
        }

        return selectorDiv;
    }

    //create and append text box
    function createTextBox(properties) {

        var textBoxDiv = $('<div class="header-controls"></div>');

        var textBox = $('<input type="text" class="header-button">');

        //create label if the text box have label text
        if (properties.label) {

            var label = $('<label class="header-label">').text(properties.label);
            label.appendTo(textBoxDiv);
        }

        textBox.appendTo(textBoxDiv);

        if (properties.trigger) {

            var mode = properties.trigger.mode;

            if (mode === DataTableExtensions.triggerModes.explicit) {

                filtersToTrigger.push({starter: properties.trigger.starter, name: properties.filterName});

                //if the filter is triggered by other control, just collect the value on change
                textBox.change(function () {

                    //starter - the other control that will send the filter
                    //name - the property name of the filter option
                    //value - the value of the filter option
                    filtersToTrigger.push({
                        starter: properties.trigger.starter,
                        name: properties.filterName,
                        value: $(this).val()
                    });
                });
            }

            else if (mode === DataTableExtensions.triggerModes.onChange) {

                textBox.change(function () {
                    properties.trigger.callback($(this).val())
                });
            }
        }

        return textBoxDiv;
    }

    //create and append button
    function createButton(properties) {

        var button = $('<button class="header-button"/>');
        button.text(properties.text);
        //button.click(properties.callback);

        switch (properties.style) {

            case DataTableExtensions.buttonStyle.info:
                button.addClass("btn btn-info");
                break;
            case DataTableExtensions.buttonStyle.warning:
                button.addClass("btn btn-warning");
                break;
            case DataTableExtensions.buttonStyle.success:
                button.addClass("btn btn-success");
                break;
            case DataTableExtensions.buttonStyle.error:
                button.addClass("btn btn-danger");
                break;
            default:
                button.addClass("btn");
                break;
        }

        if (properties.callback) {

            button.click(properties.callback);
        } else {
            //on click collect the filters registered to this control and call the callback with the changed values
            button.click(function () {

                var filters = [];
                for (var i = 0; i < filtersToTrigger.length; i++) {

                    var filter = filtersToTrigger[i];

                    console.log(filter);

                    if (filter.starter === properties.name) {

                        filters.push({filterName: filter.name, filterValue: filter.value});
                    }
                }

                filterOptionsCallback(filters);
            });
        }
        return button;
    }
}

//my implementation to switch between two css classes according to the result of the callback
(function ($) {
    $.fn.switchClass = function (conditionCallback, classA, classB) {

        var classToAdd;
        var classToRemove;

        if (conditionCallback()) {
            classToAdd = classA;
            classToRemove = classB;

        } else {
            classToAdd = classB;
            classToRemove = classA;
        }

        $(this).removeClass(classToRemove);
        $(this).addClass(classToAdd);
    };
})(jQuery);