<!doctype html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Simple single page datatable</title>
        <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.js"></script>
        <link href="vsdatatable.css" rel="stylesheet" type="text/css">
        <link href="rowextender.css" rel="stylesheet" type="text/css">
        <link href="columntemplates.css" rel="stylesheet" type="text/css">
        <script src="vsdatatable.js"></script>
      
        <link href="dpdaterangepicker.css" rel="stylesheet" type="text/css">
        <script src="dpdaterangepicker.js"></script>

        <link href="sampleapp.css" rel="stylesheet" type="text/css">
        <script src="sampleapp.js"></script>
    </head>

    <body ng-app="sampleapp">
        <div ng-controller="sampleappctrl">
            <h3>Simple single page datatable - AngularJS reusable UI component</h3>
             <ul>
                 <li>Homepage of the component is <a href="http://kekeh.github.io/vsdatatable" target="_blank">here</a>.</li>
                 <li>See all of my AngularJS UI components from <a href="http://kekeh.github.io" target="_blank">here</a>.</li>
             </ul>
            <hr/>
            <h5>Example 1. Internal pagination (the table contains 30000 items)</h5>
            
            <p>The filter of the <strong>Date</strong> column (date range picker) is separate component from
               <a class="anchor" href="http://kekeh.github.io/dpdaterangepicker" target="_blank" aria-hidden="true">here</a></p>

            <vsdatatable options="opt"></vsdatatable>
        </div>
        <div ng-controller="sampleappctrl">

            <h5>Example 2. External pagination</h5>
            <p>External pagination example is <a href="http://embed.plnkr.co/Ft1cP1/preview" target="_blank">here</a>
        </div>
    </body>
</html>
# vsdatatable

**Simple single page datatable - AngularJS reusable UI component**

## Description
AngularJS directive which implements the datatable with many useful and configurable features.

### 1. virtualization
* only visible items are rendered in the browser.

### 2. paginator
* pagination from the external data source is also supported (for example database).
* configurable buttons
 - to first page
 - to previous page
 - to previous set of pages
 - to page numbered
 - to next set of pages
 - to next page
 - to last page
* configurable page size selector
* scrollbar is not supported

### 2. filtering
* built in global filter
* column filter can be optionally added by using a HTML template
* the global and the column filter works also if the data is paginated from the external data source
* contain and exact match can be used
* internal filtering uses AngularJS filter

### 3. sorting
* built in sorting by column
* sorting works also if the data is paginated from the external data source
* internal sorting uses AngularJS filter

### 4. row extender
* support for adding, editing, deleting and viewing data
* data manipulation is done inside the table by extending the row
* user of the vsdatatable is responsible to implement these HTML templates (add, edit, delete and view)
* the vsdatatable directive loads the template and extending the row when the user wants to manipulate the data

### 5. column toggler
* columns can be shown/hidden from the menu

### 6. row selection
* enable or disable
* single or multiple selection is possible if enabled
* the selected row data is passed to the parent

### 7. responsive UI
* vsdatatable UI is responsive and scalable to different size of devices

### 8. tooltips
* tooltips are used to shown the string which are not fit to the column

### 9. touch and keyboard
* works with touch devices
* works with keyboard

### 10. column resize
* columns can be resized

### 11. accepts nested objects
* input object is array of objects (items)
* item of the array can contain nested objects

### 12. block the vsdatatable UI
* built in UI blocker in case of external pagination

### 13. column styles
* column styles can be added to the body columns
* styles are rendered if given expression rules are met


body {
    margin: 20px 60px;
    min-width: 400px;
}

h3, h5 {
    background-color: #78FA89;
    padding: 8px;
    border: 1px solid #AAA;
    border-radius: 3px;
}

p {
    font-size: 14px;
}

@media screen and (max-width: 1100px) {
    #pagemargin
    {
        margin: 20px 10px 20px 10px;
    }
}
/**
 * @ngdoc object
 * @name sampleapp
 * @description Sample application module. Injects the vsdatatable module.
 */
var sampleapp = angular.module('sampleapp', ['vsdatatable', 'dpdaterangepicker']);

/**
 * @ngdoc object
 * @name extenderctrl
 * @description Controller of row extender template. One function getNexId() which generates a new id to new
 * row of the table. Called when the user creates a new row to the table.
 */
sampleapp.controller('extenderctrl', function ($scope) {
    $scope.getNextId = function () {
        var nextId = Math.floor((Math.random() * 50000000) + 100);
        return nextId;
    };

});

/**
 * @ngdoc object
 * @name sampleappctrl
 * @description Sample application controller. This controller uses the vsdatatable.
 */
sampleapp.controller('sampleappctrl', function ($scope, $http, vsdtConf, dpdaterangeConfig) {

    // Date range picker configuration
    dpdaterangeConfig.showGrid = false;
    dpdaterangeConfig.firstDayOfWeek = 'mo';
    
    // Header column filter templates
    var colInputFilterTemplate =
        '<div class="columnTemplate">' +
        '<input type="text" class="inputField" ng-click="$event.stopPropagation();" ng-model="COLUMN_PROP_VALUE" ng-model-options="{debounce:500}" placeholder="Type filter...">' +
        '</div>';

    var colSelectActiveFilterTemplate =
        '<div class="columnTemplate">' +
        '<select class="selectMenu" ng-click="$event.stopPropagation();" ng-model="COLUMN_PROP_VALUE" ng-options="active.value as active.label for active in [{label: \'Choose\', value:undefined},{label: \'True\', value:\'true\'},{label: \'False\', value:\'false\'}]"></select>' +
        '</div>';

    var colSelectCarFeaturesFilterTemplate =
        '<div class="columnTemplate">' +
        '<select class="selectMenu" ng-click="$event.stopPropagation();" ng-model="COLUMN_PROP_VALUE" ng-options="features.value as features.label for features in [{label: \'Choose\', value:undefined},{label: \'Set 1\', value:\'1\'},{label: \'Set 2\', value:\'2\'},{label: \'Set 3\', value:\'3\'}]"></select>' +
        '</div>';

    var colSelectDateRangeTemplate =
        '<div class="columnTemplate">' +
        '<dpdaterangepicker ng-model="COLUMN_PROP_VALUE" width="100%" height="22px" options="{}"></dpdaterangepicker>' +
        '</div>';
        
    $scope.jsonData = [];

    // data operation (add, edit or delete) callback
    var onDataOperation = function (phase, operation, dataOld, dataNew) {
        console.log('*** PARENT - onDataOperation: phase: ', phase, ' - operation: ', operation, ' - dataOld: ', dataOld, ' - dataNew: ', dataNew);
        if (phase === vsdtConf.OPER_PHASE_END && operation === vsdtConf.OPER_ADD) {
            // add new item to the end of the array
            $scope.jsonData.push(dataNew);
        }
        else if (phase === vsdtConf.OPER_PHASE_END && operation === vsdtConf.OPER_EDIT) {
            // replace the old item with the new one
            var idx = $scope.jsonData.indexOf(dataOld);
            if (idx !== -1) {
                $scope.jsonData[idx] = dataNew;
            }
        }
        else if (phase === vsdtConf.OPER_PHASE_END && operation === vsdtConf.OPER_DELETE) {
            // delete the item from the array
            var idx = $scope.jsonData.indexOf(dataOld);
            if (idx !== -1) {
                $scope.jsonData.splice(idx, 1);
            }
        }
    };

    // row selected/deselected callback
    var onRowSelect = function (operation, rowData) {
        console.log('*** PARENT - onRowSelect - operation: ', operation, ' - rowData: ', rowData);
    };

    var generatedItemCount = 30000;

    // Configuration of the vsdatatable
    $scope.opt = {
        data: {
            items: '',
            dataOperationCb: onDataOperation,
            extDataPagination: false
        },
        caption: {
            text: 'vsdatatable example'
        },
        busyIcon: {         // Currently this works only if the extDataPagination is true
            visible: false,
            text: 'Loading data...'
        },
        showTooltips: true,
        showOverlay: true,
        headerVisible: true,
        showVerticalBorder: true,
        columnResize: true,
        columns: [
            {
                prop: 'id',
                label: 'Id number',
                sorting: false,
                filter: {template: colInputFilterTemplate, match: 'contain'},
                width: {number: 5, unit: '%'},
                visible: false
            },
            {
                prop: 'active',
                label: 'Active',
                textAlign: 'center',
                sorting: true,
                filter: {template: colSelectActiveFilterTemplate, match: 'exact'},
                width: {number: 8, unit: '%'},
                visible: true,
                rules: [
                    {
                        style: 'activeStyle',
                        prop: 'active',
                        expression: 'active === true'
                    },
                    {
                        style: 'inactiveStyle',
                        prop: 'active',
                        expression: 'active === false'
                    }
                ]
            },
            {
                prop: 'car.price',  // Value from second level (property price from the car object)
                label: 'Car.price',
                textAlign: 'right',
                sorting: true,
                filter: {template: colInputFilterTemplate, match: 'contain'},
                width: {number: 10, unit: '%'},
                visible: true,
                rules: [
                    {
                        style: 'carPriceStyleGreen',
                        prop: 'car.price',
                        expression: 'car.price >= 150000 && car.price <= 250000'
                    },
                    {
                        style: 'carPriceStyleRed',
                        prop: 'car.price',
                        expression: 'car.price < 30000'
                    }
                ]
            },
            {
                prop: 'car.features',  // Value from second level (property class from the car object)
                label: 'Car.features',
                textAlign: 'center',
                sorting: true,
                filter: {template: colSelectCarFeaturesFilterTemplate, match: 'exact'},
                width: {number: 10, unit: '%'},
                visible: true,
                rules: [
                    {
                        style: 'carFeatures1Style',
                        prop: 'car.features',
                        expression: 'car.features === 1'
                    },
                    {
                        style: 'carFeatures2Style',
                        prop: 'car.features',
                        expression: 'car.features === 2'
                    },
                    {
                        style: 'carFeatures3Style',
                        prop: 'car.features',
                        expression: 'car.features === 3'
                    }
                ]
            },
            {
                prop: 'car.age',
                label: 'Car.age',
                textAlign: 'right',
                sorting: true,
                filter: {template: colInputFilterTemplate, match: 'contain'},
                width: {number: 10, unit: '%'},
                visible: true
            },
            {
                prop: 'name',
                label: 'Name',
                sorting: true,
                filter: {template: colInputFilterTemplate, match: 'contain'},
                width: {number: 17, unit: '%'},
                visible: true
            },
            {
                prop: 'date',
                label: 'Date',
                textAlign: 'right',
                sorting: true,
                filter: {template: colSelectDateRangeTemplate, match: 'daterange', dateFormat: 'yyyy-mm-dd'},
                width: {number: 15, unit: '%'},
                visible: true
            },
            {
                prop: 'about',
                label: 'About',
                textAlign: 'left',
                sorting: false,
                filter: {template: colInputFilterTemplate, match: 'contain'},
                width: {number: 20, unit: '%'},
                visible: true
            }
        ],
        row: {
            selection: 1, // 0=No, 1=Single, 2=Multiple
            rowSelectCb: onRowSelect,
            hover: true
        },
        columnToggler: {
            visible: true,
            btnTooltip: 'Select columns',
            menuTitle: 'Columns'
        },
        filter: {
            global: true,
            column: true,
            autoFilter: {
                useAutoFilter: true,
                filterDelay: 600
            },
            globalPlaceholder: 'Type filter...',
            showFilterBtnTooltip: 'Show filter',
            hideFilterBtnTooltip: 'Hide filter',
            filterBtn: {
                visible: true,
                filterBtnTooltip: 'Filter'
            }
        },
        paginator: {
            visible: true,
            numberBtnCount: 3,
            prevNextBtn: {
                visible: true,
                labels: ['back', 'next']
            },
            prevNextSetBtn: {
                visible: true,
                labels: ['...', '...']
            },
            firstLastBtn: {
                visible: true,
                labels: ['first', 'last']
            },
            pageSizeOptions: [
                {label: '4', rows: 4, default: true},
                {label: '7', rows: 7},
                {label: '15', rows: 15},
                {label: '20', rows: 20}],
            pageSizeTxt: 'Page size: ',
            totalItemsTxt: 'Total: '
        },
        useTemplates: true,
        actionColumnText: 'Action',
        templates: {
            add: {
                path: 'add_edit.html',
                actionBtnShow: true,
                btnTooltip: 'Add',
                defaultValues: {car: {features: 1}, active: false} // Set car features default to 1 and active to false
            },
            edit: {path: 'add_edit.html', actionBtnShow: true, btnTooltip: 'Edit'},
            delete: {path: 'view_delete.html', actionBtnShow: true, btnTooltip: 'Delete'},
            view: {path: 'view_delete.html', actionBtnShow: true, btnTooltip: 'View'}
        }
    };

    // Helper function to generate sample data to the vsdatatable
    function generateData() {
        for (var i = 0; i < generatedItemCount; i++) {
            var pr = price(300000, 10000);
            var item = {
                id: i + 1,
                active: (i % 6 === 0 || i % 7 === 0) ? true : false,
                car: {
                    price: pr,
                    features: pr <= 100000 ? 1 : pr <= 200000 ? 2 : 3,
                    age: Math.round((Math.random() * 58) + 1)
                },
                name: 'User ' + Math.round((Math.random() * 5000) + 10),
                date: Math.round((Math.random() * 25) + 1990) + '-' + date(12, 1) + '-' + date(28, 1),
                about: 'About number ' + Math.round((Math.random() * 5000000) + 1, 2) + ' with lorem ipsum dolor sit amet, consectetuer adipiscing elit sed posuere interdum sem sini rea dolor amet elit number ' + Math.round((Math.random() * 5000000) + 1, 2) + '.'
            };
            $scope.jsonData.push(item);
        }

        $scope.opt.data.items = $scope.jsonData;
    }

    // Helper function to generate sample data to the vsdatatable
    function date(max, min) {
        var d = Math.round((Math.random() * max) + min).toString();
        if (d.length === 1) {
            return '0' + d;
        }
        return d;
    }

    // Helper function to generate sample data to the vsdatatable
    function price(max, min) {
        var b = Math.round((Math.random() * max) + min) + '.' + Math.round((Math.random() * 99) + 1).toString();
        return parseFloat(b);
    }

    generateData();

});

.vsdatatable {
    width: 100%;
    position: relative;
}

.vsdatatable * {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    font-family: Arial, Helvetica, sans-serif;
    padding: 0;
    margin: 0;
}

.vsdatatable .caption,
.vsdatatable .bodyRow,
.vsdatatable .headerRow,
.vsdatatable .paginatorTxt,
.vsdatatable .paginatorTotalNbr,
.vsdatatable .paginatorPagesNbr,
.vsdatatable .busyIconTxt,
.vsdatatable .overlay,
.vsdatatable .tooltip {
    font-size: 14px;
}

.vsdatatable table {
    display: table;
    color: #000;
}

.vsdatatable table,
.vsdatatable table tr,
.vsdatatable table td,
.vsdatatable table th {
    border-collapse: collapse;
    border: none;
    text-align: left;
    line-height: 1.1;
    padding: 0;
}

