Created
May 24, 2013 16:24
-
-
Save oligriffiths/5644674 to your computer and use it in GitHub Desktop.
DataTable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function($){ | |
var filters = { | |
int: function(value){return typeof parseInt(value) !== 'NaN' ? parseInt(value) : null}, | |
float: function(value){return typeof parseFloat(value) !== 'NaN' ? parseFloat(value) : null}, | |
string: function(value){return ['string','boolean','number'].indexOf(typeof value) !== -1 ? value+'' : null}, | |
array: function(value){return typeof value === 'object' && value.constructor === Array ? value : null}, | |
object: function(value){return typeof value === 'object' ? value : null} | |
} | |
/** | |
* State object | |
* @param model (optional) - Model reference | |
* @constructor | |
*/ | |
var State = function(model){ | |
this.model = model | |
} | |
State.prototype = { | |
constructor: State, | |
states: {}, | |
/** | |
* Inserts a new state | |
* @param name - The state name | |
* @param filter - A filter type, defaults supported are int, float, string, array, object | |
* @param fallback - Default fallback value, used when the state is reset | |
* @param unique - Defines a unique state, | |
* @param required - Array of required states, all states must be set in order to be unique | |
* @returns {*} | |
*/ | |
insert: function(name, filter, fallback, unique, required){ | |
this.states[name] = { | |
filter: typeof filter === 'string' ? filters[filter] : filter, | |
fallback: typeof fallback !== 'undefined' ? fallback : null, | |
value: null, | |
unique: typeof unique !== 'undefined' ? unique : false, | |
required: typeof required === 'object' && required.constructor === Array ? required : [] | |
} | |
return this | |
}, | |
/** | |
* Retrieve a configuration item and return fallback if there is no element set. | |
* @param name - The state name | |
* @param fallback - The state default value if no state can be found | |
* @returns mixed | |
*/ | |
get: function(name, fallback){ | |
return this.has(name) ? this.states[name].value : fallback; | |
}, | |
/** | |
* Set the a state value | |
* | |
* This function only acts on existing states, if a state has changed it will call back to the model triggering the | |
* onStateChange notifier. | |
* @param name | |
* @param value | |
* @returns {*} | |
*/ | |
set: function(name, value){ | |
if(this.has(name)){ | |
var prev = this.states[name].value; | |
if(prev != value && this.model && this.model.onStateChange){ | |
this.states[name].value = this.states[name].filter ? this.states[name].filter(value) : value; | |
this.model.onStateChange(name, value, prev) | |
} | |
} | |
return this | |
}, | |
/** | |
* Check if a state exists | |
* | |
* @param name - State name | |
* @returns {boolean} | |
*/ | |
has: function(name){ | |
return this.states[name] ? true : false | |
}, | |
/** | |
* Remove an existing state | |
* | |
* @param name | |
* @returns {*} | |
*/ | |
remove: function(name){ | |
this.states[name] = null; | |
return this | |
}, | |
/** | |
* Reset all state data and revert to the default state | |
* | |
* @param fallback If TRUE use defaults when resetting. Default is TRUE | |
* @returns {*} | |
*/ | |
reset: function(fallback){ | |
fallback = typeof fallback !== 'undefined' ? fallback : true | |
for(var i in this.states){ | |
if(typeof this.states[i] == 'Object'){ | |
this.states[i].value = fallback ? this.states[i].default : null; | |
} | |
} | |
return this | |
}, | |
/** | |
* Set the state values from an array | |
* | |
* @param values - An associative array/object of state values by name | |
* @returns {*} | |
*/ | |
setValues: function(values){ | |
for(var i in values){ | |
this.set(i, values[i]) | |
} | |
return this | |
}, | |
/** | |
* Get the state data | |
* | |
* This function only returns states that have been been set. | |
* | |
* @param boolean unique If TRUE only retrieve unique state values, default FALSE | |
* @return array An associative array/object of state values by name | |
* @param unique | |
* @returns {{}} | |
*/ | |
getValues: function(unique){ | |
var data = {} | |
for(var i in this.states){ | |
var state = this.states[i] | |
if(i && state == 'object'){ | |
if(unique){ | |
if(state.unique){ | |
var result = true; | |
//Check related states to see if they're set | |
for(var j in state.required){ | |
if(!this.has(state.required[j]) || !this._validate(this.states[state.required[j]])){ | |
result = false; | |
break; | |
} | |
} | |
//Prepare the data to be returned, Include required states | |
if(result){ | |
data[i] = state.value; | |
for(var j in state.required){ | |
data[j] = this.get(state.required[j]) | |
} | |
} | |
} | |
}else{ | |
data[i] = state.value | |
} | |
} | |
} | |
return data; | |
}, | |
/** | |
* Check if the state information is unique | |
* | |
* @return boolean TRUE if the state is unique, otherwise FALSE. | |
*/ | |
_validate: function(state) | |
{ | |
// Unique values can't be null or empty string. | |
if(!state && ['number','string'].indexOf(state.value) == -1){ | |
return false; | |
} | |
if(typeof state.value === 'object' && state.value.constructor === Array){ | |
var result = false; | |
//The first element of the array can't be null or empty string | |
for(var i in state.value){ | |
if(state.value[i] && ['number','string'].indexOf(state.value) !== -1){ | |
result = true; | |
break; | |
} | |
} | |
return result; | |
} | |
return true; | |
} | |
} | |
/** | |
* Class constructor | |
* @param el - The element datatable is being applied to | |
* @param options - Configuration options | |
* @constructor | |
*/ | |
var DataTable = function(el, options){ | |
this.options = $.extend(this.options, options) | |
this.element = $(el) | |
this.wrapper = $('<div/>',{'style':'position: relative'}) | |
this.element.before(this.wrapper) | |
this.wrapper.append(this.element) | |
this.state = new State(this); | |
this.init(options); | |
} | |
DataTable.prototype = { | |
options: { | |
url: '', | |
icon_white: true, | |
request: true | |
}, | |
constructor: DataTable, | |
/** | |
* Initialize method, loads the header, defines the state, parses and makes a request | |
* @param options | |
*/ | |
init: function(options){ | |
this.loaded = false | |
this.header = this.element.find('thead') | |
this.body = this.element.find('tbody') | |
this.state | |
.insert('sort','string') | |
.insert('direction','string') | |
.setValues(options.state ? options.state : options) | |
this.parseHeader() | |
if(this.options.request) this.request() | |
}, | |
/** | |
* Parses the header of the table and finds and heading with data-sort set. | |
* Optionally, a data-direction can be set on the header to define the default sort direction | |
* @returns {*} | |
*/ | |
parseHeader: function(){ | |
this.header.find('th[data-sort]').each($.proxy(function(index, el){ | |
var $el = $(el); | |
$el.on('click', $.proxy(this.clickHeading, this)) | |
$el.css('cursor','pointer') | |
$el.addClass('clickable') | |
$el.append($('<span />', {'class': 'icon '+(this.options.icon_white ? 'icon-white ' : '')+(true ? 'icon-chevron-up' : 'icon-chevron-down')})) | |
$el.data('direction', $el.data('direction') ? $el.data('direction').toLowerCase(): 'asc'); | |
this.setClasses($el, $el.data('direction')) | |
}, this)) | |
return this | |
}, | |
/** | |
* Heading click event handler, if the sort state hasn't changed, the direction is flipped | |
* @param e | |
*/ | |
clickHeading: function(e){ | |
e.preventDefault(); | |
var el = $(e.target), | |
sort = el.data('sort'), | |
dir = el.data('direction') | |
if(this.state.get('direction') && sort == this.state.get('sort')){ | |
dir = this.toggleDirection(this.state.get('direction')) | |
} | |
this.sort(sort, dir, el) | |
}, | |
/** | |
* Toggles the direction between asc/desc | |
* @param dir | |
* @returns {string} | |
*/ | |
toggleDirection: function(dir){ | |
dir = dir ? dir.toLowerCase() : 'desc'; | |
return dir == 'desc' ? 'asc' : 'desc' | |
}, | |
/** | |
* Sets the classes on the heading and the icon up/down | |
* @param sort | |
* @param dir | |
* @param el | |
* @returns {*} | |
*/ | |
setClasses: function(sort, dir, el){ | |
el = el ? el : this.header.find('th[data-sort="'+sort+'"]') | |
this.header.find('th.active').removeClass('active') | |
if(el){ | |
el.addClass('active') | |
el.data('direction', dir) | |
el.removeClass('dir-asc').removeClass('dir-desc').addClass('dir-'+dir) | |
el.find('span.icon').removeClass('icon-chevron-up').removeClass('icon-chevron-down').addClass('icon-chevron-'+(dir == 'asc' ? 'up':'down')) | |
} | |
return this | |
}, | |
/** | |
* Main sort method, sorts by a column in a direction | |
* @param sort - The sort column | |
* @param dir - The direction, ASC or DESC | |
* @param el (optional) - The element to toggle classes on, if not defined, the element will be matched by data attribute | |
* @returns {*} | |
*/ | |
sort: function(sort, dir, el){ | |
this.setClasses(sort,dir,el) | |
this.state.set('sort', sort) | |
if(dir) this.state.set('direction', dir) | |
return this.request() | |
}, | |
/** | |
* Shows the loader layer | |
*/ | |
showLoader: function(){ | |
if(!this.loader){ | |
this.element.before(this.loader = $('<div />',{'class':'loader',style: 'position: absolute; background: white; text-align: center'})) | |
this.loader.append(this.loader.title = $('<span />', {'class':'text',html: 'Loading...',style: 'position: absolute; top: 50%; left: 50%'})) | |
this.loader.title.css('margin-top', (this.loader.title.height()/-2)+'px') | |
this.loader.title.css('margin-left', (this.loader.title.width()/-2)+'px') | |
this.loader.css('opacity',0.85) | |
this.loader.css('display','none') | |
} | |
this.loader.css('display', 'block') | |
this.loader.css('width', this.element.width()+'px') | |
this.loader.css('height', (this.element.height()-this.header.height())+'px') | |
this.loader.css('top', this.header.height()+'px') | |
this.loader.css('left', 0) | |
}, | |
/** | |
* Hides the loader layer | |
*/ | |
hideLoader: function(){ | |
if(this.loader) this.loader.hide() | |
}, | |
/** | |
* State change event, when the state changes this function gets called | |
* @param name | |
* @param value | |
* @param prev | |
*/ | |
onStateChange: function(name, value, prev){ | |
this.loaded = false; | |
}, | |
/** | |
* Main ajax request function. Converts the state into a querystring, | |
* shows loader, requests and places response into body | |
* @returns {*} | |
*/ | |
request: function(){ | |
if(this.loaded){ | |
if(console) console.log('The state hasn\'t changed since the last request') | |
return this; | |
} | |
this.showLoader() | |
var states = this.state.getValues(); | |
var query = ''; | |
for(var i in states){ | |
if(typeof states[i] !== 'object') query += '&'+i+'='+states[i] | |
} | |
$.ajax({ | |
type: 'GET', | |
url: this.options.url+query, | |
success: $.proxy(function(response){ | |
this.hideLoader(); | |
this.body.empty(); | |
this.body.html(response) | |
this.loaded = true; | |
}, this), | |
error: $.proxy(function(err){ | |
this.hideLoader() | |
}, this) | |
}) | |
return this | |
} | |
} | |
/** | |
* jQuery definition | |
* @param option | |
* @returns {*} | |
*/ | |
$.fn.datatable = function (option) { | |
return this.each(function () { | |
var $this = $(this) | |
, data = $this.data('datatable') | |
, options = typeof option == 'object' && option | |
if(options.state && typeof options.state == 'string') { | |
options.state = null | |
if(console) console.log('The state parameter passed to datatable was not formatted correctly as a JSON string') | |
} | |
if (!data) $this.data('datatable', (data = new DataTable(this, options))) | |
if (typeof option == 'string') data[option]() | |
}) | |
} | |
/** | |
* Data API | |
*/ | |
$(window).on('ready', function () { | |
$('[data-table]').each(function () { | |
var $table = $(this) | |
$table.datatable($table.data()) | |
}) | |
}) | |
})(window.jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment