Skip to content

maca88/datatables.plugins

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

75 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DataTables.plugins

This repository contains various plugins for DataTables. Most of them are integrations with AngularJS and Breezejs.

NOTE: These plugins works only with DataTables 1.10 and above!

Browser support: IE9+, Chrome, Firefox, Opera

Motivation:

As I started using AngularJS a few months ago, I was looking for a grid system that would work well with the technology, and the only free system at that time was ng-grid. Since I had a lot of experience with DataTables from creating various plugins, I was wondering what it would take for me to integrate it completely with AngularJS. All current projects that tried integrating AngularJS with DataTables were either done poorly (recreating/refilling the table on each change), or incomplete, that is when I decided to create my own repository.

Demonstration:

The demo is based on NorthBreeze, which is using ng-grid for tables. We modified it, so that DataTables is used instead of ng-grid.

The demo can be seen HERE.

Table of Contents

Plugins:

dataTables.rowDetails

This plugin provides a simple way to create row details content. To use this plugin you have to add the char 'D' to your dom. (i.e. lfrDtip). In addition to that you have to specify which column will be for opening and closing the row details, use property iconColumn for this.

Sample:

...
columns: [
    { iconColumn: true, defaultContent: "" },
    { data: "engine", title: "Engine" },
    { data: "browser", title: "Browser" }
],
...

If you want to specify the template for the open and close icons you can do by providing rowDetails property in your DataTables options:

...
rowDetails: {
    animation: 'none',
    icon: {
        className: 'row-detail-icon',
        closeHtml: '<button><span class="row-detail-icon">Close</span></button>',
        openHtml: '<button><span class="row-detail-icon">Open</span></button>',
        loadingHtml: 'Loading',
        defaultHtml: '',
        hasIcon: (row) => { return true; }
    },
    cell: {
        className: '',
    },
    behavior: 'default', //accordion
    destroyOnClose: false,
    buttonPanel: {
        attrs: {},
        classes: []
    },
    buttons: {
        expandAll: {
            visible: false,
            tagName: 'button',
            html: null,
            attrs: {},
            classes: [],
            click: function(e) {
                e.preventDefault();
                if (!this.dt.api.table().hasRows()) return;
                this.dt.api.table().rows().expandAll();
            }
        },
        collapseAll: {
            visible: false,
            tagName: 'button',
            html: null,
            attrs: {},
            classes: [],
            click: function (e) {
                e.preventDefault();
                if (!this.dt.api.table().hasRows()) return;
                this.dt.api.table().rows().collapseAll();
            }
        },
    },
    row: {
        expandClass: '',
        collapseClass: ''  
    },
    expandRow: {
        trClass: '',
        tdClass: '',
    },
    rowCreated: null,
    rowExpanded: null,
    rowDestroying: null,
    rowCollapsed: null,
    bindingAdapter: null,
    template: null,
    language: {
        'collapseAll': 'Collapse all',
        'expandAll': 'Expand all'
    }
}
...

The above configuration is the default that will be used if is not specified in initial DataTables options.

For populating row details you have to specify rowDetailCreated function in the DataTables options. rowDetailCreated takes 3 parameters. (1. dt row, 2. details node wrapper in jQuery, 3. rowDetails settings)

Properties:

animation specify what animation will be used when open/close icon will be clicked. Options: 'slide' (uses: slideDown/slideUp) and 'none' (uses: hide/show)