/*
    These two selector groups change color theme.
*/
.vsdatatable .caption,
.vsdatatable .colTogglerTitle,
.vsdatatable .tableFooter,
.vsdatatable .headerCol,
.vsdatatable .extenderTitle {
    background: #FAFAFA;
    background-image: -webkit-linear-gradient(#FAFAFA 1%, #EEE 100%);
    background-image: -moz-linear-gradient(#FAFAFA 1%, #EEE 100%);
    background-image: -o-linear-gradient(#FAFAFA 1%, #EEE 100%);
    background-image: -ms-linear-gradient(#FAFAFA 1%, #EEE 100%);
    background-image: linear-gradient(#FAFAFA 1%, #EEE 100%);
}

.vsdatatable .paginatorBtn {
    background: #FAFAFA;
    background-image: -webkit-linear-gradient(#F0F0F0 1%, #AEC2E1 100%);
    background-image: -moz-linear-gradient(#F0F0F0 1%, #AEC2E1 100%);
    background-image: -o-linear-gradient(#F0F0F0 1%, #AEC2E1 100%);
    background-image: -ms-linear-gradient(#F0F0F0 1%, #AEC2E1 100%);
    background-image: linear-gradient(#F0F0F0 1%, #AEC2E1 100%);
}

.vsdatatable .tableRows .selectedHeaderCol {
    background: #CCE0FF;
    background-image: -webkit-linear-gradient(#CCE0FF 1%, #FAFAFA 100%);
    background-image: -moz-linear-gradient(#CCE0FF 1%, #FAFAFA 100%);
    background-image: -o-linear-gradient(#CCE0FF 1%, #FAFAFA 100%);
    background-image: -ms-linear-gradient(#CCE0FF 1%, #FAFAFA 100%);
    background-image: linear-gradient(#CCE0FF 1%, #FAFAFA 100%);
}

.vsdatatable .actionIcon {
    cursor: pointer;
    font-size: 14px;
}

.vsdatatable .tableRows .sortColIcon {
    font-size: 13px;
}

.vsdatatable .headerRow {
    height: 32px;
}

.vsdatatable .bodyRow {
    height: 32px;
}

.vsdatatable .caption {
    width: 100%;
    min-height: 30px;
    margin-bottom: -1px;
}

.vsdatatable .caption table tr {
    height: 32px;
}

.vsdatatable .captionColToggler {
    padding-right: 4px;
}

.vsdatatable .colTogglerMenu {
    background-color: #FFF;
    z-index: 3;
}

.vsdatatable .colTogglerTitle {
    font-size: 12px;
    text-align: center;
    border-bottom: 1px solid #C0C0C0;
    border-radius: 4px 4px;
    padding: 2px 0;
    cursor: default;
    display: table;
    height: 24px;
    width: 100%;
}

.vsdatatable .colTogglerTitleTxt,
.vsdatatable .colTogglerCloseIcon {
    display: table-cell;
    vertical-align: middle;
}

.vsdatatable .colTogglerTitleTxt {
    padding-left: 20px;
}

.vsdatatable .colTogglerCloseIcon {
    width: 20px;
    font-size: 10px;
}

.vsdatatable .colTogglerMenuItem {
    display: table;
    cursor: pointer;
    border-bottom: 1px solid #C0C0C0;
    width: 100%;
}

.vsdatatable .colTogglerMenuItem:last-child {
    border-radius: 0 0 4px 4px;
}

.vsdatatable .addItemIcon {
    font-size: 13px;
}

.vsdatatable .colTogglerMenuItem:last-child {
    border-bottom: none;
}

.vsdatatable .colTogglerMenuItemTxt {
    cursor: pointer;
    display: table-cell;
    min-width: 100px;
    padding: 4px 8px;
}

.vsdatatable .colTogglerMenuItemIcon {
    display: table-cell;
    width: 20px;
    text-align: right;
    padding: 4px 8px;
    box-sizing: content-box;
}

.vsdatatable .colTogglerMenuItemIcon span {
    font-size: 12px;
}

.vsdatatable .selectedColTogglerMenuItem {
    background-color: #FAFAFA;
}

.vsdatatable .captionTitle {
    white-space: nowrap;
    padding-right: 4px;
    width: 50%;
}

.vsdatatable .captionTitle span {
    font-weight: bold;
}

.vsdatatable .captionFilter {
    text-align: right;
    width: 50%;
}

.vsdatatable .filterTxtBox {
    padding-left: 4px;
    width: 60%;
    height: 30px;
    background-color: #FAFAFA;
    border: 1px solid #AAA;
    -moz-box-shadow: inset 0 0 1px #1589FF;
    -webkit-box-shadow: inset 0 0 1px #1589FF;
    box-shadow: inset 0 0 1px #1589FF;
}

.vsdatatable .searchIcon,
.vsdatatable .filterIcon {
    padding: 2px 4px;
    font-size: 15px;
}

.vsdatatable .tableFooter {
    width: 100%;
    min-height: 30px;
    margin-top: -1px;
}

.vsdatatable .tableFooter table tr {
    height: 32px;
}

.vsdatatable .paginator td {
    vertical-align: middle;
}

.vsdatatable .paginator td:nth-child(1),
.vsdatatable .paginator td:nth-child(3) {
    width: 28.3%;
}

.vsdatatable .paginator td:nth-child(2) {
    width: 43.3%;
    text-align: center;
}

.vsdatatable .paginatorBtn {
    min-height: 30px;
    min-width: 36px;
    padding: 0 3px;
    font-size: 13px;
    border: 1px solid #988E8E;
    color: #000;
}

.vsdatatable .paginatorBtn:enabled {
    cursor: pointer;
}

.vsdatatable .paginatorBtn:hover:enabled {
    background: #CAFFCA;
}

.vsdatatable .paginatorBtn:focus:enabled {
    outline: #CCC dotted 1px;
    outline-offset: -2px;
    color: #1589FF;
}

.vsdatatable .bodyRow:focus,
.vsdatatable .colTogglerMenuItem:focus,
.vsdatatable .icon:focus {
    color: #1589FF;
    outline: none;
}

.vsdatatable .selectedPaginatorBtn {
    color: #1589FF;
    background: #CFECEC;
}

.vsdatatable .disabledPaginatorBtn {
    pointer-events: none;
    color: #BBB;
}

.vsdatatable .tableRows {
    width: 100%;
    table-layout: fixed;
}

.vsdatatable .tableRows,
.vsdatatable .tableRows .headerCol,
.vsdatatable .tableRows .bodyCol,
.vsdatatable .caption,
.vsdatatable .extenderTitle,
.vsdatatable .tableFooter {
    border: 1px solid #C0C0C0;
    padding: 5px;
}

.vsdatatable .tableRows tbody .oddRow {
    background-color: #FFF;
}

.vsdatatable .tableRows tbody .evenRow {
    background-color: #FAFAFA;
}

.vsdatatable .tableRows .selectedRow {
    background-color: #CFECEC !important;
}

.vsdatatable .colTogglerMenuItem:hover,
.vsdatatable .tableRows .hoverRow {
    background-color: #CAFFCA !important;
    cursor: pointer;
}

.vsdatatable .tableRows .bodyCol {

}

.vsdatatable .tableRows .bodyColAction {
    text-align: center;
}

.vsdatatable .tableRows .headerCol {
    text-align: center;
}

.vsdatatable .tableRows .headerCol:hover {

}

.vsdatatable .tableRows .headerColAction {
    cursor: default;
}

.vsdatatable .tableRows .headerColFilter {
    overflow: hidden;
}

.vsdatatable .textOverflow {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    width: 100%;
}

.vsdatatable .overlay,
.vsdatatable .tooltip {
    background-color: #FFFFCC;
    color: #000;
    padding: 6px;
    z-index: 2;
    max-width: 300px;
    font-weight: normal;
}

.vsdatatable .overlay {
    white-space: normal;
    word-wrap: break-word;
}

.vsdatatable .tooltip {
    white-space: nowrap;
}

.vsdatatable .overlay:before {
    border-right: 10px solid #888;
    left: -10px;
}

.vsdatatable .overlay:after {
    border-right: 8px solid #FFFFCC;
    left: -8px;
}

.vsdatatable .overlay:before,
.vsdatatable .overlay:after {
    content: ' ';
    border-top: 8px solid transparent;
    border-bottom: 8px solid transparent;
    position: absolute;
    z-index: 2;
    top: 5px;
}

.vsdatatable .colTogglerMenu,
.vsdatatable .overlay,
.vsdatatable .tooltip,
.vsdatatable .busyIconContainer {
    border: 1px solid #AAA;
    border-radius: 4px;
    position: absolute;
    -moz-box-shadow: 0 0 14px #000;
    -webkit-box-shadow: 0 0 14px #000;
    box-shadow: 0 0 14px #000;
}

.vsdatatable .busyIconContainer {
    display: table;
    background-color: #FAFAFA;
    padding: 8px;
    z-index: 4;
    left: 50%;
    top: 50%;
    -webkit-transform: translate(-50%, -50%);
    -moz-transform: translate(-50%, -50%);
    transform: translate(-50%, -50%);
}

.vsdatatable .busyIcon {
    border: 5px solid #3A87AD;
    border-top: 5px solid transparent;
    border-radius: 100%;
    background-color: inherit;
    width: 30px;
    height: 30px;
    -moz-animation: animate 0.6s infinite linear;
    -webkit-animation: animate 0.6s infinite linear;
    animation: animate 0.6s infinite linear;
    display: table-cell;
}

.vsdatatable .busyIconTxt {
    display: table-cell;
    vertical-align: middle;
    padding-left: 8px;
    color: #3A87AD;
}

.vsdatatable .removeVerticalBorder {
    border-left: 1px solid transparent !important;
    border-right: 1px solid transparent !important;
}

@-moz-keyframes animate {
    0% {
        -moz-transform: rotate(0deg);
    }
    100% {
        -moz-transform: rotate(360deg);
    }
}

@-webkit-keyframes animate {
    0% {
        -webkit-transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(360deg);
    }
}

@keyframes animate {
    0% {
        transform: rotate(0deg);
    }
    100% {
        transform: rotate(360deg);
    }
}

.vsdatatable .icon:hover {
    color: #3BB9FF;
}

.vsdatatable ::-ms-clear {
    display: none;
}

.vsdatatable button::-moz-focus-inner {
    border: 0;
}

@media screen and (max-width: 500px) {
    .vsdatatable .paginatorBtnSet {
        display: none;
    }
}

@media screen and (max-width: 700px) {
    .vsdatatable .paginatorBtnPageSize,
    .vsdatatable .paginatorTotalNbr,
    .vsdatatable .paginatorPagesNbr,
    .vsdatatable .paginatorBtnAll {
        display: none;
    }

    .vsdatatable .paginator td:nth-child(1),
    .vsdatatable .paginator td:nth-child(3) {
        width: 0;
    }

    .vsdatatable .paginator td:nth-child(2) {
        width: 100%;
    }
}

@media screen and (max-width: 960px) {
    .vsdatatable .paginatorBtnNbr,
    .vsdatatable .paginatorTxt {
        display: none;
    }
}

@font-face {
    font-family: 'vsdatatable';
    src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SAxEAAAC8AAAAYGNtYXAaVsyTAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZgIulTwAAAF4AAAIwGhlYWQGZBdKAAAKOAAAADZoaGVhB8kDzAAACnAAAAAkaG10eDoABHoAAAqUAAAARGxvY2ETnhVGAAAK2AAAACRtYXhwABsCJAAACvwAAAAgbmFtZcvbO1kAAAscAAABtnBvc3QAAwAAAAAM1AAAACAAAwPbAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADmDAPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg5gz//f//AAAAAAAg5gD//f//AAH/4xoEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAACACoAcgQGAxUABAAJAAABFwEnAQE3AQcBA4KE/cKEAj78qIYBoIb+YAMVjv3rjgIV/uyM/nOMAY0AAAAJACAAAAPgA4gAGQB+AJsAxwDzAWQBkAG8AiEAADcOARU4ATEVFBYzITI2PQE0JiMhKgExKgEjAQ4BFSYGBzQUBzYwFTAUMRUwFDEwFDEeARcUFhcwFjMiMDEwMjEzMDIxMDIxPgE3FDY3MDY1FDQxPAExNTA0MTQmJy4BJzImJzImJxYUIyoBMTAiMTAiMSMwIjEwIjEwIiMqATEFDgEVMBQdARQWFyE+AT0BNDA1NCYnITgBIzgBIxMeATMwMjEzOAEzMjY1OAExETgBMTQmIyIwMSM4ATEiBhU4ATERHAEVHAEVJR4BMzAyMTM4ATEyNjU4ATEROAExNCYjOAExIzgBMSIGFTgBMREwFBUcATEBMDIVMDIVMDIVMDIVMDIVMDIxOgE3BjI3MxY2NwY0MTA2MTA2MTA2MTA2MTA2MTwBJxYyMTA2MTwBJzc0JjUUJicwJjEuATEiNDEiNDEiBiM2BisBIgYjMgYHOAExBjAVDgEVMBQXJhQXBxwBFyYUFwMeATMyMDsBOAExMjY1OAE1ETQwMTQmIzgBMSMwIjEiBhU4ARURHAEVMBQxMx4BMzAyOwE4ATEyNjU4ATURNDAxNCYjOAErASIwMSIGFTgBFREUBhUwFjEDMDIVMDIVMDIVMDIVMDIVMDIxOgE3BjI3MzoBNwY2NwY2MTA2MT4BNT4BPQEuAScwJjEuASMwNDEiNCMiBiM2BisBIgYjMgYHOAEjFDAVIjAHOAEVBhYVNBQVNBQdARwBFyIUF4EOExYPAvYPFhYP/QoBAQEBAQEoAgMBAQEBAQEBAgMDAwEBAaQBAQIDAQMBAgEBAQMBAQIBAQIBAgEBAQEBpAEBAQEBAf6aDhQWDgN4DhYVD/yIAQEeAx0TARgBFR4eFQEYFh4CwAIdFAEYFh4eFhgWHv6BAQEBAQEBAgICAgICDAIDAgIBAQEBAQEBAQEBAQEBAQEBAQEBAgICAQECDAICAgICAQEBAQEBAQEBAQFIAxUNAQESEBcXEBIBEBbtAxQOAQESEBYWEAERARAWAQEMAQEBAQEBAQMBAQIBDQEDAQECAQEBAQEBAQEBAQEBAQEBAQEBAwEBAgEMAgMBAQEBAQEBAQEBAQGAAh4UGBYeHhYYFh4DBgEDAwECAQEDAQEBAgoBAQIDAQEDAQEBAQIBAQIEAgEBAQEKAQIEAQECAQEBAQEBAUYCHhUBARUVHgEBHhUVAQEWHgH8/AwQFA4Cog4UFA79XgEBAQEBAQINERQOAqIOFBQO/V4BAQEBAuEBAQEBAQEBAQEBAQEBAQEBAQECAgIDAQICAlACAgICAgEBAQEBAQEBAQEBAQEBAQICAwEBAgFRAQMBAQMB/dIGCQsIAQFYAQgLCwgB/qgBAQECBgkLCAEBWAEICwsIAf6oAQEBAgIwAQEBAQEBAQEBAQIBAQEBAQMBAQIBUwIEAQEBAQEBAQEBAQEBAQEBAQEDAQECAQECAVEBBAECAQAAAgBwADADkANQAAQACQAANwEnARcDATcBB+kCp3n9WXl5Aqd5/Vl5MAKnef1ZeQKn/Vl5Aqd5AAABAEAAoAPAAuAAAwAAEwUBA0ADgP493wLgA/3DASAAAAkAgAAAA8sDiAAJAA4AEwAeACMAKAAtADIANwAAAQ8DPwMnCQEXAScfAQEnATcHFzcnMCYnLgExBREzESMBETMRIyUhNSEVESE1IRUJARcBJwFLCAkHCSQjJCNtAdX+Nm4Bym4CFf6KFQF2UD1ZPRMnDAUO/Q5gYAKgYGD9YAJZ/acDAP0AAoD+eGABiGABZSQkJCQLCgwKZQHe/itjAdZiUxP+gRIBgJhFUEQSIwsEDQj8gAOA/rb9ygI26mBg/OBgYAMZ/mVdAZtdAAEA4AAAAyADgAADAAABAwElAyAD/cMBIAOA/IABw98AAgBAAAADwAOAAAQACQAAEyE1IRUBETMRI0ADgPyAAWDAwAFgwMACIPyAA4AAAAAAAQDgAAADIAOAAAMAADcTAQXgAwI9/uAAA4D+Pd8AAAAEAAD/1wQAA8AAFAApAEkAaQAAATIeAhUUDgIjIi4CNTQ+AjMVIg4CFRQeAjMyPgI1NC4CIwE+ATMyFh8BHgEVFAYPAQ4BIyImLwEuATU0Nj8BPgE3Jz4BMzIWHwEeARUUBg8BDgEjIiYvAS4BNTQ2PwE+ATcBgE+MaTw8aYxPT4xpPDxpjE85ZUsrK0tlOTllSysrS2U5AWkGDggJEAbOBggIBj4GEAkKEQbNBgcHBj4DCAVvBAoFBgsEigUFBQUpBAsGBwsEigQFBQQpAwUDA8A9aIxPT4xoPT1ojE9PjGg9aytLZTo6ZkssLEtmOjplSyv93gQGBwbNBhEJChEGPgYHCAbNBhAJCRAGPgQGAkUDBAUEiQQMBgcLBCoEBAUEiQQLBgYLBCoCBQEAAAAABQAAAMAD/ALAAAQACQAOABMAGAAAEyEVITUVIRUhNRUhFSE1ARcBJwEFNwEHAQABIP7gASD+4AEg/uADnl7+Zl4Bmv2eXwEoX/7YArBgYMBgYMBgYAGQbP5sbAGU0Wr+0moBLgAAAAACAIAAAAOAA4AAAwAHAAATBQEnASUBF4ADAP59vgJB/QABg74BggL+gMEBPQIBgMEAAAAAAQBAAKADwALgAAMAAC0BARMDwPyAAcPfoAMCPf7gAAAGAEAAUAPAAzAABAAJAA4AEwAYAB0AAAEVITUhIRUzNSMBFSE1ISEVMzUjARUhNSEhFTM1IwFAAoD9gP8AoKABAAKA/YD/AKCgAQACgP2A/wCgoAMwoKCgoP7goKCgoP7goKCgoAAAAAEAAAABAACXfi0xXw889QALBAAAAAAA0Z9pWwAAAADRn2lbAAD/1wQGA8AAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAP/6BAYAAQAAAAAAAAAAAAAAAAAAABEEAAAAAAAAAAAAAAACAAAABAAAKgQAACAEAABwBAAAQAQAAIAEAADgBAAAQAQAAOAEAAAABAAAAAQAAIAEAABABAAAQAAAAAAACgAUAB4APAJsAogCmAL+Aw4DJgM2A84EAgQcBCwEYAABAAAAEQIiAAkAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEACwAAAAEAAAAAAAIABwCEAAEAAAAAAAMACwBCAAEAAAAAAAQACwCZAAEAAAAAAAUACwAhAAEAAAAAAAYACwBjAAEAAAAAAAoAGgC6AAMAAQQJAAEAFgALAAMAAQQJAAIADgCLAAMAAQQJAAMAFgBNAAMAAQQJAAQAFgCkAAMAAQQJAAUAFgAsAAMAAQQJAAYAFgBuAAMAAQQJAAoANADUdnNkYXRhdGFibGUAdgBzAGQAYQB0AGEAdABhAGIAbABlVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwdnNkYXRhdGFibGUAdgBzAGQAYQB0AGEAdABhAGIAbABldnNkYXRhdGFibGUAdgBzAGQAYQB0AGEAdABhAGIAbABlUmVndWxhcgBSAGUAZwB1AGwAYQBydnNkYXRhdGFibGUAdgBzAGQAYQB0AGEAdABhAGIAbABlRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format('truetype');
    font-weight: normal;
    font-style: normal;
}

.vsdatatable .icon {
    font-family: 'vsdatatable';
    color: #000;
    background: transparent !important;
    font-weight: normal;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.vsdatatable .icon-check:before {
    content: "\e600";
}

.vsdatatable .icon-clear:before {
    content: "\e601";
}

.vsdatatable .icon-cross:before {
    content: "\e602";
}

.vsdatatable .icon-down:before {
    content: "\e603";
}

.vsdatatable .icon-edit:before {
    content: "\e604";
}

.vsdatatable .icon-left:before {
    content: "\e605";
}

.vsdatatable .icon-plus:before {
    content: "\e606";
}

.vsdatatable .icon-right:before {
    content: "\e607";
}

.vsdatatable .icon-search:before {
    content: "\e608";
}

.vsdatatable .icon-selections:before {
    content: "\e609";
}

.vsdatatable .icon-sort:before {
    content: "\e60a";
}

.vsdatatable .icon-up:before {
    content: "\e60b";
}

.vsdatatable .icon-view:before {
    content: "\e60c";
}
/* 
*  Name: vsdatatable 
*  Description: Simple single page datatable - AngularJS reusable UI component 
*  Version: 0.1.8 
*  Author: kekeh 
*  Homepage: http://kekeh.github.io/vsdatatable 
*  License: MIT 
*  Date: 2015-11-08 
*/ 
angular.module('template-vsdatatable-0.1.8.html', []).run(['$templateCache', function($templateCache) {
  $templateCache.put("templates/vsdatatable.html",
    "<div class=vsdatatable ng-style=\"{'pointer-events':busyIcon?'none':'auto'}\"><div ng-if=options.busyIcon.visible ng-include=\"'templates/vsdtbusyicon.html'\"></div><div ng-style=\"{'opacity':busyIcon?'0.4':'1'}\"><div caption-bar></div><table class=tableRows><thead class=tableHeader ng-if=options.headerVisible><tr class=headerRow><th style=position:static class=\"headerCol textOverflow\" col-resizer ng-if=\"h.visible===undefined||h.visible\" ng-style=\"{'width':h.width.number+h.width.unit, 'cursor':h.sorting?'pointer':'default'}\" ng-class=\"sort.col===h.prop&&h.sorting?'selectedHeaderCol':''\" ng-repeat=\"h in options.columns\" ng-click=h.sorting?sortByCol($event,h.prop):null ng-keydown=h.sorting?sortByCol($event,h.prop):null>{{h.label}} <span class=\"icon sortColIcon\" ng-if=h.sorting ng-class=\"{'selectedHeaderCol':sort.col===h.prop&&h.sorting, 'icon-sort':h.sorting&&sort.col!==h.prop, 'icon-down':sort.col===h.prop&&sort.reverse, 'icon-up':sort.col===h.prop&&!sort.reverse}\" tabindex=0></span> <span class=\"icon icon-cross sortColIcon\" ng-if=\"h.sorting&&sort.col===h.prop\" ng-click=\"sortByCol($event,'')\" ng-keydown=\"sortByCol($event,'')\" tabindex=0></span></th><th id=headerColAction class=\"headerCol headerColAction\" ng-if=options.useTemplates ng-style=\"{'width': config.DEFAULT_ACTION_COL_WIDTH + 'px'}\"><span>{{options.actionColumnText}}</span> <span class=\"icon icon-plus actionIcon addItemIcon\" ng-if=options.templates.add.actionBtnShow ng-click=addRow(); ng-keydown=checkEvent($event)?addRow():null vstooltip={{options.templates.add.btnTooltip}} tabindex=0></span></th></tr><tr ng-if=\"options.filter.column!==undefined&&options.filter.column&&filterFocus\"><th class=\"headerCol headerColFilter\" ng-repeat=\"h in options.columns\" ng-show=\"h.visible===undefined||h.visible\" col-filter-template=h></th><th class=\"headerCol headerColAction\"></th></tr></thead><tbody class=tableBody><tr class=bodyRow ng-repeat=\"obj in !extDataPagination?filteredItems.slice(paginator.visiblePageIdx*pageSize.rows, paginator.visiblePageIdx*pageSize.rows+pageSize.rows):filteredItems track by $index\" ng-class-odd=\"'oddRow'\" ng-class-even=\"'evenRow'\" ng-click=\"rowClicked($event, obj)\" ng-keydown=\"rowClicked($event, obj)\" ng-class=\"{'selectedRow':isRowSelected(obj)}\" table-body-row tabindex=0><td class=\"bodyCol textOverflow {{getColumnStyle(obj,col)}}\" ng-repeat=\"col in options.columns track by $index\" ng-if=\"options.columns[$index].visible===undefined||options.columns[$index].visible\" ng-style=\"{'text-align':col.textAlign}\" ng-class=\"{'removeVerticalBorder': !options.showVerticalBorder}\" overlay-window={{getPropertyValue(obj,col.prop)}}>{{getPropertyValue(obj,col.prop)}}</td><td class=\"bodyCol bodyColAction\" ng-class=\"{'removeVerticalBorder': !options.showVerticalBorder}\" ng-if=options.useTemplates><span class=\"icon icon-edit actionIcon\" ng-if=options.templates.edit.actionBtnShow ng-click=\"editRow($event, obj);$event.stopPropagation()\" ng-keydown=\"editRow($event, obj);$event.stopPropagation()\" vstooltip={{options.templates.edit.btnTooltip}} tabindex=0></span> <span class=\"icon icon-clear actionIcon\" ng-if=options.templates.delete.actionBtnShow ng-click=\"deleteRow($event, obj);$event.stopPropagation()\" ng-keydown=\"deleteRow($event, obj);$event.stopPropagation()\" vstooltip={{options.templates.delete.btnTooltip}} tabindex=0></span> <span class=\"icon icon-view actionIcon\" ng-if=options.templates.view.actionBtnShow ng-click=\"viewRow($event, obj);$event.stopPropagation()\" ng-keydown=\"viewRow($event, obj);$event.stopPropagation()\" vstooltip={{options.templates.view.btnTooltip}} tabindex=0></span></td></tr></tbody></table><div class=tableFooter table-paginator></div></div></div>");
  $templateCache.put("templates/vsdtbusyicon.html",
    "<div class=busyIconContainer ng-show=busyIcon><div class=busyIcon></div><div class=busyIconTxt>{{options.busyIcon.text}}</div></div>");
  $templateCache.put("templates/vsdtcaption.html",
    "<div class=caption><table style=width:100%><tr><td class=captionColToggler ng-show=options.columnToggler.visible ng-click=$event.stopPropagation()><div col-toggle-menu></div></td><td class=captionTitle><span ng-if=\"options.caption.text!==undefined\">{{options.caption.text}}</span></td><td class=captionFilter><div ng-show=\"options.filter.global!==undefined&&options.filter.global||options.filter.column!==undefined&&options.filter.column\"><span ng-show=filterFocus&&options.filter.global><input class=filterTxtBox placeholder={{options.filter.globalPlaceholder}} ng-model=globalFilter ng-model-options={debounce:options.filter.autoFilter.useAutoFilter?options.filter.autoFilter.filterDelay:config.FILTER_EXECUTION_DELAY} data-ng-trim=false filter-focus> <span class=\"icon icon-check actionIcon filterIcon\" ng-show=options.filter.global&&options.filter.filterBtn.visible vstooltip={{options.filter.filterBtn.filterBtnTooltip}} ng-click=execFilterAndSort() ng-keydown=checkEvent($event)?execFilterAndSort():null tabindex=0></span></span> <span class=\"icon icon-search actionIcon searchIcon\" vstooltip={{!filterFocus?options.filter.showFilterBtnTooltip:options.filter.hideFilterBtnTooltip}} ng-click=filterBtnClick($event) ng-keydown=filterBtnClick($event) tabindex=0></span></div></td></tr></table></div>");
  $templateCache.put("templates/vsdtcolresizer.html",
    "<div class=colresizer ng-click=$event.stopPropagation() style=\"position:absolute;border:1px solid transparent;background-color:transparent;top:0;bottom:0;right:0;width:6px;cursor:col-resize\"></div>");
  $templateCache.put("templates/vsdtcoltogglemenu.html",
    "<div><span class=\"icon icon-selections actionIcon\" ng-click=colTogglerShowClicked($event) ng-keydown=colTogglerShowClicked($event) tabindex=0 vstooltip={{options.columnToggler.btnTooltip}}></span><div class=colTogglerMenu ng-show=colTogglerShow><div class=colTogglerTitle ng-show=\"options.columnToggler.menuTitle !== undefined\"><span class=colTogglerTitleTxt>{{options.columnToggler.menuTitle}}</span> <span class=\"icon icon-cross actionIcon colTogglerCloseIcon\" ng-click=colTogglerShowClicked($event) ng-keydown=colTogglerShowClicked($event) tabindex=0></span></div><div class=colTogglerMenuItem ng-repeat=\"h in options.columns\" ng-class=\"{'selectedColTogglerMenuItem':h.visible}\" ng-click=colToggleMenuClicked($event,h) ng-keydown=colToggleMenuClicked($event,h) ng-model=h.visible tabindex=0><div class=colTogglerMenuItemTxt>{{h.label}}</div><div class=colTogglerMenuItemIcon><span class=\"icon icon-check actionIcon\" ng-show=h.visible></span></div></div></div></div>");
  $templateCache.put("templates/vsdtoverlaywindow.html",
    "<div class=overlay ng-click=closeOverlay($event)></div>");
  $templateCache.put("templates/vsdtpaginator.html",
    "<table style=\"width: 100%\"><tr class=paginator ng-if=options.paginator.visible><td><div style=float:left><span class=paginatorTxt>{{options.paginator.totalItemsTxt}}</span> <span class=paginatorTotalNbr>{{totalCount}}</span> <span class=paginatorPagesNbr ng-if=options.paginator.visible>({{paginator.visiblePageIdx+1}}/{{totalPages===0?1:totalPages}})</span></div></td><td><button class=paginatorBtn ng-style=\"{'margin-left':$index>0?'-1px':'0'}\" ng-class=\"{'selectedPaginatorBtn':b.id===paginator.visiblePageIdx+1, 'disabledPaginatorBtn':isDisabledBtn(b), 'paginatorBtnNbr': !isNavigateBtn(b), 'paginatorBtnSet': b===btnPrevSet||b===btnNextSet, 'paginatorBtnAll': b===btnFirst||b===btnLast}\" ng-click=paginatorBtnClick(b,$index) ng-repeat=\"b in paginatorButtons track by $index\">{{b.label}}</button></td><td><div style=float:right><span class=paginatorTxt>{{options.paginator.pageSizeTxt}}</span> <button class=\"paginatorBtn paginatorBtnPageSize\" ng-style=\"{'margin-left':$index>0?'-1px':'0'}\" ng-class=\"{'selectedPaginatorBtn':o.rows===pageSize.rows}\" ng-click=pageSizeButtonClick(o) ng-repeat=\"o in pageSizeOptions track by $index\">{{o.label}}</button></div></td></tr></table>");
  $templateCache.put("templates/vsdtrowextender.html",
    "<td class=bodyCol colspan={{visibleColCount+1}}><div ng-include src=template.path></div></td>");
  $templateCache.put("templates/vsdttooltip.html",
    "<div class=tooltip></div>");
}]);

var vsdt = angular.module('vsdatatable', ["template-vsdatatable-0.1.8.html"]);

/**
 * @ngdoc object
 * @name vsdtConf
 * @description Constants of the vsdatatable module.
 */
vsdt.constant('vsdtConf', {
    OVERLAY_SHOW_DELAY: 500,
    TOOLTIP_SHOW_DELAY: 500,
    TOOLTIP_CLOSE_DELAY: 1200,
    FILTER_EXECUTION_DELAY: 500,
    PAGINATOR_MAX_BTN_COUNT: 6,
    PAGINATOR_EVENT: 'vsdatatable.paginatorEvent',
    FILTER_FOCUS_EVENT: 'vsdatatable.filterFocusEvent',
    OPER_PHASE_BEGIN: 'BEGIN',
    OPER_PHASE_END: 'END',
    OPER_ADD: 'ADD',
    OPER_EDIT: 'EDIT',
    OPER_DELETE: 'DELETE',
    OPER_VIEW: 'VIEW',
    EXT_INIT: 'i',
    EXT_SORT: 's',
    EXT_FLT: 'f',
    EXT_BTN: 'b',
    ROW_SELECT: 'SELECT',
    ROW_DESELECT: 'DESELECT',
    COL_RESIZER_MIN_COL_WIDTH: 35,
    DEFAULT_ACTION_COL_WIDTH: 90,
    COLUMN_PROP_VALUE: 'COLUMN_PROP_VALUE',
    DOT_SEPARATOR: '.',
    YEAR: 'yyyy',
    MONTH: 'mm',
    DAY: 'dd',
    DATES_SEPARATOR: ' - '
});

/**
 * @ngdoc object
 * @name vsdtServ
 * @description vsdtServ provides internal functions to the vsdatable directives.
 */
vsdt.service('vsdtServ', ['$templateCache', 'vsdtConf', function ($templateCache, vsdtConf) {
    var vsdts = {};
    vsdts.isUndefined = function (val) {
        return angular.isUndefined(val);
    };

    vsdts.isEqual = function (a, b) {
        return angular.equals(a, b);
    };

    vsdts.isObject = function (val) {
        return angular.isObject(val);
    };

    vsdts.setFilterFocus = function (scope) {
        scope.$broadcast(vsdtConf.FILTER_FOCUS_EVENT);
    };

    vsdts.paginatorEvent = function (scope) {
        scope.$broadcast(vsdtConf.PAGINATOR_EVENT);
    };

    vsdts.getTemplate = function (tpl) {
        return angular.element($templateCache.get(tpl));
    };
    return vsdts;
}]);

/**
 * @ngdoc object
 * @name dateRangeFilter
 * @description dateRangeFilter filter which filters items by date range.
 */
vsdt.filter("dateRangeFilter", function () {
    return function (items, key, from, to) {
        var result = [];
        angular.forEach(items, function (item) {
            var date = new Date(item[key]);
            if (date >= from && date <= to) {
                result.push(item);
            }
        });
        return result;
    };
});

/**
 * @ngdoc object
 * @name vsdatatable
 * @description vsdatatable is main directive of the vsdatatable. Options is passed as an attribute to this
 * directive.
 */
vsdt.directive('vsdatatable', ['$compile', 'vsdtConf', 'vsdtServ', function ($compile, vsdtConf, vsdtServ) {
    return {
        restrict: 'EA',
        templateUrl: 'templates/vsdatatable.html',
        scope: {
            options: '='
        },
        controller: ['$scope', function ($scope) {
            $scope.config = vsdtConf;
            $scope.colInitDone = false, $scope.colTogglerShow = false, $scope.busyIcon = false;
            $scope.filteredItems = [], $scope.selectedRows = [];
            $scope.totalCount = 0;
            $scope.sort = {col: '', reverse: false};
            $scope.globalFilter = '';
            $scope.columnFilter = {contain: {}, exact: {}, daterange: {}};
        }],
        link: function (scope, element, attrs) {
            var extPendingOper = null;
            var rowExtender = null;
            var operObject = {};
            var itemsChangeWatch = null;

            scope.addRow = function () {
                operObject = {
                    oper: scope.config.OPER_ADD,
                    dataOld: {},
                    dataNew: vsdtServ.isUndefined(scope.options.templates.add.defaultValues) ? {} : angular.copy(scope.options.templates.add.defaultValues)
                };
                dataOperation(scope.config.OPER_PHASE_BEGIN);
                scope.template = scope.options.templates.add;
                var bodyElem = angular.element(element[0].querySelector('.tableRows .tableBody'));
                createRowExtender(bodyElem);
            };

            scope.editRow = function (event, data) {
                if (scope.checkEvent(event)) {
                    operObject = {oper: scope.config.OPER_EDIT, dataOld: data, dataNew: angular.copy(data)};
                    dataOperation(scope.config.OPER_PHASE_BEGIN);
                    scope.template = scope.options.templates.edit;
                    createRowExtender(getTableRow(event));
                }
            };

            scope.deleteRow = function (event, data) {
                if (scope.checkEvent(event)) {
                    operObject = {oper: scope.config.OPER_DELETE, dataOld: data, dataNew: {}};
                    dataOperation(scope.config.OPER_PHASE_BEGIN);
                    scope.template = scope.options.templates.delete;
                    createRowExtender(getTableRow(event));
                }
            };

            scope.viewRow = function (event, data) {
                if (scope.checkEvent(event)) {
                    operObject = {oper: scope.config.OPER_VIEW, dataOld: data, dataNew: {}};
                    dataOperation(scope.config.OPER_PHASE_BEGIN);
                    scope.template = scope.options.templates.view;
                    createRowExtender(getTableRow(event));
                }
            };

            scope.acceptClicked = function () {
                removeRowExtender();
                dataOperation(scope.config.OPER_PHASE_END);
                if (!scope.extDataPagination && vsdtServ.isEqual(operObject.oper, scope.config.OPER_EDIT)) {
                    scope.execFilter();
                }
                else if (scope.extDataPagination && !vsdtServ.isEqual(operObject.oper, scope.config.OPER_VIEW)) {
                    scope.paginationOperation(operObject.oper);
                }
                deselectRow(operObject.dataOld);
            };

            scope.cancelClicked = function () {
                removeRowExtender();
            };

            scope.notifyRowSelect = function (oper, data) {
                if (!vsdtServ.isUndefined(scope.options.row.rowSelectCb)) {
                    scope.options.row.rowSelectCb(oper, data);
                }
            };

            scope.getColumns = function () {
                return scope.options.columns;
            };

            scope.getOperationDataObject = function () {
                if (vsdtServ.isEqual(operObject.oper, scope.config.OPER_ADD) || vsdtServ.isEqual(operObject.oper, scope.config.OPER_EDIT)) {
                    return {oper: operObject.oper, data: operObject.dataNew};
                }
                else {
                    return {oper: operObject.oper, data: operObject.dataOld};
                }
            };

            scope.getPropertyValue = function (obj, prop) {
                if (vsdtServ.isEqual(prop.indexOf(scope.config.DOT_SEPARATOR), -1)) {
                    return obj[prop];
                }
                // Nested object
                var parts = prop.split(scope.config.DOT_SEPARATOR);
                var tempVal = angular.copy(obj);
                angular.forEach(parts, function (p) {
                    tempVal = tempVal[p];
                });
                return tempVal;
            };

            scope.getColumnStyle = function (obj, colOpt) {
                if (!vsdtServ.isUndefined(colOpt.rules)) {
                    // Get the column value, evaluate the rule and return the style class
                    var style = '';
                    for (var i in colOpt.rules) {
                        var val = scope.getPropertyValue(obj, colOpt.rules[i].prop);
                        var exp = angular.copy(colOpt.rules[i].expression.toString());
                        // Replace the prop names string to value string fron the expression
                        exp = exp.split(colOpt.rules[i].prop).join(val.toString());
                        if (scope.$eval(exp)) {
                            style = colOpt.rules[i].style;
                            break;
                        }
                    }
                    return style;
                }
            };

            scope.paginationOperation = function (oper) {
                // External pagination
                if (scope.extDataPagination) {
                    if (scope.options.busyIcon.visible) {
                        scope.busyIcon = true;
                    }

                    extPendingOper = oper;
                    if (vsdtServ.isEqual(oper, scope.config.OPER_ADD) || vsdtServ.isEqual(oper, scope.config.OPER_DELETE) || vsdtServ.isEqual(oper, scope.config.EXT_FLT)) {
                        // Reset paginator
                        scope.paginator.visiblePageIdx = 0;
                    }
                    if (vsdtServ.isEqual(oper, scope.config.OPER_ADD) || vsdtServ.isEqual(oper, scope.config.OPER_DELETE)) {
                        // Reset filter and sort - no refresh
                        resetFilterAndSort(false);
                    }

                    // Notify parent
                    scope.options.data.extPaginationOperationCb({
                        columnFilter: scope.columnFilter,
                        globalFilter: scope.globalFilter,
                        page: scope.paginator.visiblePageIdx + 1,
                        pageSize: scope.pageSize.rows,
                        sort: scope.sort
                    });
                }
            };

            scope.checkEvent = function (event) {
                // Mouse or enter key
                return (vsdtServ.isEqual(event.which, 1) || vsdtServ.isEqual(event.which, 13)) && !scope.busyIcon;
            };

            var tableAreaClick = element.on("click", function (event) {
                if (event.which === 1 && scope.colTogglerShow) {
                    scope.colTogglerShow = false;
                    scope.$apply();
                }
            });

            function deselectRow(data) {
                if (vsdtServ.isEqual(scope.options.row.selection, 1) || vsdtServ.isEqual(scope.options.row.selection, 2)) {
                    var idx = scope.selectedRows.indexOf(data);
                    if (!vsdtServ.isEqual(idx, -1)) {
                        scope.selectedRows.splice(idx, 1);
                        scope.notifyRowSelect(scope.config.ROW_DESELECT, data);
                    }
                }
            }

            function resetFilterAndSort(refresh) {
                scope.sort = {col: '', reverse: false};
                scope.resetFilter(refresh);
            }

            function itemsChangeIntWatchFn() {
                // Internal pagination
                scope.filteredItems = scope.options.data.items;
                scope.totalCount = scope.filteredItems.length;
                resetFilterAndSort(false);
                vsdtServ.paginatorEvent(scope);
            }

            function itemsChangeExtWatchFn(val) {
                // External pagination
                scope.filteredItems = val.items;
                scope.totalCount = val.totalCount;
                if (!vsdtServ.isEqual(extPendingOper, scope.config.EXT_BTN)
                    && !vsdtServ.isEqual(extPendingOper, scope.config.OPER_EDIT)
                    && !vsdtServ.isEqual(extPendingOper, scope.config.EXT_SORT)) {
                    vsdtServ.paginatorEvent(scope);
                }
                if (scope.options.busyIcon.visible) {
                    scope.busyIcon = false;
                }
            }

            function getTableRow(event) {
                // Returns table row based on the click of the row icon
                return angular.element(event.target).parent().parent();
            }

            function createRowExtender(rowElem) {
                removeRowExtender();
                rowExtender = vsdtServ.getTemplate('templates/vsdtrowextender.html');
                if (vsdtServ.isEqual(operObject.oper, scope.config.OPER_ADD)) {
                    rowElem.prepend(rowExtender);
                }
                else {
                    rowElem.after(rowExtender);
                }
                $compile(rowExtender)(scope);
            }

            function removeRowExtender() {
                if (!vsdtServ.isEqual(rowExtender, null)) {
                    rowExtender.remove();
                    rowExtender = null;
                }
            }

            function dataOperation(phase) {
                // Notify the parent if the data operation callback is defined
                if (!vsdtServ.isUndefined(scope.options.data.dataOperationCb)) {
                    scope.options.data.dataOperationCb(
                        phase, operObject.oper, operObject.dataOld,
                        vsdtServ.isEqual(phase, scope.config.OPER_PHASE_BEGIN) ? {} : operObject.dataNew);
                }
            }

            function init() {
                scope.extDataPagination = scope.options.data.extDataPagination;
                if (!scope.extDataPagination) {
                    itemsChangeWatch = scope.$watch('options.data.items.length', itemsChangeIntWatchFn);
                }
                else {
                    itemsChangeWatch = scope.$watchCollection('options.data.extItems', itemsChangeExtWatchFn);
                }

                var width = 90 / scope.options.columns.length;
                scope.visibleColCount = 0;
                angular.forEach(scope.options.columns, function (col) {
                    scope.visibleColCount = vsdtServ.isUndefined(col.visible) || col.visible ? scope.visibleColCount + 1 : scope.visibleColCount;
                    if (vsdtServ.isUndefined(col.width)) {
                        col.width = {number: width, unit: '%'};
                    }
                });
            }

            scope.$on('$destroy', function () {
                itemsChangeWatch();
                element.off('click', tableAreaClick);
            });

            init();
        }
    };
}]);

/**
 * @ngdoc object
 * @name filterFocus
 * @description filterFocus is directive which set focus to the global filter input box when the filter icon is clicked.
 */
vsdt.directive('filterFocus', ['$timeout', function ($timeout) {
    return {
        restrict: 'A',
        scope: false,
        link: function (scope, element, attrs) {
            scope.$on(scope.config.FILTER_FOCUS_EVENT, function () {
                $timeout(function () {
                    element[0].focus();
                });
            });

            scope.$on('$destroy', function () {
                scope.$off(scope.config.FILTER_FOCUS_EVENT);
            });
        }
    };
}]);

/**
 * @ngdoc object
 * @name colFilterTemplate
 * @description colFilterTemplate adds column filter (for example input box) to the each column defined in the configuration.
 */
vsdt.directive('colFilterTemplate', ['$compile', 'vsdtServ', function ($compile, vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        link: function (scope, element, attrs) {
            function init() {
                var colOpt = scope.$eval(attrs.colFilterTemplate);
                if (!vsdtServ.isUndefined(colOpt.filter) && !vsdtServ.isUndefined(colOpt.filter.template)
                    && !vsdtServ.isUndefined(colOpt.filter.match)) {
                    // Add the column filter template
                    var colTpl = angular.copy(colOpt.filter.template);
                    colTpl = colTpl.replace(scope.config.COLUMN_PROP_VALUE, 'columnFilter' + scope.config.DOT_SEPARATOR + colOpt.filter.match + scope.config.DOT_SEPARATOR + colOpt.prop + '"');
                    var elem = angular.element(colTpl);
                    $compile(elem)(scope);
                    element.append(elem);
                }
            }

            init();
        }
    };
}]);

/**
 * @ngdoc object
 * @name tableBodyRow
 * @description tableBodyRow directive handles row clicks done by user. It also hover the row in case defined in
 * the configuration.
 */
vsdt.directive('tableBodyRow', ['vsdtServ', function (vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        link: function (scope, element, attrs) {
            scope.rowClicked = function (event, data) {
                if (checkEvent(event)) {
                    var oper = scope.config.ROW_SELECT;
                    var idx = scope.selectedRows.indexOf(data);
                    if (scope.options.row.selection === 1 && vsdtServ.isEqual(idx, -1)) {
                        if (scope.selectedRows.length > 0) {
                            scope.notifyRowSelect(scope.config.ROW_DESELECT, scope.selectedRows[0]);
                        }
                        scope.selectedRows[0] = data;
                    }
                    else if (scope.options.row.selection === 1 && !vsdtServ.isEqual(idx, -1)) {
                        scope.selectedRows.splice(0, 1);
                        oper = scope.config.ROW_DESELECT;
                    }
                    else if (scope.options.row.selection === 2 && vsdtServ.isEqual(idx, -1)) {
                        scope.selectedRows.push(data);
                    }
                    else if (scope.options.row.selection === 2 && !vsdtServ.isEqual(idx, -1)) {
                        scope.selectedRows.splice(idx, 1);
                        oper = scope.config.ROW_DESELECT;
                    }
                    scope.notifyRowSelect(oper, data);
                }
            };

            scope.isRowSelected = function (data) {
                return !vsdtServ.isEqual(scope.selectedRows.indexOf(data), -1);
            };

            function checkEvent(event) {
                return (vsdtServ.isEqual(event.which, 1) || vsdtServ.isEqual(event.which, 13))
                    && (scope.options.row.selection === 1 || scope.options.row.selection === 2);
            }

            function onMouseEnter() {
                element.addClass('hoverRow');
            }

            function onMouseLeave() {
                element.removeClass('hoverRow');
            }

            scope.$on('$destroy', function () {
                if (scope.options.row.hover) {
                    element.off('mouseenter', onMouseEnter);
                    element.off('mouseleave', onMouseLeave);
                }
            });

            function init() {
                if (scope.options.row.hover) {
                    element.on('mouseenter', onMouseEnter);
                    element.on('mouseleave', onMouseLeave);
                }
            }

            init();
        }
    };
}]);

/**
 * @ngdoc object
 * @name tablePaginator
 * @description tablePaginator directive implements paginator.
 */
vsdt.directive('tablePaginator', ['vsdtServ', function (vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        templateUrl: 'templates/vsdtpaginator.html',
        link: function (scope, element, attrs) {
            scope.paginator = {visiblePageIdx: 0, pageFirstIdx: 0};
            scope.paginatorButtons = [], scope.disabledButtons = [];
            var initBtnCount = 0, filteredBtnCount = 0;

            scope.pageSizeButtonClick = function (value) {
                scope.pageSize = scope.pageSizeOptions[scope.pageSizeOptions.indexOf(value)];
                reset();

                if (scope.extDataPagination) {
                    scope.paginationOperation(scope.config.EXT_BTN);
                }
            };

            scope.paginatorBtnClick = function (val, idx) {
                if (!scope.isDisabledBtn(val)) {
                    if (scope.isNavigateBtn(val)) {
                        pageNavigated(val);
                    }
                    else {
                        setPaginatorValues(val.id - 1, scope.paginator.pageFirstIdx, idx - 2);
                    }

                    if (scope.extDataPagination) {
                        scope.paginationOperation(scope.config.EXT_BTN);
                    }
                }
            };

            scope.isNavigateBtn = function (val) {
                return vsdtServ.isEqual(val, scope.btnFirst)
                    || vsdtServ.isEqual(val, scope.btnPrev)
                    || vsdtServ.isEqual(val, scope.btnPrevSet)
                    || vsdtServ.isEqual(val, scope.btnNext)
                    || vsdtServ.isEqual(val, scope.btnNextSet)
                    || vsdtServ.isEqual(val, scope.btnLast);
            };

            scope.isDisabledBtn = function (val) {
                return scope.isNavigateBtn(val) && !vsdtServ.isEqual(scope.disabledButtons.indexOf(val), -1);
            };

            scope.$on(scope.config.PAGINATOR_EVENT, function () {
                reset();
            });

            function pageNavigated(val) {
                if (vsdtServ.isEqual(val, scope.btnFirst)) {
                    toPage(0, val);
                }
                else if (vsdtServ.isEqual(val, scope.btnPrev)) {
                    if (vsdtServ.isEqual(scope.paginator.visiblePageIdx - scope.paginator.pageFirstIdx, 0)) {
                        toPage(scope.paginator.visiblePageIdx - filteredBtnCount, val);
                    }
                    else {
                        setPaginatorValues(scope.paginator.visiblePageIdx - 1, scope.paginator.pageFirstIdx);
                    }
                }
                else if (vsdtServ.isEqual(val, scope.btnPrevSet)) {
                    toPage(scope.paginator.pageFirstIdx - filteredBtnCount, val);
                }
                else if (vsdtServ.isEqual(val, scope.btnLast)) {
                    toPage(scope.totalPages - 1, val);
                }
                else if (vsdtServ.isEqual(val, scope.btnNext)) {
                    if (vsdtServ.isEqual(scope.paginator.visiblePageIdx - scope.paginator.pageFirstIdx, filteredBtnCount - 1)) {
                        toPage(scope.paginator.pageFirstIdx + filteredBtnCount, val);
                    }
                    else {
                        setPaginatorValues(scope.paginator.visiblePageIdx + 1, scope.paginator.pageFirstIdx);
                    }
                }
                else if (vsdtServ.isEqual(val, scope.btnNextSet)) {
                    toPage(scope.paginator.pageFirstIdx + filteredBtnCount, val);
                }
            }

            function calcTotalPages(itemCount) {
                scope.totalPages = Math.ceil(itemCount / scope.pageSize.rows);
            }

            function toPage(pageIdx, val) {
                var visiblePageIdx = 0, pageFirstIdx = 0;
                if (pageIdx > scope.paginator.visiblePageIdx) {
                    // Forward navigate
                    visiblePageIdx = pageIdx;
                    if (vsdtServ.isEqual(val, scope.btnLast)) {
                        pageFirstIdx = visiblePageIdx - filteredBtnCount + 1;
                    }
                    else {
                        var checkedVal = checkMaxPageIdx(pageIdx);
                        pageFirstIdx = !vsdtServ.isEqual(checkedVal, pageIdx) ? checkedVal : pageIdx;
                    }
                }
                else if (pageIdx < scope.paginator.visiblePageIdx && !vsdtServ.isEqual(val, scope.btnFirst)) {
                    // Backward navigate
                    var checkedVal = checkMinPageIdx(pageIdx);
                    visiblePageIdx = pageIdx + filteredBtnCount - 1;
                    pageFirstIdx = !vsdtServ.isEqual(checkedVal, pageIdx) ? checkedVal : pageIdx;
                }
                setPaginatorValues(visiblePageIdx, pageFirstIdx);
            }

            function setPaginatorValues(visiblePageIdx, pageFirstIdx) {
                scope.paginator = {visiblePageIdx: visiblePageIdx, pageFirstIdx: pageFirstIdx};
                setPaginatorButtons();
            }

            function checkMaxPageIdx(value) {
                return value + filteredBtnCount > scope.totalPages ? scope.totalPages - filteredBtnCount : value;
            }

            function checkMinPageIdx(value) {
                return value < 0 ? 0 : value;
            }

            function setPaginatorButtons() {
                filteredBtnCount = scope.totalPages > initBtnCount ? initBtnCount : scope.totalPages;
                var startIdx = scope.paginator.visiblePageIdx !== scope.paginator.pageFirstIdx ? scope.paginator.pageFirstIdx : scope.paginator.visiblePageIdx;
                scope.paginatorButtons.length = 0;

                // Navigate back buttons
                if (scope.options.paginator.firstLastBtn.visible) {
                    scope.paginatorButtons.push(scope.btnFirst);
                }
                if (scope.options.paginator.prevNextBtn.visible) {
                    scope.paginatorButtons.push(scope.btnPrev);
                }
                if (scope.options.paginator.prevNextSetBtn.visible) {
                    scope.paginatorButtons.push(scope.btnPrevSet);
                }

                // Number buttons
                for (var i = startIdx; i < filteredBtnCount + startIdx; i++) {
                    scope.paginatorButtons.push({id: i + 1, label: i + 1});
                }

                // Navigate forward buttons
                if (scope.options.paginator.prevNextSetBtn.visible) {
                    scope.paginatorButtons.push(scope.btnNextSet);
                }
                if (scope.options.paginator.prevNextBtn.visible) {
                    scope.paginatorButtons.push(scope.btnNext);
                }
                if (scope.options.paginator.firstLastBtn.visible) {
                    scope.paginatorButtons.push(scope.btnLast);
                }

                // Set disabled buttons if needed
                setDisabledButtons();
            }

            function setDisabledButtons() {
                scope.disabledButtons.length = 0;
                if (vsdtServ.isEqual(scope.paginator.visiblePageIdx, 0)) {
                    scope.disabledButtons.push(scope.btnFirst);
                    scope.disabledButtons.push(scope.btnPrev);
                }
                if (vsdtServ.isEqual(scope.paginator.pageFirstIdx, 0)) {
                    scope.disabledButtons.push(scope.btnPrevSet);
                }
                if (scope.paginator.pageFirstIdx + filteredBtnCount >= scope.totalPages) {
                    scope.disabledButtons.push(scope.btnNextSet);
                }
                if (scope.paginator.visiblePageIdx >= scope.totalPages - 1) {
                    scope.disabledButtons.push(scope.btnLast);
                    scope.disabledButtons.push(scope.btnNext);
                }
            }

            function reset() {
                calcTotalPages(scope.totalCount);
                setPaginatorValues(0, 0);
            }

            function init() {
                // Set labels of the paginator buttons
                scope.btnPrev = {id: 'b', label: scope.options.paginator.prevNextBtn.labels[0]};
                scope.btnNext = {id: 'n', label: scope.options.paginator.prevNextBtn.labels[1]};
                scope.btnFirst = {id: 'f', label: scope.options.paginator.firstLastBtn.labels[0]};
                scope.btnLast = {id: 'l', label: scope.options.paginator.firstLastBtn.labels[1]};
                scope.btnPrevSet = {id: 'ps', label: scope.options.paginator.prevNextSetBtn.labels[0]};
                scope.btnNextSet = {id: 'ns', label: scope.options.paginator.prevNextSetBtn.labels[1]};

                scope.pageSizeOptions = scope.options.paginator.pageSizeOptions;
                initBtnCount = scope.options.paginator.numberBtnCount > scope.config.PAGINATOR_MAX_BTN_COUNT ? scope.config.PAGINATOR_MAX_BTN_COUNT : scope.options.paginator.numberBtnCount;
                var idx = 0;
                for (var i in scope.pageSizeOptions) {
                    if (scope.pageSizeOptions[i].hasOwnProperty('default') && angular.equals(scope.pageSizeOptions[i].default, true)) {
                        idx = i;
                        break;
                    }
                }
                scope.pageSize = scope.pageSizeOptions[idx];
                reset();
            }

            scope.$on('$destroy', function () {
                scope.$off(scope.config.PAGINATOR_EVENT);
            });

            init();
        }
    };
}]);

/**
 * @ngdoc object
 * @name colToggleMenu
 * @description colToggleMenu directive implements column toggle menu.
 */
vsdt.directive('colToggleMenu', function () {
    return {
        restrict: 'A',
        scope: false,
        templateUrl: 'templates/vsdtcoltogglemenu.html',
        link: function (scope, element, attrs) {
            scope.colTogglerShowClicked = function (event) {
                if (scope.checkEvent(event)) {
                    scope.colTogglerShow = !scope.colTogglerShow;
                }
            };

            scope.colToggleMenuClicked = function (event, col) {
                if (scope.checkEvent(event)) {
                    scope.visibleColCount = col.visible ? scope.visibleColCount - 1 : scope.visibleColCount + 1;
                    col.visible = !col.visible;
                    scope.colInitDone = true;
                }
            };
        }
    };
});

/**
 * @ngdoc object
 * @name captionBar
 * @description captionBar directive implements captionBar of the datatable.
 */
vsdt.directive('captionBar', ['$filter', 'vsdtServ', function ($filter, vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        templateUrl: 'templates/vsdtcaption.html',
        link: function (scope, element, attrs) {
            scope.filterFocus = false;
            var filterChangeWatch = null;
            var filterFocusWatch = null;
            var orderItems = $filter('orderBy');
            var filterItems = $filter('filter');
            var filterDateRange = $filter('dateRangeFilter');
            var refreshFlag = true;

            scope.filterBtnClick = function (event) {
                if (scope.checkEvent(event)) {
                    scope.filterFocus = !scope.filterFocus;
                    if (!scope.filterFocus) {
                        scope.resetFilter(true);
                    }
                }
            };

            scope.execFilterAndSort = function () {
                if (!scope.extDataPagination) {
                    scope.execFilter();
                    execSort();
                    vsdtServ.paginatorEvent(scope);
                }
                else {
                    scope.paginationOperation(scope.config.EXT_FLT);
                }
            };

            scope.resetFilter = function (refresh) {
                refreshFlag = refresh;
                scope.globalFilter = '';
                resetColumnFilter(scope.columnFilter.contain);
                resetColumnFilter(scope.columnFilter.exact);
                resetColumnFilter(scope.columnFilter.daterange);
                if (!scope.options.filter.autoFilter.useAutoFilter && refresh) {
                    scope.execFilterAndSort();
                }
            };

            scope.sortByCol = function (event, col) {
                event.stopPropagation();
                if (scope.checkEvent(event)) {
                    if (vsdtServ.isEqual(scope.sort.col, col)) {
                        scope.sort.reverse = !scope.sort.reverse;
                    }
                    else {
                        scope.sort.reverse = false;
                    }
                    scope.sort.col = col;
                    if (!scope.extDataPagination) {
                        if (vsdtServ.isEqual(col, '')) {
                            scope.execFilter();
                        }
                        execSort();
                    }
                    scope.paginationOperation(scope.config.EXT_SORT);
                }
            };

            scope.execFilter = function () {
                scope.filteredItems = scope.options.data.items;
                if (!vsdtServ.isEqual(scope.globalFilter, '')) {
                    scope.filteredItems = filterItems(scope.filteredItems, scope.globalFilter);
                }

                var containFilter = getFilterExpression(scope.columnFilter.contain);
                if (!vsdtServ.isEqual(containFilter, {})) {
                    scope.filteredItems = filterItems(scope.filteredItems, containFilter);
                }

                var exactFilter = getFilterExpression(scope.columnFilter.exact);
                if (!vsdtServ.isEqual(exactFilter, {})) {
                    scope.filteredItems = filterItems(scope.filteredItems, exactFilter, function (a, b) {
                        return vsdtServ.isEqual(a.toString(), b) || vsdtServ.isEqual(b, '');
                    });
                }

                var drFilter = getFilterExpression(scope.columnFilter.daterange);
                if (!vsdtServ.isEqual(drFilter, {})) {
                    angular.forEach(drFilter, function (v, k) {
                        var dates = v.split(scope.config.DATES_SEPARATOR);
                        if (vsdtServ.isEqual(dates.length, 2) && vsdtServ.isEqual(dates[0].length, 10) && vsdtServ.isEqual(dates[1].length, 10)) {
                            var fmt = getDateFormat(k);
                            scope.filteredItems = filterDateRange(scope.filteredItems, k, createDate(dates[0], fmt), createDate(dates[1], fmt));
                        }
                    });
                }

                scope.totalCount = scope.filteredItems.length;
            };

            function getDateFormat(prop) {
                for (var i in scope.options.columns) {
                    if (vsdtServ.isEqual(scope.options.columns[i].prop, prop)) {
                        return scope.options.columns[i].filter.dateFormat;
                    }
                }
            }

            function createDate(dateStr, fmt) {
                fmt = fmt.toLowerCase();
                var y = fmt.indexOf(scope.config.YEAR);
                var m = fmt.indexOf(scope.config.MONTH);
                var d = fmt.indexOf(scope.config.DAY);
                return new Date(parseInt(dateStr.substring(y, y + 4)), parseInt(dateStr.substring(m, m + 2)) - 1, parseInt(dateStr.substring(d, d + 2)));
            }

            function getFilterExpression(filterData) {
                var exp = {};
                angular.forEach(filterData, function (v, k) {
                    if (!vsdtServ.isEqual(v, '')) {
                        exp[k] = v;
                    }
                });
                return exp;
            }

            function execSort() {
                if (!vsdtServ.isEqual(scope.sort.col, '')) {
                    scope.filteredItems = orderItems(scope.filteredItems, scope.sort.col, scope.sort.reverse);
                }
            }

            function filterChangeWatchFn(newVal, oldVal) {
                if (vsdtServ.isObject(newVal) && vsdtServ.isEqual(oldVal.contain, {}) && vsdtServ.isEqual(oldVal.exact, {})) {
                    return;
                }
                if (!vsdtServ.isEqual(newVal, oldVal) && refreshFlag) {
                    scope.execFilterAndSort();
                }
                refreshFlag = true;
            }

            function filterFocusWatchFn(newVal, oldVal) {
                if (!vsdtServ.isEqual(newVal, oldVal) && newVal) {
                    vsdtServ.setFilterFocus(scope);
                }
            }

            function createFilterExpression() {
                var filterExp = '';
                angular.forEach(scope.options.columns, function (col) {
                    if (!vsdtServ.isUndefined(col.filter) && !vsdtServ.isUndefined(col.filter.template)) {
                        filterExp += 'columnFilter.' + col.filter.match + scope.config.DOT_SEPARATOR + col.prop + ' + ';
                    }
                });
                return filterExp;
            }

            function resetColumnFilter(filterData) {
                angular.forEach(filterData, function (v, k) {
                    filterData[k] = '';
                });
            }

            function init() {
                if (scope.options.filter.global || scope.options.filter.column) {
                    var filterExp = createFilterExpression();
                    if (scope.options.filter.autoFilter.useAutoFilter) {
                        filterExp += 'globalFilter';
                        filterChangeWatch = scope.$watch(filterExp, filterChangeWatchFn);
                    }
                }
                if (scope.options.filter.global) {
                    filterFocusWatch = scope.$watch('filterFocus', filterFocusWatchFn);
                }
            }

            scope.$on('$destroy', function () {
                if (!vsdtServ.isEqual(filterChangeWatch, null)) {
                    filterChangeWatch();
                }
                if (!vsdtServ.isEqual(filterFocusWatch, null)) {
                    filterFocusWatch();
                }
            });

            init();

        }
    };
}]);


/**
 * @ngdoc object
 * @name overlayWindow
 * @description overlayWindow directive implements overlay window to long values in the columns.
 */
vsdt.directive('overlayWindow', ['$compile', '$timeout', 'vsdtServ', function ($compile, $timeout, vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        link: function (scope, element, attrs) {
            var overlay = null;
            var timer = null;

            scope.closeOverlay = function (event) {
                event.stopPropagation();
                onMouseLeave();
            };

            function onMouseEnter() {
                if (element[0].scrollWidth > element[0].offsetWidth) {
                    timer = $timeout(function () {
                        overlay = vsdtServ.getTemplate('templates/vsdtoverlaywindow.html');
                        overlay.css('margin-top', '-20px');
                        overlay.css('margin-left', '14px');
                        overlay.text(attrs.overlayWindow);
                        element.append($compile(overlay)(scope));
                    }, scope.config.OVERLAY_SHOW_DELAY);
                }
            }

            function onMouseLeave() {
                cancelTimer();
                if (!angular.equals(overlay, null)) {
                    overlay.remove();
                    overlay = null;
                }
            }

            function cancelTimer() {
                $timeout.cancel(timer);
                timer = null;
            }

            scope.$on('$destroy', function () {
                element.off('mouseenter', onMouseEnter);
                element.off('mouseleave', onMouseLeave);
            });

            function init() {
                if (scope.options.showOverlay) {
                    element.on('mouseenter', onMouseEnter);
                    element.on('mouseleave', onMouseLeave);
                }
            }

            init();
        }
    };
}]);

/**
 * @ngdoc object
 * @name vstooltip
 * @description vstooltip directive implements tooltips.
 */
vsdt.directive('vstooltip', ['$compile', '$timeout', 'vsdtServ', function ($compile, $timeout, vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        link: function (scope, element, attrs) {
            var tooltip = null;
            var openTimer = null, closeTimer = null;

            function onMouseEnter() {
                openTimer = $timeout(function () {
                    showTooltip();
                    closeTimer = $timeout(function () {
                        hideTooltip();
                    }, scope.config.TOOLTIP_CLOSE_DELAY, true);
                }, scope.config.TOOLTIP_SHOW_DELAY, true);
            };

            function onMouseLeave() {
                cancelTimer();
                hideTooltip();
            }

            function showTooltip() {
                tooltip = vsdtServ.getTemplate('templates/vsdttooltip.html');
                tooltip.css('margin-left', element.prop('offsetLeft') + 'px');
                tooltip.text(attrs.vstooltip);
                element.append($compile(tooltip)(scope));
            }

            function hideTooltip() {
                if (!angular.equals(tooltip, null)) {
                    tooltip.remove();
                    tooltip = null;
                }
            }

            function cancelTimer() {
                $timeout.cancel(openTimer);
                $timeout.cancel(closeTimer);
            }

            scope.$on('$destroy', function () {
                element.off('mouseenter', onMouseEnter);
                element.off('mouseleave', onMouseLeave);
            });

            function init() {
                if (scope.options.showTooltips) {
                    element.on('mouseenter', onMouseEnter);
                    element.on('mouseleave', onMouseLeave);
                }
            }

            init();
        }
    };
}]);

/**
 * @ngdoc object
 * @name colResizer
 * @description colResizer directive implements column resize of the vsdatatable.
 */
vsdt.directive('colResizer', ['$compile', '$document', 'vsdtServ', function ($compile, $document, vsdtServ) {
    return {
        restrict: 'A',
        scope: false,
        link: function (scope, element, attrs) {
            var startPos = 0, nextElem = 0, currWidth = 0, nextWidth = 0, headerWidth = 0;
            var colResizer = null;

            function onResizeStart(event) {
                event.preventDefault();
                startPos = event.clientX;
                nextElem = element.next();
                if (!vsdtServ.isEqual(nextElem.prop('id'), 'headerColAction')) {
                    currWidth = element.prop('offsetWidth');
                    nextWidth = nextElem.prop('offsetWidth');
                    headerWidth = element.parent().prop('offsetWidth');

                    // Register events
                    $document.on('mousemove', onResizeMove);
                    $document.on('mouseup', onResizeEnd);
                    setCursor('col-resize');
                }
            }

            function onResizeMove(event) {
                // if newPos > 0 move id forward - if newPos < 0 move is backward
                var newPos = event.clientX - startPos;
                var newCurrWidth = currWidth + newPos;
                var newNextWidth = nextWidth - newPos;
                if (newPos > 0 && newNextWidth < scope.config.COL_RESIZER_MIN_COL_WIDTH) {
                    return;
                }
                else if (newPos < 0 && newCurrWidth < scope.config.COL_RESIZER_MIN_COL_WIDTH) {
                    return;
                }
                // Change to the percent value
                element.css('width', (newCurrWidth / headerWidth * 100) + '%');
                nextElem.css('width', (newNextWidth / headerWidth * 100) + '%');
            }

            function onResizeEnd() {
                // Deregister events
                $document.off('mousemove', onResizeMove);
                $document.off('mouseup', onResizeEnd);
                setCursor('default');
            }

            function setCursor(type) {
                $document.prop('body').style.cursor = type;
            }

            function colDefaultWidth() {
                var colSpace = 100 - (scope.config.DEFAULT_ACTION_COL_WIDTH / element.parent().prop('offsetWidth') * 100);
                return colSpace / scope.visibleColCount;
            }

            function resetColumnsWidth() {
                var width = colDefaultWidth();
                angular.forEach(scope.options.columns, function (col) {
                    if (col.visible) {
                        col.width = {number: width, unit: '%'};
                    }
                });
            }

            function init() {
                if (scope.options.columnResize) {
                    // Create column resizer
                    colResizer = vsdtServ.getTemplate('templates/vsdtcolresizer.html');
                    colResizer.on('mousedown', onResizeStart);
                    element.css('background-clip', 'padding-box');
                    element.css('position', 'relative');
                    element.append($compile(colResizer)(scope));
                }
                if (scope.colInitDone) {
                    resetColumnsWidth();
                }
            }

            scope.$on('$destroy', function () {
                colResizer.off('mousedown', onResizeStart);
                resetColumnsWidth();
            });

            init();
        }
    };
}]);
/*
    Header column template styles
*/
.columnTemplate .inputField,
.columnTemplate .selectMenu {
    padding-left: 4px;
    width: 100%;
    background-color: #FAFAFA;
    height: 24px;
    border:1px solid #AAA;
    -moz-box-shadow: inset 0 0 1px #1589FF;
    -webkit-box-shadow: inset 0 0 1px #1589FF;
    box-shadow: inset 0 0 1px #1589FF;
}

.columnTemplate .selectMenu {
    cursor: pointer;
}

/*
    Body column styles: active column
*/
.activeStyle {
    color: #21af17;
}

.inactiveStyle {
    color: #DD0000;
}

/*
    Body column styles: car.price column
*/
.carPriceStyleGreen {
    color: #21af17;
}

.carPriceStyleRed {
    color: #DD0000;
}

.carPriceStyleGreen,
.carPriceStyleRed {
    font-weight: bold;
    font-style: italic;
}

/*
    Body column styles: car.features column
*/
.carFeatures1Style {
    background-color: #FFE6E6;
}

.carFeatures2Style {
    background-color: #FFFFAA;
}

.carFeatures3Style {
    background-color: #DCFFDC;
}
.rowExtender table {
    width: 100%;
}

.rowExtender table,
.rowExtender table tr,
.rowExtender .extenderTitle,
.rowExtender .labelCol,
.rowExtender .valueCol,
.rowExtender .textInput,
.rowExtender .textArea,
.rowExtender .error,
.rowExtender .selectMenu,
.rowExtender input.ng-invalid.ng-touched,
.rowExtender textarea.ng-invalid.ng-touched {
    font-size: 14px;
}

.rowExtender table tr {
    height: 32px;
    background-color: #FFF; 
}

.rowExtender .labelCol,
.rowExtender .valueCol {
    border: 1px solid #C0C0C0;
    text-align: left;
}

.rowExtender .labelCol {
    padding-left: 6px;
    font-weight: bold;
}

.rowExtender .valueCol {
    padding: 4px;
}

.rowExtender .textInput,
.rowExtender .textArea,
.rowExtender .selectMenu {
    width: 100%;
    padding: 4px;
    background-color: #F5F9F3;
    border: 1px solid #CCC;
}

.rowExtender .textInput {

}

.rowExtender .textArea {
    resize: none;
}

.rowExtender .textField {
    width: 100%;
    padding: 4px;
}

.rowExtender .checkbox, 
.rowExtender .radioBtn {
    cursor: pointer;
}

.rowExtender tr th:first-child {
    width: 40%;
}

.rowExtender tr th:last-child {
    width: 60%;
}

.rowExtender .extenderTitle {
    margin-bottom: 4px;
    font-weight: bold;
    text-align: center;
}

.rowExtender .rowExtenderBtnColor {
    background: #FAFAFA;
    background-image: -webkit-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: -moz-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: -o-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: -ms-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: linear-gradient(#F0F0F0 30%, #AEC2E1 100%)
}

.rowExtender .rowExtenderBtn {
    border: 1px solid #A9A9A9;
    padding: 2px;
    min-width: 85px;
    height: 30px;
}

.vsdatatable .rowExtenderBtn:enabled {
    cursor: pointer;
}

.vsdatatable .rowExtenderBtn:hover:enabled {
    background: #CAFFCA !important;
    color: #0066CC;
}

.vsdatatable .rowExtenderBtn:focus:enabled {
    color: #1589FF;
    outline: none;
}

.rowExtender .rowExtenderFooter {
    border: 1px solid #CCC;
    padding: 5px;
    text-align: center;
    margin-top: -1px;
}

.rowExtender .rowExtenderBtnIcon {
    font-size: 12px;
    cursor: pointer;
}

.rowExtender .rowExtenderBtnIconGreen,
.rowExtender .rowExtenderBtnIconGreen:hover {
    color: #21AF17;
}

.rowExtender .rowExtenderBtnIconRed,
.rowExtender .rowExtenderBtnIconRed:hover {
    color: #DD0000;
}

.rowExtender .error {
    padding: 4px;
    border-radius: 2px;
    background-color: #F2DEDE;
    color: #A94444;
    margin: 4px 0 0 0;
    text-align: left;
}

.rowExtender .error, 
.rowExtender input.ng-invalid.ng-touched,
.rowExtender textarea.ng-invalid.ng-touched {
    border: 1px solid #FA787E;
    -moz-box-shadow: 0 0 1px #FF0000;
    -webkit-box-shadow: 0 0 1px #FF0000;
    box-shadow: 0 0 1px #FF0000;
}

.rowExtender input.ng-valid.ng-touched {
    //border: 1px solid #78FA89;
}
<form class="rowExtender" novalidate name="editForm" ng-controller="extenderctrl">
    
    <div class="extenderTitle" ng-init="objectInOper=getOperationDataObject();column=getColumns()">      
        {{objectInOper.oper===config.OPER_ADD?'Add a new object':'Edit the object'}}
    </div>
    
    <table>
        <thead>
            <tr>
                <th class="headerCol">Property</th>
                <th class="headerCol">Value</th>
            </tr>
        </thead>
        <tbody>

            <tr>
                <td class="labelCol">{{column[0].label}}</td>
                <td class="valueCol">
                    <div class="textField" ng-init="objectInOper.data[column[0].prop]=getNextId()"
                         ng-if="objectInOper.oper===config.OPER_ADD">{{objectInOper.data[column[0].prop]}}
                    </div>
                    <div class="textField" ng-if="objectInOper.oper===config.OPER_EDIT">
                        {{objectInOper.data[column[0].prop]}}
                    </div>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[1].label}}</td>
                <td class="valueCol">
                    <input class="checkbox" ng-model="objectInOper.data[column[1].prop]" type="checkbox"/>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[2].label}}</td>
                <td class="valueCol">
                    <input class="textInput"
                           ng-model="objectInOper.data['car']['price']"
                           placeholder="{{column[2].label}}"
                           type="text"
                           name="car_price" ng-pattern="/^[+-]?\d{1,9}\.\d{1,2}$/" required/>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[3].label}}</td>
                <td class="valueCol">
                    <select class="selectMenu"
                            name="class"
                            ng-model="objectInOper.data['car']['features']"
                            ng-options="features.value as features.label for features in [{label: 'Set 1', value: 1}, {label: 'Set 2', value: 2}, {label: 'Set 3', value: 3}]"></select>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[4].label}}</td>
                <td class="valueCol">
                    <input class="textInput"
                           ng-model="objectInOper.data['car']['age']"
                           placeholder="{{column[4].label}}"
                           type="number"
                           name="car_age" min="1" max="60" required/>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[5].label}}</td>
                <td class="valueCol">
                    <input class="textInput"
                           ng-model="objectInOper.data[column[5].prop]"
                           placeholder="{{column[5].label}}"
                           type="text"
                           name="name" ng-maxlength="50" required/>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[6].label}}</td>
                <td class="valueCol">
                    <input class="textInput"
                           ng-model="objectInOper.data[column[6].prop]"
                           placeholder="{{column[6].label}}"
                           type="text"
                           name="date" ng-pattern="/^\d{4}[-/.]\d{2,2}[-/.]\d{2,2}$/" required/>
                </td>
            </tr>

            <tr>
                <td class="labelCol">{{column[7].label}}</td>
                <td class="valueCol">         
                    <textarea rows="4" class="textArea"
                            placeholder="{{column[7].label}}"
                              ng-model="objectInOper.data[column[7].prop]"
                            name="about" required></textarea>
                </td>
            </tr>
            
        </tbody>
    </table>  
    
    <div class="tableFooter rowExtenderFooter">
        <button class="rowExtenderBtn rowExtenderBtnColor" ng-click="acceptClicked(objectInOper.oper)"
                ng-disabled="!editForm.$valid">
            OK
            <span class="icon icon-check rowExtenderBtnIcon rowExtenderBtnIconGreen"></span> 
        </button>
        <button class="rowExtenderBtn rowExtenderBtnColor" ng-click="cancelClicked();">
            Cancel
            <span class="icon icon-cross rowExtenderBtnIcon rowExtenderBtnIconRed"></span> 
        </button>
    </div>

    <div class="error" ng-show="editForm.car_price.$error.required && !editForm.car_price.$pristine">{{column[2].label}}
        - value is required!
    </div>
    <div class="error" ng-show="editForm.car_price.$error.pattern && !editForm.car_price.$pristine">{{column[2].label}}
        - number with two decimals is allowed!
    </div>

    <div class="error" ng-show="editForm.car_age.$error.required && !editForm.car_age.$pristine">{{column[4].label}} -
        value is required!
    </div>
    <div class="error"
         ng-show="(editForm.car_age.$error.min || editForm.car_age.$error.max || editForm.car_age.$error.number) && !editForm.car_age.$pristine">
        {{column[4].label}} - integer between 1 and 60 is allowed!
    </div>

    <div class="error" ng-show="editForm.name.$error.required && !editForm.name.$pristine">{{column[5].label}} - value
        is required!
    </div>
    <div class="error" ng-show="editForm.name.$error.maxlength && !editForm.name.$pristine">{{column[5].label}} -
        maximum length is 60 characters!
    </div>

    <div class="error" ng-show="editForm.date.$error.required && !editForm.date.$pristine">{{column[6].label}} - value is required!</div>
    <div class="error" ng-show="editForm.date.$error.pattern && !editForm.date.$pristine">{{column[6].label}} - is not in the valid format (YYYY-MM-dd)!</div>
    
    <div class="error" ng-show="editForm.about.$error.required && !editForm.about.$pristine">{{column[7].label}} - value is required!</div>
</form>
<form class="rowExtender" novalidate name="viewDeleteForm" ng-controller="extenderctrl">
    <div class="extenderTitle" ng-init="objectInOper=getOperationDataObject()">
        {{objectInOper.oper===config.OPER_VIEW?'Object properties':'Delete the object'}}
    </div>
    
    <table class="rowExtender">
        <thead>
            <tr>
                <th class="headerCol">Property</th>
                <th class="headerCol">Value</th>
            </tr>
        </thead>
        <tbody>
            <tr ng-repeat="column in getColumns() track by $index">
                <td class="labelCol">{{column.label}}</td>
                <td class="valueCol">
                    <div class="textField">
                        {{getPropertyValue(objectInOper.data, column.prop)}}
                    </div>
                </td>
            </tr>
        </tbody>
    </table>

    <div class="tableFooter rowExtenderFooter">
        <button class="rowExtenderBtn rowExtenderBtnColor" ng-click="acceptClicked(objectInOper.oper)">
            {{objectInOper.oper===config.OPER_VIEW?'Close':'Delete'}}
            <span class="icon icon-check rowExtenderBtnIcon rowExtenderBtnIconGreen"></span>
        </button>
        <button class="rowExtenderBtn rowExtenderBtnColor" ng-click="cancelClicked();"
                ng-if="objectInOper.oper===config.OPER_DELETE">
            Cancel
            <span class="icon icon-cross rowExtenderBtnIcon rowExtenderBtnIconRed"></span>
        </button>
    </div>
    
