diff --git a/airtime_mvc/application/controllers/LibraryController.php b/airtime_mvc/application/controllers/LibraryController.php
index 606e1606f..acbba568d 100644
--- a/airtime_mvc/application/controllers/LibraryController.php
+++ b/airtime_mvc/application/controllers/LibraryController.php
@@ -33,12 +33,15 @@ class LibraryController extends Zend_Controller_Action
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.pluginAPI.js','text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.fnSetFilteringDelay.js','text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.ColVis.js','text/javascript');
+ $this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.ColReorder.js','text/javascript');
+ $this->view->headScript()->appendFile($baseUrl.'/js/datatables/plugin/dataTables.FixedColumns.js','text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/library.js','text/javascript');
$this->view->headScript()->appendFile($baseUrl.'/js/airtime/library/advancedsearch.js','text/javascript');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/media_library.css');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/contextmenu.css');
$this->view->headLink()->appendStylesheet($baseUrl.'/css/datatables/css/ColVis.css');
+ $this->view->headLink()->appendStylesheet($baseUrl.'/css/datatables/css/ColReorder.css');
$this->_helper->layout->setLayout('library');
$this->_helper->viewRenderer->setResponseSegment('library');
diff --git a/airtime_mvc/public/css/datatables/css/ColReorder.css b/airtime_mvc/public/css/datatables/css/ColReorder.css
new file mode 100644
index 000000000..6bb55e22c
--- /dev/null
+++ b/airtime_mvc/public/css/datatables/css/ColReorder.css
@@ -0,0 +1,14 @@
+/*
+ * Namespace DTCR - "DataTables ColReorder" plug-in
+ */
+
+table.DTCR_clonedTable {
+ background-color: white;
+ z-index: 998;
+}
+
+div.DTCR_pointer {
+ width: 1px;
+ background-color: #5B5B5B;
+ z-index: 997;
+}
\ No newline at end of file
diff --git a/airtime_mvc/public/css/styles.css b/airtime_mvc/public/css/styles.css
index 6bf53511c..e2acc5daa 100644
--- a/airtime_mvc/public/css/styles.css
+++ b/airtime_mvc/public/css/styles.css
@@ -622,10 +622,22 @@ dl.inline-list dd {
padding: 2px 2px 2px 0;
vertical-align: top;
}
+#library_display thead th, #library_display tbody td {
+ cursor: pointer;
+}
+#library_display thead th.library_checkbox,
+#library_display thead th.library_id,
+#library_display thead th.library_title {
+ cursor: default;
+}
.ColVis.TableTools .ui-button {
height: 21px;
}
+button.ColVis_Button.ColVis_ShowAll {
+ text-align: center;
+ margin-top: 10px;
+}
.library_toolbar .ui-button, .ColVis.TableTools .ui-button {
float: right;
text-align:center;
diff --git a/airtime_mvc/public/js/airtime/library/library.js b/airtime_mvc/public/js/airtime/library/library.js
index 57d3aea41..0bd77535a 100644
--- a/airtime_mvc/public/js/airtime/library/library.js
+++ b/airtime_mvc/public/js/airtime/library/library.js
@@ -1,5 +1,6 @@
var dTable;
var checkedCount = 0;
+var checkedPLCount = 0;
//used by jjmenu
function getId() {
@@ -319,6 +320,12 @@ function getNumEntriesPreference(data) {
}
function groupAdd() {
+ if (checkedPLCount > 0) {
+ alert("Can't add playlist to another playlist");
+ return;
+ }
+ disableGroupBtn('library_group_add');
+
var ids = new Array();
var addGroupUrl = '/Playlist/add-group';
var newSPLUrl = '/Playlist/new/format/json';
@@ -354,6 +361,8 @@ function groupAdd() {
}
function groupDelete() {
+ disableGroupBtn('library_group_delete');
+
var auIds = new Array();
var plIds = new Array();
var auUrl = '/Library/delete-group';
@@ -387,10 +396,18 @@ function groupDelete() {
function toggleAll() {
var checked = $(this).attr("checked");
$('#library_display tr').each(function() {
+ var idSplit = $(this).attr('id').split("_");
+ var type = idSplit[0];
$(this).find(":checkbox").attr("checked", checked);
if (checked) {
+ if (type == "pl") {
+ checkedPLCount++;
+ }
$(this).addClass('selected');
} else {
+ if (type == "pl") {
+ checkedPLCount--;
+ }
$(this).removeClass('selected');
}
});
@@ -401,6 +418,7 @@ function toggleAll() {
enableGroupBtn('library_group_delete', confirmDeleteGroup);
} else {
checkedCount = 0;
+ checkedPLCount = 0;
disableGroupBtn('library_group_add');
disableGroupBtn('library_group_delete');
}
@@ -427,10 +445,15 @@ function checkBoxChanged() {
var cbAllChecked = cbAll.attr("checked");
var checked = $(this).attr("checked");
var size = $('#library_display tbody tr').size();
+ var idSplit = $(this).parent().parent().attr('id').split("_");
+ var type = idSplit[0];
if (checked) {
if (checkedCount < size) {
checkedCount++;
}
+ if (type == "pl" && checkedPLCount < size) {
+ checkedPLCount++;
+ }
enableGroupBtn('library_group_add', groupAdd);
enableGroupBtn('library_group_delete', confirmDeleteGroup);
$(this).parent().parent().addClass('selected');
@@ -438,6 +461,9 @@ function checkBoxChanged() {
if (checkedCount > 0) {
checkedCount--;
}
+ if (type == "pl" && checkedPLCount > 0) {
+ checkedPLCount--;
+ }
if (checkedCount == 0) {
disableGroupBtn('library_group_add');
disableGroupBtn('library_group_delete');
@@ -491,17 +517,17 @@ function createDataTable(data) {
"fnRowCallback": dtRowCallback,
"fnDrawCallback": dtDrawCallback,
"aoColumns": [
- /* Checkbox */ { "sTitle": "", "bSortable": false, "bSearchable": false, "mDataProp": "checkbox", "sWidth": "25px", "sClass": "library_checkbox"},
- /* Id */ { "sName": "id", "bSearchable": false, "bVisible": false, "mDataProp": "id", "sClass": "library_id"},
- /* Title */ { "sTitle": "Title", "sName": "track_title", "mDataProp": "track_title", "sClass": "library_title"},
- /* Creator */ { "sTitle": "Creator", "sName": "artist_name", "mDataProp": "artist_name", "sClass": "library_creator"},
- /* Album */ { "sTitle": "Album", "sName": "album_title", "mDataProp": "album_title", "sClass": "library_album"},
- /* Genre */ { "sTitle": "Genre", "sName": "genre", "mDataProp": "genre", "sWidth": "10%", "sClass": "library_genre"},
- /* Year */ { "sTitle": "Year", "sName": "year", "mDataProp": "year", "sWidth": "8%", "sClass": "library_year"},
- /* Length */ { "sTitle": "Length", "sName": "length", "mDataProp": "length", "sWidth": "16%", "sClass": "library_length"},
- /* Type */ { "sTitle": "Type", "sName": "ftype", "bSearchable": false, "mDataProp": "ftype", "sWidth": "9%", "sClass": "library_type"},
- /* Upload Time */ { "sTitle": "Upload Time", "sName": "upload_time", "mDataProp": "upload_time", "sClass": "library_upload_time"},
- ],
+ /* Checkbox */ {"sTitle": "", "bSortable": false, "bSearchable": false, "mDataProp": "checkbox", "sWidth": "25px", "sClass": "library_checkbox"},
+ /* Id */ {"sName": "id", "bSearchable": false, "bVisible": false, "mDataProp": "id", "sClass": "library_id"},
+ /* Title */ {"sTitle": "Title", "sName": "track_title", "mDataProp": "track_title", "sClass": "library_title"},
+ /* Creator */ {"sTitle": "Creator", "sName": "artist_name", "mDataProp": "artist_name", "sClass": "library_creator"},
+ /* Album */ {"sTitle": "Album", "sName": "album_title", "mDataProp": "album_title", "sClass": "library_album"},
+ /* Genre */ {"sTitle": "Genre", "sName": "genre", "mDataProp": "genre", "sWidth": "10%", "sClass": "library_genre"},
+ /* Year */ {"sTitle": "Year", "sName": "year", "mDataProp": "year", "sWidth": "8%", "sClass": "library_year"},
+ /* Length */ {"sTitle": "Length", "sName": "length", "mDataProp": "length", "sWidth": "16%", "sClass": "library_length"},
+ /* Type */ {"sTitle": "Type", "sName": "ftype", "bSearchable": false, "mDataProp": "ftype", "sWidth": "9%", "sClass": "library_type"},
+ /* Upload Time */ {"sTitle": "Upload Time", "sName": "upload_time", "mDataProp": "upload_time", "sClass": "library_upload_time"},
+ ],
"aaSorting": [[2,'asc']],
"sPaginationType": "full_numbers",
"bJQueryUI": true,
@@ -511,18 +537,30 @@ function createDataTable(data) {
},
"iDisplayLength": getNumEntriesPreference(data),
"bStateSave": true,
- "sDom": 'lfr<"H"C<"library_toolbar">>t<"F"ip>',
+ // R = ColReorder, C = ColVis, see datatables doc for others
+ "sDom": 'Rlfr<"H"C<"library_toolbar">>t<"F"ip>',
"oColVis": {
- "sAlign": "right",
- "aiExclude": [0, 1, 2],
- "sSize": "css"
+ "buttonText": "Show/Hide Columns",
+ "sAlign": "right",
+ "aiExclude": [0, 1, 2],
+ "sSize": "css",
+ "bShowAll": true
+ },
+ "oColReorder": {
+ "aiOrder": [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ] /* code this */,
+ "iFixedColumns": 3
}
});
dTable.fnSetFilteringDelay(350);
-
- $("div.library_toolbar").html('Delete' +
+ $("div.library_toolbar").html('Reset Order' +
+ 'Delete' +
'Add');
+
+ $('#library_order_reset').click(function() {
+ ColReorder.fnReset( dTable );
+ return false;
+ });
}
$(document).ready(function() {
diff --git a/airtime_mvc/public/js/datatables/plugin/dataTables.ColReorder.js b/airtime_mvc/public/js/datatables/plugin/dataTables.ColReorder.js
new file mode 100644
index 000000000..95c39fcec
--- /dev/null
+++ b/airtime_mvc/public/js/datatables/plugin/dataTables.ColReorder.js
@@ -0,0 +1,987 @@
+/*
+ * File: ColReorder.js
+ * Version: 1.0.4
+ * CVS: $Id$
+ * Description: Controls for column visiblity in DataTables
+ * Author: Allan Jardine (www.sprymedia.co.uk)
+ * Created: Wed Sep 15 18:23:29 BST 2010
+ * Modified: $Date$ by $Author$
+ * Language: Javascript
+ * License: GPL v2 or BSD 3 point style
+ * Project: DataTables
+ * Contact: www.sprymedia.co.uk/contact
+ *
+ * Copyright 2010-2011 Allan Jardine, all rights reserved.
+ *
+ * This source file is free software, under either the GPL v2 license or a
+ * BSD style license, available at:
+ * http://datatables.net/license_gpl2
+ * http://datatables.net/license_bsd
+ *
+ */
+
+
+(function($, window, document) {
+
+
+/**
+ * Switch the key value pairing of an index array to be value key (i.e. the old value is now the
+ * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ].
+ * @method fnInvertKeyValues
+ * @param array aIn Array to switch around
+ * @returns array
+ */
+function fnInvertKeyValues( aIn )
+{
+ var aRet=[];
+ for ( var i=0, iLen=aIn.length ; i= iCols )
+ {
+ this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom );
+ return;
+ }
+
+ if ( iTo < 0 || iTo >= iCols )
+ {
+ this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo );
+ return;
+ }
+
+ /*
+ * Calculate the new column array index, so we have a mapping between the old and new
+ */
+ var aiMapping = [];
+ for ( i=0, iLen=iCols ; i this.s.fixed-1 )
+ {
+ this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh );
+ }
+
+ /* Mark the original column order for later reference */
+ this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i;
+ }
+
+ /* State saving */
+ this.s.dt.aoStateSave.push( {
+ "fn": function (oS, sVal) {
+ return that._fnStateSave.call( that, sVal );
+ },
+ "sName": "ColReorder_State"
+ } );
+
+ /* An initial column order has been specified */
+ var aiOrder = null;
+ if ( typeof this.s.init.aiOrder != 'undefined' )
+ {
+ aiOrder = this.s.init.aiOrder.slice();
+ }
+
+ /* State loading, overrides the column order given */
+ if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' &&
+ this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length )
+ {
+ aiOrder = this.s.dt.oLoadedState.ColReorder;
+ }
+
+ /* If we have an order to apply - do so */
+ if ( aiOrder )
+ {
+ /* We might be called during or after the DataTables initialisation. If before, then we need
+ * to wait until the draw is done, if after, then do what we need to do right away
+ */
+ if ( !that.s.dt._bInitComplete )
+ {
+ var bDone = false;
+ this.s.dt.aoDrawCallback.push( {
+ "fn": function () {
+ if ( !that.s.dt._bInitComplete && !bDone )
+ {
+ bDone = true;
+ var resort = fnInvertKeyValues( aiOrder );
+ that._fnOrderColumns.call( that, resort );
+ }
+ },
+ "sName": "ColReorder_Pre"
+ } );
+ }
+ else
+ {
+ var resort = fnInvertKeyValues( aiOrder );
+ that._fnOrderColumns.call( that, resort );
+ }
+ }
+ },
+
+
+ /**
+ * Set the column order from an array
+ * @method _fnOrderColumns
+ * @param array a An array of integers which dictate the column order that should be applied
+ * @returns void
+ * @private
+ */
+ "_fnOrderColumns": function ( a )
+ {
+ if ( a.length != this.s.dt.aoColumns.length )
+ {
+ this.s.dt.oInstance.oApi._fnLog( oDTSettings, 1, "ColReorder - array reorder does not "+
+ "match known number of columns. Skipping." );
+ return;
+ }
+
+ for ( var i=0, iLen=a.length ; i 0 )
+ {
+ this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('caption')[0] );
+ }
+ while ( this.dom.drag.getElementsByTagName('tbody').length > 0 )
+ {
+ this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('tbody')[0] );
+ }
+ while ( this.dom.drag.getElementsByTagName('tfoot').length > 0 )
+ {
+ this.dom.drag.removeChild( this.dom.drag.getElementsByTagName('tfoot')[0] );
+ }
+
+ $('thead tr:eq(0)', this.dom.drag).each( function () {
+ $('th:not(:eq('+that.s.mouse.targetIndex+'))', this).remove();
+ } );
+ $('tr', this.dom.drag).height( $('tr:eq(0)', that.s.dt.nTHead).height() );
+
+ $('thead tr:gt(0)', this.dom.drag).remove();
+
+ $('thead th:eq(0)', this.dom.drag).each( function (i) {
+ this.style.width = $('th:eq('+that.s.mouse.targetIndex+')', that.s.dt.nTHead).width()+"px";
+ } );
+
+ this.dom.drag.style.position = "absolute";
+ this.dom.drag.style.top = "0px";
+ this.dom.drag.style.left = "0px";
+ this.dom.drag.style.width = $('th:eq('+that.s.mouse.targetIndex+')', that.s.dt.nTHead).outerWidth()+"px";
+
+
+ this.dom.pointer = document.createElement( 'div' );
+ this.dom.pointer.className = "DTCR_pointer";
+ this.dom.pointer.style.position = "absolute";
+
+ if ( this.s.dt.oScroll.sX === "" && this.s.dt.oScroll.sY === "" )
+ {
+ this.dom.pointer.style.top = $(this.s.dt.nTable).offset().top+"px";
+ this.dom.pointer.style.height = $(this.s.dt.nTable).height()+"px";
+ }
+ else
+ {
+ this.dom.pointer.style.top = $('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top+"px";
+ this.dom.pointer.style.height = $('div.dataTables_scroll', this.s.dt.nTableWrapper).height()+"px";
+ }
+
+ document.body.appendChild( this.dom.pointer );
+ document.body.appendChild( this.dom.drag );
+ }
+};
+
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Static parameters
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/**
+ * Array of all ColReorder instances for later reference
+ * @property ColReorder.aoInstances
+ * @type array
+ * @default []
+ * @static
+ */
+ColReorder.aoInstances = [];
+
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Static functions
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+/**
+ * Reset the column ordering for a DataTables instance
+ * @method ColReorder.fnReset
+ * @param object oTable DataTables instance to consider
+ * @returns void
+ * @static
+ */
+ColReorder.fnReset = function ( oTable )
+{
+ for ( var i=0, iLen=ColReorder.aoInstances.length ; i
+ * Freezes the left or right most columns to the side of the table
+ * Option to freeze two or more columns
+ * Full integration with DataTables' scrolling options
+ * Speed - FixedColumns is fast in its operation
+ *
+ *
+ * @class
+ * @constructor
+ * @param {object} oDT DataTables instance
+ * @param {object} [oInit={}] Configuration object for FixedColumns. Options are defined by {@link FixedColumns.defaults}
+ *
+ * @requires jQuery 1.3+
+ * @requires DataTables 1.8.0.dev+
+ *
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable );
+ */
+FixedColumns = function ( oDT, oInit ) {
+ /* Sanity check - you just know it will happen */
+ if ( ! this instanceof FixedColumns )
+ {
+ alert( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." );
+ return;
+ }
+
+ if ( typeof oInit == 'undefined' )
+ {
+ oInit = {};
+ }
+
+ /**
+ * Settings object which contains customisable information for FixedColumns instance
+ * @namespace
+ * @extends FixedColumns.defaults
+ */
+ this.s = {
+ /**
+ * DataTables settings objects
+ * @type object
+ * @default Obtained from DataTables instance
+ */
+ "dt": oDT.fnSettings(),
+
+ /**
+ * Number of columns in the DataTable - stored for quick access
+ * @type int
+ * @default Obtained from DataTables instance
+ */
+ "iTableColumns": oDT.fnSettings().aoColumns.length,
+
+ /**
+ * Original widths of the columns as rendered by DataTables
+ * @type array.
+ * @default []
+ */
+ "aiWidths": [],
+
+ /**
+ * Flag to indicate if we are dealing with IE6/7 as these browsers need a little hack
+ * in the odd place
+ * @type boolean
+ * @default Automatically calculated
+ * @readonly
+ */
+ "bOldIE": ($.browser.msie && ($.browser.version == "6.0" || $.browser.version == "7.0"))
+ };
+
+
+ /**
+ * DOM elements used by the class instance
+ * @namespace
+ *
+ */
+ this.dom = {
+ /**
+ * DataTables scrolling element
+ * @type node
+ * @default null
+ */
+ "scroller": null,
+
+ /**
+ * DataTables header table
+ * @type node
+ * @default null
+ */
+ "header": null,
+
+ /**
+ * DataTables body table
+ * @type node
+ * @default null
+ */
+ "body": null,
+
+ /**
+ * DataTables footer table
+ * @type node
+ * @default null
+ */
+ "footer": null,
+
+ /**
+ * Display grid elements
+ * @namespace
+ */
+ "grid": {
+ /**
+ * Grid wrapper. This is the container element for the 3x3 grid
+ * @type node
+ * @default null
+ */
+ "wrapper": null,
+
+ /**
+ * DataTables scrolling element. This element is the DataTables
+ * component in the display grid (making up the main table - i.e.
+ * not the fixed columns).
+ * @type node
+ * @default null
+ */
+ "dt": null,
+
+ /**
+ * Left fixed column grid components
+ * @namespace
+ */
+ "left": {
+ "wrapper": null,
+ "head": null,
+ "body": null,
+ "foot": null
+ },
+
+ /**
+ * Right fixed column grid components
+ * @namespace
+ */
+ "right": {
+ "wrapper": null,
+ "head": null,
+ "body": null,
+ "foot": null
+ }
+ },
+
+ /**
+ * Cloned table nodes
+ * @namespace
+ */
+ "clone": {
+ /**
+ * Left column cloned table nodes
+ * @namespace
+ */
+ "left": {
+ /**
+ * Cloned header table
+ * @type node
+ * @default null
+ */
+ "header": null,
+
+ /**
+ * Cloned body table
+ * @type node
+ * @default null
+ */
+ "body": null,
+
+ /**
+ * Cloned footer table
+ * @type node
+ * @default null
+ */
+ "footer": null
+ },
+
+ /**
+ * Right column cloned table nodes
+ * @namespace
+ */
+ "right": {
+ /**
+ * Cloned header table
+ * @type node
+ * @default null
+ */
+ "header": null,
+
+ /**
+ * Cloned body table
+ * @type node
+ * @default null
+ */
+ "body": null,
+
+ /**
+ * Cloned footer table
+ * @type node
+ * @default null
+ */
+ "footer": null
+ }
+ }
+ };
+
+ /* Attach the instance to the DataTables instance so it can be accessed easily */
+ this.s.dt.oFixedColumns = this;
+
+ /* Let's do it */
+ this._fnConstruct( oInit );
+};
+
+
+
+FixedColumns.prototype = {
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Public methods
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /**
+ * Update the fixed columns - including headers and footers. Note that FixedColumns will
+ * automatically update the display whenever the host DataTable redraws.
+ * @returns {void}
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * var oFC = new FixedColumns( oTable );
+ *
+ * // at some later point when the table has been manipulated....
+ * oFC.fnUpdate();
+ */
+ "fnUpdate": function ()
+ {
+ this._fnDraw( true );
+ },
+
+
+ /**
+ * Recalculate the resizes of the 3x3 grid that FixedColumns uses for display of the table.
+ * This is useful if you update the width of the table container. Note that FixedColumns will
+ * perform this function automatically when the window.resize event is fired.
+ * @returns {void}
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * var oFC = new FixedColumns( oTable );
+ *
+ * // Resize the table container and then have FixedColumns adjust its layout....
+ * $('#content').width( 1200 );
+ * oFC.fnRedrawLayout();
+ */
+ "fnRedrawLayout": function ()
+ {
+ this.__fnGridLayout();
+ },
+
+
+ /**
+ * Mark a row such that it's height should be recalculated when using 'semiauto' row
+ * height matching. This function will have no effect when 'none' or 'auto' row height
+ * matching is used.
+ * @param {Node} nTr TR element that should have it's height recalculated
+ * @returns {void}
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * var oFC = new FixedColumns( oTable );
+ *
+ * // manipulate the table - mark the row as needing an update then update the table
+ * // this allows the redraw performed by DataTables fnUpdate to recalculate the row
+ * // height
+ * oFC.fnRecalculateHeight();
+ * oTable.fnUpdate( $('#example tbody tr:eq(0)')[0], ["insert date", 1, 2, 3 ... ]);
+ */
+ "fnRecalculateHeight": function ( nTr )
+ {
+ nTr._DTTC_iHeight = null;
+ nTr.style.height = 'auto';
+ },
+
+
+ /**
+ * Set the height of a given row - provides cross browser compatibility
+ * @param {Node} nTarget TR element that should have it's height recalculated
+ * @param {int} iHeight Height in pixels to set
+ * @returns {void}
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * var oFC = new FixedColumns( oTable );
+ *
+ * // You may want to do this after manipulating a row in the fixed column
+ * oFC.fnSetRowHeight( $('#example tbody tr:eq(0)')[0], 50 );
+ */
+ "fnSetRowHeight": function ( nTarget, iHeight )
+ {
+ var jqBoxHack = $(nTarget).children(':first');
+ var iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height();
+
+ /* Can we use some kind of object detection here?! This is very nasty - damn browsers */
+ if ( $.browser.mozilla || $.browser.opera )
+ {
+ nTarget.style.height = iHeight+"px";
+ }
+ else
+ {
+ $(nTarget).children().height( iHeight-iBoxHack );
+ }
+ },
+
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Private methods (they are of course public in JS, but recommended as private)
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+ /**
+ * Initialisation for FixedColumns
+ * @param {Object} oInit User settings for initialisation
+ * @returns {void}
+ * @private
+ */
+ "_fnConstruct": function ( oInit )
+ {
+ var i, iLen, iWidth,
+ that = this;
+
+ /* Sanity checking */
+ if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' ||
+ this.s.dt.oInstance.fnVersionCheck( '1.8.0' ) !== true )
+ {
+ alert( "FixedColumns "+FixedColumns.VERSION+" required DataTables 1.8.0 or later. "+
+ "Please upgrade your DataTables installation" );
+ return;
+ }
+
+ if ( this.s.dt.oScroll.sX === "" )
+ {
+ this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+
+ "x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+
+ "column fixing when scrolling is not enabled" );
+ return;
+ }
+
+ /* Apply the settings from the user / defaults */
+ this.s = $.extend( true, this.s, FixedColumns.defaults, oInit );
+
+ /* Set up the DOM as we need it and cache nodes */
+ this.dom.grid.dt = $(this.s.dt.nTable).parents('div.dataTables_scroll')[0];
+ this.dom.scroller = $('div.dataTables_scrollBody', this.dom.grid.dt )[0];
+
+ var iScrollWidth = $(this.dom.grid.dt).width();
+ var iLeftWidth = 0;
+ var iRightWidth = 0;
+
+ $('tbody>tr:eq(0)>td', this.s.dt.nTable).each( function (i) {
+ iWidth = $(this).outerWidth();
+ that.s.aiWidths.push( iWidth );
+ if ( i < that.s.iLeftColumns )
+ {
+ iLeftWidth += iWidth;
+ }
+ if ( that.s.iTableColumns-that.s.iRightColumns <= i )
+ {
+ iRightWidth += iWidth;
+ }
+ } );
+
+ if ( this.s.iLeftWidth === null )
+ {
+ this.s.iLeftWidth = this.s.sLeftWidth == 'fixed' ?
+ iLeftWidth : (iLeftWidth/iScrollWidth) * 100;
+ }
+
+ if ( this.s.iRightWidth === null )
+ {
+ this.s.iRightWidth = this.s.sRightWidth == 'fixed' ?
+ iRightWidth : (iRightWidth/iScrollWidth) * 100;
+ }
+
+ /* Set up the DOM that we want for the fixed column layout grid */
+ this._fnGridSetup();
+
+ /* Use the DataTables API method fnSetColumnVis to hide the columns we are going to fix */
+ for ( i=0 ; i 0 )
+ {
+ that.dom.grid.right.body.scrollTop = that.dom.scroller.scrollTop;
+ }
+ } );
+
+ $(window).resize( function () {
+ that._fnGridLayout.call( that );
+ } );
+
+ var bFirstDraw = true;
+ this.s.dt.aoDrawCallback = [ {
+ "fn": function () {
+ that._fnDraw.call( that, bFirstDraw );
+ that._fnGridHeight( that );
+ bFirstDraw = false;
+ },
+ "sName": "FixedColumns"
+ } ].concat( this.s.dt.aoDrawCallback );
+
+ /* Get things right to start with - note that due to adjusting the columns, there must be
+ * another redraw of the main table. It doesn't need to be a full redraw however.
+ */
+ this._fnGridLayout();
+ this._fnGridHeight();
+ this.s.dt.oInstance.fnDraw(false);
+ },
+
+
+ /**
+ * Set up the DOM for the fixed column. The way the layout works is to create a 1x3 grid
+ * for the left column, the DataTable (for which we just reuse the scrolling element DataTable
+ * puts into the DOM) and the right column. In each of he two fixed column elements there is a
+ * grouping wrapper element and then a head, body and footer wrapper. In each of these we then
+ * place the cloned header, body or footer tables. This effectively gives as 3x3 grid structure.
+ * @returns {void}
+ * @private
+ */
+ "_fnGridSetup": function ()
+ {
+ var that = this;
+
+ this.dom.body = this.s.dt.nTable;
+ this.dom.header = this.s.dt.nTHead.parentNode;
+ this.dom.header.parentNode.parentNode.style.position = "relative";
+
+ var nSWrapper =
+ $('')[0];
+ nLeft = nSWrapper.childNodes[0];
+ nRight = nSWrapper.childNodes[1];
+
+ this.dom.grid.wrapper = nSWrapper;
+ this.dom.grid.left.wrapper = nLeft;
+ this.dom.grid.left.head = nLeft.childNodes[0];
+ this.dom.grid.left.body = nLeft.childNodes[1];
+
+ if ( this.s.iRightColumns > 0 )
+ {
+ this.dom.grid.right.wrapper = nRight;
+ this.dom.grid.right.head = nRight.childNodes[0];
+ this.dom.grid.right.body = nRight.childNodes[1];
+ }
+
+ if ( this.s.dt.nTFoot )
+ {
+ this.dom.footer = this.s.dt.nTFoot.parentNode;
+ this.dom.grid.left.foot = nLeft.childNodes[2];
+ if ( this.s.iRightColumns > 0 )
+ {
+ this.dom.grid.right.foot = nRight.childNodes[2];
+ }
+ }
+
+ nSWrapper.appendChild( nLeft );
+ this.dom.grid.dt.parentNode.insertBefore( nSWrapper, this.dom.grid.dt );
+ nSWrapper.appendChild( this.dom.grid.dt );
+
+ this.dom.grid.dt.style.position = "absolute";
+ this.dom.grid.dt.style.top = "0px";
+ this.dom.grid.dt.style.left = this.s.iLeftWidth+"px";
+ this.dom.grid.dt.style.width = ($(this.dom.grid.dt).width()-this.s.iLeftWidth-this.s.iRightWidth)+"px";
+ },
+
+
+ /**
+ * Style and position the grid used for the FixedColumns layout based on the instance settings.
+ * Specifically sLeftWidth ('fixed' or 'absolute'), iLeftWidth (px if fixed, % if absolute) and
+ * there 'right' counterparts.
+ * @returns {void}
+ * @private
+ */
+ "_fnGridLayout": function ()
+ {
+ var oGrid = this.dom.grid;
+ var iTotal = $(oGrid.wrapper).width();
+ var iLeft = 0, iRight = 0, iRemainder = 0;
+
+ if ( this.s.sLeftWidth == 'fixed' )
+ {
+ iLeft = this.s.iLeftWidth;
+ }
+ else
+ {
+ iLeft = ( this.s.iLeftWidth / 100 ) * iTotal;
+ }
+
+ if ( this.s.sRightWidth == 'fixed' )
+ {
+ iRight = this.s.iRightWidth;
+ }
+ else
+ {
+ iRight = ( this.s.iRightWidth / 100 ) * iTotal;
+ }
+
+ iRemainder = iTotal - iLeft - iRight;
+
+ oGrid.left.wrapper.style.width = iLeft+"px";
+ oGrid.dt.style.width = iRemainder+"px";
+ oGrid.dt.style.left = iLeft+"px";
+
+ if ( this.s.iRightColumns > 0 )
+ {
+ oGrid.right.wrapper.style.width = iRight+"px";
+ oGrid.right.wrapper.style.left = (iTotal-iRight)+"px";
+ }
+ },
+
+
+ /**
+ * Recalculate and set the height of the grid components used for positioning of the
+ * FixedColumn display grid.
+ * @returns {void}
+ * @private
+ */
+ "_fnGridHeight": function ()
+ {
+ var oGrid = this.dom.grid;
+ var iHeight = $(this.dom.grid.dt).height();
+
+ oGrid.wrapper.style.height = iHeight+"px";
+ oGrid.left.body.style.height = $(this.dom.scroller).height()+"px";
+ oGrid.left.wrapper.style.height = iHeight+"px";
+
+ if ( this.s.iRightColumns > 0 )
+ {
+ oGrid.right.wrapper.style.height = iHeight+"px";
+ oGrid.right.body.style.height = $(this.dom.scroller).height()+"px";
+ }
+ },
+
+
+ /**
+ * Clone and position the fixed columns
+ * @returns {void}
+ * @param {Boolean} bAll Indicate if the header and footer should be updated as well (true)
+ * @private
+ */
+ "_fnDraw": function ( bAll )
+ {
+ this._fnCloneLeft( bAll );
+ this._fnCloneRight( bAll );
+
+ /* Draw callback function */
+ if ( this.s.fnDrawCallback !== null )
+ {
+ this.s.fnDrawCallback.call( this, this.dom.clone.left, this.dom.clone.right );
+ }
+
+ /* Event triggering */
+ $(this).trigger( 'draw', {
+ "leftClone": this.dom.clone.left,
+ "rightClone": this.dom.clone.right
+ } );
+ },
+
+
+ /**
+ * Clone the right columns
+ * @returns {void}
+ * @param {Boolean} bAll Indicate if the header and footer should be updated as well (true)
+ * @private
+ */
+ "_fnCloneRight": function ( bAll )
+ {
+ if ( this.s.iRightColumns <= 0 )
+ {
+ return;
+ }
+
+ var that = this,
+ i, jq,
+ aiColumns = [];
+
+ for ( i=this.s.iTableColumns-this.s.iRightColumns ; ithead', oClone.header);
+ jqCloneThead.empty();
+
+ /* Add the created cloned TR elements to the table */
+ for ( i=0, iLen=aoCloneLayout.length ; ithead th:eq('+iIndex+')', oClone.header)[0].className =
+ this.s.dt.aoColumns[ aiColumns[iIndex] ].nTh.className;
+
+ $('>thead th:eq('+iIndex+') span.DataTables_sort_icon', oClone.header).each( function (i) {
+ this.className = $('span.DataTables_sort_icon', that.s.dt.aoColumns[ aiColumns[iIndex] ].nTh)[i].className;
+ } );
+ }
+ }
+ this._fnEqualiseHeights( 'thead', this.dom.header, oClone.header );
+
+ /*
+ * Body
+ */
+ if ( this.s.sHeightMatch == 'auto' )
+ {
+ /* Remove any heights which have been applied already and let the browser figure it out */
+ $('>tbody>tr', that.dom.body).css('height', 'auto');
+ }
+
+ if ( oClone.body !== null )
+ {
+ oClone.body.parentNode.removeChild( oClone.body );
+ oClone.body = null;
+ }
+
+ oClone.body = $(this.dom.body).clone(true)[0];
+ oClone.body.className += " DTFC_Cloned";
+ oClone.body.style.paddingBottom = this.s.dt.oScroll.iBarWidth+"px";
+ oClone.body.style.marginBottom = (this.s.dt.oScroll.iBarWidth*2)+"px"; /* For IE */
+ if ( oClone.body.getAttribute('id') !== null )
+ {
+ oClone.body.removeAttribute('id');
+ }
+
+ $('>thead>tr', oClone.body).empty();
+ $('>tfoot', oClone.body).empty();
+
+ var nBody = $('tbody', oClone.body)[0];
+ $(nBody).empty();
+ if ( this.s.dt.aiDisplay.length > 0 )
+ {
+ $('>tbody>tr', that.dom.body).each( function (z) {
+ var n = this.cloneNode(false);
+ var i = that.s.dt.oFeatures.bServerSide===false ?
+ that.s.dt.aiDisplay[ that.s.dt._iDisplayStart+z ] : z;
+ for ( iIndex=0 ; iIndextbody>tr', that.dom.body).each( function (z) {
+ nClone = this.cloneNode(true);
+ nClone.className += ' DTFC_NoData';
+ $('td', nClone).html('');
+ nBody.appendChild( nClone );
+ } );
+ }
+
+ oClone.body.style.width = "100%";
+ oGrid.body.appendChild( oClone.body );
+
+ this._fnEqualiseHeights( 'tbody', that.dom.body, oClone.body );
+
+ /*
+ * Footer
+ */
+ if ( this.s.dt.nTFoot !== null )
+ {
+ if ( bAll )
+ {
+ if ( oClone.footer !== null )
+ {
+ oClone.footer.parentNode.removeChild( oClone.footer );
+ }
+ oClone.footer = $(this.dom.footer).clone(true)[0];
+ oClone.footer.className += " DTFC_Cloned";
+ oClone.footer.style.width = "100%";
+ oGrid.foot.appendChild( oClone.footer );
+
+ /* Copy the footer just like we do for the header */
+ var aoCloneLayout = this._fnCopyLayout( this.s.dt.aoFooter, aiColumns );
+ var jqCloneTfoot = $('>tfoot', oClone.footer);
+ jqCloneTfoot.empty();
+
+ for ( i=0, iLen=aoCloneLayout.length ; itfoot th:eq('+iIndex+')', oClone.footer)[0].className =
+ this.s.dt.aoColumns[ aiColumns[iIndex] ].nTf.className;
+ }
+ }
+ this._fnEqualiseHeights( 'tfoot', this.dom.footer, oClone.footer );
+ }
+
+ /* Equalise the column widths between the header footer and body - body get's priority */
+ var jqBody = $('>tbody>tr:eq(0)', oClone.body);
+ var jqHead = $('>thead>tr:eq(0)', oClone.header);
+ if ( this.s.dt.nTFoot !== null )
+ {
+ var jqFoot = $('>tfoot>tr:eq(0)', oClone.footer);
+ }
+
+ jqBody.children().each( function (i) {
+ var iWidth = $(this).width();
+
+ jqHead.children(':eq('+i+')').width( iWidth );
+ if ( that.s.dt.nTFoot !== null )
+ {
+ jqFoot.children(':eq('+i+')').width( iWidth );
+ }
+ } );
+ },
+
+
+ /**
+ * From a given table node (THEAD etc), get a list of TR direct child elements
+ * @param {Node} nIn Table element to search for TR elements (THEAD, TBODY or TFOOT element)
+ * @returns {Array} List of TR elements found
+ * @private
+ */
+ "_fnGetTrNodes": function ( nIn )
+ {
+ var aOut = [];
+ for ( var i=0, iLen=nIn.childNodes.length ; i'+nodeName+'>tr:eq(0)', original).children(':first'),
+ iBoxHack = jqBoxHack.outerHeight() - jqBoxHack.height(),
+ anOriginal = this._fnGetTrNodes( rootOriginal ),
+ anClone = this._fnGetTrNodes( rootClone );
+
+ for ( i=0, iLen=anClone.length ; i iHeightOriginal ? iHeightClone : iHeightOriginal;
+
+ if ( this.s.sHeightMatch == 'semiauto' )
+ {
+ anOriginal[i]._DTTC_iHeight = iHeight;
+ }
+
+ /* Can we use some kind of object detection here?! This is very nasty - damn browsers */
+ if ( $.browser.msie )
+ {
+ $(anClone[i]).children().height( iHeight-iBoxHack );
+ $(anOriginal[i]).children().height( iHeight-iBoxHack );
+ }
+ else
+ {
+ anClone[i].style.height = iHeight+"px";
+ anOriginal[i].style.height = iHeight+"px";
+ }
+ }
+ }
+};
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Statics
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+/**
+ * FixedColumns default settings for initialisation
+ * @namespace
+ * @static
+ */
+FixedColumns.defaults = {
+ /**
+ * Number of left hand columns to fix in position
+ * @type int
+ * @default 1
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "iLeftColumns": 2
+ * } );
+ */
+ "iLeftColumns": 1,
+
+ /**
+ * Number of right hand columns to fix in position
+ * @type int
+ * @default 0
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "iRightColumns": 1
+ * } );
+ */
+ "iRightColumns": 0,
+
+ /**
+ * Draw callback function which is called when FixedColumns has redrawn the fixed assets
+ * @type function(object, object):void
+ * @default null
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "fnDrawCallback": function () {
+ * alert( "FixedColumns redraw" );
+ * }
+ * } );
+ */
+ "fnDrawCallback": null,
+
+ /**
+ * Type of left column size calculation. Can take the values of "fixed", whereby the iLeftWidth
+ * value will be treated as a pixel value, or "relative" for which case iLeftWidth will be
+ * treated as a percentage value.
+ * @type string
+ * @default fixed
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "sLeftWidth": "relative",
+ * "iLeftWidth": 10 // percentage
+ * } );
+ */
+ "sLeftWidth": "fixed",
+
+ /**
+ * Width to set for the width of the left fixed column(s) - note that the behaviour of this
+ * property is directly effected by the sLeftWidth property. If not defined then this property
+ * is calculated automatically from what has been assigned by DataTables.
+ * @type int
+ * @default null
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "iLeftWidth": 100 // pixels
+ * } );
+ */
+ "iLeftWidth": null,
+
+ /**
+ * Type of right column size calculation. Can take the values of "fixed", whereby the
+ * iRightWidth value will be treated as a pixel value, or "relative" for which case
+ * iRightWidth will be treated as a percentage value.
+ * @type string
+ * @default fixed
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "sRightWidth": "relative",
+ * "iRightWidth": 10 // percentage
+ * } );
+ */
+ "sRightWidth": "fixed",
+
+ /**
+ * Width to set for the width of the right fixed column(s) - note that the behaviour of this
+ * property is directly effected by the sRightWidth property. If not defined then this property
+ * is calculated automatically from what has been assigned by DataTables.
+ * @type int
+ * @default null
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "iRightWidth": 200 // pixels
+ * } );
+ */
+ "iRightWidth": null,
+
+ /**
+ * Height matching algorthim to use. This can be "none" which will result in no height
+ * matching being applied by FixedColumns (height matching could be forced by CSS in this
+ * case), "semiauto" whereby the height calculation will be performed once, and the result
+ * cached to be used again (fnRecalculateHeight can be used to force recalculation), or
+ * "auto" when height matching is performed on every draw (slowest but must accurate)
+ * @type string
+ * @default semiauto
+ * @static
+ * @example
+ * var oTable = $('#example').dataTable( {
+ * "sScrollX": "100%"
+ * } );
+ * new FixedColumns( oTable, {
+ * "sHeightMatch": "auto"
+ * } );
+ */
+ "sHeightMatch": "semiauto"
+};
+
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Constants
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+/**
+ * Name of this class
+ * @constant CLASS
+ * @type String
+ * @default FixedColumns
+ */
+FixedColumns.prototype.CLASS = "FixedColumns";
+
+
+/**
+ * FixedColumns version
+ * @constant FixedColumns.VERSION
+ * @type String
+ * @default See code
+ * @static
+ */
+FixedColumns.VERSION = "2.0.1";
+
+
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Fired events (for documentation)
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+/**
+ * Event fired whenever FixedColumns redraws the fixed columns (i.e. clones the table elements from the main DataTable). This will occur whenever the DataTable that the FixedColumns instance is attached does its own draw.
+ * @name FixedColumns#draw
+ * @event
+ * @param {event} e jQuery event object
+ * @param {object} o Event parameters from FixedColumns
+ * @param {object} o.leftClone Instance's object dom.clone.left for easy reference. This object contains references to the left fixed clumn column's nodes
+ * @param {object} o.rightClone Instance's object dom.clone.right for easy reference. This object contains references to the right fixed clumn column's nodes
+ */
+
+})(jQuery, window, document);