<!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,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) 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";
}