</form>

/* 
*  Name: dpdaterangepicker 
*  Description: Date range picker - AngularJS reusable UI component 
*  Version: 0.1.3 
*  Author: kekeh 
*  Homepage: http://kekeh.github.io/dpdaterangepicker 
*  License: MIT 
*  Date: 2015-09-06 
*/ 
angular.module('template-dpdaterangepicker-0.1.3.html', []).run(['$templateCache', function($templateCache) {
  $templateCache.put("templates/dpdaterangepicker.html",
    "<div class=dpdaterangepicker ng-style=\"{'width':width}\"><div class=vstooltip ng-show=showTooltip ng-mouseleave=\"showTooltip=false\"><span class=vstooltiptext>{{selectedRangeTxt}}</span></div><div class=dpselectiongroup ng-click=picker($event)><span class=dpselection ng-style=\"{'line-height': height}\" ng-click=picker($event) tooltip-window>{{selectedRangeTxt}}</span> <span class=dpselbtngroup ng-style=\"{'height': height}\"><button class=dpbtnclear ng-show=\"selectedRangeTxt.length > 0\" ng-click=clearSelection($event)><span class=\"icon icon-cross\"></span></button> <button class=dpbtnpicker ng-click=picker($event)><span class=\"icon icon-calendar\"></span></button></span></div><div class=dpselector ng-if=showSelector><div class=dptitlearea ng-class=\"{'dptitlerangeok': rangeOk, 'dptitlerangenotok': !rangeOk}\"><div class=dptitleareatxt>{{titleTxt}}</div></div><table class=dpheader><tr><td><div style=float:left><div class=dpheaderbtn ng-click=prevMonth()><span class=\"icon icon-left\"></span></div><div class=dpheadermonthtxt ng-bind=visibleMonth.monthTxt></div><div class=dpheaderbtn ng-click=nextMonth()><span class=\"icon icon-right\"></span></div></div></td><td><button class=dpheadertodaybtn ng-click=today()>{{options.buttons.todayBtnText!==undefined?options.buttons.todayBtnText:cf.buttons.todayBtnText}}</button></td><td><div style=float:right><div class=dpheaderbtn ng-click=prevYear()><span class=\"icon icon-left\"></span></div><div class=dpheaderyeartxt ng-bind=visibleMonth.year></div><div class=dpheaderbtn ng-click=nextYear()><span class=\"icon icon-right\"></span></div></div></td></tr></table><table class=dptable><thead><tr><th ng-class=\"{'dpnogrid': !showGrid}\" ng-repeat=\"d in weekDays track by $index\" ng-bind=d></th></tr></thead><tbody><tr ng-repeat=\"w in dates track by $index\"><td ng-repeat=\"d in w track by $index\" ng-class=\"{'dpnogrid':!showGrid,'dpcurrmonth':d.cmo===cf.CURR_MONTH,'dpcurrday':d.currDay && (options.currDayHighlight!==undefined?options.currDayHighlight:cf.currDayHighlight),'dpselectedday':selectedDate.day===d.day && selectedDate.month===d.month && selectedDate.year===d.year && d.cmo===cf.CURR_MONTH}\" ng-click=cellClicked(d)><span style=background-color:inherit ng-class=\"{'dpprevmonth':d.cmo===cf.PREV_MONTH,'dpcurrmonth':d.cmo===cf.CURR_MONTH,'dpnextmonth':d.cmo===cf.NEXT_MONTH,'dpsunday':d.sun && d.cmo===cf.CURR_MONTH && (options.sunHighlight!==undefined?options.sunHighlight:cf.sunHighlight)}\" ng-bind=d.day></span></td></tr></tbody></table><div class=dpfooterarea><button class=dpfooterbtn ng-class=\"{'dpbtndisable': !rangeOk}\" ng-disabled=!rangeOk ng-show=beginDateStep ng-click=toEndDate()>{{options.buttons.nextBtnText!==undefined?options.buttons.nextBtnText:cf.buttons.nextBtnText}}</button> <button class=dpfooterbtn ng-show=!beginDateStep ng-click=toBeginDate()>{{options.buttons.prevBtnText!==undefined?options.buttons.prevBtnText:cf.buttons.prevBtnText}}</button> <button class=dpfooterbtn ng-class=\"{'dpbtndisable': !rangeOk}\" ng-disabled=!rangeOk ng-show=!beginDateStep ng-click=accept()>{{options.buttons.okBtnText!==undefined?options.buttons.okBtnText:cf.buttons.okBtnText}}</button></div></div></div>");
}]);

