Skip to content

Instantly share code, notes, and snippets.

@oligriffiths
Created May 24, 2013 16:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oligriffiths/5644674 to your computer and use it in GitHub Desktop.
Save oligriffiths/5644674 to your computer and use it in GitHub Desktop.
DataTable
(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