/******************************************************************************* jquery.mb.components Copyright (c) 2001-2010. Matteo Bicocchi (Pupunzi); Open lab srl, Firenze - Italy email: info@pupunzi.com site: http://pupunzi.com Licences: MIT, GPL http://www.opensource.org/licenses/mit-license.php http://www.gnu.org/licenses/gpl.html ******************************************************************************/ /* * Name:jquery.mb.menu * Version: 2.8.5rc5 * * added: boxMenu menu modality by: Sven Dowideit http://trunk.fosiki.com/Sandbox/WebSubMenu */ // to get the element that is fireing a contextMenu event you have $.mbMenu.lastContextMenuEl that returns an object. jQuery.noConflict(); (function($) { $.mbMenu = { name:"mbMenu", author:"Matteo Bicocchi", version:"2.8.5rc5", actualMenuOpener:false, options: { template:"yourMenuVoiceTemplate",// the url that returns the menu voices via ajax. the data passed in the request is the "menu" attribute value as "menuId" additionalData:"", menuSelector:".menuContainer", menuWidth:400, openOnRight:false, containment:"window", iconPath:"../images/", hasImages:true, fadeInTime:100, fadeOutTime:200, menuTop:0, menuLeft:0, submenuTop:0, submenuLeft:4, opacity:1, openOnClick:true, closeOnMouseOut:false, closeAfter:500, minZindex:"auto", // or number hoverIntent:0, //if you use jquery.hoverIntent.js set this to time in milliseconds; 0= false; submenuHoverIntent:200, //if you use jquery.hoverIntent.js set this to time in milliseconds; 0= false; onContextualMenu:function(){} //it pass 'o' (the menu you clicked on) and 'e' (the event) }, buildMenu : function (options){ return this.each (function () { var thisMenu =this; thisMenu.id = !this.id ? "menu_"+Math.floor (Math.random () * 1000): this.id; this.options = {}; $.extend (this.options, $.mbMenu.options); $.extend (this.options, options); $(".mbmenu").hide(); thisMenu.clicked = false; thisMenu.rootMenu=false; thisMenu.actualOpenedMenu=false; thisMenu.menuvoice=false; var root=$(this); var openOnClick=this.options.openOnClick; var closeOnMouseOut=this.options.closeOnMouseOut; //build roots $(root).each(function(){ /* *using metadata plugin you can add attribute writing them inside the class attr with a JSON sintax * for ex: class="rootVoice {menu:'menu_2'}" */ if ($.metadata){ $.metadata.setType("class"); thisMenu.menuvoice=$(this).find(".rootVoice"); $(thisMenu.menuvoice).each(function(){ if ($(this).metadata().menu) $(this).attr("menu",$(this).metadata().menu); if ($(this).metadata().disabled) $(this).attr("isDisable",$(this).metadata().disabled); }); } thisMenu.menuvoice=$(this).find("[menu]").add($(this).filter("[menu]")); thisMenu.menuvoice.filter("[isDisable]").addClass("disabled"); $(thisMenu.menuvoice).css("white-space","nowrap"); if(openOnClick){ $(thisMenu.menuvoice).bind("click",function(){ $(document).unbind("click.closeMbMenu"); if (!$(this).attr("isOpen")){ $(this).buildMbMenu(thisMenu,$(this).attr("menu")); $(this).attr("isOpen","true"); }else{ $(this).removeMbMenu(thisMenu,true); $(this).addClass("selected"); } //empty if($(this).attr("menu")=="empty"){ if(thisMenu.actualOpenedMenu){ $("[isOpen]").removeAttr("isOpen"); } $(this).removeMbMenu(thisMenu); } $(document).unbind("click.closeMbMenu"); }); } var mouseOver=$.browser.msie?"mouseenter":"mouseover"; var mouseOut=$.browser.msie?"mouseleave":"mouseout"; $(thisMenu.menuvoice).mb_hover( this.options.hoverIntent, function(){ if(!$(this).attr("isOpen")) $("[isOpen]").removeAttr("isOpen"); if (closeOnMouseOut) clearTimeout($.mbMenu.deleteOnMouseOut); if (!openOnClick) $(thisMenu).find(".selected").removeClass("selected"); if(thisMenu.actualOpenedMenu){ $(thisMenu.actualOpenedMenu).removeClass("selected");} $(this).addClass("selected"); if((thisMenu.clicked || !openOnClick) && !$(this).attr("isOpen")){ $(this).removeMbMenu(thisMenu); $(this).buildMbMenu(thisMenu,$(this).attr("menu")); if ($(this).attr("menu")=="empty"){ $(this).removeMbMenu(thisMenu); } $(this).attr("isOpen","true"); } }, function(){ if (closeOnMouseOut) $.mbMenu.deleteOnMouseOut= setTimeout(function(){ $(this).removeMbMenu(thisMenu,true); $(document).unbind("click.closeMbMenu"); },$(root)[0].options.closeAfter); if ($(this).attr("menu")=="empty"){ $(this).removeClass("selected"); } if(!thisMenu.clicked) $(this).removeClass("selected"); $(document).one("click.closeMbMenu",function(){ $("[isOpen]").removeAttr("isOpen"); $(this).removeClass("selected"); $(this).removeMbMenu(thisMenu,true); thisMenu.rootMenu=false;thisMenu.clicked=false; }); } ); }); }); }, buildContextualMenu: function (options){ return this.each (function () { var thisMenu = this; thisMenu.options = {}; $.extend (thisMenu.options, $.mbMenu.options); $.extend (thisMenu.options, options); $(".mbmenu").hide(); thisMenu.clicked = false; thisMenu.rootMenu=false; thisMenu.actualOpenedMenu=false; thisMenu.menuvoice=false; /* *using metadata plugin you can add attribut writing them inside the class attr with a JSON sintax * for ex: class="rootVoice {menu:'menu_2'}" */ var cMenuEls; if ($.metadata){ $.metadata.setType("class"); cMenuEls= $(this).find(".cmVoice"); $(cMenuEls).each(function(){ if ($(this).metadata().cMenu) $(this).attr("cMenu",$(this).metadata().cMenu); }); } cMenuEls= $(this).find("[cMenu]").add($(this).filter("[cMenu]")); $(cMenuEls).each(function(){ $(this).css({"-webkit-user-select":"none","-moz-user-select":"none"}); var cm=this; cm.id = !cm.id ? "menu_"+Math.floor (Math.random () * 100): cm.id; $(cm).css({cursor:"default"}); $(cm).bind("contextmenu","mousedown",function(event){ event.preventDefault(); event.stopPropagation(); event.cancelBubble=true; $.mbMenu.lastContextMenuEl=cm; if ($.mbMenu.options.actualMenuOpener) { $(thisMenu).removeMbMenu($.mbMenu.options.actualMenuOpener); } /*add custom behavior to contextMenuEvent passing the el and the event *you can for example store to global var the obj that is fireing the event *mbActualContextualMenuObj=cm; * * you can for example create a function that manipulate the voices of the menu * you are opening according to a certain condition... */ thisMenu.options.onContextualMenu(this,event); $(this).buildMbMenu(thisMenu,$(this).attr("cMenu"),"cm",event); $(this).attr("isOpen","true"); }); }); }); } }; $.fn.extend({ buildMbMenu: function(op,m,type,e){ var msie6=$.browser.msie && $.browser.version=="6.0"; var mouseOver=$.browser.msie?"mouseenter":"mouseover"; var mouseOut=$.browser.msie?"mouseleave":"mouseout"; if (e) { this.mouseX=$(this).getMouseX(e); this.mouseY=$(this).getMouseY(e); } if ($.mbMenu.options.actualMenuOpener && $.mbMenu.options.actualMenuOpener!=op) $(op).removeMbMenu($.mbMenu.options.actualMenuOpener); $.mbMenu.options.actualMenuOpener=op; if(!type || type=="cm") { if (op.rootMenu) { $(op.rootMenu).removeMbMenu(op); $(op.actualOpenedMenu).removeAttr("isOpen"); $("[isOpen]").removeAttr("isOpen"); } op.clicked=true; op.actualOpenedMenu=this; $(op.actualOpenedMenu).attr("isOpen","true"); $(op.actualOpenedMenu).addClass("selected"); } //empty if($(this).attr("menu")=="empty"){ return; } var opener=this; var where=(!type|| type=="cm")?$(document.body):$(this).parent().parent(); var menuClass= op.options.menuSelector.replace(".",""); if(op.rootMenu) menuClass+=" submenuContainer"; if(!op.rootMenu && $(opener).attr("isDisable")) menuClass+=" disabled"; where.append(""); this.menu = where.find(".menuDiv"); $(this.menu).css({width:0, height:0}); if (op.options.minZindex!="auto"){ $(this.menu).css({zIndex:op.options.minZindex++}); }else{ $(this.menu).mb_bringToFront(); } this.menuContainer = $(this.menu).find(op.options.menuSelector); $(this.menuContainer).bind(mouseOver,function(){ $(opener).addClass("selected"); }); $(this.menuContainer).css({ position:"absolute", opacity:op.options.opacity }); if (!$("#"+m).html()){ $.ajax({ type: "POST", url: op.options.template, cache: false, async: false, data:"menuId="+m+(op.options.additionalData!=""?"&"+op.options.additionalData:""), success: function(html){ $("body").append(html); $("#"+m).hide(); } }); } $(this.menuContainer).attr("id", "mb_"+m).hide(); //LITERAL MENU SUGGESTED BY SvenDowideit var isBoxmenu=$("#"+m).hasClass("boxMenu"); if (isBoxmenu) { this.voices = $("#"+m).clone(true); this.voices.css({display: "block"}); this.voices.attr("id", m+"_clone"); } else { //TODO this will break - if there are nested a's this.voices= $("#"+m).find("a").clone(true); } /* *using metadata plugin you can add attribut writing them inside the class attr with a JSON sintax * for ex: class="rootVoice {menu:'menu_2'}" */ if ($.metadata){ $.metadata.setType("class"); $(this.voices).each(function(){ if ($(this).metadata().disabled) $(this).attr("isdisable",$(this).metadata().disabled); if ($(this).metadata().img) $(this).attr("img",$(this).metadata().img); if ($(this).metadata().menu) $(this).attr("menu",$(this).metadata().menu); if ($(this).metadata().action) $(this).attr("action",$(this).metadata().action); }); } // build each voices of the menu $(this.voices).each(function(i){ var voice=this; var imgPlace=""; var isText=$(voice).attr("rel")=="text"; var isTitle=$(voice).attr("rel")=="title"; var isDisabled=$(voice).is("[isdisable]"); if(!op.rootMenu && $(opener).attr("isDisable")) isDisabled=true; var isSeparator=$(voice).attr("rel")=="separator"; // boxMenu SUGGESTED by Sven Dowideit if (op.options.hasImages && !isText && !isBoxmenu){ var imgPath=$(voice).attr("img")?$(voice).attr("img"):"blank.gif"; imgPath=(imgPath.length>3 && imgPath.indexOf(".")>-1)?"":imgPath; imgPlace=""+imgPath+""; } var line=""+imgPlace+"
"; if(isSeparator) line="