angular.module('dpdaterangepicker', ["template-dpdaterangepicker-0.1.3.html"])

/**
 * @ngdoc object
 * @name dpdaterangeConfig
 * @description dpdaterangeConfig the default values and the constants of the date picker.
 */
    .constant('dpdaterangeConfig', {
        // Configurable values with default value
        monthLabels: {1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun', 7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec'},
        dayLabels: {su: 'Sun', mo: 'Mon', tu: 'Tue', we: 'Wed', th: 'Thu', fr: 'Fri', sa: 'Sat'},
        dateFormat: 'yyyy-mm-dd',
        firstDayOfWeek: 'su',
        showGrid: true,
        buttons: {
            todayBtnText: 'Today',
            nextBtnText: 'Next',
            prevBtnText: 'Previous',
            okBtnText: 'OK'
        },
        beginDateText: 'begin date',
        endDateText: 'end date',
        sunHighlight: true,
        currDayHighlight: true,

        // Constants
        YEAR_CONST: 'yyyy',
        MONTH_CONST: 'mm',
        DATE_CONST: 'dd',
        PREV_MONTH: 1,
        CURR_MONTH: 2,
        NEXT_MONTH: 3,
        DATES_SEPARATOR: ' - ',
        TOOLTIP_SHOW_DELAY: 600,
        HEIGHT: '30px',
        WIDTH: '260px'
    })

