Return-Path: X-Original-To: apmail-geode-commits-archive@minotaur.apache.org Delivered-To: apmail-geode-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 0C3E4187D4 for ; Tue, 3 Nov 2015 11:25:44 +0000 (UTC) Received: (qmail 63083 invoked by uid 500); 3 Nov 2015 11:25:44 -0000 Delivered-To: apmail-geode-commits-archive@geode.apache.org Received: (qmail 63052 invoked by uid 500); 3 Nov 2015 11:25:43 -0000 Mailing-List: contact commits-help@geode.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@geode.incubator.apache.org Delivered-To: mailing list commits@geode.incubator.apache.org Received: (qmail 63043 invoked by uid 99); 3 Nov 2015 11:25:43 -0000 Received: from Unknown (HELO spamd2-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 03 Nov 2015 11:25:43 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd2-us-west.apache.org (ASF Mail Server at spamd2-us-west.apache.org) with ESMTP id 4230D1A2F81 for ; Tue, 3 Nov 2015 11:25:43 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd2-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.791 X-Spam-Level: * X-Spam-Status: No, score=1.791 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, T_RP_MATCHES_RCVD=-0.01, URIBL_BLOCKED=0.001] autolearn=disabled Received: from mx1-eu-west.apache.org ([10.40.0.8]) by localhost (spamd2-us-west.apache.org [10.40.0.9]) (amavisd-new, port 10024) with ESMTP id D3zPUhDVPBrt for ; Tue, 3 Nov 2015 11:25:27 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-eu-west.apache.org (ASF Mail Server at mx1-eu-west.apache.org) with SMTP id 743C524CA1 for ; Tue, 3 Nov 2015 11:25:24 +0000 (UTC) Received: (qmail 61936 invoked by uid 99); 3 Nov 2015 11:25:23 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 03 Nov 2015 11:25:23 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 7EAD8E01FB; Tue, 3 Nov 2015 11:25:23 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: tushark@apache.org To: commits@geode.incubator.apache.org Date: Tue, 03 Nov 2015 11:25:25 -0000 Message-Id: <79e79945fdd143e4a28e1e4638ea8159@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [03/79] [partial] incubator-geode git commit: GEODE-12: Imported pulse from geode-1.0.0-SNAPSHOT-2.src.tar http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/1922937f/pulse/src/main/webapp/scripts/selectbox-customize/jquery.sb.js ---------------------------------------------------------------------- diff --git a/pulse/src/main/webapp/scripts/selectbox-customize/jquery.sb.js b/pulse/src/main/webapp/scripts/selectbox-customize/jquery.sb.js new file mode 100644 index 0000000..4235f5e --- /dev/null +++ b/pulse/src/main/webapp/scripts/selectbox-customize/jquery.sb.js @@ -0,0 +1,864 @@ +(function( $, window, undefined ) { + // utility functions + $.fn.borderWidth = function() { return $(this).outerWidth() - $(this).innerWidth(); }; + $.fn.paddingWidth = function() { return $(this).innerWidth() - $(this).width(); }; + $.fn.extraWidth = function() { return $(this).outerWidth(true) - $(this).width(); }; + $.fn.offsetFrom = function( e ) { + var $e = $(e); + return { + left: $(this).offset().left - $e.offset().left, + top: $(this).offset().top - $e.offset().top + }; + }; + $.fn.maxWidth = function() { + var max = 0; + $(this).each(function() { + if($(this).width() > max) { + max = $(this).width(); + } + }); + return max; + }; + $.fn.triggerAll = function(event, params) { + return $(this).each(function() { + $(this).triggerHandler(event, params); + }); + }; + var aps = Array.prototype.slice, + randInt = function() { + return Math.floor(Math.random() * 999999999); + }; + + // jQuery-Proto + $.proto = function() { + var name = arguments[0], // The name of the jQuery function that will be called + clazz = arguments[1], // A reference to the class that you are associating + klazz = clazz, // A version of clazz with a delayed constructor + extOpt = {}, // used to extend clazz with a variable name for the init function + undefined; // safety net + + opts = $.extend({ + elem: "elem", // the property name on the object that will be set to the current jQuery context + access: "access", // the name of the access function to be set on the object + init: "init", // the name of the init function to be set on the object + instantAccess: false // when true, treat all args as access args (ignore constructor args) and allow construct/function call at the same time + }, arguments[2]); + + if(clazz._super) { + extOpt[opts.init] = function(){}; + klazz = clazz.extend(extOpt); + } + + $.fn[name] = function() { + var result, args = arguments; + + $(this).each(function() { + var $e = $(this), + obj = $e.data(name), + isNew = !obj; + + // if the object is not defined for this element, then construct + if(isNew) { + + // create the new object and restore init if necessary + obj = new klazz(); + if(clazz._super) { + obj[opts.init] = clazz.prototype.init; + } + + // set the elem property and initialize the object + obj[opts.elem] = $e[0]; + if(obj[opts.init]) { + obj[opts.init].apply(obj, opts.instantAccess ? [] : aps.call(args, 0)); + } + + // associate it with the element + $e.data(name, obj); + + } + + // if it is defined or we allow instance access, then access + if(!isNew || opts.instantAccess) { + + // call the access function if it exists (allows lazy loading) + if(obj[opts.access]) { + obj[opts.access].apply(obj, aps.call(args, 0)); + } + + // do something with the object + if(args.length > 0) { + + if($.isFunction(obj[args[0]])) { + + // use the method access interface + result = obj[args[0]].apply(obj, aps.call(args, 1)); + + } else if(args.length === 1) { + + // just retrieve the property (leverage deep access with getObject if we can) + if($.getObject) { + result = $.getObject(args[0], obj); + } else { + result = obj[args[0]]; + } + + } else { + + // set the property (leverage deep access with setObject if we can) + if($.setObject) { + $.setObject(args[0], args[1], obj); + } else { + obj[args[0]] = args[1]; + } + + } + + } else if(result === undefined) { + + // return the first object if there are no args + result = $e.data(name); + + } + } + }); + + // chain if no results were returned from the clazz's method (it's a setter) + if(result === undefined) { + return $(this); + } + + // return the first result if not chaining + return result; + }; + }; + + var falseFunc = function() { + return false; + }, + SelectBox = function() { + + var self = this, + o = {}, + $orig = null, + $label = null, + $sb = null, + $display = null, + $dd = null, + $items = null, + searchTerm = "", + cstTimeout = null, + delayReloadTimeout = null, + resizeTimeout = null, + + // functions + loadSB, + createOption, + focusOrig, + blurOrig, + destroySB, + reloadSB, + delayReloadSB, + openSB, + centerOnSelected, + closeSB, + positionSB, + positionSBIfOpen, + delayPositionSB, + clickSB, + clickSBItem, + keyupSB, + keydownSB, + focusSB, + blurSB, + addHoverState, + removeHoverState, + addActiveState, + removeActiveState, + getDDCtx, + getSelected, + getEnabled, + selectItem, + clearSearchTerm, + findMatchingItem, + selectMatchingItem, + selectNextItemStartsWith, + closeAll, + closeAllButMe, + closeAndUnbind, + blurAllButMe, + stopPageHotkeys, + flickerDisplay, + unbind; + + loadSB = function() { + + // create the new sb + $sb = $("
") + .attr("role", "listbox") + .attr("aria-has-popup", "true") + .attr("aria-labelledby", $label.attr("id") ? $label.attr("id") : ""); + $("body").append($sb); + + // generate the display markup + var displayMarkup = $orig.children().size() > 0 + ? o.displayFormat.call($orig.find("option:selected")[0], 0, 0) + : " "; + $display = $("
") + .append($("
").append(displayMarkup)) + .append(o.arrowMarkup); + $sb.append($display); + + // generate the dropdown markup + $dd = $("") + .attr("aria-hidden", "true"); + $sb.append($dd) + .attr("aria-owns", $dd.attr("id")); + if($orig.children().size() === 0) { + $dd.append(createOption().addClass("selected")); + } else { + $orig.children().each(function( i ) { + var $opt, $og, $ogItem, $ogList; + if($(this).is("optgroup")) { + $og = $(this); + $ogItem = $("
  • " + o.optgroupFormat.call($og[0], i+1) + "
  • ") + .addClass($og.is(":disabled") ? "disabled" : "") + .attr("aria-disabled", $og.is(":disabled") ? "true" : ""); + $ogList = $("
      "); + $ogItem.append($ogList); + $dd.append($ogItem); + $og.children("option").each(function() { + $opt = createOption($(this), i) + .addClass($og.is(":disabled") ? "disabled" : "") + .attr("aria-disabled", $og.is(":disabled") ? "true" : ""); + $ogList.append($opt); + }); + } else { + $dd.append(createOption($(this), i)); + } + }); + } + + // cache all sb items + $items = $dd.find("li").not(".optgroup"); + + // for accessibility/styling + $sb.attr("aria-active-descendant", $items.filter(".selected").attr("id")); + $dd.children(":first").addClass("first"); + $dd.children(":last").addClass("last"); + + // modify width based on fixedWidth/maxWidth options + if(!o.fixedWidth) { + var largestWidth = $dd.find(".text, .optgroup").maxWidth() + $display.extraWidth() + 1; + $sb.width(o.maxWidth ? Math.min(o.maxWidth, largestWidth) : largestWidth); + } else if(o.maxWidth && $sb.width() > o.maxWidth) { + $sb.width(o.maxWidth); + } + + // place the new markup in its semantic location (hide/show fixes positioning bugs) + $orig.before($sb).addClass("has_sb").hide().show(); + + // these two lines fix a div/span display bug on load in ie7 + positionSB(); + flickerDisplay(); + + // hide the dropdown now that it's initialized + $dd.hide(); + + // bind events + if(!$orig.is(":disabled")) { + $orig + .bind("blur.sb", blurOrig) + .bind("focus.sb", focusOrig); + $display + .mouseup(addActiveState) + .mouseup(clickSB) + .click(falseFunc) + .focus(focusSB) + .blur(blurSB) + .hover(addHoverState, removeHoverState); + getEnabled() + .click(clickSBItem) + .hover(addHoverState, removeHoverState); + $dd.find(".optgroup") + .hover(addHoverState, removeHoverState) + .click(falseFunc); + $items.filter(".disabled") + .click(falseFunc); + if(!$.browser.msie || $.browser.version >= 9) { + $(window).resize($.throttle ? $.throttle(100, positionSBIfOpen) : delayPositionSB); + } + } else { + $sb.addClass("disabled").attr("aria-disabled"); + $display.click(function( e ) { e.preventDefault(); }); + } + + // bind custom events + $sb.bind("close.sb", closeSB).bind("destroy.sb", destroySB); + $orig.bind("reload.sb", reloadSB); + if($.fn.tie && o.useTie) { + $orig.bind("domupdate.sb", delayReloadSB); + } + }; + + delayPositionSB = function() { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(positionSBIfOpen, 50); + }; + + positionSBIfOpen = function() { + if($sb.is(".open")) { + positionSB(); + openSB(true); + } + } + + // create new markup from an "); + index = 0; + } + var $li = $("
    • ") + .attr("role", "option") + .data("orig", $option[0]) + .data("value", $option ? $option.attr("value") : "") + .addClass($option.is(":selected") ? "selected" : "") + .addClass($option.is(":disabled") ? "disabled" : "") + .attr("aria-disabled", $option.is(":disabled") ? "true" : ""), + $inner = $("
      "), + $text = $("
      ") + .html(o.optionFormat.call($option[0], 0, index + 1)); + return $li.append($inner.append($text)); + }; + + // causes focus if original is focused + focusOrig = function() { + blurAllButMe(); + $display.triggerHandler("focus"); + }; + + // loses focus if original is blurred + blurOrig = function() { + if(!$sb.is(".open")) { + $display.triggerHandler("blur"); + } + }; + + // unbind and remove + destroySB = function( internal ) { + $sb.remove(); + $orig + .unbind(".sb") + .removeClass("has_sb"); + $(window).unbind("resize", delayPositionSB); + if(!internal) { + $orig.removeData("sb"); + } + }; + + // destroy then load, maintaining open/focused state if applicable + reloadSB = function() { + var isOpen = $sb.is(".open"), + isFocused = $display.is(".focused"); + closeSB(true); + destroySB(true); + self.init(o); + if(isOpen) { + $orig.focus(); + openSB(true); + } else if(isFocused) { + $orig.focus(); + } + }; + + // debouncing when useTie === true + delayReloadSB = function() { + clearTimeout(delayReloadTimeout); + delayReloadTimeout = setTimeout(reloadSB, 30); + }; + + // when the user clicks outside the sb + closeAndUnbind = function() { + $sb.removeClass("focused"); + closeSB(); + unbind(); + }; + + unbind = function() { + $(document) + .unbind("click", closeAndUnbind) + .unbind("keyup", keyupSB) + .unbind("keypress", stopPageHotkeys) + .unbind("keydown", stopPageHotkeys) + .unbind("keydown", keydownSB); + }; + + // trigger all sbs to close + closeAll = function() { + $(".sb.open." + o.selectboxClass).triggerAll("close"); + }; + + // trigger all sbs to blur + blurAllButMe = function() { + $(".sb.focused." + o.selectboxClass).not($sb[0]).find(".display").blur(); + }; + + // to prevent multiple selects open at once + closeAllButMe = function() { + $(".sb.open." + o.selectboxClass).not($sb[0]).triggerAll("close"); + }; + + // hide and reset dropdown markup + closeSB = function( instantClose ) { + if($sb.is(".open")) { + $display.blur(); + $items.removeClass("hover"); + unbind(); + $dd.attr("aria-hidden", "true"); + if(instantClose === true) { + $dd.hide(); + $sb.removeClass("open"); + $sb.append($dd); + } else { + $dd.fadeOut(o.animDuration, function() { + $sb.removeClass("open"); + $sb.append($dd); + }); + } + } + }; + + // since the context can change, we should get it dynamically + getDDCtx = function() { + var $ddCtx = null; + if(o.ddCtx === "self") { + $ddCtx = $sb; + } else if($.isFunction(o.ddCtx)) { + $ddCtx = $(o.ddCtx.call($orig[0])); + } else { + $ddCtx = $(o.ddCtx); + } + return $ddCtx; + }; + + // DRY + getSelected = function() { + return $items.filter(".selected"); + }; + + // DRY + getEnabled = function() { + return $items.not(".disabled"); + }; + + // reposition the scroll of the dropdown so the selected option is centered (or appropriately onscreen) + centerOnSelected = function() { + $dd.scrollTop($dd.scrollTop() + getSelected().offsetFrom($dd).top - $dd.height() / 2 + getSelected().outerHeight(true) / 2); + }; + + flickerDisplay = function() { + if($.browser.msie && $.browser.version < 8) { + $("." + o.selectboxClass + " .display").hide().show(); // fix ie7 display bug + } + }; + + // show, reposition, and reset dropdown markup + openSB = function( instantOpen ) { + var dir, + $ddCtx = getDDCtx(); + blurAllButMe(); + $sb.addClass("open"); + $ddCtx.append($dd); + dir = positionSB(); + $dd.attr("aria-hidden", "false"); + if(instantOpen === true) { + $dd.show(); + centerOnSelected(); + } else if(dir === "down") { + $dd.slideDown(o.animDuration, centerOnSelected); + } else { + $dd.fadeIn(o.animDuration, centerOnSelected); + } + $orig.focus(); + }; + + // position dropdown based on collision detection + positionSB = function() { + var $ddCtx = getDDCtx(), + ddMaxHeight = 0, + ddX = $display.offsetFrom($ddCtx).left, + ddY = 0, + dir = "", + ml, mt, + bottomSpace, topSpace, + bottomOffset, spaceDiff, + bodyX, bodyY; + + // modify dropdown css for getting values + $dd.removeClass("above"); + $dd.show().css({ + maxHeight: "none", + position: "relative", + visibility: "hidden" + }); + if(!o.fixedWidth) { + $dd.width($display.outerWidth() - $dd.extraWidth() + 1); + } + + // figure out if we should show above/below the display box + bottomSpace = $(window).scrollTop() + $(window).height() - $display.offset().top - $display.outerHeight(); + topSpace = $display.offset().top - $(window).scrollTop(); + bottomOffset = $display.offsetFrom($ddCtx).top + $display.outerHeight(); + spaceDiff = bottomSpace - topSpace + o.dropupThreshold; + if($dd.outerHeight() < bottomSpace) { + ddMaxHeight = o.maxHeight ? o.maxHeight : bottomSpace; + ddY = bottomOffset; + dir = "down"; + } else if($dd.outerHeight() < topSpace) { + ddMaxHeight = o.maxHeight ? o.maxHeight : topSpace; + ddY = $display.offsetFrom($ddCtx).top - Math.min(ddMaxHeight, $dd.outerHeight()); + dir = "up"; + } else if(spaceDiff >= 0) { + ddMaxHeight = o.maxHeight ? o.maxHeight : bottomSpace; + ddY = bottomOffset; + dir = "down"; + } else if(spaceDiff < 0) { + ddMaxHeight = o.maxHeight ? o.maxHeight : topSpace; + ddY = $display.offsetFrom($ddCtx).top - Math.min(ddMaxHeight, $dd.outerHeight()); + dir = "up"; + } else { + ddMaxHeight = o.maxHeight ? o.maxHeight : "none"; + ddY = bottomOffset; + dir = "down"; + } + + ml = ("" + $("body").css("margin-left")).match(/^\d+/) ? $("body").css("margin-left") : 0; + mt = ("" + $("body").css("margin-top")).match(/^\d+/) ? $("body").css("margin-top") : 0; + bodyX = $().jquery >= "1.4.2" + ? parseInt(ml) + : $("body").offset().left; + bodyY = $().jquery >= "1.4.2" + ? parseInt(mt) + : $("body").offset().top; + + + // modify dropdown css for display + $dd.hide().css({ + left: ddX + ($ddCtx.is("body") ? bodyX : 0), + maxHeight: ddMaxHeight, + position: "absolute", + top: ddY + ($ddCtx.is("body") ? bodyY : 0), + visibility: "visible" + }); + if(dir === "up") { + $dd.addClass("above"); + } + return dir; + }; + + // when the user explicitly clicks the display + clickSB = function( e ) { + if($sb.is(".open")) { + closeSB(); + } else { + openSB(); + } + return false; + }; + + // when the user selects an item in any manner + selectItem = function() { + var $item = $(this), + oldVal = $orig.val(), + newVal = $item.data("value"); + + // update the original if necessary + if(oldVal !== newVal) { + $orig.change(); + } + }; + + // when the user explicitly clicks an item + clickSBItem = function( e ) { + closeAndUnbind(); + $orig.focus(); + selectItem.call(this); + return false; + }; + + // start over for generating the search term + clearSearchTerm = function() { + searchTerm = ""; + }; + + // iterate over all the options to see if any match the search term + findMatchingItem = function( term ) { + var i, t, $tNode, + $available = getEnabled(); + for(i=0; i < $available.size(); i++) { + $tNode = $available.eq(i).find(".text"); + t = $tNode.children().size() == 0 ? $tNode.text() : $tNode.find("*").text(); + if(term.length > 0 && t.toLowerCase().match("^" + term.toLowerCase())) { + return $available.eq(i); + } + } + return null; + }; + + // if we get a match for any options, select it + selectMatchingItem = function( text ) { + var $matchingItem = findMatchingItem(text); + if($matchingItem !== null) { + selectItem.call($matchingItem[0]); + return true; + } + return false; + }; + + // stop up/down/backspace/space from moving the page + stopPageHotkeys = function( e ) { + if(e.ctrlKey || e.altKey) { + return; + } + if(e.which === 38 || e.which === 40 || e.which === 8 || e.which === 32) { + e.preventDefault(); + } + }; + + // if a normal match fails, try matching the next element that starts with the pressed letter + selectNextItemStartsWith = function( c ) { + var i, t, + $selected = getSelected(), + $available = getEnabled(); + for(i = $available.index($selected) + 1; i < $available.size(); i++) { + t = $available.eq(i).find(".text").text(); + if(t !== "" && t.substring(0,1).toLowerCase() === c.toLowerCase()) { + selectItem.call($available.eq(i)[0]); + return true; + } + } + return false; + }; + + // go up/down using arrows or attempt to autocomplete based on string + keydownSB = function( e ) { + if(e.altKey || e.ctrlKey) { + return false; + } + var $selected = getSelected(), + $enabled = getEnabled(); + switch(e.which) { + case 9: // tab + closeSB(); + blurSB(); + break; + case 35: // end + if($selected.size() > 0) { + e.preventDefault(); + selectItem.call($enabled.filter(":last")[0]); + centerOnSelected(); + } + break; + case 36: // home + if($selected.size() > 0) { + e.preventDefault(); + selectItem.call($enabled.filter(":first")[0]); + centerOnSelected(); + } + break; + case 38: // up + if($selected.size() > 0) { + if($enabled.filter(":first")[0] !== $selected[0]) { + e.preventDefault(); + selectItem.call($enabled.eq($enabled.index($selected)-1)[0]); + } + centerOnSelected(); + } + break; + case 40: // down + if($selected.size() > 0) { + if($enabled.filter(":last")[0] !== $selected[0]) { + e.preventDefault(); + selectItem.call($enabled.eq($enabled.index($selected)+1)[0]); + centerOnSelected(); + } + } else if($items.size() > 1) { + e.preventDefault(); + selectItem.call($items.eq(0)[0]); + } + break; + default: + break; + } + }; + + // the user is typing -- try to select an item based on what they press + keyupSB = function( e ) { + if(e.altKey || e.ctrlKey) { + return false; + } + if(e.which !== 38 && e.which !== 40) { + + // add to the search term + searchTerm += String.fromCharCode(e.keyCode); + + if(selectMatchingItem(searchTerm)) { + + // we found a match, continue with the current search term + clearTimeout(cstTimeout); + cstTimeout = setTimeout(clearSearchTerm, o.acTimeout); + + } else if(selectNextItemStartsWith(String.fromCharCode(e.keyCode))) { + + // we selected the next item that starts with what you just pressed + centerOnSelected(); + clearTimeout(cstTimeout); + cstTimeout = setTimeout(clearSearchTerm, o.acTimeout); + + } else { + + // no matches were found, clear everything + clearSearchTerm(); + clearTimeout(cstTimeout); + + } + } + }; + + // when the sb is focused (by tab or click), allow hotkey selection and kill all other selectboxes + focusSB = function() { + closeAllButMe(); + $sb.addClass("focused"); + $(document) + .click(closeAndUnbind) + .keyup(keyupSB) + .keypress(stopPageHotkeys) + .keydown(stopPageHotkeys) + .keydown(keydownSB); + }; + + // when the sb is blurred (by tab or click), disable hotkey selection + blurSB = function() { + $sb.removeClass("focused"); + $display.removeClass("active"); + $(document) + .unbind("keyup", keyupSB) + .unbind("keydown", stopPageHotkeys) + .unbind("keypress", stopPageHotkeys) + .unbind("keydown", keydownSB); + }; + + // add hover class to an element + addHoverState = function() { + $(this).addClass("hover"); + }; + + // remove hover class from an element + removeHoverState = function() { + $(this).removeClass("hover"); + }; + + // add active class to the display + addActiveState = function() { + $display.addClass("active"); + $(document).bind("mouseup", removeActiveState); + }; + + // remove active class from an element + removeActiveState = function() { + $display.removeClass("active"); + $(document).unbind("mouseup", removeActiveState); + }; + + // constructor + this.init = function( opts ) { + + // this plugin is not compatible with IE6 and below; + // a normal and