430 lines
13 KiB
JavaScript
430 lines
13 KiB
JavaScript
/**
|
|
* jQuery list plug-in 1.2.1
|
|
* Copyright 2012, Digital Fusion
|
|
* Licensed under the MIT license.
|
|
* http://teamdf.com/jquery-plugins/license/
|
|
*
|
|
* @author Sam Sehnert, Phil Taylor
|
|
*/
|
|
|
|
(function($){
|
|
"use strict";
|
|
|
|
// The name of your plugin. This is used to namespace your
|
|
// plugin methods, object data, and registerd events.
|
|
var plugin_name = 'list';
|
|
var plugin_version = '1.2.1';
|
|
var plugin_logging = true;
|
|
|
|
// Set up the plugin defaults.
|
|
// These will be stored in $this.data(plugin_name).settings,
|
|
// and can be overwritten by having 'options' passed through
|
|
// as the parameter to the init method.
|
|
var defaults = {
|
|
headerSelector : 'dt',
|
|
zIndex : 1
|
|
};
|
|
|
|
var scrollTimeout = null;
|
|
|
|
// Any private methods that the plugin uses, such as event handlers,
|
|
// or small utility functions for parsing data etc that isn't useful
|
|
// outside the context of the plugin code itself.
|
|
var _private = {
|
|
|
|
log : function(){
|
|
// SAFELY call console.log without fear of errors
|
|
// also provides an override for turning off all plugin console output.
|
|
if( plugin_logging && window.console && window.console.log ){
|
|
try {
|
|
// IE 8 doesn't implement apply on the console methods.
|
|
window.console.log.apply(console,arguments);
|
|
} catch (e) {
|
|
// Because IE8 doesn't implement apply for console methods,
|
|
// we simply pass the arguments directly to the log.
|
|
window.console.log( arguments );
|
|
}
|
|
}
|
|
},
|
|
|
|
// Contains events for this plugin.
|
|
events : {
|
|
|
|
/**
|
|
* Window resize (should also be called when resizing the target element).
|
|
*/
|
|
resize : function(){
|
|
var $this = $(this),data = $this.data(plugin_name);
|
|
|
|
if( data ){
|
|
data.fakeHeader.width(data.headers.width());
|
|
data.wrapper.css('maxHeight',$this.css('maxHeight'));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* List element scroll handler event. Called to animate and substitute heading blocks.
|
|
*/
|
|
scroll : function(){
|
|
var $this = $(this),data = $this.data(plugin_name);
|
|
|
|
if( data ){
|
|
|
|
var newHeader = null,
|
|
currentHeader = data.headers.eq( data.currentHeader ),
|
|
nextHeader = data.currentHeader >= data.headers.length-1 ? null : data.headers.eq( data.currentHeader+1 ),
|
|
prevHeader = data.currentHeader <= 0 ? null : data.headers.eq( data.currentHeader-1 ),
|
|
trigger = false;
|
|
|
|
// Make sure the container top position is fresh.
|
|
data.containerTop = $this.offset().top + parseInt($this.css('marginTop'),10) + parseInt($this.css('borderTopWidth'),10);
|
|
data.fakeHeader.css('top',0);
|
|
|
|
// Check the position of the current header rather than the previous header.
|
|
if( prevHeader !== null ){
|
|
|
|
var top = currentHeader.offset().top,
|
|
height = currentHeader.outerHeight();
|
|
|
|
if( top > data.containerTop ){
|
|
|
|
data.fakeHeader.css('top',(top-height)-data.containerTop);
|
|
data.fakeHeader.html(prevHeader.html());
|
|
data.currentHeader = data.currentHeader-1;
|
|
trigger = true;
|
|
}
|
|
|
|
if( (top-height) > data.containerTop ){
|
|
|
|
data.fakeHeader.css('top',0);
|
|
newHeader = data.currentHeader-1;
|
|
}
|
|
|
|
}
|
|
|
|
// Check the position of the next header element.
|
|
if( nextHeader !== null ){
|
|
|
|
var top = nextHeader.offset().top,
|
|
height = nextHeader.outerHeight();
|
|
|
|
if( (top-height) < data.containerTop ){
|
|
|
|
data.fakeHeader.css('top',(top-height)-data.containerTop);
|
|
}
|
|
|
|
if( top < data.containerTop ){
|
|
|
|
data.fakeHeader.css('top',0);
|
|
newHeader = data.currentHeader+1;
|
|
}
|
|
}
|
|
|
|
// Now assign the contents of the previous header.
|
|
if( newHeader !== null ){
|
|
|
|
var $header = data.headers.eq(newHeader);
|
|
|
|
data.currentHeader = newHeader;
|
|
data.fakeHeader.html($header.html());
|
|
trigger = true;
|
|
}
|
|
|
|
var max = ( data.wrapper.scrollTop() >= data.wrapper.prop('scrollHeight') - data.wrapper.height() );
|
|
|
|
if( trigger || max || data.max && !max ){
|
|
// Trigger the headingChange event.
|
|
$this.trigger('headingChange',[data.currentHeader,data.headers.eq(data.currentHeader),max]);
|
|
}
|
|
|
|
data.max = max;
|
|
|
|
// Save the new data.
|
|
$this.data(plugin_name,data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The methods array will allow you to define public methods that
|
|
// can be called using the plugin function using the following syntax;
|
|
//
|
|
// $('.selector').plugin_name( 'my_method'/*, optional arguments */);
|
|
//
|
|
// The 'init' method is special, and will be called when the user calls;
|
|
//
|
|
// $('.selector').plugin_name(/*{ options object }*/);
|
|
var methods = {
|
|
|
|
/**
|
|
* Initialises the plugin.
|
|
*
|
|
* @param options object : An object containing any overrides to the default settings.
|
|
*
|
|
* @return collection : Returns the jQuery collection
|
|
*/
|
|
init : function( options ){
|
|
|
|
// Loop through each passed element.
|
|
return $(this).each(function(){
|
|
|
|
// Settings to the defaults.
|
|
var settings = $.extend({},defaults);
|
|
|
|
// If options exist, lets merge them
|
|
// with our default settings.
|
|
if( typeof options == 'object' ) $.extend( settings, options );
|
|
|
|
// Create shortcuts, and get any existing data.
|
|
var $this = $(this), data = $this.data(plugin_name);
|
|
|
|
// If the plugin hasn't been initialized yet
|
|
if ( ! data ) {
|
|
|
|
// Create the data object.
|
|
data = {
|
|
target : $this, // This element.
|
|
wrapper : $this.wrapInner('<div class="ui-'+plugin_name+'" />').find('.ui-'+plugin_name+''),
|
|
settings : settings, // The settings for this plugin.
|
|
headers : [],
|
|
containerTop : 0,
|
|
currentHeader : 0,
|
|
fakeHeader : null,
|
|
scrolllist : [],
|
|
original : {
|
|
position : '',
|
|
overflowX : '',
|
|
overflowY : ''
|
|
},
|
|
max : false
|
|
}
|
|
|
|
// Add the container class, and the base HTML structure
|
|
$this.addClass('-'+plugin_name+'-container').css({
|
|
position : $this.css('position') == 'absolute' ? 'absolute' : 'relative',
|
|
overflowY : 'hidden'
|
|
});
|
|
|
|
// Grab some variables to set up the list.
|
|
data.headers = $this.find(data.settings.headerSelector);
|
|
data.fakeHeader = data.headers.eq(0).clone().removeAttr('id').addClass('-'+plugin_name+'-fakeheader').replaceWith('<div class="-'+plugin_name+'-fakeheader">'+data.headers.eq(0).html()+'</div>');
|
|
|
|
// bind a scroll event and change the text of the fake heading
|
|
data.wrapper.bind('scroll.'+plugin_name,$.proxy(_private.events.scroll,$this)).css({
|
|
height : '100%',
|
|
maxHeight : $this.css('maxHeight'),
|
|
overflowY : 'scroll',
|
|
position : 'relative'
|
|
});
|
|
|
|
// Set the fake headers
|
|
data.fakeHeader.css({
|
|
position : 'absolute',
|
|
top : 0,
|
|
width : data.headers.width(),
|
|
zIndex : data.settings.zIndex
|
|
});
|
|
|
|
// Bind the resize event to the window.
|
|
$(window).bind('resize.'+plugin_name,$.proxy(_private.events.resize,$this));
|
|
|
|
// Add the fake header before all other children, and set the HTML.
|
|
$this.data(plugin_name,data).prepend( data.fakeHeader );
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Retrieves the current header index that the user is scrolled to.
|
|
*
|
|
* @return int : The index position of the header (relative to the headers collection).
|
|
*/
|
|
header : function(){
|
|
|
|
var $this = $(this),
|
|
data = $this.data(plugin_name);
|
|
|
|
// Only bother if we've set this up before.
|
|
if( data ){
|
|
return data.currentHeader;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Used to scroll to a new header element, or to retrieve the currently set header.
|
|
*
|
|
* @param int newHeader : The index position of the header (relative to the headers collection).
|
|
* @param mixed speed : The animation speed of scrolling to the new header.
|
|
* @param mixed easing : The animation easing to use.
|
|
* @param function completion : The animation completion function to call.
|
|
*
|
|
* @return collection : The collection that this was called with
|
|
*/
|
|
scrollTo : function( newHeader, speed, easing, completion ){
|
|
|
|
return this.each(function(){
|
|
|
|
var $this = $(this),
|
|
data = $this.data(plugin_name);
|
|
|
|
// Only bother if we've set this up before.
|
|
if( data ){
|
|
|
|
// If we've got the header, and its a number
|
|
if( newHeader !== undefined && !isNaN( newHeader ) && newHeader >= 0 && newHeader < data.headers.length ){
|
|
|
|
// Get the new header.
|
|
var $header = data.headers.eq(newHeader),
|
|
borderHeight = $header.outerHeight() - $header.height(),
|
|
scrollTo = $header.position().top + data.wrapper.scrollTop() + borderHeight;
|
|
|
|
// If we're not animating, we need to set the element directly.
|
|
if( !speed ){
|
|
|
|
data.wrapper.stop().scrollTop( scrollTo );
|
|
|
|
// Set as the current header.
|
|
data.currentHeader = newHeader;
|
|
data.fakeHeader.html($header.html());
|
|
|
|
// Trigger the headingChange event.
|
|
$this.trigger('headingChange',[newHeader,$header]);
|
|
|
|
// Store the new header data.
|
|
$this.data(plugin_name,data);
|
|
|
|
} else {
|
|
// If we are animating, the scroll event will fire.
|
|
data.wrapper.stop().animate({scrollTop:scrollTo},speed,easing,completion);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Used to modify settings after initialisation has taken place.
|
|
* an example switch construct has been written to show how you
|
|
* might fire off an update procedure for certain defaults.
|
|
* Should only be called on one element at a time.
|
|
*
|
|
* @param key string : The option name that you want to update.
|
|
* @param value mixed : (opt) The value that you want to set the option to.
|
|
*
|
|
* @return mixed : If no value is passed, will return the value for the passed key, otherwise, returns the jQuery collection.
|
|
*/
|
|
option : function( key, value ){
|
|
|
|
var $this = $(this),
|
|
data = $this.data(plugin_name);
|
|
|
|
// Only bother if we've set this up before.
|
|
if( data ){
|
|
|
|
// Return settings array if no key is provided.
|
|
if( typeof key == 'undefined' ) return data.settings;
|
|
|
|
// The key has to exist, otherwise its invalid.
|
|
if( !key in data.settings ) return false;
|
|
|
|
// Check if we're adding or updating.
|
|
if( typeof value == 'undefined' ){
|
|
return data.settings[key];
|
|
} else {
|
|
data.settings[key] = value;
|
|
return $this;
|
|
}
|
|
}
|
|
},
|
|
|
|
|
|
/**
|
|
* Get the current name and version number of this plugin.
|
|
*
|
|
* @param num bool : Whether to return the version number, or string.
|
|
*
|
|
* @return string | number : The version string or version number.
|
|
*/
|
|
version : function( num ){
|
|
// Returns the version string for this plugin.
|
|
if( num ){
|
|
// Calculate the version number.
|
|
var v = plugin_version.split('.'),
|
|
major = ( Number( v[0] ) || 1 )+'',
|
|
minor = ( Number( v[1] ) || 0 )+'',
|
|
bugfix = ( Number( v[2] ) || 0 )+'',
|
|
fill = '000';
|
|
|
|
// Return the version as a comparable number.
|
|
return Number( fill.slice(0,3-major.length)+major+fill.slice(0,3-minor.length)+minor+fill.slice(0,3-bugfix.length)+bugfix );
|
|
|
|
} else {
|
|
// Return the version string for this plugin.
|
|
return plugin_name+' v'+plugin_version;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Remove all data and events associated with the plugin, and restore
|
|
* the status of any manipulated elmenets to pre-plugin state.
|
|
*
|
|
* @return collection : Returns the jQuery collection.
|
|
*/
|
|
destroy: function(){
|
|
/* Remove the ui plugin from these elements that have it */
|
|
|
|
return this.each(function(){
|
|
|
|
var $this = $(this),
|
|
data = $this.data(plugin_name);
|
|
|
|
// Only bother if we've set this up before.
|
|
if( data ){
|
|
|
|
// Remove wrapper and fakeheader.
|
|
data.wrapper.children().unwrap();
|
|
data.fakeHeader.remove();
|
|
|
|
// Now, remove all data, etc, then
|
|
// reattach this element to its parent, then delete list div.
|
|
$this.css(data.original)
|
|
.removeData(plugin_name)
|
|
.removeClass('-'+plugin_name+'-container')
|
|
.unbind('.'+plugin_name);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Plugin method calling handler.
|
|
*
|
|
* @param method string : (opt) Calls the defined method (or init function if omitted).
|
|
* @param arguments : Any remaining arguments will be passed as arguments to the recieving method.
|
|
*
|
|
* @return mixed : Returns the result of the called method.
|
|
*/
|
|
$.fn[plugin_name] = function( method ){
|
|
// Method calling logic
|
|
if ( methods[method] ) {
|
|
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
|
|
} else if ( typeof method === 'object' || ! method ) {
|
|
return methods.init.apply( this, arguments );
|
|
} else {
|
|
$.error( 'Method ' + method + ' does not exist on jQuery.' + plugin_name );
|
|
}
|
|
}
|
|
|
|
// Initialise $.plugin_name for methods below.
|
|
$[plugin_name] = {};
|
|
|
|
/**
|
|
* Allow console logging from this plugin?
|
|
*
|
|
* @param l bool : (opt) Turn logging on or off...
|
|
*
|
|
* @return bool : Whether this plugin will print to the log or not.
|
|
*/
|
|
$[plugin_name].log = function(l){ if(l!==undefined){ plugin_logging=l; } return plugin_logging; };
|
|
|
|
})(jQuery); |