icon.class the class used to create a jQuery delegate for click event
icon.closeHtml the template for the close icon (don't forget to use icon.class in order the click delegate to work)
icon.openHtml the template for the open icon (don't forget to use icon.class in order the click delegate to work)
icon.loadingHtml the template for loading process, this template will be displayed when open icon is clicked and removed when the expanded row is shown
icon.hasIcon function that get the row data as parameter and has to return a boolean, where true means that open or close icon will be shown and false means that icon.defaultHtml will be shown.
icon.defaultHtml the template that will be used when a row has not an icon (is not expandable)

cell.classclass for the cell where the expand/collapse icon is located

behaviuor used to specify the behaviour for the plugin. Options: 'default' (multiple rows can be expanded at once) and 'accordion' (only one row can be expanded at once)
destroyOnClose specify whether the row details will be removed from the dom or will remain hidden

buttonPanel.attrsspecify additional attributes for the button panel (panel for expandAll and collapseAll buttons)
buttonPanel.classesclasses that will be applied to the button panel

buttonsobject that contains all the buttons that will be displayed in the button panel

row.expandClassclass for the rows that are currently expanded
row.collapseClassclass for the rows that are currently collapsed

expandRow.trClass class for the tr tag that is created when a row is expanded
expandRow.tdClass class for the td tag that is created when a row is expanded

rowCreatedcallback that is called when a expand row is created. Takes 2 parameters: 1. dt row, 2. details node wrapper in jQuery
rowExpandedcallback that is called when a expand row is expanded. Takes 2 parameters: 1. dt row, 2. details node wrapper in jQuery
rowDestroyingcallback that is called before an expanded row is destroyed. Takes 2 parameters: 1. dt row, 2. icon cell wrapped in jQuery
rowCollapsedcallback that is called when a expand row was collapsed. Takes 2 parameters: 1. dt row, 2. icon cell wrapped in jQuery

bindingAdapterspecify adapter for binding framework like angular. Angular adapter is built-in and will be automatically applied when angular is present on the site. Below is the interface for the adapter:

...
interface IRowDetailsBindingAdapter {
    rowCreated(row, rowDetails): void;
    rowExpanded(row, rowDetails, iconCell): void;
    rowCollapsed(row, rowDetails, iconCell): void;
    destroyDetails(details): void;
    cacheTemplate(url: string, template: string): void;
    getTemplate(url: string): string;
}
...

Example using dataTables.rowDetails with AngularJS. angular.dataTables must be also included in order to work

Sample :

<div id="row-details-tpl" ng-non-bindable style="display: none">
  <h4>SubItems</h4>
  <table class="table table-striped table-bordered" dt-table dt-data="item.subItems" dt-options="subItemOptions">
    <thead>
      <tr>
        <th dt-data="prop1">Prop1</th>
      </tr>
    </thead>
  </table>
</div>

<table dt-table dt-data="data" dt-options="options" dt-row-detail-tpl="#row-details-tpl">
  <thead>
    <tr>
      <th dt-row-detail-icon></th>
      <th dt-data="engine">Rendering engine</th>
      <th dt-data="browser">Browser</th>
    </tr>
  </thead>
</table>

templatethe template for the expanded row. Can be a string that contains a selector or inline html or an object for a remote template:

...
{
    url: 'someurl', (required)
    requesting: null, //callback that is called before the request. Takes 2 parameters: 1. dt row, 2. details node wrapper in jQuery (optional)
    ajax: null //ajax settings that will be used in jQuery.ajax call (optional)
}
...

If you want to programmatically open or close details you can do this with the functions that this plugin exposes:

row().details.collapse() for collapsing an expanded row
row().details.expand() for expanding a collapsed row
row().details.toggle() for expand/collapse a row
row().details.isOpen() check whether a row is expanded or not

dataTables.entityFilter

This plugin provide a select box to filter entities. In order to enable this plugin you have to add char 'G' to the dom (i.e. lfrGtip). This plugin has a BreezeJs and JayData adapted built-in that will be automatically set when BreezeJS or JayData exist on the site.

Default configurations :

entityFilter: {
    selectedState: 'default',
    adapter: null,
    states: {
        'default': { 'filter': ['Added', 'Modified', 'Unchanged', 'Detached', 'NotTracked'] },
        'all': { 'filter': [] },
        'added': { 'filter': ['Added'] },
        'modified': { 'filter': ['Modified'] },
        'unchanged': { 'filter': ['Unchanged'] },
        'edited': { 'filter': ['Added', 'Modified'] },
        'detached': { 'filter': ['Detached'] },
        'deleted': { 'filter': ['Deleted'] }
    },
    dom: {
        containerClass: '',
        selectClass: 'form-control'  
    },
    language: {
        'entityFilter': 'Entity filter',
        'default': 'Default',
        'all': 'All',
        'added': 'Added',
        'modified': 'Modified',
        'unchanged': 'Unchanged',
        'edited': 'Edited',
        'detached': 'Detached',
        'deleted': 'Deleted'
    }

Filtering is done by checking the state of the entity. Note that empty array means that all states are matched.

dataTables.remoteFilter

Dependencies: Breezejs

This plugin enables paging/searching/ordering on the server side using an adapter that is provided to the plugin. BreezeJs and JayData adapters are built-in and will be automatically applied when exist on site. In order to enable this plugin you have to define remoteFilter property in DataTables configuration.

Default configurations :

remoteFilter: {
	adapter: null,
    prefetchPages: 1,
    tracking: true,
    method: 'GET',
    sendExtraData: false,
    encoding: null,
    query: null, //breeze.EntityQuery
    entityManager: null, //breeze.EntityManager
    resultEntityType: null, //breeze.EntityType or string (optional, used for automaticaly detect types, alternatively can be passed by toType function of breeze.EntityType)
    projectOnlyTableColumns: false,
    beforeQueryExecution: null //function
}

Properties:

prefetchPages number of pages to prefetch. The idea was taken from DataTables pipelining
tracking specify whether the fetched entities will be tracked by BreezeJs or JayData
method method to be used when getting data from the server. For POST method breezeajaxpostjs must be included in order to work correctly
sendExtraData can be a boolean or a function that is used to filter the data that will be send to the server.
encoding specifies the encoding used when requesting the server. Possible values: ‘JSON’ or x-www-form-urlencoded (the default). breezeajaxpostjs must be included in order to work
query is an instance of Entity.Query
entityManager the entityManger that will be used for querying the server (Note: this is not required if the query was populated with the entity manager using the 'using' function)
resultEntityType the entity name that will be returned by the server. can be breeze.EntityType or string (optional, used for automaticaly detect types, alternatively can be passed by toType function of breeze.EntityQuery)
projectOnlyTableColumns specify if only the column that are defined in the table will be fetched. Note if this option is set to true then server results will be plain objects (not breeze.Entity)
beforeQueryExecution is a callback that will be called before the request will be send to the server

dataTables.remoteState

This plugin enables to store the table state on a remote location. One table can have multiple states in which one can be set as the default. The states will be shown on in a select box, when switching the table will load the selected state on the fly. This also works with plugins: ColVis, ColReorder, AdvancedFilter, ColResize, ColPin and FormFilter. The default classes are designed to work with Bootstrap but can be overridden to a custom ones. To use this plugin you have to add the char 'B' to your dom. (i.e. lfrBtip)

Default configurations :

remoteState: {
	storeId: null,
	defaultState: '',
	currentState: null,
	minStateLength: 2,
	defaultTableState: 'Default',
	states: null,
	getStatesFromServer: false,
	sendCurrentStateToServer: null,
	dom: {
		inputWidth: '200px',
		stateSelectClass: 'form-control',
		setDefaultButton: {
			'class': 'btn btn-default btn-sm',
			'icon': 'glyphicon glyphicon-star-empty'
		},
		settingButton: {
			'class': 'btn btn-default btn-sm',
			'icon': 'glyphicon glyphicon-list-alt'
		},
		deleteButton: {
			'class': 'btn btn-default btn-sm',
			'icon': 'glyphicon glyphicon-remove'
		},
		saveButton: {
			'class': 'btn btn-default btn-sm',
			'icon': 'glyphicon glyphicon-floppy-disk'
		},
		deleteForm: {
			'class': 'form-horizontal',
			'groupClass': 'form-group',
			'labelClass': 'col-sm-2 control-label',
			'selectDivClass': 'col-sm-10'
		},
		saveForm: {
			'class': 'form-horizontal',
			'inputClass': 'form-control',
			'groupClass': 'form-group',
			'labelClass': 'col-sm-2 control-label',
			'selectDivClass': 'col-sm-10'
		}
	},
	language: {
		'settings': 'Settings',
		'default': 'Default',
		'load': 'Load',
		'save': 'Save',
		'add': 'Add',
		'delete': 'Delete',
		'setDefault': 'Set default',
		'createNew': 'Create new',
		'state': 'State'
	},
	settingsDisplayAction: null,
	ajax: {
		'getAll': {
			url: null,
			type: 'POST',
			beforeSendAction: null,
			doneAction: null,
			failAction: null
		},
		'save': {
			url: null,
			type: 'POST',
			beforeSendAction: null,
			doneAction: null,
			failAction: null
		},
		'delete': {
			url: null,
			type: 'POST',
			beforeSendAction: null,
			doneAction: null,
			failAction: null
		},
		'setDefault': {
			url: null,
			type: 'POST',
			beforeSendAction: null,
			doneAction: null,
			failAction: null
		}
	}
}

dataTables.advancedFilter

Dependencies : Bootstrap (Modal and Popover)

The AdvancedFilter plugin is used to create complex filters that can be applied to our data (client side or server side). The big difference between DataTables builtin search engine and AdvancedFilter is that AdvancedFilter support nested predicates on a column level, with a friendly UI. There are two ways to add a filter with the AdvancedFilter plugin. One is by clicking on the filter icon whitin the column header or by clicking the filter icon above the table. When clicking on the icon above the table a modal window will be shown with all the filters that are currently applied. To use this plugin you have to add the char 'A' to your dom. (i.e. lfrAtip).

Default configurations :

advancedFilter: {
	operators: {
		types: {
			'string': ['nn', 'nl', 'eq', 'ne', 'co', 'nc', 'sw', 'ew'],
			'number': ['nn', 'nl', 'eq', 'ne', 'lt', 'le', 'gt', 'ge'],
			'boolean': ['nn', 'nl', 'eq', 'ne'],
			'date': ['nn', 'nl', 'eq', 'ne', 'lt', 'le', 'gt', 'ge'],
			'undefined': ['nn', 'nl', 'eq', 'ne']
		},
		eq: {
			fn: null//function (data, input) { return data === input; }
		}
	},
	typesEditor: {
		'string': {
			tag: 'input',
			attr: { type: 'text' },
			className: 'form-control input-sm',

			customCreationFn: null, //function(column, operator, value, settings): editor - this -> api
			setFilterValue: null,
			getFilterValue: null
		},
		'date': {
			tag: 'input',
			attr: { type: 'date' },
			className: 'form-control input-sm',
			getFilterValue: function () {
				return this.get(0).valueAsDate;
			},
			setFilterValue: function (date) {
				this.get(0).valueAsDate = date;
			}
		},
		'time': {
			tag: 'input',
			attr: { type: 'time' },
			className: 'form-control input-sm'
		},
		'dateTime': {
			tag: 'input',
			attr: { type: 'datetime-local' },
			className: 'form-control input-sm'
		},
		'number': {
			tag: 'input',
			attr: { type: 'number', step: 'any' },
			className: 'form-control input-sm',
			getFilterValue: function () {
				return this.get(0).valueAsNumber;
			},
			setFilterValue: function (num) {
				this.get(0).valueAsNumber = num;
			}
		},
		'select': {
			tag: 'select',
			attr: {},
			className: 'form-control input-sm'
		}
	},
	dom: {
		settingButtonDiv: {
			className: 'dt-global-filter-div'
		},
		settingButton: {
			buttonClass: 'btn btn-default btn-sm',
			spanClass: 'glyphicon glyphicon-filter'
		},
		addGroupButton: {
			buttonClass: 'btn btn-default btn-sm',
			spanClass: 'glyphicon glyphicon-plus'
		},
		removeGroupButton: {
			buttonClass: 'btn btn-default btn-sm',
			spanClass: 'glyphicon glyphicon-minus'
		},
		addRuleButton: {
			buttonClass: 'btn btn-default btn-sm',
			spanClass: 'glyphicon glyphicon-plus'
		},
		removeRuleButton: {
			buttonClass: 'btn btn-default btn-sm',
			spanClass: 'glyphicon glyphicon-minus'
		},
		ruleOperatorSelect: {
			className: 'form-control input-sm',
		},
		groupOperatorSelect: {
			className: 'form-control input-sm',
		},
		columnSelect: {
			className: 'form-control input-sm',
		},
		columnFilterIcon: {
			className: 'glyphicon glyphicon-filter'
		},
		filterButton: {
			className: 'btn btn-primary'
		},
		clearButton: {
			className: 'btn btn-default'
		}
	},
	language: {
		'all': 'All',
		'filter': 'Filter',
		'clear': 'Clear',
		'and': 'And',
		'or': 'Or',
		'columnFilter': 'Column filter',
		'filterFor': 'Filter for',
		'removeGroup': 'Remove group',
		'removeRule': 'Remove rule',
		'addGroup': 'Add group',
		'addRule': 'Add rule',
		'filterSettings': 'Filter settings',
		'operators': {
			'nl': 'Is null',
			'nn': 'Not null',
			'eq': 'Equal',
			'ne': 'Not equal',
			'co': 'Contains',
			'nc': 'Not contain',
			'sw': 'Starts with',
			'ew': 'Ends with',
			'lt': 'Less than',
			'le': 'Less or equal',
			'gt': 'Greater than',
			'ge': 'Greater or equal'
		}
	},
	stateData: null,
	getDefaultFilterEditor: (settings, column) => {
		var type = column.sType ? column.sType.toLowerCase() : '';
		switch (type) {
			case 'num-fmt':
			case 'num':
				return settings.typesEditor['number'];
			default:
				return settings.typesEditor['string'];
		}
	},
	getDefaultColumnOperators: (settings, column) => { //this will execute for types that are not defined in operators.types and in the column itself
		var type = column.sType ? column.sType.toLowerCase() : '';
		switch (type) {
			case 'num-fmt':
			case 'num':
				return settings.operators.types['number'];
			default:
				return settings.operators.types['undefined'];
		}
	},
	createFilterEditor: null, //function(column, operator, value): editor - this -> api
	filterEditorCreated: null, //function(editor, column, operator, value): void - this -> api
	ruleRemoving: null, //function(table): void - this -> api
}

dataTables.colPin

Dependencies : dataTables.fixedColumns

The ColPin plugin is used for pinning columns, so that they are always visible, even when scrolling horizontally. Under the hood ColPin uses the official DataTables plugin called FixedColumns. By clicking on the pin, the column will be automatically pinned to the left side of the table and the pin icon will be colored red. We can obtain a mirrored effect by holding shift + click, this way the column will be pinned on the right side. For unpinning the column, we just habe to click again on the icon and the column will not be pinned anymore. To use this plugin you have to add the char 'I' to your dom. (i.e. lfrItip). When angular is detected the plugin will automatically set the built-in angular adapter.

Default configurations :

{
    classes: {
        iconClass: 'pin',
        pinnedClass: 'pinned',
        unpinnedClass: 'unpinned'
    },
    fixedColumns: null,
    bindingAdapter: null,
}

dataTables.colResize

ColResize is a plugin that is used for column resizing. It works for tables that have scrollX either enabled or disabled, and even with the official DataTables plugin called Scroller. This plugin was initialy base on ColReorderWithresize but was modified in order to work as standalone plugin. To use this plugin you have to add the char 'J' to your dom. (i.e. lfrJtip).

dataTables.formFilter

The FormFilter is used for the integration of html forms with the DataTables filtering engine. Before filtering FormFilter will fetch all the data from the forms, so that can be used for server or client side filtering. This plugin comes handy when we have a special condition that cannot be created throught the AdvancedFilter. To use this plugin you have to add the char 'K' to your dom. (i.e. lfrKtip).

Default configurations :

{
	formSelectors: [],
	getFormData: (form) => { return form.serializeArray(); },
	setFormData: (form, data) => { $.each(data, (i, item) => { $('input[name="' + item.name + '"], select[name="' + item.name + '"], textarea[name="' + item.name + '"]', form).val(item.value); }); },
	mergeFormData: (data, fData) => { return !$.isArray(fData) ? data : (data || []).concat(fData); },
	resetForm: (form) => { $(':input', form).not(':button, :submit, :reset, :hidden').removeAttr('checked').removeAttr('selected').not(':checkbox, :radio, select').val(''); },
	clientFilter: null, //function(currentFormsData, data, dataIndex, rowData)
}

AngularJs integrations:

angular.dataTables

This is the main plugin for integrating DataTables with AngularJS. Instead of recreating or empty/fill the whole table on collection changes as other similar plugins, this one handles model changes in a smart way, so that only the collection items that are removed/added/swapped will be removed/added/swapped in DataTables. It populates items with the special property $$dtHash that is used for faster item searching (similar approach that ngRepeat).

Simple usage:

<table dt-table dt-data="items" dt-options="options">
  <thead>
    <tr>
      <th dt-data="OrderID">Id</th>
      <th dt-data="OrderDate">Order Date</th>
      <th dt-data="ShipAddress">Ship Address</th>
    </tr>
  </thead>
</table>

When using dt-table directive the following attributes are possible for table tag:

dt-data path of the collection in the current scope
dt-options path of DataTables options in the current scope
dt-witdh width that will be set using the css function of jQLite/JQuery
dt-row-data-path path of the row data (default is 'data')
dt-row-invalidate row invalidation mode possible values: "none" and "rendered" (default is "none"). When set to "rendered" row that are rendered will be automatically invalidated with rows().invalidate() when row data will change.
dt-draw-digest digest the rendered rows on draw. Possible values "true" or "false" (default is "true")
dt-no-row-binding when set to "false" binding for rows will not be created. If we have a read-only table or we will just adding and removing rows without later modifications, this option can be used in order to gain performance. Default is "false"

NOTE: if you want to use dt instance in your controller give the dt-table attribute the path you want to have the instance defined (i.e. dt-table="variableName")

Columns can be defined in code inside dt-options attribute or in html like in the above example.

When defining a column in html the following attributes are possible for th tag:

dt-data http://datatables.net/reference/option/columns.data
dt-name http://datatables.net/reference/option/columns.name
dt-class-name http://datatables.net/reference/option/columns.className
dt-orderable http://datatables.net/reference/option/columns.orderable
dt-searchable http://datatables.net/reference/option/columns.searchable
dt-width http://datatables.net/reference/option/columns.width
dt-default-content http://datatables.net/reference/option/columns.defaultContent
dt-order-data-type http://datatables.net/reference/option/columns.orderDataType

NOTE: the title will be the content inside th element

In addition to standard column options there are two more:

dt-template selector for the template that will be used

Sample usage :

<div id="options-tpl" ng-non-bindable style="display: none">
  <span>data.engine</span>
  <button ng-click="removeItem($rowIndex)"><span>Delete</span></button>
</div>

<table dt-table dt-data="data" dt-options="options">
  <thead>
    <tr>
      <th dt-data="engine">Rendering engine</th>
      <th dt-data="browser">Browser</th>
      <th dt-data="platform">Platform(s)</th>
      <th dt-data="version">Engine version</th>
      <th dt-data="grade">CSS grade</th>
      <th dt-template="#options-tpl">Options</th>
    </tr>
  </thead>
</table>

Note that the template has special attribute ng-non-bindable that has to be applied to every template in order to work correctly. Within the template the following special properties can be used:

  • $rowIndex iterator offset of the row (0..row length-1).
  • $firstRow true if the row is first in the iterator.
  • $lastRow true if the row is last in the iterator.
  • $middleRow true if the row is between the first and last in the iterator.
  • $oddRow true if the iterator position $rowIndex is odd (otherwise false).
  • $cellIndex iterator offset of the cell (0..column length-1).

In the above example 'data' represent the data of the collection item. If you want to change that you can with the attribute dt-row-data-path in table tag.

If you want to define the template in code define template property in the column definition.

NOTE: when using this option searching and ordering will be disabled. NOTE: with official DataTables library row invalidation will break the template!

dt-expression AngularJS expression to be evaluated

Sample usage :

<table dt-table dt-data="orders" dt-options="orderGridOpts" dt-row-data-path="order">
    <thead>
        <th dt-data="OrderID">Order ID</th>
        <th dt-expression="order.Freight | currency">Freight</th>
        <th dt-expression="order.OrderDate | date:'shortDate'">Order Date</th>
    </thead>
</table>

Note that in the expression you cannot use special properties like in the template option, but in opposite to the template option this option can be searchable and orderable.

In the above sample you can see the special dt-row-detail-tpl attribute on the table tag that defines the row details template and dt-row-detail-icon on the th tag specifying the icon column.

angular.dataTables.selectable

Dependencies: angular.dataTables, datatables.tableTools

This plugin integrates the official TableTools extension with AngularJS. It add a special property selectedRows to the DataTable instance that can be binded in the view template. Property selectedRows returns an array of DataTables row instances.

Sample usage:

<table dt-table dt-data="data" dt-options="options" dt-selectable="os">
  <thead>
    <tr>
      <th dt-selectable-column="true"></th>
      <th dt-data="browser">Browser</th>
      <th dt-data="platform">Platform(s)</th>
      <th dt-data="version">Engine version</th>
    </tr>
  </thead>
</table>

angular.dataTables.command

Dependencies: angular.dataTables

TODO

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published