jquery.datagrid
Version:
A jquery plugin to render datagrid. Works with local or remote data. Designed to be adaptive, simple to use and extendable.
869 lines (770 loc) • 34.2 kB
JavaScript
/*
* Project: jquery.datagrid
* Description: datagrid
* Version: 0.2
* Author: Creative Area www.creative-area.net
* License: Dual licensed under the MIT or GPL Version 2 licenses.
*/
// the semi-colon before function invocation is a safety net against concatenated
// scripts and/or other plugins which may not be closed properly.
;(function ( $, window, document, undefined ) {
// undefined is used here as the undefined global variable in ECMAScript 3 is
// mutable (ie. it can be changed by someone else). undefined isn't really being
// passed in so we can ensure the value of it is truly undefined. In ES5, undefined
// can no longer be modified.
// window is passed through as local variable rather than global
// as this (slightly) quickens the resolution process and can be more efficiently
// minified (especially when both are regularly referenced in your plugin).
// Create the defaults once
var pluginName = "datagrid";
// Default plugin options
var defaults = {
source: "default", // plugin
url: "",
data: false,
autoload: true,
paramsDefault: {},
paramsMapping: {
page: "page",
paging: "paging",
orderby: "orderby",
direction: "direction"
},
parse: function( data ) {
if ( $.type( data ) === "string" ) {
return JSON.parse( data );
} else {
return data;
}
},
col: [],
attr: {},
attrSortable: {},
noData: "no data",
onBefore: false,
onData: false,
onRowData: false,
onComplete: false,
sorter: "default", // plugin
pager: "default", // plugin
pagerPosition: "bottom",
resetContainer: true
};
// Default column options
var defaultsColumn = {
field: "",
title: "",
render: "default", // plugin
sortable: false,
sortableDefaultAsc: true,
attr: {},
attrHeader: {}
};
// tools
var tools = {
// pager helper
getPagerLimits: function( behavior, page, lastpage ) {
if ( $.type( behavior ) === "string" ) {
var oldbehavior = behavior;
behavior = {};
behavior[ oldbehavior ] = {};
}
if ( behavior.sliding ) {
var pages = ( behavior.sliding.pages ) ? behavior.sliding.pages : 3;
return {
minpage: Math.max( 1, Math.min( page - pages, lastpage - ( 2 * pages ) ) ),
maxpage: Math.min( lastpage, Math.max( page + pages, ( 2 * pages ) + 1 ) )
};
}
},
data: {
// sort array of objects by key
sorter: function( data, key, comparator ) {
return data.sort(function(a, b) {
var x = a[key];
var y = b[key];
if ( 1*x - x === 0 ) {
x = 1*x;
}
if ( 1*y - y === 0 ) {
y = 1*y;
}
return ((x < y) ? -comparator : ((x > y) ? comparator : 0));
});
},
// filter data
filter: function( data, filters ) {
var filteredData = [];
for ( var i=0 ; i<data.length ; i++ ) {
var good = true;
for ( var filter in filters ) {
if ( "" + filters[ filter ] !== "" ) {
good = good && ( "" + data[ i ][ filter ] === "" + filters[ filter ] );
}
}
if ( good ) {
filteredData.push( data[ i ] );
}
}
return filteredData;
}
}
};
// Plugins
var plugins = {
sourceArgs: 0,
source: {
"default": function( sourceOptions ) {
var datagrid = this;
var options = {
url: datagrid.settings.url
};
$.extend( options, sourceOptions );
$.post( options.url, datagrid.params(), function( result ) {
datagrid.render( result );
} );
},
"data": function( sourceOptions ) {
var datagrid = this;
var params = datagrid.params();
var page = params[ datagrid.settings.paramsMapping.page ];
var paging = params[ datagrid.settings.paramsMapping.paging ];
var orderby = params[ datagrid.settings.paramsMapping.orderby ];
var direction = params[ datagrid.settings.paramsMapping.direction ];
var options = {
sorter: tools.data.sorter,
filter: tools.data.filter,
data: datagrid.settings.data
};
$.extend( options, sourceOptions );
var filters = $.extend( {}, params );
delete filters[ datagrid.settings.paramsMapping.page ];
delete filters[ datagrid.settings.paramsMapping.paging ];
delete filters[ datagrid.settings.paramsMapping.orderby ];
delete filters[ datagrid.settings.paramsMapping.direction ];
if ( options.data ) {
if ( orderby !== "" ) {
options.data = options.sorter( options.data, orderby, ( direction === "desc" ) ? -1 : 1 );
}
if ( options.filter !== false ) {
options.data = options.filter( options.data, filters );
}
pagedata = options.data.slice( (page-1)*paging, page*paging );
datagrid.render( { total: options.data.length, data: pagedata } );
}
}
},
sorterArgs: 1,
sorter: {
"default": function( ascendant, sorterOptions ) {
var options = {
up: " ↑",
down: " ↓"
};
if ( sorterOptions ) {
$.extend( options, sorterOptions );
}
if ( ascendant ) {
this.append( options.up );
} else {
this.append( options.down );
}
}
},
cellArgs: 1,
cell: {
"default": function( data, cellOptions ) {
return data.value;
}
},
pagerArgs: 2,
pager: {
"default": function( page, lastpage, pagerOptions ) {
var datagrid = this;
var options = {
container: "div",
attrContainer: {},
attrUl: {},
item: "span", // "span", "div", "li" (auto insert ul)
attrItemActive: {},
attrItemDisabled: {},
before: " ",
after: " ",
link: false,
firstPage: false,
prevPage: false,
nextPage: false,
lastPage: false,
hideDisabled: false,
behavior: false // "sliding", "paging"
};
if ( pagerOptions ) {
$.extend( options, pagerOptions );
}
var container = $( "<" + options.container + ">" ).attr( options.attrContainer );
if ( options.item === "li" ) {
var ul = $( "<ul>" ).attr( options.attrUl );
container.append( ul );
container = ul;
}
// default limits (all pages)
var pagerLimits = {
minpage: 1,
maxpage: lastpage
};
// behavior helper
if ( options.behavior ) {
pagerLimits = datagrid.tools.getPagerLimits( options.behavior, datagrid.page(), lastpage );
}
var pagerExtremes = function ( disabled, gopage, label ) {
var element;
if ( disabled ) {
element = ( options.hideDisabled ) ? false : $("<" + options.item + ">").attr( options.attrItemDisabled );
} else {
element = $("<" + options.item + ">", {
data: { "page": gopage },
"class": pluginName + "-page"
});
}
if ( element ) {
element.append(
$("<a>").html( label )
);
container.append( element );
}
};
if ( options.firstPage ) {
pagerExtremes( ( page == 1 ), 1, options.firstPage );
}
if ( options.prevPage ) {
pagerExtremes( ( page == 1 ), page - 1, options.prevPage );
}
for ( var i = pagerLimits.minpage ; i <= pagerLimits.maxpage ; i++ ) {
container.append(
$("<" + options.item + ">", {
data: { "page": i },
attr: ( page == i ) ? options.attrItemActive : {},
"class": pluginName + "-page"
})
.append(
( options.link ) ? $("<a>").html( options.before + i + options.after ) : options.before + i + options.after
)
);
}
if ( options.nextPage ) {
pagerExtremes( ( page == lastpage ), page + 1, options.nextPage );
}
if ( options.lastPage ) {
pagerExtremes( ( page == lastpage ), lastpage, options.lastPage );
}
return container;
}
}
};
// post alias
plugins.source.post = plugins.source.default;
// The actual plugin constructor
function Plugin( element, options ) {
this.element = element;
this.$el = $(element);
this.settings = $.extend( true, {}, defaults, options ) ;
if ( $.type( this.settings.pagerPosition ) === "string" ) {
this.settings.pagerPosition = [ this.settings.pagerPosition ];
}
// extend columns settings with default definition
for ( i=0 ; i<this.settings.col.length ; i++ ) {
this.settings.col[i] = $.extend( {}, defaultsColumn, this.settings.col[i] );
}
// init default _params
this._params = {};
this._params[ this.settings.paramsMapping.page ] = 1;
this._params[ this.settings.paramsMapping.paging ] = 15; // 0 for no paging
this._params[ this.settings.paramsMapping.orderby ] = "";
this._params[ this.settings.paramsMapping.direction ] = "";
$.extend( this._params, this.settings.paramsDefault );
// backup default params for reset
this._paramsDefault = $.extend( {}, this._params, this.settings.paramsDefault );
this.tools = tools;
this._defaults = defaults;
this._name = pluginName;
// cache auto filtered elements (needed to remove filters events)
this._filters = [];
// static data
if ( this.settings.data !== false && !this.settings.source.data ) {
this.settings.source = "data";
}
this.init();
}
Plugin.prototype = {
init: function () {
var datagrid = this;
// sorter events
this.$el.on( "click", "." + pluginName + "-sortable", function(e) {
e.preventDefault();
var $this = $(this);
datagrid.page( 1 );
datagrid.orderby( $this.data( "field" ) );
datagrid.direction( ( $this.data( "direction" ) ) ? "asc" : "desc" );
datagrid.settings.col[ $this.data( "colIndex" ) ].sortableDefaultAsc = !datagrid.settings.col[ $this.data( "colIndex" ) ].sortableDefaultAsc;
datagrid.fetch();
});
// pager events
this.$el.on( "click", "." + pluginName + "-page", function(e) {
e.preventDefault();
datagrid.page( $(this).data( "page" ) );
datagrid.fetch();
});
// if autoload, get data
if ( this.settings.autoload ) {
this.fetch();
}
},
// get params
params: function() {
return this._params;
},
// reset params
reset: function() {
this._params = $.extend( {}, this._paramsDefault );
},
// get / set page
page: function( page ) {
if ( page === undefined ) {
return this._params[ this.settings.paramsMapping.page ];
} else {
this._params[ this.settings.paramsMapping.page ] = page;
}
},
// get / set paging
paging: function( paging ) {
if ( paging === undefined ) {
return this._params[ this.settings.paramsMapping.paging ];
} else {
this._params[ this.settings.paramsMapping.paging ] = paging;
}
},
// get / set orderby
orderby: function( orderby ) {
if ( orderby === undefined ) {
return this._params[ this.settings.paramsMapping.orderby ];
} else {
this._params[ this.settings.paramsMapping.orderby ] = orderby;
}
},
// get / set direction
direction: function( direction ) {
if ( direction === undefined ) {
return this._params[ this.settings.paramsMapping.direction ];
} else {
this._params[ this.settings.paramsMapping.direction ] = direction;
}
},
fetch: function( filters ) {
// onBefore
if ( this.settings.onBefore !== false ) {
this.settings.onBefore.call( this );
}
// extend default params with filters
if ( filters ) {
$.extend( this._params, filters );
}
// loading
this.$el.find( "table" ).css( "opacity", 0.5 );
// get data by source
this.getSource();
},
// plugins
getSource: function() {
( this.source[ $.type( this.settings.source ) ] || $.noop )( this );
},
source: {
"function": function( self ) {
$.when(
self.settings.source.call( self )
).done(
function( result ) {
self.render( result );
}
);
},
"string": function( self ) {
if ( plugins.source[ self.settings.source ] ) {
plugins.source[ self.settings.source ].call( self );
} else {
// no plugin found
self._onError( "Unknown source", self.settings.source );
return false;
}
},
"object": function( self ) {
// plugin
var isLoaded = false;
for ( var p in self.settings.source ) {
if ( plugins.source[ p ] ) {
isLoaded = true;
plugins.source[ p ].call( self, self.settings.source[ p ] );
return true;
}
}
if ( !isLoaded ) {
// no plugin found
self._onError( "Unknown source", JSON.stringify( self.settings.source ) );
return false;
}
}
},
render: function( data ) {
if ( $.type( this.settings.parse ) === "function" ) data = this.settings.parse( data );
this.renderTable( data );
},
// render html table
renderTable: function( result ) {
if ( result === undefined ) {
return;
}
var i;
var table, total;
var self = this;
total = ( result.total ) ? result.total : result.data.length;
// event : onData
if ( $.type( this.settings.onData ) === "function" ) result = ( this.settings.onData( result ) || result );
// reset container
if ( this.settings.resetContainer ) {
this.$el.html("");
}
if ( result.data && result.data.length > 0 ) {
table = $( "<table>" ).attr( this.settings.attr );
var thead = $( "<thead>" ),
tbody = $( "<tbody>" ),
tr = $( "<tr>" ),
td;
// table header
for ( i=0 ; i<this.settings.col.length ; i++ ) {
tr.append( function() {
var th = $( "<th>" , {
"html": self.settings.col[i].title,
"data": { "field": self.settings.col[i].field }
}).attr(self.settings.col[ i ].attrHeader);
// sortable column
if ( self.settings.col[i].sortable ) {
th
.data({
"direction": self.settings.col[i].sortableDefaultAsc,
"colIndex": i
})
.css( "cursor", "pointer" )
.attr( self.settings.attrSortable )
.addClass( pluginName + "-sortable" );
if ( self.orderby() == self.settings.col[i].field ) {
var ascendant = !self.settings.col[i].sortableDefaultAsc;
th.addClass( pluginName + "-sortable-" + (ascendant ? "asc" : "desc") );
// event : sorter
self.getSorter( th, ascendant );
}
}
return th;
});
}
table.append( thead.append( tr ) );
for ( var row = 0 ; row < result.data.length ; row++ ) {
tr = $( "<tr>" );
// event : onRowData
if ( $.type( this.settings.onRowData ) === "function" ) result.data[ row ] = ( this.settings.onRowData( result.data[ row ], row, tr ) || result.data[ row ] );
for ( i = 0 ; i < this.settings.col.length ; i++ ) {
td = $( "<td>" ).attr( this.settings.col[ i ].attr );
// cell render
tr.append(
td.append( this.getCell( result, i, row, td ) )
);
}
tbody.append( tr );
}
table.append( tbody );
// pager
if ( $.inArray( "top", this.settings.pagerPosition ) >= 0 ) {
this.renderPager( total );
}
this.$el.append( table );
// pager
if ( $.inArray( "bottom", this.settings.pagerPosition ) >= 0 ) {
this.renderPager( total );
}
} else {
// no data
this.getNoData();
}
// onComplete
if ( this.settings.onComplete !== false ) {
this.settings.onComplete.call( this );
}
},
renderPager: function( total ) {
this.$el.append( this.getPager( this._params[ this.settings.paramsMapping.page ], Math.ceil( total / this._params[ this.settings.paramsMapping.paging ] ) ) );
},
getSorter: function( th, ascendant ) {
( this.sorter[ $.type( this.settings.sorter ) ] || $.noop )( this, th, ascendant );
},
sorter: {
"function": function( self, th, ascendant ) {
this.settings.sorter.call( th, ascendant );
},
"string": function( self, th, ascendant ) {
if ( plugins.sorter[ self.settings.sorter ] ) {
// plugin
plugins.sorter[ self.settings.sorter ].call( th, ascendant );
return true;
} else {
// no plugin found
self._onError( "Unknown sorter", self.settings.sorter );
return false;
}
},
"object": function( self, th, ascendant ) {
// plugin
var isLoaded = false;
for ( var p in self.settings.sorter ) {
if ( plugins.sorter[ p ] ) {
isLoaded = true;
plugins.sorter[ p ].call( th, ascendant, self.settings.sorter[ p ] );
return true;
}
}
if ( !isLoaded ) {
// no plugin found
self._onError( "Unknown sorter", JSON.stringify( self.settings.sorter ) );
return false;
}
}
},
getCell: function( result, i, row, td ) {
return ( this.cell[ $.type( this.settings.col[ i ].render ) ] || function() { return ""; } )( this, i, td, {
value: result.data[ row ][ this.settings.col[ i ].field ],
field: this.settings.col[ i ].field,
row: result.data[ row ],
colindex: i
});
},
cell: {
"function": function( self, i, td, renderParams ) {
// "this" is the $(td) in the callback
return self.settings.col[ i ].render.call( td, renderParams );
},
"string": function( self, i, td, renderParams ) {
if ( plugins.cell[ self.settings.col[ i ].render ] ) {
// plugin
return plugins.cell[ self.settings.col[ i ].render ].call( td, renderParams );
} else {
// no plugin found
self._onError( "Unknown cell render", self.settings.col[ i ].render );
return "";
}
},
"object": function( self, i, td, renderParams ) {
// plugin
var isLoaded = false;
for ( var p in self.settings.col[ i ].render ) {
if ( plugins.cell[ p ] ) {
isLoaded = true;
return plugins.cell[ p ].call( td, renderParams, self.settings.col[ i ].render[ p ] );
}
}
if ( !isLoaded ) {
// no plugin found
self._onError( "Unknown cell render", JSON.stringify( self.settings.col[ i ].render ) );
return "";
}
}
},
getPager: function( page, lastpage ) {
return ( this.pager[ $.type( this.settings.pager ) ] || this.pager[ "default" ] )( this, page, lastpage );
},
pager: {
"string": function( self, page, lastpage ) {
if ( plugins.pager[ self.settings.pager ] ) {
// plugin
return plugins.pager[ self.settings.pager ].call( self, page, lastpage );
} else {
// no plugin found
self._onError( "Unknown pager", self.settings.pager );
return false;
}
},
"function": function( self, page, lastpage ) {
return self.settings.pager.call( self, page, lastpage );
},
"object": function( self, page, lastpage ) {
// plugin
var isLoaded = false;
for ( var p in self.settings.pager ) {
if ( plugins.pager[ p ] ) {
isLoaded = true;
return plugins.pager[ p ].call( self, page, lastpage, self.settings.pager[ p ] );
}
}
if ( !isLoaded ) {
// no plugin found
self._onError( "Unknown pager", JSON.stringify( self.settings.pager ) );
return false;
}
}
},
getNoData: function() {
return ( this.noData[ $.type( this.settings.noData ) ] || function() { this.element.html(""); } )( this );
},
noData: {
"string": function( self ) {
self.$el.html( self.settings.noData );
},
"function": function( self ) {
self.$el.html( self.settings.noData() );
}
},
// auto filters
filters: function( selector ) {
var self = this;
if ( $.type( selector ) === "string" ) selector = $( selector );
selector.each( function() {
$selector = $(this);
switch ( this.tagName ) {
case "SELECT":
case "TEXTAREA":
case "INPUT":
self.addElementFilter( $selector, "change" );
break;
default:
// loop to find form elements
$selector.find( "[name]" ).each( function() {
if ( $.inArray( $(this)[0].tagName, [ "SELECT", "TEXTAREA", "INPUT" ] ) != -1 ) {
self.addElementFilter( $(this), "change", $selector );
}
});
break;
}
});
},
// add filter on input, select, textarea
addElementFilter: function( $element, eventName, selector ) {
var self = this;
// no auto add if data datagrid-filter = "disable"
if ( $element.data("datagrid-filter") == "disable" ) return false;
$element.on( eventName, function() {
switch ( $element[0].type ) {
case "checkbox":
var isMultiple = ( $element[0].name.substr(-2) == "[]" );
if ( isMultiple ) {
self._params[ $element[0].name ] = [];
selector.find( "[name='" + $element[0].name + "']" ).each( function() {
if ( this.checked ) self._params[ $element[0].name ].push( $(this).val() );
});
} else {
self._params[ $element[0].name ] = ( $element[0].checked ) ? $element.val() : "";
}
break;
default:
self._params[ $element[0].name ] = $element.val();
break;
}
self.page( 1 );
self.fetch();
});
this._filters.push([$element, eventName]);
},
// error
_onError: function( err, value ) {
console.error( "[jquery.datagrid error] " + err + ": " + value + "" );
}
};
// You don't need to change something below:
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations and allowing any
// public function (ie. a function whose name doesn't start
// with an underscore) to be called via the jQuery plugin,
// e.g. $(element).datagrid('functionName', arg1, arg2)
$.fn[pluginName] = function ( options ) {
var args = arguments;
// Is the first parameter an object (options), or was omitted,
// instantiate a new instance of the plugin.
if (options === undefined || typeof options === "object") {
return this.each(function () {
// Only allow the plugin to be instantiated once,
// so we check that the element has no plugin instantiation yet
if (!$.data(this, "plugin_" + pluginName)) {
// if it has no instance, create a new one,
// pass options to our plugin constructor,
// and store the plugin instance
// in the elements jQuery data object.
$.data(this, "plugin_" + pluginName, new Plugin( this, options ));
}
});
// If the first parameter is a string and it doesn't start
// with an underscore or "contains" the `init`-function,
// treat this as a call to a public method.
} else if (typeof options === "string" && options[0] !== "_" && options !== "init") {
// Cache the method call
// to make it possible
// to return a value
var returns;
// Plugins
if (options === "plugin") {
// Add Plugins
// $.fn.datagrid( "plugin", "source|sorter|pager|cell", "pluginName", function );
if ( args.length === 4 && typeof args[1] === "string" && typeof args[2] === "string" && typeof args[3] === "function" ) {
// plugins[ type ][ name ] = callback;
plugins[ args[1] ][ args[2] ] = args[3];
return this;
}
// Extend Plugins
// $.fn.datagrid( "plugin", "source|sorter|pager|cell", "pluginName", "extendedPluginName", extendedOptions );
if ( args.length === 5 && typeof args[1] === "string" && typeof args[2] === "string" && typeof args[3] === "string" ) {
// plugins[ type ][ name ] = callback;
var pluginType = args[1];
plugins[ pluginType ][ args[2] ] = function() {
var _args = [];
for ( var i=0 ; i < arguments.length ; i++ ) {
_args.push(arguments[i]);
}
if ( _args.length > plugins[ pluginType + "Args" ] ) {
_args[ _args.length - 1 ] = $.extend( args[4], _args[ _args.length - 1 ] );
} else {
_args.push( args[4] );
}
return plugins[ pluginType ][ args[3] ].apply( this, _args );
};
return this;
}
}
// Get datagrid instance
if (options === pluginName) {
return this.data("plugin_" + pluginName);
}
this.each(function () {
var instance = $.data(this, "plugin_" + pluginName);
// Tests that there's already a plugin-instance
// and checks that the requested public method exists
if (instance instanceof Plugin && typeof instance[options] === "function") {
// Call the method of our plugin instance,
// and pass it the supplied arguments.
returns = instance[options].apply( instance, Array.prototype.slice.call( args, 1 ) );
}
// Allow instances to be destroyed via the 'destroy' method
if (options === "destroy") {
// remove events
instance.$el.off( "click", "." + pluginName + "-sortable");
instance.$el.off( "click", "." + pluginName + "-page");
for ( var i=0 ; i<instance._filters.length ; i++ ) {
instance._filters[i][0].off(instance._filters[i][1]);
}
// remove plugin
instance.$el.children().remove();
// delete plugin instance saved on element data
$.data(this, "plugin_" + pluginName, null);
instance = null;
}
});
// If the earlier cached method
// gives a value back return the value,
// otherwise return this to preserve chainability.
return returns !== undefined ? returns : this;
}
};
}(jQuery, window, document));