"; if(isText) line="
"; // boxMenu SUGGESTED by Sven Dowideit if(isBoxmenu) line="
"; $(opener.menuContainer).append(line); var menuLine = $(opener.menuContainer).find("#" + m + "_" + i); var menuVoice = menuLine.find(".voice"); if(!isSeparator){ menuVoice.append(this); if($(this).attr("menu") && !isDisabled){ menuLine.find(".voice a").wrap(""); menuLine.find(".menuArrow").addClass("subMenuOpener"); menuLine.css({cursor:"default"}); this.isOpener=true; } if(isText){ menuVoice.addClass("textBox"); if ($.browser.msie) menuVoice.css({maxWidth:op.options.menuWidth}); this.isOpener=true; } if(isDisabled){ menuLine.addClass("disabled").css({cursor:"default"}); } if(!(isText || isTitle || isDisabled ||isBoxmenu)){ menuLine.css({cursor:"pointer"}); menuLine.bind("mouseover",function(){ clearTimeout($.mbMenu.deleteOnMouseOut); $(this).addClass("selected"); }); menuLine.bind("mouseout",function(){ $(this).removeClass("selected"); }); menuLine.mb_hover( op.options.submenuHoverIntent, function(event){ if(opener.menuContainer.actualSubmenu && !$(voice).attr("menu")){ $(opener.menu).find(".menuDiv").remove(); $(opener.menuContainer.actualSubmenu).removeClass("selected"); opener.menuContainer.actualSubmenu=false; } if ($(voice).attr("menu")){ if(opener.menuContainer.actualSubmenu && opener.menuContainer.actualSubmenu!=this){ $(opener.menu).find(".menuDiv").remove(); $(opener.menuContainer.actualSubmenu).removeClass("selected"); opener.menuContainer.actualSubmenu=false; } if (!$(voice).attr("action")) $(opener.menuContainer).find("#"+m+"_"+i).css("cursor","default"); if (!opener.menuContainer.actualSubmenu || opener.menuContainer.actualSubmenu!=this){ $(opener.menu).find(".menuDiv").remove(); opener.menuContainer.actualSubmenu=false; $(this).buildMbMenu(op,$(voice).attr("menu"),"sm",event); opener.menuContainer.actualSubmenu=this; } $(this).attr("isOpen","true"); return false; } }, function(){} ); } if(isDisabled || isTitle || isText || isBoxmenu){ $(this).removeAttr("href"); menuLine.bind(mouseOver,function(){ if (closeOnMouseOut) clearTimeout($.mbMenu.deleteOnMouseOut); if(opener.menuContainer.actualSubmenu){ $(opener.menu).find(".menuDiv").remove(); opener.menuContainer.actualSubmenu=false; } }).css("cursor","default"); } menuLine.bind("click",function(){ if (($(voice).attr("action") || $(voice).attr("href")) && !isDisabled && !isBoxmenu && !isText){ var target=$(voice).attr("target")?$(voice).attr("target"):"_self"; if ($(voice).attr("href") && $(voice).attr("href").indexOf("javascript:")>-1){ $(voice).attr("action",$(voice).attr("href").replace("javascript:","")); } var link=$(voice).attr("action")?$(voice).attr("action"):"window.open('"+$(voice).attr("href")+"', '"+target+"')"; $(voice).removeAttr("href"); eval(link); $(this).removeMbMenu(op,true); }else{ $(document).unbind("click.closeMbMenu"); } }); } }); // Close on Mouseout var closeOnMouseOut=$(op)[0].options.closeOnMouseOut; if (closeOnMouseOut){ $(opener.menuContainer).bind("mouseenter",function(){ clearTimeout($.mbMenu.deleteOnMouseOut); }); $(opener.menuContainer).bind("mouseleave",function(){ var menuToRemove=$.mbMenu.options.actualMenuOpener; $.mbMenu.deleteOnMouseOut= setTimeout(function(){$(this).removeMbMenu(menuToRemove,true);$(document).unbind("click.closeMbMenu");},$(op)[0].options.closeAfter); }); } //positioning opened var t=0,l=0; $(this.menuContainer).css({ minWidth:op.options.menuWidth }); if ($.browser.msie) $(this.menuContainer).css("width",$(this.menuContainer).width()+2); switch(type){ case "sm": t=$(this).position().top+op.options.submenuTop; l=$(this).position().left+$(this).width()-op.options.submenuLeft; break; case "cm": t=this.mouseY-5; l=this.mouseX-5; break; default: if (op.options.openOnRight){ t=$(this).offset().top-($.browser.msie?2:0)+op.options.menuTop; l=$(this).offset().left+$(this).outerWidth()-op.options.menuLeft-($.browser.msie?2:0); }else{ t=$(this).offset().top+$(this).outerHeight()-(!$.browser.mozilla?2:0)+op.options.menuTop; l=$(this).offset().left+op.options.menuLeft; } break; } $(this.menu).css({ position:"absolute", top:t, left:l }); if (!type || type=="cm") op.rootMenu=this.menu; $(this.menuContainer).bind(mouseOut,function(){ $(document).one("click.closeMbMenu",function(){$(document).removeMbMenu(op,true);}); }); if (op.options.fadeInTime>0) $(this.menuContainer).fadeIn(op.options.fadeInTime); else $(this.menuContainer).show(); var wh= (op.options.containment=="window")?$(window).height():$("#"+op.options.containment).offset().top+$("#"+op.options.containment).outerHeight(); var ww=(op.options.containment=="window")?$(window).width():$("#"+op.options.containment).offset().left+$("#"+op.options.containment).outerWidth(); var mh=$(this.menuContainer).outerHeight(); var mw=$(this.menuContainer).outerWidth(); var actualX=$(where.find(".menuDiv:first")).offset().left-$(window).scrollLeft(); var actualY=$(where.find(".menuDiv:first")).offset().top-$(window).scrollTop(); switch(type){ case "sm": if ((actualX+mw)>= ww && mw= ww && mw= ww && mw= wh-10 && mh zi ? parseInt($(this).css('zIndex')) : zi; } }); $(this).css('zIndex',zi+=10); }, mb_hover:function(hoverIntent, fn1, fn2){ if(hoverIntent==0) $(this).hover(fn1,fn2); else $(this).hoverIntent({ sensitivity: 30, interval: hoverIntent, timeout: 0, over:fn1, out:fn2 }); } }); $.fn.buildMenu = $.mbMenu.buildMenu; $.fn.buildContextualMenu = $.mbMenu.buildContextualMenu; })(jQuery); /******************************************************************************* jquery.mb.components Copyright (c) 2001-2010. Matteo Bicocchi (Pupunzi); Open lab srl, Firenze - Italy email: info@pupunzi.com site: http://pupunzi.com Licences: MIT, GPL http://www.opensource.org/licenses/mit-license.php http://www.gnu.org/licenses/gpl.html ******************************************************************************/ /** * Sets the type of metadata to use. Metadata is encoded in JSON, and each property * in the JSON will become a property of the element itself. * * There are three supported types of metadata storage: * * attr: Inside an attribute. The name parameter indicates *which* attribute. * * class: Inside the class attribute, wrapped in curly braces: { } * * elem: Inside a child element (e.g. a script tag). The * name parameter indicates *which* element. * * The metadata for an element is loaded the first time the element is accessed via jQuery. * * As a result, you can define the metadata type, use $(expr) to load the metadata into the elements * matched by expr, then redefine the metadata type and run another $(expr) for other elements. * * @name $.metadata.setType * * @example