/**
 * @ngdoc object
 * @name dpdaterangepicker
 * @description dpdaterangepicker is main directive of the component and it implements the date range picker.
 */
    .directive('dpdaterangepicker', ['$timeout', '$document', function ($timeout, $document) {
        return {
            restrict: 'EA',
            templateUrl: 'templates/dpdaterangepicker.html',
            scope: {
                ngModel: '=?',
                options: '='
            },
            controller: ['$scope', 'dpdaterangeConfig', function ($scope, dpdaterangeConfig) {
                $scope.cf = dpdaterangeConfig;
                $scope.showTooltip = false;
            }],
            link: function (scope, element, attrs) {
                scope.dates = [], scope.weekDays = [];
                scope.selectedRangeTxt = '', scope.titleTxt = '';
                scope.showSelector = false, scope.rangeOk = false, scope.beginDateStep = true;
                scope.selectedDate = {day: 0, month: 0, year: 0};
                scope.visibleMonth = {monthTxt: '', monthNbr: 0, year: 0};
                scope.width = scope.cf.WIDTH, scope.height = scope.cf.HEIGHT;

                var dayIdx = 0;
                var selectedBeginDate = {day: 0, month: 0, year: 0};
                var today = new Date();

                scope.prevMonth = function () {
                    // Previous month selected
                    var m = scope.visibleMonth.monthNbr;
                    var y = scope.visibleMonth.year;
                    if (m === 1) {
                        m = 12;
                        y--;
                    }
                    else {
                        m--;
                    }
                    scope.visibleMonth = {monthTxt: monthText(m), monthNbr: m, year: y};
                };

                scope.nextMonth = function () {
                    // Next month selected
                    var m = scope.visibleMonth.monthNbr;
                    var y = scope.visibleMonth.year;
                    if (m === 12) {
                        m = 1;
                        y++;
                    }
                    else {
                        m++;
                    }
                    scope.visibleMonth = {monthTxt: monthText(m), monthNbr: m, year: y};
                };

                scope.prevYear = function () {
                    // Previous year selected
                    scope.visibleMonth.year--;
                };

                scope.nextYear = function () {
                    // Next year selected
                    scope.visibleMonth.year++;
                };

                scope.today = function () {
                    // Today selected
                    var m = today.getMonth() + 1;
                    scope.visibleMonth = {monthTxt: monthText(m), monthNbr: m, year: today.getFullYear()};
                };

                scope.cellClicked = function (cell) {
                    // Cell clicked in the selector
                    if (cell.cmo === scope.cf.PREV_MONTH) {
                        // Previous month of day
                        scope.prevMonth();
                    }
                    else if (cell.cmo === scope.cf.CURR_MONTH) {
                        // Current month of day
                        handleSelect(cell);
                    }
                    else if (cell.cmo === scope.cf.NEXT_MONTH) {
                        // Next month of day
                        scope.nextMonth();
                    }
                };

                scope.toBeginDate = function () {
                    // Back to begin date selection
                    scope.selectedDate = selectedBeginDate;
                    scope.titleTxt = formatDate(selectedBeginDate);
                    scope.beginDateStep = true;
                    scope.rangeOk = true;
                };

                scope.toEndDate = function () {
                    // To end date selection
                    reset(!angular.isUndefined(scope.options.endDateText) ? scope.options.endDateText : scope.cf.endDateText, false);
                };

                scope.accept = function () {
                    // OK button clicked
                    scope.selectedRangeTxt = scope.titleTxt;
                    scope.showSelector = false;
                    notifyParent(selectedBeginDate, scope.selectedDate);
                };

                scope.picker = function (event) {
                    // Show or hide selector
                    event.stopPropagation();
                    scope.showSelector = !scope.showSelector;
                    if (scope.showSelector) {
                        // Reset values
                        reset(!angular.isUndefined(scope.options.beginDateText) ? scope.options.beginDateText : scope.cf.beginDateText, true);

                        var y = 0, m = 0;
                        // Initial selector month
                        if (scope.options.initSelectorMonth === undefined) {
                            y = today.getFullYear();
                            m = today.getMonth() + 1;
                        }
                        else {
                            y = scope.options.initSelectorMonth.year;
                            m = scope.options.initSelectorMonth.month;
                        }

                        // Set current month
                        scope.visibleMonth = {monthTxt: getMonthLabels()[m], monthNbr: m, year: y};

                        // Create current month
                        createMonth(m, y);
                    }
                };

                scope.clearSelection = function (event) {
                    // Clear selected range
                    event.stopPropagation();
                    scope.selectedRangeTxt = '';
                    scope.selectedDate = {day: 0, month: 0, year: 0};
                    notifyParent(scope.selectedDate, scope.selectedDate);
                };

                scope.$watch('visibleMonth', function (newVal, oldVal) {
                    // Listens the month and the year changes
                    if (newVal !== oldVal) {
                        createMonth(newVal.monthNbr, newVal.year);
                    }
                }, true);

                scope.$watch('ngModel', function (newVal, oldVal) {
                    // Listens the ngModel changes
                    if (newVal !== oldVal && newVal === '') {
                        scope.selectedRangeTxt = newVal;
                    }
                });

                function notifyParent(begin, end) {
                    if (scope.options.dateRangeSelectCb) {
                        scope.options.dateRangeSelectCb(
                            {day: begin.day, month: begin.month, year: begin.year, formatted: formatDate(begin)},
                            {day: end.day, month: end.month, year: end.year, formatted: formatDate(end)},
                            scope.selectedRangeTxt);
                    }
                    scope.ngModel = scope.selectedRangeTxt;
                }

                function reset(titleTxt, beginDateStep) {
                    scope.selectedDate = {day: 0, month: 0, year: 0};
                    scope.titleTxt = titleTxt;
                    scope.beginDateStep = beginDateStep;
                    scope.rangeOk = false;
                }

                function handleSelect(val) {
                    scope.selectedDate = {day: val.day, month: val.month, year: val.year};
                    if (scope.beginDateStep) {
                        scope.rangeOk = true;
                        scope.titleTxt = formatDate(val);
                        selectedBeginDate = angular.copy(scope.selectedDate);
                    }
                    else {
                        var b = new Date(selectedBeginDate.year, selectedBeginDate.month - 1, selectedBeginDate.day);
                        var e = new Date(scope.selectedDate.year, scope.selectedDate.month - 1, scope.selectedDate.day);
                        scope.rangeOk = b <= e;
                        scope.titleTxt = formatDate(selectedBeginDate) + scope.cf.DATES_SEPARATOR + formatDate(val);
                    }
                }

                function formatDate(val) {
                    if (val.day === 0 && val.month === 0 && val.year === 0) {
                        return '';
                    }
                    var fmt = angular.copy(!angular.isUndefined(scope.options.dateFormat) ? scope.options.dateFormat : scope.cf.dateFormat);
                    return fmt.replace(scope.cf.YEAR_CONST, val.year)
                        .replace(scope.cf.MONTH_CONST, preZero(val.month))
                        .replace(scope.cf.DATE_CONST, preZero(val.day));
                }

                function preZero(val) {
                    // Prepend zero if smaller than 10
                    return val < 10 ? '0' + val : val;
                }

                function monthText(m) {
                    // Returns mont as a text
                    return getMonthLabels()[m];
                }

                function monthStartIdx(y, m) {
                    // Month start index
                    var d = new Date();
                    d.setDate(1);
                    d.setMonth(m - 1);
                    d.setYear(y);
                    var idx = d.getDay() + sundayIdx();
                    return idx >= 7 ? idx - 7 : idx;
                }

                function sundayIdx() {
                    // Index of Sunday day
                    return dayIdx > 0 ? 7 - dayIdx : 0;
                }

                function daysInMonth(m, y) {
                    // Return number of days of current month
                    return new Date(y, m, 0).getDate();
                }

                function daysInPrevMonth(m, y) {
                    // Return number of days of the previous month
                    if (m === 1) {
                        m = 12;
                        y--;
                    }
                    else {
                        m--;
                    }
                    return daysInMonth(m, y);
                }

                function isCurrDay(d, m, y, cmo) {
                    // Check is a given date the current date
                    return d === today.getDate() && m === today.getMonth() + 1 && y === today.getFullYear() && cmo === 2;
                }

                function createMonth(m, y) {
                    scope.dates.length = 0;
                    var monthStart = monthStartIdx(y, m);
                    var dInThisM = daysInMonth(m, y);
                    var dInPrevM = daysInPrevMonth(m, y);
                    var sunIdx = sundayIdx();

                    var dayNbr = 1;
                    var cmo = scope.cf.PREV_MONTH;
                    for (var i = 1; i < 7; i++) {
                        var week = [];
                        if (i === 1) {
                            // First week
                            var pm = dInPrevM - monthStart + 1;
                            // Previous month
                            for (var j = pm; j <= dInPrevM; j++) {
                                week.push({
                                    day: j, month: m, year: y, cmo: cmo, currDay: isCurrDay(j, m, y, cmo), sun: week.length === sunIdx
                                });
                            }
                            cmo = scope.cf.CURR_MONTH;
                            // Current month
                            var daysLeft = 7 - week.length;
                            for (var j = 0; j < daysLeft; j++) {
                                week.push({
                                    day: dayNbr, month: m, year: y, cmo: cmo, currDay: isCurrDay(dayNbr, m, y, cmo), sun: week.length === sunIdx
                                });
                                dayNbr++;
                            }
                        }
                        else {
                            // Rest of the weeks
                            for (var j = 1; j < 8; j++) {
                                if (dayNbr > dInThisM) {
                                    // Next month
                                    dayNbr = 1;
                                    cmo = scope.cf.NEXT_MONTH;
                                }
                                week.push({
                                    day: dayNbr, month: m, year: y, cmo: cmo, currDay: isCurrDay(dayNbr, m, y, cmo), sun: week.length === sunIdx
                                });
                                dayNbr++;
                            }
                        }
                        scope.dates.push(week);
                    }
                }

                function onOutClick(event) {
                    if (!element[0].contains(event.target) && event.which === 1) {
                        // Clicked outside of the element - close the selector
                        if (scope.showSelector) {
                            scope.showSelector = false;
                        }
                        scope.$apply();
                    }
                }

                function getMonthLabels() {
                    return !angular.isUndefined(scope.options.monthLabels) ? scope.options.monthLabels : scope.cf.monthLabels;
                }

                function getDayLabels() {
                    return !angular.isUndefined(scope.options.dayLabels) ? scope.options.dayLabels : scope.cf.dayLabels;
                }

                scope.$on('$destroy', function () {
                    $document.off("click", onOutClick);
                });

                function init() {
                    // Show grid value
                    scope.showGrid = !angular.isUndefined(scope.options.showGrid) ? scope.options.showGrid : scope.cf.showGrid;

                    // Selection element height/width
                    scope.height = !angular.isUndefined(attrs.height) ? attrs.height : scope.height;
                    scope.width = !angular.isUndefined(attrs.width) ? attrs.width : scope.width;

                    // Weekdays to calendar
                    var days = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa'];
                    dayIdx = days.indexOf(!angular.isUndefined(scope.options.firstDayOfWeek) ? scope.options.firstDayOfWeek : scope.cf.firstDayOfWeek);
                    if(dayIdx !== -1) {
                        var idx = dayIdx;
                        for(var i = 0; i < days.length; i++) {
                            scope.weekDays.push(getDayLabels()[days[idx]]);
                            idx = days[idx] === 'sa' ? 0 : idx + 1;
                        }
                    }

                    // Initial selected date range
                    if (scope.options.initSelectedDateRange !== undefined) {
                        scope.selectedRangeTxt = formatDate(scope.options.initSelectedDateRange.begin) +
                        scope.cf.DATES_SEPARATOR +
                        formatDate(scope.options.initSelectedDateRange.end);
                    }

                    // Register outside of element click event
                    $document.on("click", onOutClick);
                }

                $timeout(init);
            }
        };
    }])

