2013-07-02 08:46:44 +00:00
/ * !
* FooTable - Awesome Responsive Tables
* http : //themergency.com/footable
*
* Requires jQuery - http : //jquery.com/
*
* Copyright 2012 Steven Usher & Brad Vincent
* Released under the MIT license
* You are free to use FooTable in commercial projects as long as this copyright header is left intact .
*
* Date : 18 Nov 2012
* /
( function ( $ , w , undefined ) {
w . footable = {
options : {
delay : 100 , // The number of millseconds to wait before triggering the react event
breakpoints : { // The different screen resolution breakpoints
phone : 480 ,
tablet : 1024
} ,
parsers : { // The default parser to parse the value out of a cell (values are used in building up row detail)
alpha : function ( cell ) {
return $ ( cell ) . data ( 'value' ) || $ . trim ( $ ( cell ) . text ( ) ) ;
}
} ,
toggleSelector : '.detail-row' , //the selector to show/hide the detail row
createDetail : function ( element , data ) { //creates the contents of the detail row
for ( var i = 0 ; i < data . length ; i ++ ) {
element . append ( '<div><strong>' + data [ i ] . name + '</strong> : ' + data [ i ] . display + '</div>' ) ;
}
} ,
classes : {
loading : 'footable-loading' ,
loaded : 'footable-loaded' ,
sorted : 'footable-sorted' ,
descending : 'footable-sorted-desc' ,
indicator : 'footable-sort-indicator'
} ,
debug : false // Whether or not to log information to the console.
} ,
version : {
major : 0 , minor : 1 ,
toString : function ( ) {
return w . footable . version . major + '.' + w . footable . version . minor ;
} ,
parse : function ( str ) {
version = /(\d+)\.?(\d+)?\.?(\d+)?/ . exec ( str ) ;
return {
major : parseInt ( version [ 1 ] ) || 0 ,
minor : parseInt ( version [ 2 ] ) || 0 ,
patch : parseInt ( version [ 3 ] ) || 0
} ;
}
} ,
plugins : {
_validate : function ( plugin ) {
///<summary>Simple validation of the <paramref name="plugin"/> to make sure any members called by Foobox actually exist.</summary>
///<param name="plugin">The object defining the plugin, this should implement a string property called "name" and a function called "init".</param>
if ( typeof plugin [ 'name' ] !== 'string' ) {
if ( w . footable . options . debug == true ) console . error ( 'Validation failed, plugin does not implement a string property called "name".' , plugin ) ;
return false ;
}
if ( ! $ . isFunction ( plugin [ 'init' ] ) ) {
if ( w . footable . options . debug == true ) console . error ( 'Validation failed, plugin "' + plugin [ 'name' ] + '" does not implement a function called "init".' , plugin ) ;
return false ;
}
if ( w . footable . options . debug == true ) console . log ( 'Validation succeeded for plugin "' + plugin [ 'name' ] + '".' , plugin ) ;
return true ;
} ,
registered : [ ] , // An array containing all registered plugins.
register : function ( plugin , options ) {
///<summary>Registers a <paramref name="plugin"/> and its default <paramref name="options"/> with Foobox.</summary>
///<param name="plugin">The plugin that should implement a string property called "name" and a function called "init".</param>
///<param name="options">The default options to merge with the Foobox's base options.</param>
if ( w . footable . plugins . _validate ( plugin ) ) {
w . footable . plugins . registered . push ( plugin ) ;
if ( options != undefined && typeof options === 'object' ) $ . extend ( true , w . footable . options , options ) ;
if ( w . footable . options . debug == true ) console . log ( 'Plugin "' + plugin [ 'name' ] + '" has been registered with the Foobox.' , plugin ) ;
}
} ,
init : function ( instance ) {
///<summary>Loops through all registered plugins and calls the "init" method supplying the current <paramref name="instance"/> of the Foobox as the first parameter.</summary>
///<param name="instance">The current instance of the Foobox that the plugin is being initialized for.</param>
for ( var i = 0 ; i < w . footable . plugins . registered . length ; i ++ ) {
try {
w . footable . plugins . registered [ i ] [ 'init' ] ( instance ) ;
} catch ( err ) {
if ( w . footable . options . debug == true ) console . error ( err ) ;
}
}
}
}
} ;
var instanceCount = 0 ;
$ . fn . footable = function ( options ) {
///<summary>The main constructor call to initialize the plugin using the supplied <paramref name="options"/>.</summary>
///<param name="options">
///<para>A JSON object containing user defined options for the plugin to use. Any options not supplied will have a default value assigned.</para>
///<para>Check the documentation or the default options object above for more information on available options.</para>
///</param>
options = options || { } ;
var o = $ . extend ( true , { } , w . footable . options , options ) ; //merge user and default options
return this . each ( function ( ) {
instanceCount ++ ;
this . footable = new Footable ( this , o , instanceCount ) ;
} ) ;
} ;
//helper for using timeouts
function Timer ( ) {
///<summary>Simple timer object created around a timeout.</summary>
var t = this ;
t . id = null ;
t . busy = false ;
t . start = function ( code , milliseconds ) {
///<summary>Starts the timer and waits the specified amount of <paramref name="milliseconds"/> before executing the supplied <paramref name="code"/>.</summary>
///<param name="code">The code to execute once the timer runs out.</param>
///<param name="milliseconds">The time in milliseconds to wait before executing the supplied <paramref name="code"/>.</param>
if ( t . busy ) { return ; }
t . stop ( ) ;
t . id = setTimeout ( function ( ) {
code ( ) ;
t . id = null ;
t . busy = false ;
} , milliseconds ) ;
t . busy = true ;
} ;
t . stop = function ( ) {
///<summary>Stops the timer if its runnning and resets it back to its starting state.</summary>
if ( t . id != null ) {
clearTimeout ( t . id ) ;
t . id = null ;
t . busy = false ;
}
} ;
} ;
function Footable ( t , o , id ) {
///<summary>Inits a new instance of the plugin.</summary>
///<param name="t">The main table element to apply this plugin to.</param>
///<param name="o">The options supplied to the plugin. Check the defaults object to see all available options.</param>
///<param name="id">The id to assign to this instance of the plugin.</param>
var ft = this ;
ft . id = id ;
ft . table = t ;
ft . options = o ;
ft . breakpoints = [ ] ;
ft . breakpointNames = '' ;
ft . columns = { } ;
var opt = ft . options ;
var cls = opt . classes ;
// This object simply houses all the timers used in the footable.
ft . timers = {
resize : new Timer ( ) ,
register : function ( name ) {
ft . timers [ name ] = new Timer ( ) ;
return ft . timers [ name ] ;
}
} ;
w . footable . plugins . init ( ft ) ;
ft . init = function ( ) {
var $window = $ ( w ) , $table = $ ( ft . table ) ;
if ( $table . hasClass ( cls . loaded ) ) {
//already loaded FooTable for the table, so don't init again
ft . raise ( 'footable_already_initialized' ) ;
return ;
}
$table . addClass ( cls . loading ) ;
// Get the column data once for the life time of the plugin
$table . find ( '> thead > tr > th, > thead > tr > td' ) . each ( function ( ) {
var data = ft . getColumnData ( this ) ;
ft . columns [ data . index ] = data ;
var count = data . index + 1 ;
//get all the cells in the column
var $column = $table . find ( '> tbody > tr > td:nth-child(' + count + ')' ) ;
//add the className to the cells specified by data-class="blah"
if ( data . className != null ) $column . not ( '.footable-cell-detail' ) . addClass ( data . className ) ;
} ) ;
// Create a nice friendly array to work with out of the breakpoints object.
for ( var name in opt . breakpoints ) {
ft . breakpoints . push ( { 'name' : name , 'width' : opt . breakpoints [ name ] } ) ;
ft . breakpointNames += ( name + ' ' ) ;
}
// Sort the breakpoints so the smallest is checked first
ft . breakpoints . sort ( function ( a , b ) { return a [ 'width' ] - b [ 'width' ] ; } ) ;
//bind the toggle selector click events
ft . bindToggleSelectors ( ) ;
ft . raise ( 'footable_initializing' ) ;
$table . bind ( 'footable_initialized' , function ( e ) {
//resize the footable onload
ft . resize ( ) ;
//remove the loading class
$table . removeClass ( cls . loading ) ;
//hides all elements within the table that have the attribute data-hide="init"
$table . find ( '[data-init="hide"]' ) . hide ( ) ;
$table . find ( '[data-init="show"]' ) . show ( ) ;
//add the loaded class
$table . addClass ( cls . loaded ) ;
} ) ;
$window
. bind ( 'resize.footable' , function ( ) {
ft . timers . resize . stop ( ) ;
ft . timers . resize . start ( function ( ) {
ft . raise ( 'footable_resizing' ) ;
ft . resize ( ) ;
ft . raise ( 'footable_resized' ) ;
} , opt . delay ) ;
} ) ;
ft . raise ( 'footable_initialized' ) ;
} ;
//moved this out into it's own function so that it can be called from other add-ons
ft . bindToggleSelectors = function ( ) {
var $table = $ ( ft . table ) ;
$table . find ( opt . toggleSelector ) . unbind ( 'click.footable' ) . bind ( 'click.footable' , function ( e ) {
if ( $table . is ( '.breakpoint' ) ) {
var $row = $ ( this ) . is ( 'tr' ) ? $ ( this ) : $ ( this ) . parents ( 'tr:first' ) ;
ft . toggleDetail ( $row . get ( 0 ) ) ;
}
return false ;
} ) ;
} ;
ft . parse = function ( cell , column ) {
var parser = opt . parsers [ column . type ] || opt . parsers . alpha ;
return parser ( cell ) ;
} ;
ft . getColumnData = function ( th ) {
var $th = $ ( th ) , hide = $th . data ( 'hide' ) ;
hide = hide || '' ;
hide = hide . split ( ',' ) ;
var data = {
'index' : $th . index ( ) ,
'hide' : { } ,
'type' : $th . data ( 'type' ) || 'alpha' ,
'name' : $th . data ( 'name' ) || $ . trim ( $th . text ( ) ) ,
'ignore' : $th . data ( 'ignore' ) || false ,
'className' : $th . data ( 'class' ) || null
} ;
data . hide [ 'default' ] = ( $th . data ( 'hide' ) === "all" ) || ( $ . inArray ( 'default' , hide ) >= 0 ) ;
for ( var name in opt . breakpoints ) {
data . hide [ name ] = ( $th . data ( 'hide' ) === "all" ) || ( $ . inArray ( name , hide ) >= 0 ) ;
}
var e = ft . raise ( 'footable_column_data' , { 'column' : { 'data' : data , 'th' : th } } ) ;
return e . column . data ;
} ;
ft . getViewportWidth = function ( ) {
return window . innerWidth || ( document . body ? document . body . offsetWidth : 0 ) ;
} ;
ft . getViewportHeight = function ( ) {
return window . innerHeight || ( document . body ? document . body . offsetHeight : 0 ) ;
} ;
ft . hasBreakpointColumn = function ( breakpoint ) {
for ( var c in ft . columns ) {
if ( ft . columns [ c ] . hide [ breakpoint ] ) {
return true ;
}
}
return false ;
} ;
ft . resize = function ( ) {
var $table = $ ( ft . table ) ;
var info = {
'width' : $table . width ( ) , //the table width
'height' : $table . height ( ) , //the table height
'viewportWidth' : ft . getViewportWidth ( ) , //the width of the viewport
'viewportHeight' : ft . getViewportHeight ( ) , //the width of the viewport
'orientation' : null
} ;
info . orientation = info . viewportWidth > info . viewportHeight ? 'landscape' : 'portrait' ;
if ( info . viewportWidth < info . width ) info . width = info . viewportWidth ;
if ( info . viewportHeight < info . height ) info . height = info . viewportHeight ;
var pinfo = $table . data ( 'footable_info' ) ;
$table . data ( 'footable_info' , info ) ;
// This (if) statement is here purely to make sure events aren't raised twice as mobile safari seems to do
if ( ! pinfo || ( ( pinfo && pinfo . width && pinfo . width != info . width ) || ( pinfo && pinfo . height && pinfo . height != info . height ) ) ) {
var current = null , breakpoint ;
for ( var i = 0 ; i < ft . breakpoints . length ; i ++ ) {
breakpoint = ft . breakpoints [ i ] ;
if ( breakpoint && breakpoint . width && info . width <= breakpoint . width ) {
current = breakpoint ;
break ;
}
}
var breakpointName = ( current == null ? 'default' : current [ 'name' ] ) ;
var hasBreakpointFired = ft . hasBreakpointColumn ( breakpointName ) ;
$table
. removeClass ( 'default breakpoint' ) . removeClass ( ft . breakpointNames )
. addClass ( breakpointName + ( hasBreakpointFired ? ' breakpoint' : '' ) )
. find ( '> thead > tr > th' ) . each ( function ( ) {
var data = ft . columns [ $ ( this ) . index ( ) ] ;
var count = data . index + 1 ;
//get all the cells in the column
var $column = $table . find ( '> tbody > tr > td:nth-child(' + count + '), > tfoot > tr > td:nth-child(' + count + '), > colgroup > col:nth-child(' + count + ')' ) . add ( this ) ;
if ( data . hide [ breakpointName ] == false ) $column . show ( ) ;
else $column . hide ( ) ;
} )
. end ( )
. find ( '> tbody > tr.footable-detail-show' ) . each ( function ( ) {
ft . createOrUpdateDetailRow ( this ) ;
} ) ;
$table . find ( '> tbody > tr.footable-detail-show:visible' ) . each ( function ( ) {
var $next = $ ( this ) . next ( ) ;
if ( $next . hasClass ( 'footable-row-detail' ) ) {
if ( breakpointName == 'default' && ! hasBreakpointFired ) $next . hide ( ) ;
else $next . show ( ) ;
}
} ) ;
// adding .footable-last-column to the last th and td in order to allow for styling if the last column is hidden (which won't work using :last-child)
$table . find ( '> thead > tr > th.footable-last-column,> tbody > tr > td.footable-last-column' ) . removeClass ( 'footable-last-column' ) ;
$table . find ( '> thead > tr > th:visible:last,> tbody > tr > td:visible:last' ) . addClass ( 'footable-last-column' ) ;
ft . raise ( 'footable_breakpoint_' + breakpointName , { 'info' : info } ) ;
}
} ;
ft . toggleDetail = function ( actualRow ) {
var $row = $ ( actualRow ) ,
created = ft . createOrUpdateDetailRow ( $row . get ( 0 ) ) ,
$next = $row . next ( ) ;
if ( ! created && $next . is ( ':visible' ) ) {
$row . removeClass ( 'footable-detail-show' ) ;
//only hide the next row if it's a detail row
if ( $next . hasClass ( 'footable-row-detail' ) )
2013-07-22 08:31:44 +00:00
$next . find ( '.footable-row-detail-inner' ) . slideUp ( 300 , function ( ) {
$next . hide ( ) ;
} ) ;
2013-07-02 08:46:44 +00:00
} else {
$row . addClass ( 'footable-detail-show' ) ;
$next . show ( ) ;
2013-07-22 08:31:44 +00:00
$next . find ( '.footable-row-detail-inner' ) . slideDown ( 300 ) ;
2013-07-02 08:46:44 +00:00
}
} ;
ft . createOrUpdateDetailRow = function ( actualRow ) {
var $row = $ ( actualRow ) , $next = $row . next ( ) , $detail , values = [ ] ;
if ( $row . is ( ':hidden' ) ) return ; //if the row is hidden for some readon (perhaps filtered) then get out of here
$row . find ( '> td:hidden' ) . each ( function ( ) {
var column = ft . columns [ $ ( this ) . index ( ) ] ;
if ( column . ignore == true ) return true ;
values . push ( { 'name' : column . name , 'value' : ft . parse ( this , column ) , 'display' : $ . trim ( $ ( this ) . html ( ) ) } ) ;
} ) ;
if ( values . length == 0 ) //return if we don't have any data to show
return ;
var colspan = $row . find ( '> td:visible' ) . length ;
var exists = $next . hasClass ( 'footable-row-detail' ) ;
if ( ! exists ) { // Create
$next = $ ( '<tr class="footable-row-detail"><td class="footable-cell-detail"><div class="footable-row-detail-inner"></div></td></tr>' ) ;
$row . after ( $next ) ;
}
$next . find ( '> td:first' ) . attr ( 'colspan' , colspan ) ;
$detail = $next . find ( '.footable-row-detail-inner' ) . empty ( ) ;
opt . createDetail ( $detail , values ) ;
return ! exists ;
} ;
ft . raise = function ( eventName , args ) {
args = args || { } ;
var def = { 'ft' : ft } ;
$ . extend ( true , def , args ) ;
var e = $ . Event ( eventName , def ) ;
if ( ! e . ft ) { $ . extend ( true , e , def ) ; } //pre jQuery 1.6 which did not allow data to be passed to event object constructor
$ ( ft . table ) . trigger ( e ) ;
return e ;
} ;
ft . init ( ) ;
return ft ;
} ;
} ) ( jQuery , window ) ;