/******************************************************* Author: Tim Miller Date: 7/16/2009 Filename: jquery.contentSelector-[VERSION].js Current version: v.1.05 Change log: v.1.05 - Transition to true jQuery plugin. Updated help info. v.1.04 - Modified the midChange callback function timing formula from using just the tDelay to (tDelay+tDuration)/2. FROM: setTimeout(function(){ context.doMidChange(); }, context.tDelay*1000); TO: setTimeout(function(){ context.doMidChange(); }, (context.tDelay+context.tDuration)/2*1000); v.1.03 - Added beforeChange, midChange and afterChange callback functions. afterChange replaces callback which is still here for compatibility. v.1.02 - Only set the children to position absolute if the transition type is not equal to none. - Only set the opacity of the children if the transition type is fade. v.1.01 - Fixed next item bug. If you were to go to call nextItem for the same item you were currently on it would turn off the controls then return. Moved the return to above the control deactivation. ***** THIS PLUGIN REQUIRES THE JQUERY LIBRARY ***** ***** Some transition effects require the jQuery UI (slideLR). ***** ***** REQUIRED READING: For some of the transition effects to work properly the children elements must be POSITIONED ABSOLUTELY. When the contentSelector is created it sets the position property of the parent element to "RELATIVE" and the position property of each of the chilren to "ABSOLUTE" (only when certain transition effects are used). The height of the parent object is set to the height of the tallest child element. ***** ======================================== The following config settings are allowed: prop. name | description | default --------------------------------------------------------------------------------------------------------------- childJQSelector | The jQuery selector for children. | div isRand | If the script should randomly pick the first child to show. boolean | true defaultItemIndex | If isRand is false you can set the first item you would like to show. | 1 transition | Type of transition between objects. Options are: none, fade, slide. | none tSlideDir | Only applicable to the slide transition. Options are: left, right, up, down.| left tDuration | Transition duration (seconds). | 0.5 tDelay | Transition delay. Default depends on the transition. Enables crossfading... | null tMin | Only applicable to the fade transition. Transition min. | 0.0 tMax | Only applicable to the fade transition. Transition max. | 1.0 slideShow | Auto change. boolean | false ssTimer | Slideshow timer (seconds). | 5 beoreChange | Function runs before item change. | none midChange | Function runs mid item change. Uses tDelay time to trigger function. | none afterChange | Function runs after item change is complete. | none Still supported but use options above instead: callback | Callback function runs after next item is complete. | none ======================================== The following are internal settings and are only available as read only properties: prop. name | description | default --------------------------------------------------------------------------------------------------------------- firstLoad | Is this the first load of the content selector. | true numItems | The number of child elements matching the child selector. | 0 currentItemIndex | The currently showing item index. This one is zero based. | 0 currentItemObj | The currently showing item object. | null ssInterval | The slideshow timer interval. | null allowControls | The content selector only responds to control events if not already busy. | true tSlideOpDir | Used for previousItem navigation w/ slide transition. | right tSlideDelay | Slide transition helper. | 0 ======================================== Available methods: name/description --------------------------------------------------------------------------------------------------------------- .nextItem([index]) ----------------------------- Used to navigate to an item in the stack. If called w/o the index param, the next item in the stack will be shown. If called with the index param the item requested will be shown. If the plugin is set to use the slide transition, the tSlideDir is used. .previousItem([index]) ----------------------------- Used to navigate to an item in the stack. If called w/o the index param, the previous item in the stack will be shown. If called with the index param the item requested will be shown. If the plugin is set to use the slide transition, the tSlideOpDir (oposite direction) is used. *** The index property of the above methods is 1 based so it makes more sense to the designers. .startSlideshow() && .stopSlideshow() ----------------------------- Methods used to control the automatic changing of items. ======================================== Implementation examples: Initialize: $("div#inFocusContainer").contentSelector(); OR $("div#inFocusContainer").contentSelector({ childJQSelector : "img", isRand : false, defaultItemIndex : 4 }); To get a config setting after initialization send the setting name to the contentSelector: $("div#inFocusContainer").contentSelector('ssTimer'); To set a config setting after initialization send the setting name with the new value to the contentSelector: $("div#inFocusContainer").contentSelector('ssTimer', 7); *******************************************************/ (function($) { // Config var config = { childJQSelector : 'div', isRand : true, defaultItemIndex : 1, transition : 'none', tSlideDir : 'left', tDuration : 0.5, tDelay : null, tMin : 0.0, tMax : 1.0, slideShow : false, ssTimer : 5, beforeChange : null, midChange : null, afterChange : null, callback : null }; // Methods var methods = { // Initialize init: function(options){ return this.each(function(){ var $this = $(this); // Attempt to grab saved settings, if they don't exist we'll get "undefined". var settings = $this.data('contentSelector'); // If we could't grab settings, create them from defaults and passed options if(typeof(settings) == "undefined"){ // Internal config, should not be set in options var defaults = { firstLoad : true, numItems : 0, currentItemIndex : 0, currentItemObj : null, ssInterval : null, allowControls : true, tSlideOpDir : 'right', tSlideDelay : 0 }; settings = $.extend({}, config, options); settings = $.extend({}, settings, defaults); } else { // We got settings, merge our passed options in with them (optional) settings = $.extend({}, settings, options); } if(settings.transition == "fade"){ if(settings.tDelay == null) settings.tDelay = settings.tDuration; } else if(settings.transition == "slide"){ switch(settings.tSlideDir){ case "left": settings.tSlideOpDir = "right"; if(settings.tDelay == null) settings.tDelay = 0.1; break; case "right": settings.tSlideOpDir = "left"; if(settings.tDelay == null) settings.tDelay = 0.1; break; case "up": settings.tSlideOpDir = "down"; if(settings.tDelay == null) settings.tDelay = 0.45; break; case "down": settings.tSlideOpDir = "up"; if(settings.tDelay == null) settings.tDelay = 0.45; break; } } var parentHeight = 0; $this.children(settings.childJQSelector).each(function(){ var childObj = $(this); settings.numItems++; if(childObj.attr("id") == ""){ childObj.attr("id", "CS_"+$this.attr("id")+"_"+settings.numItems); } if($(this).outerHeight(true) > parentHeight) parentHeight = $(this).outerHeight(true); if(settings.transition != "none") childObj.css("position", "absolute"); if(settings.transition == "fade") childObj.css("opacity", settings.tMin); childObj.css({ "display": "none" }); }); if(settings.numItems == 0) $.error('No child elements were found matching the provided selector: ' + settings.childJQSelector + '.'); $this.css({ "position": "relative", "height": parentHeight+"px" }); // Save modified settings $this.data('contentSelector', settings); // Determine which item to display first and show it if(settings.isRand){ methods.rand.apply($this); }else{ methods.nextItem.call($this, settings.defaultItemIndex); } }); }, // Function to randomly select a child element inside the containing object to display. rand: function(){ return this.each(function() { var $this = $(this); var settings = $this.data('contentSelector'); var randNum = Math.ceil(Math.random() * settings.numItems); settings.currentItemIndex = randNum-1; $this.data('contentSelector', settings); methods.nextItem.apply($this); }); }, // Function to flip through the child elements inside a containing object in order. nextItem: function(opt){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); if(opt){ var index = opt; } if(!settings.allowControls) return; if(typeof index != "undefined" && index == settings.currentItemIndex) return; settings.allowControls = false; var index = (typeof index == "undefined")?settings.currentItemIndex:index-1; var nextNum = (index >= settings.numItems)?1:index+1; var nextItemObj; if(settings.slideShow) methods.clearUpdateInterval.apply($this); // Set directions for slide effect. Have to do this so the slides move the right direction when jumping to an index // instead of using the next/previous. Need to set these before the currentItemIndex gets set. var useDir = (index >= settings.currentItemIndex)?settings.tSlideDir:settings.tSlideOpDir; var useOpDir = (index >= settings.currentItemIndex)?settings.tSlideOpDir:settings.tSlideDir; settings.currentItemIndex = nextNum; methods.doBeforeChange.apply($this); var foundItems = 0; $this.children(settings.childJQSelector).each(function(){ foundItems++; if(foundItems == settings.currentItemIndex){ nextItemObj = $(this); } }); if(settings.firstLoad){ methods.doMidChange.apply($this); if(settings.transition == "none" || settings.transition == "slide"){ nextItemObj.css({ "display": "block" }); }else if(settings.transition == "fade"){ nextItemObj.fadeTo(settings.tDuration*1000, settings.tMax); } settings.firstLoad = false; settings.currentItemObj = nextItemObj; if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); }else{ if(settings.transition == "none"){ settings.currentItemObj.css("display", "none"); methods.doMidChange.apply($this); nextItemObj.css("display", "block"); settings.currentItemObj = nextItemObj; if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); } else if(settings.transition == "fade"){ settings.currentItemObj.fadeTo(settings.tDuration*1000, settings.tMin, function(){ settings.currentItemObj.css("display", "none"); settings.currentItemObj = nextItemObj; }); setTimeout(function(){ methods.doMidChange.apply($this); }, (settings.tDelay+settings.tDuration)/2*1000); nextItemObj.delay(settings.tDelay*1000).css("display", "block").fadeTo(settings.tDuration*1000, settings.tMax, function(){ if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); }); } else if(settings.transition == "slide"){ settings.currentItemObj.delay(12).hide("slide", { direction: useDir }, settings.tDuration*1000); setTimeout(function(){ methods.doMidChange.apply($this); }, (settings.tDelay+settings.tDuration)/2*1000); nextItemObj.delay(settings.tDelay*1000).show("slide", { direction: useOpDir }, settings.tDuration*1000, function(){ if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); }); settings.currentItemObj = nextItemObj; } } }); }, // Function to flip through the child elements inside a containing object in reverse-order. previousItem: function(options){ return this.each(function() { var $this = $(this); var settings = $this.data('contentSelector'); if(options){ var index = options.index; } if(!settings.allowControls) return; settings.allowControls = false; var index = (typeof index == "undefined")?settings.currentItemIndex:index-1; var prevNum = (index <= 1)?settings.numItems:index-1; var previousItemObj; if(settings.slideShow) methods.clearUpdateInterval.apply($this); settings.currentItemIndex = prevNum; methods.doBeforeChange.apply($this); var foundItems = 0; $this.children(settings.childJQSelector).each(function(){ foundItems++; if(foundItems == settings.currentItemIndex){ previousItemObj = $(this); } }); if(settings.firstLoad){ methods.doMidChange.apply($this); previousItemObj.css({ "display": "block" }); settings.firstLoad = false; settings.currentItemObj = previousItemObj; if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); }else{ if(settings.transition == "none"){ settings.currentItemObj.css("display", "none"); methods.doMidChange.apply($this); previousItemObj.css("display", "block"); settings.currentItemObj = previousItemObj; if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); } else if(settings.transition == "fade"){ settings.currentItemObj.fadeTo(settings.tDuration*1000, settings.tMin, function(){ settings.currentItemObj.css("display", "none"); settings.currentItemObj = previousItemObj; }); setTimeout(function(){ methods.doMidChange.apply($this); }, (settings.tDelay+settings.tDuration)/2*1000); previousItemObj.delay(settings.tDelay*1000).css("display", "block").fadeTo(settings.tDuration*1000, settings.tMax, function(){ if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); }); } else if(settings.transition == "slide"){ settings.currentItemObj.delay(12).hide("slide", { direction: settings.tSlideOpDir }, settings.tDuration*1000); setTimeout(function(){ methods.doMidChange.apply($this); }, (settings.tDelay+settings.tDuration)/2*1000); previousItemObj.delay(settings.tDelay*1000).show("slide", { direction: settings.tSlideDir }, settings.tDuration*1000, function(){ if(settings.slideShow) methods.setupUpdateInterval.apply($this); settings.allowControls = true; methods.doAfterChange.apply($this); methods.docallback.apply($this); }); settings.currentItemObj = previousItemObj; } } }); }, // Slideshow functions setupUpdateInterval: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); settings.ssInterval = window.setInterval(function(){ methods.nextItem.apply($this); }, settings.ssTimer*1000); $this.data('contentSelector', settings); }); }, clearUpdateInterval: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); window.clearInterval(settings.ssInterval); settings.ssInterval = null; $this.data('contentSelector', settings); }); }, startSlideshow: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); settings.slideShow = true; methods.setupUpdateInterval.apply($this); $this.data('contentSelector', settings); }); }, stopSlideshow: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); settings.slideShow = false; methods.clearUpdateInterval.apply($this); $this.data('contentSelector', settings); }); }, // Callback functions doBeforeChange: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); if($.isFunction(settings.beforeChange)){ settings.beforeChange(); } }); }, doMidChange: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); if($.isFunction(settings.midChange)){ settings.midChange(); } }); }, doAfterChange: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); if($.isFunction(settings.afterChange)){ settings.afterChange(); } }); }, docallback: function(){ return this.each(function(){ var $this = $(this); var settings = $this.data('contentSelector'); if($.isFunction(settings.callback)){ settings.callback(); } }); }, // Settings getters/setters value: function(options){ var settings = $(this).data('contentSelector'); if(arguments[1] === undefined){ // Getter return settings[arguments[0]]; }else{ // Setter if(config[arguments[0]]){ settings[arguments[0]] = arguments[1]; $(this).data('contentSelector', settings); }else{ $.error("The '" + arguments[0] + "' setting is read only."); } return; } }, // Destroy method stops slideshow, and removes data from element(s) destroy: function(options){ // Repeat over each element in selector return $(this).each(function() { var $this = $(this); var settings = $this.data('contentSelector'); if(settings.slideShow){ methods.stopSlideshow.apply($this); } // Remove settings data when deallocating our plugin $this.removeData('contentSelector'); }); } }; // Method calling logic $.fn.contentSelector = function(){ var method = arguments[0]; var $this = $(this); var settings = $this.data('contentSelector'); if(methods[method]){ method = methods[method]; arguments = Array.prototype.slice.call(arguments, 1); }else if(settings && settings[method]){ method = methods.value; // If the passed in "method" is actually a setting instead send it to the value method that will handle getting/setting. }else if(typeof(method) == 'object' || !method){ method = methods.init; }else{ $.error('Method ' + method + ' does not exist on jQuery.contentSelector'); return this; } return method.apply(this, arguments); } })(jQuery);