var app = angular.module('plunker', []);
app.controller('MainCtrl', ['$scope', '$rootScope', '$sce',
function($scope, $rootScope, $sce) {
$scope.name = 'World';
$scope.Appcontrols = [{
defvalue: 222,
"label": "Username:",
"element": 'inputtag',
"id": "uname"
}, {
defvalue: 222,
"label": "Password:",
"element": 'inputtag',
"id": "upwd"
}, {
defvalue: 222,
"label": "Email:",
"element": 'inputtag',
"id": "email"
}, {
defvalue: 222,
"label": "email",
"element": 'savetag',
"id": "submit"
}, {
defvalue: 222,
"label": "email",
"element": 'cleartag',
"id": "submit"
}];
$scope.Appcontrols.forEach(function(entry) {
html = "";
html = "<" + entry.element + " ng-model='$parent.form." + entry.id + "' id='" + entry.id + "' label='"+entry.label+"' >" + "</" + entry.element + ">";
entry.element = $sce.trustAsHtml(html);
});
$scope.save = function() {
alert('Saved ' + $scope.form.uname);
}
$scope.clear = function() {
$scope.form = {};
}
$scope.counter = 0;
var dataSource = $scope.Appcontrols;
//fake database
var dataSource1 = [
{ id : 0, engine: "Trident", browser: "Internet Explorer 4.0", platform: "Win 95+", version: 4, grade: "X" },
{ id : 1, engine: "Trident", browser: "Internet Explorer 5.0", platform: "Win 95+", version: 5, grade: "C" },
{ id : 2, engine: "Trident", browser: "Internet Explorer 5.5", platform: "Win 95+", version: 5.5, grade: "A" },
{ id : 3, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
{ id : 4, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
{ id : 5, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
{ id : 6, engine: "Gecko", browser: "Firefox 2", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
{ id : 7, engine: "Gecko", browser: "Firefox 3", platform: "Win 2k+ / OSX.3+", version: 1.9, grade: "A" },
{ id : 8, engine: "Webkit", browser: "Safari 1.2", platform: "OSX.3", version: 125.5, grade: "A" },
{ id : 9, engine: "Webkit", browser: "Safari 1.3", platform: "OSX.3", version: 312.8, grade: "A" },
{ id : 10, engine: "Webkit", browser: "Safari 2.0", platform: "OSX.4+", version: 419.3, grade: "A" },
{ id : 11, engine: "Webkit", browser: "Safari 3.0", platform: "OSX.4+", version: 522.1, grade: "A" },
{ id : 12, engine: "Trident", browser: "Internet Explorer 4.0", platform: "Win 95+", version: 4, grade: "X" },
{ id : 13, engine: "Trident", browser: "Internet Explorer 5.0", platform: "Win 95+", version: 5, grade: "C" },
{ id : 14, engine: "Trident", browser: "Internet Explorer 5.5", platform: "Win 95+", version: 5.5, grade: "A" },
{ id : 15, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
{ id : 16, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
{ id : 17, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
{ id : 18, engine: "Gecko", browser: "Firefox 2", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" },
{ id : 19, engine: "Gecko", browser: "Firefox 3", platform: "Win 2k+ / OSX.3+", version: 1.9, grade: "A" },
{ id : 20, engine: "Webkit", browser: "Safari 1.2", platform: "OSX.3", version: 125.5, grade: "A" },
{ id : 21, engine: "Webkit", browser: "Safari 1.3", platform: "OSX.3", version: 312.8, grade: "A" },
{ id : 22, engine: "Webkit", browser: "Safari 2.0", platform: "OSX.4+", version: 419.3, grade: "A" },
{ id : 23, engine: "Webkit", browser: "Safari 3.0", platform: "OSX.4+", version: 522.1, grade: "A" },
{ id : 24, engine: "Trident", browser: "Internet Explorer 4.0", platform: "Win 95+", version: 4, grade: "X" },
{ id : 25, engine: "Trident", browser: "Internet Explorer 5.0", platform: "Win 95+", version: 5, grade: "C" },
{ id : 26, engine: "Trident", browser: "Internet Explorer 5.5", platform: "Win 95+", version: 5.5, grade: "A" },
{ id : 27, engine: "Trident", browser: "Internet Explorer 6.0", platform: "Win 98+", version: 6, grade: "A" },
{ id : 28, engine: "Trident", browser: "Internet Explorer 7.0", platform: "Win XP SP2+", version: 7, grade: "A" },
{ id : 29, engine: "Gecko", browser: "Firefox 1.5", platform: "Win 98+ / OSX.2+", version: 1.8, grade: "A" }
];
//<editor-fold desc='CONFIGURE DATA TABLE SECTION'>
//define custom headers for columns
var customTitles = [
{propertyName : 'defvalue', title : '1. defvalue'},
{propertyName : 'label', title : '2. label'},
{propertyName : 'element', title : '3. element'},
{propertyName : 'id', title : '4. id'}
];
$scope.customTitles = customTitles;
//custom header controls of the data table
var selector = {
type: DataTableExtensions.headerControls.select,
properties: {
label: "Version:",
options: ["0-10", "10-100", "100-1000"],
filterName: "version",
trigger: {mode: DataTableExtensions.triggerModes.explicit, starter: "buttonFilter"}
}
};
var textBox = {
type: DataTableExtensions.headerControls.textBox,
properties: {
label: "Search:",
filterName: "search",
trigger: {mode: DataTableExtensions.triggerModes.explicit, starter: "buttonFilter"}
}
};
var button1 = {
type: DataTableExtensions.headerControls.button,
properties: {
name: "buttonFilter",
text: "Search"
//callback: function(){console.log("fucking awesome")} //option to bind controller function to button
}
};
var button2 = {
type: DataTableExtensions.headerControls.button,
properties: {
name: "buttonDoNotClick",
text: "DO NOT CLICK",
callback: function(){alert("I said do not click it!")} //option to bind controller function to button
}
};
//array of controls inside the array makes the controls into one logical group
$scope.headerControls = [[selector, textBox, button1], button2];
//paging - page sizes
$scope.pageSizes = [10,50,100];
//sorting allowed only by this properties
$scope.sortingProperties = ["engine", "grade"];
//hide the following properties
$scope.hidingProperties = ["id"];
//data source of the data table's first page
/******************************IMPLICIT PAGINATION*************************************/
/* Remove slice and add the whole collection as source if you use implicit pagination */
$scope.dataSource = dataSource.slice(0, 10);
/**************************************************************************************/
//need to store the last used settings to reuse it after delete/edit
var lastUsedData;
//callback function of the data table
$scope.refresh = function (data) {
lastUsedData = data;
var tempDataSource = dataSource;
//filter options
if(data.filterOptions){
for(var i = 0; i < data.filterOptions.length; i++){
//filter names are from the custom header controls defined earlier
if(data.filterOptions[i].filterName == "version"){
tempDataSource = sortDataByVersion(tempDataSource, data.filterOptions[i].filterValue);
}
if(data.filterOptions[i].filterName == "search"){
tempDataSource = sortDataBySearch(tempDataSource, data.filterOptions[i].filterValue);
}
}
}
//now it use only the paging changes
/***************IMPLICIT PAGINATION*****************/
/* Comment this row out if you use implicit paging */
$scope.dataSource = tempDataSource.slice(data.pageIndex * data.pageSize, (data.pageIndex + 1) * data.pageSize);
/***************************************************/
};
var sortDataByVersion = function(source, versionRangeString){
if(versionRangeString){
var versions = versionRangeString.split("-");
var result = [];
if(versions){
var start = parseInt(versions[0]);
var end = parseInt(versions[1]);
for(var i = 0; i < source.length; i++){
if(source[i].version >= start && source[i].version <= end){
result.push(source[i]);
}
}
}
return result;
}
return source;
};
var sortDataBySearch = function(source, searchCondition){
if(searchCondition){
var result = [];
for(var i = 0; i < source.length; i++){
if(source[i].platform.indexOf(searchCondition) != -1){
result.push(source[i]);
}
}
return result;
}
return source;
};
//fake function on select
$scope.select = function(item){
console.log("SELECTED");
console.log(item);
};
//fake function on edit
$scope.editRow = function(item){
console.log("EDIT");
console.log(item);
};
//fake function on select
$scope.deleteRow = function(item){
dataSource.deleteById(item);
var tmpData = {pageIndex : 0, pageSize : 10};
$scope.refresh(lastUsedData ? lastUsedData : tmpData);
};
$scope.gradeConverter = function (value) {
if (value === 'A') {
return '5';
} else if(value === 'C'){
return '3';
}else{
return '1';
}
};
$scope.converters = [
{ property: 'grade', converterCallback: 'gradeConverter(value)'}
];
//</editor-fold>
//helper function to delete item by id
Array.prototype.deleteById = function (obj) {
for (var i = 0; i < this.length; i++) {
if (this[i].id === obj.id) {
this.splice(i, 1);
}
}
}
}]);//controller
app.directive('inputtag', function($compile) {
return {
restrict: 'E',
scope: {
ngModel: '=',
label: '@'
},
template: '<div><input type="text" ng-model="ngModel" ></div>',
link: function(scope, element, attrs) {
var html = '<div class="span12 field-box"><label>'+attrs.label+'</label><input class="inputbox_custom" type="text" ng-model="ngModel" id="'+attrs.id+'"/></div>';
var e2 = $compile(html)(scope);
element.replaceWith(e2);
}
};
});
app.directive('savetag', function() {
return {
restrict: 'E',
scope: {
ngModel: '='
},
template: '<div><button ng-click="$parent.save()" href="#">Save</button></div>',
link: function(scope, elem, attrs) {}
};
});
app.directive('cleartag', function() {
return {
restrict: 'E',
scope: {
ngModel: '='
},
template: '<div><button ng-click="$parent.clear()" href="#">Clear</button></div>',
link: function(scope, elem, attrs) {}
};
});
/*This compiles dynamically created html (inputbox directive)*/
app.directive('compile', ['$compile',
function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}
]);
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<link rel="stylesheet" href="style.css" />
<link type="text/css" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/themes/redmond/jquery-ui.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.8.2/css/jquery.dataTables.css">
<script data-require="angular.js@1.2.x" src="http://code.angularjs.org/1.2.5/angular.js" data-semver="1.2.5"></script>
<script data-require="angular-sanitize@*" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular-sanitize.min.js"></script>
<!-- DataTables CSS -->
<link rel="stylesheet" type="text/css" href="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/css/jquery.dataTables.css">
<!-- jQuery -->
<script type="text/javascript" charset="utf8" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.2.min.js"></script>
<!-- DataTables -->
<script type="text/javascript" charset="utf8" src="http://ajax.aspnetcdn.com/ajax/jquery.dataTables/1.9.4/jquery.dataTables.min.js"></script>
<script src="app.js"></script>
<script src="dataTableExtensions.js"></script>
<script src="dataTable.js"></script>
</head>
<body ng-controller="MainCtrl">
<b>Custom Dynamic Form</b>
<div ng-repeat="ctrls in Appcontrols">
<div ng-bind-html="ctrls.element" compile></div>
</div>
<div my-data-table
my-source="dataSource"
refresh="refresh(data)"
header="headerControls"
page-sizes="pageSizes"
default-page-size="10"
pagination-type="explicit"
header-titles="customTitles"
sorting-properties="sortingProperties"
hiding-properties="hidingProperties"
delete-row="deleteRow(item)"
edit-row="editRow(item)"
converters="converters"
select-row="select(item)">
</div>
</body>
</html>
table.dataTable thead th {
text-transform: uppercase;
}
table.dataTable td {
vertical-align: middle;
}
.dataTables_paginate {
margin-left: auto;
margin-right: auto;
}
.paginate_disabled_previous:active,
.paginate_enabled_previous:active,
.paginate_disabled_next:active,
.paginate_enabled_next:active {
outline: none;
}
.paginate_disabled_previous,
.paginate_disabled_next {
display: none !important;
}
.paginate_disabled_previous,
.paginate_enabled_previous {
margin-left: 23px;
}
.paginate_disabled_next,
.paginate_enabled_next {
padding-right: 23px;
margin-left: 10px;
}
/*own css classes*/
#customHeader {
position: relative;
top: 28px;
margin-left: -5px;
margin-bottom: 30px;
z-index: 500;
}
.header-selector{
display: inline;
margin: 10px;
}
select.header-selector {
width:auto;
}
.header-button{
display: inline;
margin: 10px;
}
.header-textbox{
display: inline;
margin: 10px;
}
.header-label{
display: inline;
margin: 10px -5px 10px 10px;
}
.control-group{
margin-left: 50px;
margin-right: 50px;
display: inline;
}
.header-controls{
display: inline;
}
.dataTables_empty{
margin-left: auto;
margin-right: auto;
text-align: center !important;
}
angular.module('plunker')
.directive('myDataTable', function ($parse) {
'use strict';
return {
template: '<table class="display table table-striped table-hover" id="my-data-table"><div id="customHeader"></div></table>',
link: function postLink(scope, element, attrs) {
//<editor-fold desc="region: constants">
var tableRowId = '#my-data-table tbody tr';
var tableId = '#my-data-table';
var twoButtonsPagination = 'my_two_buttons';
var customButtonsPagination = 'my_custom_buttons';
//</editor-fold>
//<editor-fold desc="region: variables">
//store the original data source to get element later by row index
var originalSource;
var actualPageIndex = 0;
var orderBy;
var filterOptions = [];
var headerControls = [];
var headers = [];
var headerTitles = [];
var elementsMaxReached = false;
var lastIndex = -1;
//first element is true because the datatable initialization calls the decorate function on the first page
var pageDecorationStatus = [true];
//new option to use custom header titles
var haveCustomHeaderTitles = false;
var table;
var isInitTime = true;
var sortables = [];
var toHide = [];
//</editor-fold>
//<editor-fold desc="region: handler parsers">
//ANGULAR - parse callable functions from attributes
var refreshHandler = $parse(attrs.refresh);
var onRowSelectedHandler = $parse(attrs.selectRow);
var onEditRow = $parse(attrs.editRow);
var onDeleteRow = $parse(attrs.deleteRow);
//</editor-fold>
//<editor-fold desc="region: parsed attrs">
//new option to define one default page size
var pageSize = scope[attrs.defaultPageSize] ? parseInt(scope[attrs.defaultPageSize]) : 10;
//optional page sizes for page size selector
var pageSizesAttr = scope[attrs.pageSizes];
var headerControlsAttr = scope[attrs.header];
var convertersAttr = scope[attrs.converters];
//support case-insensitivity
var sortingProperties = scope[attrs.sortingProperties];
for (var i = 0; i < sortingProperties.length; i++) {
sortables.push(sortingProperties[i].toLowerCase())
}
var hidingProperties = scope[attrs.hidingProperties];
for (var i = 0; i < hidingProperties.length; i++) {
toHide.push(hidingProperties[i].toLowerCase())
}
var headerTitlesAttr = scope[attrs.headerTitles];
var paginationTypeAttr = attrs.paginationType;
//</editor-fold>
//<editor-fold desc="region: callbacks called from outside">
//dataTableExtensions
//get previous page - callback of dataTables's pagination
function previousCallback() {
if (actualPageIndex > 0) {
actualPageIndex--;
refreshCallback();
}
}
//dataTableExtensions
//get next page - callback of dataTables's pagination
function nextCallback() {
if (!elementsMaxReached) {
actualPageIndex++;
refreshCallback();
}
}
//inner use
//set the filter conditions - callback of the custom header controls
function setFilterOptions(options) {
for (var i = 0; i < options.length; i++) {
var filter = filterOptions.getElementByFilterName(options[i]);
if (filter) {
filter.filterValue = options[i].filterValue;
} else {
filterOptions.push(options[i]);
}
}
//after filter we want to see the results started from the first page
actualPageIndex = 0;
refreshCallback();
}
//inner use
//set the actual page size - callback of the custom header controls
function setPageSize(size) {
pageSize = size;
//after changed page size we want to see the results started from the first page
actualPageIndex = 0;
refreshCallback();
}
//inner use
//check if it has a next page or not - callback of dataTables's pagination
function hasNextPage() {
return !elementsMaxReached;
}
//inner use
//check if it has a previous page or not - callback of dataTables's pagination
function hasPreviousPage() {
return actualPageIndex !== 0;
}
//</editor-fold>
//<editor-fold desc="region: table decorators">
//calls the refresh method of the controller, sends the new filter/order/paging options to the controller
function refreshCallback() {
//collect the filter options
var conditions = {
pageIndex: actualPageIndex,
pageSize: pageSize,
orderBy: orderBy,
filterOptions: filterOptions
};
scope.$apply(function () {
refreshHandler({data: conditions}, scope, { $event: event });
});
}
//calls decoration on new page if the data tables uses the my-two-buttons pagination mode
//it stores if a page was already decorated or not
function decorateCallback(startIndex) {
var pageIndex = startIndex / pageSize;
if (!pageDecorationStatus[pageIndex]) {
decorateDataTableOnChange();
pageDecorationStatus[pageIndex] = true;
}
}
//callback on row selection
function setRowSelectionCallback() {
$(tableRowId).click(function (event) {
//TWITTER BOOTSTRAP - color the row
$(tableRowId).removeClass('info');
$(this).addClass('info');
var index = $(this).context._DT_RowIndex;
//set lastIndex to stay consistent with row change on arrow key pressed
lastIndex = $(this).context.rowIndex - 1;
var itemOnIndex = originalSource[index];
scope.$apply(function () {
onRowSelectedHandler({item: itemOnIndex}, scope, {$event: event});
});
});
}
//function to add extra buttons to rows
function addExtraButtonToRow(button) {
//create empty header (to extend the style)
if ($('#' + button.name).length === 0) {
var nCloneTh = $('<th>');
nCloneTh.attr("id", button.name);
nCloneTh.appendTo($(tableRowId));
}
var nCloneTd = $('<td>');
var btn = $('<button class="header-button"/>');
//add class by style set on button param
switch (button.style) {
case DataTableExtensions.buttonStyle.info:
btn.addClass("btn btn-info");
case DataTableExtensions.buttonStyle.warning:
btn.addClass("btn btn-warning");
case DataTableExtensions.buttonStyle.success:
btn.addClass("btn btn-success");
case DataTableExtensions.buttonStyle.error:
btn.addClass("btn btn-danger");
default:
btn.addClass("btn");
}
btn.text(button.text);
btn.appendTo(nCloneTd);
$(tableRowId).each(function (i) {
var tdButton = nCloneTd.clone(true);
tdButton.click(function (event) {
event.stopPropagation();
button.callback(event, originalSource[i]);
});
tdButton.appendTo(this);
});
}
//function to call on data table change
function decorateDataTableOnChange() {
setRowSelectionCallback();
//if the function for editing a row parsed from an attribute exists add edit button
if (onEditRow != angular.noop) {
var btnEdit = {
name: "edit",
style: DataTableExtensions.buttonStyle.warning,
text: "Edit",
callback: function (event, obj) {
event.preventDefault();
scope.$apply(function () {
onEditRow({item: obj}, scope, {$event: event});
});
}};
addExtraButtonToRow(btnEdit);
}
//if the function for deleting a row parsed from an attribute exists add delete button
if (onDeleteRow != angular.noop) {
var btnDelete = {
name: "delete",
style: DataTableExtensions.buttonStyle.error,
text: "Delete",
callback: function (event, obj) {
event.preventDefault();
scope.$apply(function () {
onDeleteRow({item: obj}, scope, {$event: event});
});
}};
addExtraButtonToRow(btnDelete);
}
}
//navigation allowed by arrow keys
function setNavigationOnArrows() {
$(document).keydown(function (event) {
var currentRow;
var rowsOnPageCount = $(tableRowId).length;
//!!!!!nice from dataTables
//$(currentRow).next().context._DT_RowIndex -- gives the original index of the row back (after ordering it remains the same)
//$(currentRow).context.rowIndex -- gives the actual index back
//change row selection and/or page on key down
switch (event.keyCode) {
//arrow down
case 40:
if (lastIndex < rowsOnPageCount - 1) {
lastIndex++;
currentRow = $(tableRowId).get(lastIndex);
$(currentRow).prev().removeClass("info");
$(currentRow).addClass("info");
} else {
if (hasNextPage()) {
var infoId = tableRowId + ".info";
$(infoId).removeClass("info");
lastIndex = -1;
nextCallback();
} else {
lastIndex = rowsOnPageCount - 1;
}
}
break;
//arrow up
case 38:
if (lastIndex > 0) {
lastIndex--;
currentRow = $(tableRowId).get(lastIndex);
$(currentRow).next().removeClass("info");
$(currentRow).addClass("info");
} else {
if (hasPreviousPage()) {
lastIndex = pageSize;
previousCallback();
} else {
lastIndex = 0;
}
}
break;
}
});
}
//</editor-fold>
//<editor-fold desc="region: draw and redraw table">
//create header of the table - set sorting and hide if needed
function prepareHeader(header) {
//get the property names for header titles
for (var i = 0; i < header.length; i++) {
var head = header[i];
var item = (haveCustomHeaderTitles === true) ? head.propertyName : head;
var title = (haveCustomHeaderTitles === true) ? head.title : head;
if (!toHide || toHide.indexOf(item.toLowerCase()) == -1) {
var sortable = attrs.allSortable === "";
if (sortables && sortables.indexOf(item.toLowerCase()) != -1) {
sortable = true;
}
headers.push(item);
headerTitles.push({ "sTitle": title, "bSortable": sortable });
}
}
}
//create rows of the table - hide if needed
function prepareRows(source) {
var rows = [];
for (var i = 0; i < source.length; i++) {
var row = [];
//use header to iterate through the elements to keep the property order
//'hide items' happens in prepareHeaders
for (var j = 0; j < headers.length; j++) {
var item = headers[j];//.sTitle;
var valueToAdd = source[i][item];
//use converters
if (convertersAttr) {
var converterString = convertersAttr.getConverterStringTo(item);
if (converterString) {
var converter = $parse(converterString);
valueToAdd = converter({value: valueToAdd}, scope, { $event: event });
}
row.push(valueToAdd);
}
}
rows.push(row);
}
return rows;
}
//prepare the header controls to add them to the header
function prepareHeaderControls() {
//if the pageSizes attr is defined add it to the header controls
if (pageSizesAttr) {
var pageSizeChanger = {
type: DataTableExtensions.headerControls.select,
properties: {
options: pageSizesAttr,
label: "Show",
trigger: {mode: DataTableExtensions.triggerModes.onChange, callback: setPageSize} //calls the data table filters changed event
}
};
headerControls.push(pageSizeChanger);
}
//collect all of the header controls
if (headerControlsAttr) {
for (var i = 0; i < headerControlsAttr.length; i++) {
var control = headerControlsAttr[i];
//check if trigger mode is 'onchange' but there is no callback set by user
if (control.trigger && control.trigger.mode === DataTableExtensions.triggerModes.onChange
&& !control.trigger.callback) {
control.trigger.callback = refreshCallback;
}
headerControls.push(control);
}
}
createCustomHeader(headerControls, setFilterOptions);
}
//call the create the header controls
prepareHeaderControls();
//call the create and set own paginations
createPagination(previousCallback, nextCallback, hasNextPage, hasPreviousPage, decorateCallback);
//function to draw the table
function drawTable(source) {
originalSource = source;
//get the property names for header titles or the user defined header titles
if (headerTitlesAttr) {
haveCustomHeaderTitles = true;
prepareHeader(headerTitlesAttr);
} else {
prepareHeader(source[0]);
}
//get the property values for rows
var rows = prepareRows(source);
//set pagination
var pagination = twoButtonsPagination;
if (paginationTypeAttr) {
switch (paginationTypeAttr) {
case 'explicit':
pagination = customButtonsPagination;
break;
case 'implicit':
pagination = twoButtonsPagination;
break;
}
}
table = $(tableId).dataTable({
"aoColumns": headerTitles,
"aaData": rows,
"iDisplayLength": pageSize,
"fnInfoCallback": onInfoChanged,
"sPaginationType": pagination, //my implementation in dataTablesExtensions
"sDom": 't<"bottom"pi>',
"bFilter": false, //hides the search field
"bLengthChange": false, //hides the page size change option
"bSort": true //allows ordering
});
decorateDataTableOnChange();
setNavigationOnArrows();
}
//function called on the source change
function refreshData(source) {
originalSource = source;
//check if it is the end of the elements or probably it is more
elementsMaxReached = source.length < pageSize;
table.fnClearTable();
var rows = prepareRows(source);
table.fnAddData(rows);
decorateDataTableOnChange();
}
//after adding the custom header it is not triggered by the default controls
//checked why? ----------> 'i' was missing from sDom...
function onInfoChanged(oSettings) { //other available params: iStart, iEnd, iMax, iTotal, sPre
//reading out the ordering info from oSettings
if (oSettings) {
var headerIndex = oSettings.aaSorting[0][0];
var direction = oSettings.aaSorting[0][1];
var header = headers[headerIndex];
var newOrder = { property: header.sTitle, direction: direction};
//need to check because at the first creation time it will be called to and at this time we are already in digest phase
if (!scope.$$phase) {
if (orderBy !== newOrder) {
orderBy = newOrder;
actualPageIndex = 0;
refreshCallback();
}
}
}
}
//set watch on the scope element
function setWatchOnTheSource() {
var mySourceId = attrs.mySource;
var mySource = scope[mySourceId];
if (mySource) {
scope.$watch(mySourceId, function (source) {
//the first time set of the collection is a watch triggering action too
if (isInitTime && source && source.length > 0) {
//initialize the table
drawTable(mySource);
isInitTime = false;
return;
} else if (!isInitTime) {
if (source && source.length > 0) {
//page size needs to be set explicitly on the table options too
var oSettings = table.fnSettings();
oSettings._iDisplayLength = pageSize;
//redraw after resize
table.fnDraw();
refreshData(source);
} else {
//if empty source comes back
elementsMaxReached = true;
table.fnClearTable();
}
}
});
}
}
//call the set watch
setWatchOnTheSource();
//</editor-fold>
}
}
});
//<editor-fold desc="array extensions">
Array.prototype.getElementByFilterName = getElementByFilterName;
function getElementByFilterName(obj) {
if (obj) {
for (var i = 0; i < this.length; i++) {
if (this[i].filterName === obj.filterName) {
return this[i];
}
}
}
}
Array.prototype.getConverterStringTo = getConverterStringTo;
function getConverterStringTo(property) {
if (property) {
for (var i = 0; i < this.length; i++) {
var element = this[i];
if (element && element.property && element.property.toLowerCase() === property.toLowerCase()) {
return element.converterCallback;
}
}
}
}
var DataTableExtensions = DataTableExtensions || {};
DataTableExtensions.headerControls = {
select: 0,
button: 1,
textBox: 2
};
DataTableExtensions.triggerModes = {
onChange: 0,
explicit: 1
};
DataTableExtensions.buttonStyle = {
info: 0,
warning: 1,
error: 2,
success: 3
};
//function to overwrite the pagination system of dataTables
function createPagination(previousCallback, nextCallback, hasNextPageCallback, hasPreviousPageCallback, decorateCallback) {
$.fn.dataTableExt.oPagination.my_custom_buttons = {
fnInit: function (oSettings, nPaging) {
var pager = $('<ul class="pager">').appendTo(nPaging);
console.log(pager);
var nPreviousWrapper = $('<li id="previousPager">').appendTo(pager);//.addClass("paginate_enabled_previous");
var nNextWrapper = $('<li id="nextPager">').appendTo(pager);//.addClass("paginate_enabled_next");
var nPrevious = $('<a>').text("Previous").appendTo(nPreviousWrapper);
var nNext = $('<a>').text("Next").appendTo(nNextWrapper);
//click event subscribe on previous
nPrevious.click(previousCallback);
//click event subscribe on next
nNext.click(nextCallback);
//disallow text selection
nPrevious.bind('selectstart', function () {
return false;
});
nNext.bind('selectstart', function () {
return false;
});
},
fnUpdate: function (oSettings) {
//check if there is the paging control allowed or not
if (!oSettings.aanFeatures.p) {
return;
}
$('#previousPager').switchClass(hasPreviousPageCallback, "paginate_enabled_previous", "paginate_disabled_previous");
$('#nextPager').switchClass(hasNextPageCallback, "paginate_enabled_next", "paginate_disabled_next")
}
};
$.fn.dataTableExt.oPagination.my_two_buttons = {
/*
* Function: oPagination.two_button.fnInit
* Purpose: Initialise dom elements required for pagination with forward/back buttons only
* Returns: -
* Inputs: object:oSettings - dataTables settings object
* node:nPaging - the DIV which contains this pagination control
* function:fnCallbackDraw - draw function which must be called on update
*/
"fnInit": function ( oSettings, nPaging, fnCallbackDraw )
{
//var oLang = oSettings.oLanguage.oPaginate;
//var oClasses = oSettings.oClasses;
var fnClickHandler = function ( e ) {
if ( oSettings.oApi._fnPageChange( oSettings, e.data.action ) )
{
fnCallbackDraw( oSettings );
}
};
var pager = $('<ul class="pager">').appendTo(nPaging);
var nPreviousWrapper = $('<li id="previousPager">').appendTo(pager);//.addClass("paginate_enabled_previous");
var nNextWrapper = $('<li id="nextPager">').appendTo(pager);//.addClass("paginate_enabled_next");
var nPrevious = $('<a>').text("Previous").appendTo(nPreviousWrapper);
var nNext = $('<a>').text("Next").appendTo(nNextWrapper);
/*var els = $('a', nPaging);
var nPrevious = els[0],
nNext = els[1];*/
oSettings.oApi._fnBindAction( nPrevious, {action: "previous"}, fnClickHandler );
oSettings.oApi._fnBindAction( nNext, {action: "next"}, fnClickHandler );
/* ID the first elements only */
if ( !oSettings.aanFeatures.p )
{
nPaging.id = oSettings.sTableId+'_paginate';
nPrevious.attr('id', oSettings.sTableId+'_previous');
nNext.attr('id', oSettings.sTableId+'_next');
nPrevious.attr('aria-controls', oSettings.sTableId);
nNext.attr('aria-controls', oSettings.sTableId);
}
},
/*
* Function: oPagination.two_button.fnUpdate
* Purpose: Update the two button pagination at the end of the draw
* Returns: -
* Inputs: object:oSettings - dataTables settings object
* function:fnCallbackDraw - draw function to call on page change
*/
"fnUpdate": function ( oSettings )
{
if (!oSettings.aanFeatures.p) {
return;
}
$('#previousPager').switchClass(function (){ return oSettings._iDisplayStart === 0 }, "paginate_disabled_previous", "paginate_enabled_previous");
$('#nextPager').switchClass(function(){ return oSettings.fnDisplayEnd() == oSettings.fnRecordsDisplay()}, "paginate_disabled_next", "paginate_enabled_next");
decorateCallback(oSettings._iDisplayStart);
}
};
}
//function to create own header for the data table
function createCustomHeader(controls, filterOptionsCallback) {
//get the placeholder div for the custom header
var customHeader = $("#customHeader");
//array of own filter options defined by new controls
var filtersToTrigger = [];
//walk through the custom controls that the user want
for (var i = 0; i < controls.length; i++) {
var control = controls[i];
//if array element is not a single element
if (Object.prototype.toString.call(control) === '[object Array]') {
createControlGroup(control);
} else {
var createFunction;
switch (control.type) {
case DataTableExtensions.headerControls.button:
createFunction = createButton;
break;
case DataTableExtensions.headerControls.textBox:
createFunction = createTextBox;
break;
case DataTableExtensions.headerControls.select:
createFunction = createSelector;
break;
}
createAndAppendControl(createFunction, control, customHeader);
}
}
function createControlGroup(controls) {
var controlGroup = $('<div></div>');
controlGroup.addClass("control-group");
//walk through the custom controls that the user want
for (var i = 0; i < controls.length; i++) {
var control = controls[i];
var createFunction;
switch (control.type) {
case DataTableExtensions.headerControls.button:
createFunction = createButton;
break;
case DataTableExtensions.headerControls.textBox:
createFunction = createTextBox;
break;
case DataTableExtensions.headerControls.select:
createFunction = createSelector;
break;
}
createAndAppendControl(createFunction, control, controlGroup);
}
controlGroup.appendTo(customHeader);
}
function createAndAppendControl(createFunction, control, addToThis) {
var newControl = createFunction(control.properties);
newControl.appendTo(addToThis);
}
//create and append selector
function createSelector(properties) {
var selectorDiv = $('<div class="header-controls"></div>');
//create the select tag with css class
var select = $('<select class="header-selector"/>');
//create label if the text box have label text
if (properties.label) {
var label = $('<label class="header-label">').text(properties.label);
label.appendTo(selectorDiv);
}
select.appendTo(selectorDiv);
//set the options for select
for (var i = 0; i < properties.options.length; i++) {
//$('<option />', {value: item, text: properties.options[item]}).appendTo(select);
$('<option />', { value: properties.options[i], text: properties.options[i] }).appendTo(select);
}
//set trigger on sending filter infos of the selector
if (properties.trigger) {
var mode = properties.trigger.mode;
if (mode === DataTableExtensions.triggerModes.explicit) {
filtersToTrigger.push({
starter: properties.trigger.starter,
name: properties.filterName,
value: properties.options[0]
});
select.change(function () {
filtersToTrigger.push({
starter: properties.trigger.starter,
name: properties.filterName,
value: $(this).val()
});
});
}
else if (mode === DataTableExtensions.triggerModes.onChange) {
select.change(function () {
properties.trigger.callback($(this).val());
});
}
}
return selectorDiv;
}
//create and append text box
function createTextBox(properties) {
var textBoxDiv = $('<div class="header-controls"></div>');
var textBox = $('<input type="text" class="header-button">');
//create label if the text box have label text
if (properties.label) {
var label = $('<label class="header-label">').text(properties.label);
label.appendTo(textBoxDiv);
}
textBox.appendTo(textBoxDiv);
if (properties.trigger) {
var mode = properties.trigger.mode;
if (mode === DataTableExtensions.triggerModes.explicit) {
filtersToTrigger.push({starter: properties.trigger.starter, name: properties.filterName});
//if the filter is triggered by other control, just collect the value on change
textBox.change(function () {
//starter - the other control that will send the filter
//name - the property name of the filter option
//value - the value of the filter option
filtersToTrigger.push({
starter: properties.trigger.starter,
name: properties.filterName,
value: $(this).val()
});
});
}
else if (mode === DataTableExtensions.triggerModes.onChange) {
textBox.change(function () {
properties.trigger.callback($(this).val())
});
}
}
return textBoxDiv;
}
//create and append button
function createButton(properties) {
var button = $('<button class="header-button"/>');
button.text(properties.text);
//button.click(properties.callback);
switch (properties.style) {
case DataTableExtensions.buttonStyle.info:
button.addClass("btn btn-info");
break;
case DataTableExtensions.buttonStyle.warning:
button.addClass("btn btn-warning");
break;
case DataTableExtensions.buttonStyle.success:
button.addClass("btn btn-success");
break;
case DataTableExtensions.buttonStyle.error:
button.addClass("btn btn-danger");
break;
default:
button.addClass("btn");
break;
}
if (properties.callback) {
button.click(properties.callback);
} else {
//on click collect the filters registered to this control and call the callback with the changed values
button.click(function () {
var filters = [];
for (var i = 0; i < filtersToTrigger.length; i++) {
var filter = filtersToTrigger[i];
console.log(filter);
if (filter.starter === properties.name) {
filters.push({filterName: filter.name, filterValue: filter.value});
}
}
filterOptionsCallback(filters);
});
}
return button;
}
}
//my implementation to switch between two css classes according to the result of the callback
(function ($) {
$.fn.switchClass = function (conditionCallback, classA, classB) {
var classToAdd;
var classToRemove;
if (conditionCallback()) {
classToAdd = classA;
classToRemove = classB;
} else {
classToAdd = classB;
classToRemove = classA;
}
$(this).removeClass(classToRemove);
$(this).addClass(classToAdd);
};
})(jQuery);