/**
 * @ngdoc object
 * @name tooltipWindow
 * @description tooltipWindow directive implements the tooltip window.
 */
    .directive('tooltipWindow', ['$timeout', function ($timeout) {
        return {
            restrict: 'A',
            scope: false,
            link: function (scope, element, attrs) {
                function onMouseEnter() {
                    if (element[0].scrollWidth > element[0].offsetWidth) {
                        $timeout(function () {
                            scope.showTooltip = true;
                        }, scope.cf.TOOLTIP_SHOW_DELAY);
                    }
                }

                scope.$on('$destroy', function () {
                    element.off('mouseenter', onMouseEnter);
                });

                function init() {
                    element.on('mouseenter', onMouseEnter);
                }

                init();
            }
        };
    }]);



.dpdaterangepicker {
    min-width: 80px;
    border-radius: 2px;
    line-height: 1.1;
}

.dpdaterangepicker * {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
    font-family: Arial, Helvetica, sans-serif;
    padding: 0;
    margin: 0;
}

.dpdaterangepicker .dpselector {
    margin-top: 3px;
    margin-left: -1px;
    position: absolute;
    max-width: 250px;
    padding: 3px;
    border-radius: 2px;
    background-color: #DDD;
    z-index: 100;
}

.dpdaterangepicker .dpselectiongroup {
    position: relative;
    display: table;
    border: none;
    background-color: #FFF;
}