This is a p

* @before $.metadata.setType("class") * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" * @desc Reads metadata from the class attribute * * @example

This is a p

* @before $.metadata.setType("attr", "data") * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" * @desc Reads metadata from a "data" attribute * * @example

This is a p

* @before $.metadata.setType("elem", "script") * @after $("#one").metadata().item_id == 1; $("#one").metadata().item_label == "Label" * @desc Reads metadata from a nested script element * * @param String type The encoding type * @param String name The name of the attribute to be used to get metadata (optional) * @cat Plugins/Metadata * @descr Sets the type of encoding to be used when loading metadata for the first time * @type undefined * @see metadata() */ (function($) { $.extend({ metadata : { defaults : { type: 'class', name: 'metadata', cre: /({.*})/, single: 'metadata' }, setType: function( type, name ){ this.defaults.type = type; this.defaults.name = name; }, get: function( elem, opts ){ var settings = $.extend({},this.defaults,opts); // check for empty string in single property if ( !settings.single.length ) settings.single = 'metadata'; var data = $.data(elem, settings.single); // returned cached data if it already exists if ( data ) return data; data = "{}"; if ( settings.type == "class" ) { var m = settings.cre.exec( elem.className ); if ( m ) data = m[1]; } else if ( settings.type == "elem" ) { if( !elem.getElementsByTagName ) return undefined; var e = elem.getElementsByTagName(settings.name); if ( e.length ) data = $.trim(e[0].innerHTML); } else if ( elem.getAttribute != undefined ) { var attr = elem.getAttribute( settings.name ); if ( attr ) data = attr; } if ( data.indexOf( '{' ) <0 ) data = "{" + data + "}"; data = eval("(" + data + ")"); $.data( elem, settings.single, data ); return data; } } }); /** * Returns the metadata object for the first member of the jQuery object. * * @name metadata * @descr Returns element's metadata object * @param Object opts An object contianing settings to override the defaults * @type jQuery * @cat Plugins/Metadata */ $.fn.metadata = function( opts ){ return $.metadata.get( this[0], opts ); }; })(jQuery); /******************************************************************************* jquery.mb.components Copyright (c) 2001-2010. Matteo Bicocchi (Pupunzi); Open lab srl, Firenze - Italy email: info@pupunzi.com site: http://pupunzi.com Licences: MIT, GPL http://www.opensource.org/licenses/mit-license.php http://www.gnu.org/licenses/gpl.html ******************************************************************************/ /** * hoverIntent is similar to jQuery's built-in "hover" function except that * instead of firing the onMouseOver event immediately, hoverIntent checks * to see if the user's mouse has slowed down (beneath the sensitivity * threshold) before firing the onMouseOver event. * * hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+ * * * hoverIntent is currently available for use in all personal or commercial * projects under both MIT and GPL licenses. This means that you can choose * the license that best suits your project, and use it accordingly. * * // basic usage (just like .hover) receives onMouseOver and onMouseOut functions * $("ul li").hoverIntent( showNav , hideNav ); * * // advanced usage receives configuration object only * $("ul li").hoverIntent({ * sensitivity: 7, // number = sensitivity threshold (must be 1 or higher) * interval: 100, // number = milliseconds of polling interval * over: showNav, // function = onMouseOver callback (required) * timeout: 0, // number = milliseconds delay before onMouseOut function call * out: hideNav // function = onMouseOut callback (required) * }); * * @param f onMouseOver function || An object with configuration options * @param g onMouseOut function || Nothing (use configuration options object) * @author Brian Cherne */ (function($) { $.fn.hoverIntent = function(f,g) { // default configuration options var cfg = { sensitivity: 7, interval: 100, timeout: 0 }; // override configuration options with user supplied object cfg = $.extend(cfg, g ? { over: f, out: g } : f ); // instantiate variables // cX, cY = current X and Y position of mouse, updated by mousemove event // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval var cX, cY, pX, pY; // A private function for getting mouse position var track = function(ev) { cX = ev.pageX; cY = ev.pageY; }; // A private function for comparing current and previous mouse position var compare = function(ev,ob) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); // compare mouse positions to see if they've crossed the threshold if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) { $(ob).unbind("mousemove",track); // set hoverIntent state to true (so mouseOut can be called) ob.hoverIntent_s = 1; return cfg.over.apply(ob,[ev]); } else { // set previous coordinates for next time pX = cX; pY = cY; // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval ); } }; // A private function for delaying the mouseOut function var delay = function(ev,ob) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); ob.hoverIntent_s = 0; return cfg.out.apply(ob,[ev]); }; // A private function for handling mouse 'hovering' var handleHover = function(e) { // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget; while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } } if ( p == this ) { return false; } // copy objects to be passed into t (required for event object to be passed in IE) var ev = jQuery.extend({},e); var ob = this; // cancel hoverIntent timer if it exists if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); } // else e.type == "onmouseover" if (e.type == "mouseover") { // set "previous" X and Y position based on initial entry point pX = ev.pageX; pY = ev.pageY; // update "current" X and Y position based on mousemove $(ob).bind("mousemove",track); // start polling interval (self-calling timeout) to compare mouse coordinates over time if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );} // else e.type == "onmouseout" } else { // unbind expensive mousemove event $(ob).unbind("mousemove",track); // if hoverIntent state is true, then call the mouseOut function after the specified delay if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );} } }; // bind the function to the two event listeners return this.mouseover(handleHover).mouseout(handleHover); }; })(jQuery);