.dpdaterangepicker .dpselection {
    background-color: #FFF;
    display: table-cell;
    position: absolute;
    width: 100%;
    text-align: left;
    font-size: 13px;
    font-weight: bold;
    padding: 0 64px 0 4px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-align: center;
}

.dpdaterangepicker .dpselbtngroup {
    position: relative;
    vertical-align: middle;
    white-space: nowrap;
    width: 1%;
    display: table-cell;
    text-align: right;
    font-size: 0;
}

.dpdaterangepicker .dpbtnpicker,
.dpdaterangepicker .dpbtnclear {
    height: 100%;
    width: 30px;
    border: none;
    border-left: 1px solid #999;
    padding: 0px;
    cursor: pointer;
    outline: 0;
    -moz-user-select: none;
    font: inherit;
}

.dpdaterangepicker .dpbtnpicker,
.dpdaterangepicker .dpbtnclear,
.dpdaterangepicker .dpheadertodaybtn,
.dpdaterangepicker .dpfooterbtn {
    background: #FAFAFA;
    background-image: -webkit-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: -moz-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: -o-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: -ms-linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
    background-image: linear-gradient(#F0F0F0 30%, #AEC2E1 100%);
}

.dpdaterangepicker .dptitlearea {
    height: 30px;
    margin-bottom: 4px;
    text-align: center;
}

.dpdaterangepicker .dptitleareatxt {
    height: 26px;
    line-height: 26px;
    font-size: 14px;
    font-weight: bold;
}

.dpdaterangepicker .dpfooterarea {
    margin-top: 4px;
    padding: 3px;
    text-align: center;
    background-color: #FAFAFA;
}

.dpdaterangepicker .dpheader {
    width: 100%;
    height: 36px;
    margin-bottom: -1px;
    background-color: #FAFAFA;
}

.dpdaterangepicker .dpheader td {
    vertical-align: middle;
    border: none;
}

.dpdaterangepicker .dpheader td:nth-child(1) {
    font-size: 16px;
    text-align: left;
    padding-left: 4px;
}

.dpdaterangepicker .dpheader td:nth-child(2) {
    text-align: center;

}

.dpdaterangepicker .dpheader td:nth-child(3) {
    font-size: 16px;
    text-align: right;
    padding-right: 4px;
}

.dpdaterangepicker .dptable {
    table-layout: fixed;
    width: 100%;
    background-color: #FFF;
    font-size: 14px;
}

.dpdaterangepicker .dptable,
.dpdaterangepicker .dptable th,
.dpdaterangepicker .dptable td {
    border-collapse: collapse;
    color: #003366;
    line-height: 1.1;
}

.dpdaterangepicker .dptable th,
.dpdaterangepicker .dptable td {
    padding: 5px;
    text-align: center;
}

.dpdaterangepicker .dptable th {
    background-color: #DDD;
    font-size: 11px;
    font-weight: bold;
}

.dpdaterangepicker .dptable td {
    cursor: pointer;
}

.dpdaterangepicker .dpprevmonth {
    color: #CCC;
    font-weight: normal;
}

.dpdaterangepicker .dpcurrmonth {
    color: #000;
    font-weight: bold;
}

.dpdaterangepicker .dpnextmonth {
    color: #CCC;
    font-weight: normal;
}

.dpdaterangepicker .dpsunday {
    color: #C30000;
}

.dpdaterangepicker .dpcurrmonth {
    background-color: #F7F7F7;
}

.dpdaterangepicker .dpcurrday {
    background-color: #64E986;
    text-decoration: underline;
}

.dpdaterangepicker .dpselectedday {
    background-color: #3BB9FF;
}

.dpdaterangepicker .dpselectmenu {
    height: 24px;
    width: 60px
}

.dpdaterangepicker .dpheaderbtn {
    background-color: #FAFAFA;
    cursor: pointer;
    display: table-cell;
}

.dpdaterangepicker,
.dpdaterangepicker .dpselector,
.dpdaterangepicker .dptitlearea,
.dpdaterangepicker .dpfooterarea,
.dpdaterangepicker .dpheader,
.dpdaterangepicker .dptable,
.dpdaterangepicker .dptable tbody,
.dpdaterangepicker .dptable th,
.dpdaterangepicker .dptable td,
.dpdaterangepicker .dpheadertodaybtn,
.dpdaterangepicker .dpfooterbtn,
.dpdaterangepicker .vstooltip {
    border: 1px solid #AAA;
}

.dpdaterangepicker .dpnogrid {
    border: none !important;
}

.dpdaterangepicker .dpbtnpicker,
.dpdaterangepicker .dpbtnclear,
.dpdaterangepicker .dpheaderbtn,
.dpdaterangepicker .dpheadermonthtxt,
.dpdaterangepicker .dpheaderyeartxt,
.dpdaterangepicker .dpheadertodaybtn,
.dpdaterangepicker .dpselection,
.dpdaterangepicker .dpfooterbtn {
    color: #000;
}

.dpdaterangepicker .dpheadertodaybtn,
.dpdaterangepicker .dpfooterbtn {
    padding: 4px 6px;
    border-radius: 2px;
    cursor: pointer;
    font-size: 12px;
}

.dpdaterangepicker .dpfooterbtn {
    width: 80px;
    font-weight: normal;
}

.dpdaterangepicker button::-moz-focus-inner {
    border: 0;
}

.dpdaterangepicker .dpheadermonthtxt,
.dpdaterangepicker .dpheaderyeartxt {
    width: 40px;
    text-align: center;
    display: table-cell;
    vertical-align: middle;
    font-weight: bold;
}

.dpdaterangepicker .dpbtnclear:focus,
.dpdaterangepicker .dpbtnpicker:focus,
.dpdaterangepicker .dpbtnclear:hover,
.dpdaterangepicker .dpbtnpicker:hover {
    background: #ADD8E6;
}

.dpdaterangepicker .icon-calendar,
.dpdaterangepicker .icon-cross {
    font-size: 16px;
}

.dpdaterangepicker .icon-left,
.dpdaterangepicker .icon-right {
    font-size: 14px;
}

.dpdaterangepicker .icon-left:hover,
.dpdaterangepicker .icon-right:hover {
    color: #63B2CC;
}

.dpdaterangepicker .dptitlerangeok {
    background-color: #AEE99F;
    color: #008700;
}

.dpdaterangepicker .dptitlerangenotok {
    background-color: #EBC0D2;
    color: #C30000;
}

.dpdaterangepicker .vstooltip {
    background-color: #FFFFCC;
    color: #000;
    padding: 6px;
    z-index: 1003;
    white-space: nowrap;
    font-weight: normal;
    font-size: 13px;
    border-radius: 4px;
    margin: 0 0 0 20px;
}

.dpdaterangepicker .vstooltip:before {
    border-right: 10px solid #888;
    left: -10px;
}

.dpdaterangepicker .vstooltip:after {
    border-right: 8px solid #FFFFCC;
    left: -8px;
}

.dpdaterangepicker .vstooltip:before,
.dpdaterangepicker .vstooltip:after {
    content: ' ';
    border-top: 8px solid transparent;
    border-bottom: 8px solid transparent;
    position: absolute;
    z-index: 1003;
    top: 5px;
}

.dpdaterangepicker .vstooltiptext {

}

.dpdaterangepicker .vstooltip {
    position: absolute;
    -moz-box-shadow: 0 0 14px #000;
    -webkit-box-shadow: 0 0 14px #000;
    box-shadow: 0 0 14px #000;
}

.dpdaterangepicker .dpbtndisable {
    cursor: default;
    opacity: 0.5;
}

.dpdaterangepicker table {
    display: table;
}

.dpdaterangepicker table td {
    padding: 0;
}

@font-face {
    font-family: 'dpdaterangepicker';
    src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SAssAAAC8AAAAYGNtYXDMUczTAAABHAAAAGxnYXNwAAAAEAAAAYgAAAAIZ2x5ZmFQ1q4AAAGQAAABbGhlYWQGZuTFAAAC/AAAADZoaGVhB4IDyQAAAzQAAAAkaG10eBYAAnAAAANYAAAAIGxvY2EBdAE0AAADeAAAABJtYXhwABUAPgAAA4wAAAAgbmFtZQ5R9RkAAAOsAAABnnBvc3QAAwAAAAAFTAAAACAAAwOaAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADmBwPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAUAAAABAAEAADAAAAAQAg5gDmAuYF5gf//f//AAAAAAAg5gDmAuYF5gf//f//AAH/4xoEGgMaARoAAAMAAQAAAAAAAAAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAAMAEAAAAPAA4AABAAJAA4AEwAYAB0AIgAnACwAMQA2ADsAABMRMxEjFyE1IRUDITUhFQERMxEjJRUzNSMTFTM1IzMVMzUjMxUzNSMBFTM1IzMVMzUjMxUzNSMTFTM1I0Bzc0ADAP0AQAOA/IADDXNz/ZOAgCCAgMCAgMCAgP6AgIDAgIDAgIAggIADAP1AAsBzc3P9c3NzAwD9QALAgMDA/sCAgICAgID/AICAgICAgAJAwMAAAAAAAgBwADADkANQAAQACQAANwEnARcDATcBB+kCp3n9WXl5Aqd5/Vl5MAKnef1ZeQKn/Vl5Aqd5AAABAOAAAAMgA4AAAwAAAQMBJQMgA/3DASADgPyAAcPfAAEA4AAAAyADgAADAAA3EwEF4AMCPf7gAAOA/j3fAAAAAQAAAAEAAF0/BsNfDzz1AAsEAAAAAADRxFAkAAAAANHEUCQAAAAAA8ADgAAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADwAABAAAAAAAAAAAAAAAAAAAACAQAAAAAAAAAAAAAAAIAAAAEAABABAAAcAQAAOAEAADgAAAAAAAKABQAHgB6AJYApgC2AAAAAQAAAAgAPAAMAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAA4ArgABAAAAAAABAAkAAAABAAAAAAACAAcAcgABAAAAAAADAAkAPAABAAAAAAAEAAkAhwABAAAAAAAFAAsAGwABAAAAAAAGAAkAVwABAAAAAAAKABoAogADAAEECQABABIACQADAAEECQACAA4AeQADAAEECQADABIARQADAAEECQAEABIAkAADAAEECQAFABYAJgADAAEECQAGABIAYAADAAEECQAKADQAvHZzZHBpY2tlcgB2AHMAZABwAGkAYwBrAGUAclZlcnNpb24gMS4wAFYAZQByAHMAaQBvAG4AIAAxAC4AMHZzZHBpY2tlcgB2AHMAZABwAGkAYwBrAGUAcnZzZHBpY2tlcgB2AHMAZABwAGkAYwBrAGUAclJlZ3VsYXIAUgBlAGcAdQBsAGEAcnZzZHBpY2tlcgB2AHMAZABwAGkAYwBrAGUAckZvbnQgZ2VuZXJhdGVkIGJ5IEljb01vb24uAEYAbwBuAHQAIABnAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAEkAYwBvAE0AbwBvAG4ALgAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=) format('truetype');
    font-weight: normal;
    font-style: normal;
}

.dpdaterangepicker .icon {
    font-family: 'dpdaterangepicker';
    speak: none;
    font-style: normal;
    font-weight: normal;
    font-variant: normal;
    text-transform: none;
    line-height: 1;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.dpdaterangepicker .icon-calendar:before {
    content: "\e600";
}

.dpdaterangepicker .icon-cross:before {
    content: "\e602";
}

.dpdaterangepicker .icon-left:before {
    content: "\e605";
}

.dpdaterangepicker .icon-right:before {
    content: "\e607";
}