I'm new to building jQuery plugins.
I have seen and used a lot of tooltip plugins, and today I've decided to build my own.
Can I get some feedback on the code?
What work, what doesn't.
Optimizations.
Caching.
What can I do to make it faster and better?
This would be really helpful for my learning and hopefully for others too.
Heres my plugin:
;(function($) {
$.fn.jTooltip = function(options) {
opts = $.extend({}, $.fn.jTooltip.defaults, options);
return this.each(function() {
var $this = $(this);
var content;
var showTimeout;
$tip = $('#jTooltip');
if($tip.size() == 0){
$('body').append('<div id="jTooltip" style="display:none;position:absolute;"><div></div></div>');
$tipContent = $('#jTooltip div');
}
$this.mouseover(function(event) {
content = $this.attr('title');
$this.attr('title', '');
$tipContent.html(content);
$body.bind('mousemove', function(event){
$tip.css({
top: $(document).scrollTop() + (event.pageY + opts.yOffset),
left: $(document).scrollLeft() + (event.pageX + opts.xOffset)
});
});
showTimeout = setTimeout('$tip.fadeIn(' + opts.fadeTime + ')', opts.delay);
});
$this.mouseout(function(event) {
clearTimeout(showTimeout);
$this.attr('title', content);
$('body').unbind('mousemove');
$tip.hide();
});
});
};
$.fn.jTooltip.defaults = {
delay: 0,
fadeTime: 300,
yOffset: 10,
xOffset: 10
};
})(jQuery);
Updated code
;(function($) {
$.fn.jTooltip = function(options) {
opts = $.extend({}, $.fn.jTooltip.defaults, options);
return this.each(function() {
var $this = $(this);
var showTimeout;
$this.data('title',$this.attr('title'));
$this.removeAttr('title');
$document = $(document);
$body = $('body');
$tip = $('#jTooltip');
$tipContent = $('#jTooltip div');
if($tip.length == 0){
$body.append('<div id="jTooltip" style="display:none;position:absolute;"><div></div></div>');
}
$this.hover(function(event){
$tipContent.html($this.data('title'));
$body.bind('mousemove', function(event){
$tip.css({
top: $document.scrollTop() + (event.pageY + opts.yOffset),
left: $document.scrollLeft() + (event.pageX + opts.xOffset)
});
});
showTimeout = setTimeout(function(){
$tip.fadeIn(opts.fadeTime);
}, opts.delay);
}, function(){
clearTimeout(showTimeout);
$body.unbind('mousemove');
$tip.hide();
});
});
};
$.fn.jTooltip.defaults = {
delay: 0,
fadeTime: 300,
yOffset: 10,
xOffset: 10
};
})(jQuery);
Let me know if you have some more feedback ;)
Use .length rather than .size(). Internally size() calls length so just saves the extra call.
Consider removing the title attribute when you create the tip and storing the tip on the element using .data('tip', title). This will avoid the need for you to constantly blank the title and then add it back again.
Create a function for the work you do inside the setTimeout. Implied eval is considered bad which is what happends when you pass a string to setTimeout/Interval.
window.setTimeout( yourFadeTipFunc , opts.fadeTime);
Cache the $(document) in a var rather than calling it twice.
You could chain the mouseout to the mouseover.
Other than that it is a really good, clean first effort.
Can't say there's anything horribly wrong (others correct me if I'm wrong) but if I were to nitpick I would say use this:
$this.removeAttr('title'); //slightly more readable
instead of this:
$this.attr('title', '');
I just think it's prettier to call removeAttr instead of setting the attribute to the empty string - also, it allows other code to conditionally check for the existence of that attribute, rather than testing it's currently set value against the empty string.
Related
I'm developing a jQuery plugin to create modal windows, so, now, I want to restore element original state after hide it.
Someone can help me?
Thanks!
---- update ---
Sorry,
I want to store element html in some place when show it, then put the stored data back when hide it.
This is my plugin:
(function ($) { // v2ui_modal
var methods = {
show: function (options) {
var _this = this;
var defaults = {
showOverlay: true,
persistentContent: true
};
var options = $.extend(defaults, options);
if (!_this.attr('id')) {
_this.attr('id', 'v2ui-id_' + Math.random().toString().replace('.', ''));
}
if (options.showOverlay) {
$('<div />', { // overlay
id: 'v2-ui-plugin-modal-overlay-' + this.attr('id'),
css: {
zIndex: ($.topZIndex() + 1),
display: 'none',
position: 'fixed',
width: '100%',
height: '100%',
top: 0,
left: 0
}
}).addClass('v2-ui').addClass('plugin').addClass('overlay').appendTo('body');
};
_this.css({
zIndex: ($.topZIndex() + 2),
position: 'fixed'
});
_this.center();
$('#v2-ui-plugin-modal-overlay-' + _this.attr('id')).fadeIn(function () {
_this.fadeIn();
});
},
hide: function () {
var _this = this;
_this.fadeOut();
$('#v2-ui-plugin-modal-overlay-' + _this.attr('id')).fadeOut(function () {
$('#v2-ui-plugin-modal-overlay-' + _this.attr('id')).remove();
if ((_this.attr('id')).substr(0, 8) == 'v2ui-id_') {
_this.removeAttr('id');
};
});
}
};
jQuery.fn.v2ui_modal = function (methodOrOptions) {
if (methods[methodOrOptions]) {
methods[methodOrOptions].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof methodOrOptions === 'object' || !methodOrOptions) {
methods.show.apply(this, arguments);
};
};
})(jQuery);
You can take a look at jQuery.detach.
The .detach() method is the same as .remove(), except that .detach()
keeps all jQuery data associated with the removed elements. This
method is useful when removed elements are to be reinserted into the
DOM at a later time.
But I am having a hard time understanding your problem fully, so I apologize if my answer does not fit your question.
I wrote a slideshow plugin, but for some reason maybe because I've been working on it all day, I can't figure out exactly how to get it to go back to state one, once it's reached the very last state when it's on auto mode.
I'm thinking it's an architectual issue at this point, because basically I'm attaching the amount to scroll left to (negatively) for each panel (a panel contains 4 images which is what is currently shown to the user). The first tab should get: 0, the second 680, the third, 1360, etc. This is just done by calculating the width of the 4 images plus the padding.
I have it on a setTimeout(function(){}) currently to automatically move it which works pretty well (unless you also click tabs, but that's another issue). I just want to make it so when it's at the last state (numTabs - 1), to animate and move its state back to the first one.
Code:
(function($) {
var methods = {
init: function(options) {
var settings = $.extend({
'speed': '1000',
'interval': '1000',
'auto': 'on'
}, options);
return this.each(function() {
var $wrapper = $(this);
var $sliderContainer = $wrapper.find('.js-slider-container');
$sliderContainer.hide().fadeIn();
var $tabs = $wrapper.find('.js-slider-tabs li a');
var numTabs = $tabs.size();
var innerWidth = $wrapper.find('.js-slider-container').width();
var $elements = $wrapper.find('.js-slider-container a');
var $firstElement = $elements.first();
var containerHeight = $firstElement.height();
$sliderContainer.height(containerHeight);
// Loop through each list element in `.js-slider-tabs` and add the
// distance to move for each "panel". A panel in this example is 4 images
$tabs.each(function(i) {
// Set amount to scroll for each tab
if (i === 1) {
$(this).attr('data-to-move', innerWidth + 20); // 20 is the padding between elements
} else {
$(this).attr('data-to-move', innerWidth * (i) + (i * 20));
}
});
// If they hovered on the panel, add paused to the data attribute
$('.js-slider-container').hover(function() {
$sliderContainer.attr('data-paused', true);
}, function() {
$sliderContainer.attr('data-paused', false);
});
// Start the auto slide
if (settings.auto === 'on') {
methods.auto($tabs, settings, $sliderContainer);
}
$tabs.click(function() {
var $tab = $(this);
var $panelNum = $(this).attr('data-slider-panel');
var $amountToMove = $(this).attr('data-to-move');
// Remove the active class of the `li` if it contains it
$tabs.each(function() {
var $tab = $(this);
if ($tab.parent().hasClass('active')) {
$tab.parent().removeClass('active');
}
});
// Add active state to current tab
$tab.parent().addClass('active');
// Animate to panel position
methods.animate($amountToMove, settings);
return false;
});
});
},
auto: function($tabs, settings, $sliderContainer) {
$tabs.each(function(i) {
var $amountToMove = $(this).attr('data-to-move');
setTimeout(function() {
methods.animate($amountToMove, settings, i, $sliderContainer);
}, i * settings.interval);
});
},
animate: function($amountToMove, settings, i, $sliderContainer) {
// Animate
$('.js-slider-tabs li').eq(i - 1).removeClass('active');
$('.js-slider-tabs li').eq(i).addClass('active');
$('#js-to-move').animate({
'left': -$amountToMove
}, settings.speed, 'linear', function() {});
}
};
$.fn.slider = function(method) {
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 {
return false;
}
};
})(jQuery);
$(window).ready(function() {
$('.js-slider').slider({
'speed': '10000',
'interval': '10000',
'auto': 'on'
});
});
The auto and animate methods are where the magic happens. The parameters speed is how fast it's animated and interval is how often, currently set at 10 seconds.
Can anyone help me figure out how to get this to "infinitely loop", if you will?
Here is a JSFiddle
It would probably be better to let go of the .each() and setTimeout() combo and use just setInterval() instead. Using .each() naturally limits your loop to the length of your collection, so it's better to use a looping mechanism that's not, and that you can break at any point you choose.
Besides, you can readily identify the current visible element by just checking for .active, from what I can see.
You'd probably need something like this:
setInterval(function () {
// do this check here.
// it saves you a function call and having to pass in $sliderContainer
if ($sliderContainer.attr('data-paused') === 'true') { return; }
// you really need to just pass in the settings object.
// the current element you can identify (as mentioned),
// and $amountToMove is derivable from that.
methods.animate(settings);
}, i * settings.interval);
// ...
// cache your slider tabs outside of the function
// and just form a closure on that to speed up your manips
var slidertabs = $('.js-slider-tabs');
animate : function (settings) {
// identify the current tab
var current = slidertabs.find('li.active'),
// and then do some magic to determine the next element in the loop
next = current.next().length >= 0 ?
current.next() :
slidertabs.find('li:eq(0)')
;
current.removeClass('active');
next.addClass('active');
// do your stuff
};
The code is not optimized, but I hope you see where I'm getting at here.
I'm building on a WordPress theme and wants to load posts and pages with AJAX. I got that sorted out through the snippet below, but now I just need to suppress the function when clicking on the logo, obviously linking to the home url. So when clicking on the logo it should force a normal reload, instead of using the function.
I figure it would have something to do with "if hasClass(logo) then use default"... Yeah, I'm fairly new to JavaScript, but I have been searching a lot, so any help in the right direction will be much appreciated. Thanks!
The snippet:
$(".home li.home").removeClass("home").addClass("current_page_item");
var $wrapperAjax = $("#wrapper-ajax"),
URL = '',
siteURL = "http://" + top.location.host.toString(),
$internalLinks = $("a[href^='"+siteURL+"']"),
hash = window.location.hash,
$ajaxSpinner = $("#ajax-loader"),
$el, $allLinks = $("a");
function hashizeLinks() {
$("a[href^='"+siteURL+"']").each(function() {
$el = $(this);
if ($.browser.msie) {
$el.attr("href", "#/" + this.pathname)
.attr("rel", "internal");
} else {
$el.attr("href", "#" + this.pathname)
.attr("rel", "internal");
}
});
};
hashizeLinks();
$("a[rel='internal']").live("click", function() {
$ajaxSpinner.fadeIn();
$wrapperAjax.animate({ opacity: "0.1" });
$el = $(this);
$(".current_page_item").removeClass("current_page_item");
$allLinks.removeClass("current_link");
URL = $el.attr("href").substring(1);
URL = URL + " .entry";
$wrapperAjax.load(URL, function() {
$el.addClass("current_link").parent().addClass("current_page_item");
$ajaxSpinner.fadeOut();
$wrapperAjax.animate({ opacity: "1" });
hashizeLinks();
});
});
$("#searchform").submit(function(e) {
$ajaxSpinner.fadeIn();
$wrapperAjax.animate({ opacity: "0.1" });
$el = $(this);
$(".current_page_item").removeClass("current_page_item");
$allLinks.removeClass("current_link");
URL = "/?s=" + $("#s").val() + " .entry";
$wrapperAjax.load(URL, function() {
$ajaxSpinner.fadeOut();
$wrapperAjax.animate({ opacity: "1" });
hashizeLinks();
});
e.preventDefault();
});
if ((hash) && (hash != "#/")) {
$("a[href*='"+hash+"']").trigger("click");
}
I'm guessing you mean the script from this line: $("a[rel='internal']")
In that case, $("a[rel='internal']").not('.logo') should do the trick.
I should've read the entire code. Replace $("a[href^='"+siteURL+"']") with $("a[href^='"+siteURL+"']").not('.logo') as well.
If it has the class .logo you could add this at the top of the function:
if ($(this).hasClass('logo')) return true;
See the simple example.
I need to prevent IE from recognizing the fadeIn/Out effect in this plugin. How can I add a line of jquery feature detection code to this:
$(function() {
var newHash = "",
$mainContent = $("#main-content"),
$pageWrap = $("#page-wrap"),
baseHeight = 0,
$el;
$("nav#footer").delegate("a", "click", function() {
window.location.hash = $(this).attr("href");
return false;
});
$(window).bind('hashchange', function(){
newHash = window.location.hash.substring(1);
if (newHash) {
$mainContent
.find("#guts")
.fadeOut(200, function() {
$mainContent.show().load(newHash + " #guts", function() {
$mainContent.fadeIn(200, function() {
});
$("nav#footer a").removeClass("current");
$("nav#footer a[href="+newHash+"]").addClass("current");
});
});
};
});
$(window).trigger('hashchange');
});
I had some code like
var FADE_TIME = 500; if(!($.support.opacity)) { FADE_TIME = 0}
$('element').fadeOut(FADE_TIME)
Where would I add this in the code? can someone help me get this working for real?please!!
Can't figure out why somebody didn't just edit the code and answer the question.
All you needed to do was create a new variable with it's value determined by the $.support.Opacity property then reference that in the fade in/out sections of the code.
$(function() {
var newHash = "",
$mainContent = $("#main-content"),
$pageWrap = $("#page-wrap"),
baseHeight = 0,
$el,
// new fadeTime property.
fadeTime = $.support.opacity ? 500 : 0;
$("nav#footer").delegate("a", "click", function() {
window.location.hash = $(this).attr("href");
return false;
});
$(window).bind('hashchange', function(){
newHash = window.location.hash.substring(1);
if (newHash) {
$mainContent
.find("#guts")
.fadeOut(fadeTime, function() {
$mainContent.show().load(newHash + " #guts", function() {
$mainContent.fadeIn(fadeTime, function() {
});
$("nav#footer a").removeClass("current");
$("nav#footer a[href="+newHash+"]").addClass("current");
});
});
};
});
$(window).trigger('hashchange');
});
You just want to run something if the user isn't using Internet Explorer?
Try this:
if ($.browser.msie) {
return false;
} else {
//do something
}
I presume you're talking the support for the CSS opacity property and not the slightly buggy -ms-filter and filter which IE8 and below uses. In that case, the best method would be to test for the existence of the property in the style object of an element:
if('opacity' in document.createElement('div').style) {
// Do something that requires native opacity support
}
(You might want to cache the test element there if you want to test multiple properties)
so im a little rusty with my JS, but here is my code...
basically i have an image, that on mouseover, it cycles through a hidden div full of other images... fading it out, replacing the src, and fading back in. it works great. but once it gets through all the images, i want it to start back over and keep looping through them until the mouseout event stops it.
i thought i could just call the function again from within the function cycle_images($(current_image));, but that leads to the browser freaking out, understandably. what is a good method to accomplish this?
$.fn.image_cycler = function(options){
// default configuration properties
var defaults = {
fade_delay: 150,
image_duration: 1500,
repeatCycle: true,
};
var options = $.extend(defaults, options);
this.each(function(){
var product = $(this);
var image = $('div.image>a.product_link>img', product);
var description = $('div.image>div.description', product);
var all_images = $('div.all_images', product);
var next_image = ($(all_images).find('img[src="' + $(image).attr('src') + '"]').next('img').attr('src')) ? $(all_images).find('img[src="' + $(image).attr('src') + '"]').next('img') : $(all_images).children('img').first();;
// mouseover
image.fadeOut(options.fade_delay, function(){
image.attr('src', next_image.attr('src'));
image.fadeIn(options.fade_delay);
});
if (options.repeatCycle){
var loop = function() {
product.image_cycler();
}
setTimeout(loop,options.image_duration);
}
});
};
$(document).ready(function() {
$('div.product').hover(function(){
$(this).image_cycler();
}, function(){
$(this).image_cycler({repeatCycle: false});
});
});
It sounds like you want it to re-cycle and stop on mouseout.
After you define the cycle_images() function, add a flag, repeatCycle
cycle_images.repeatCycle = true;
At the end of the cycle_images function, add a recursive call with a timeout
if (this.repeatCycle) {
var f = function() {
return cycle_images.apply(this, [$current_image]);
}
setTimeout(f,50);
}
Then in mouseout,
cycle_images.repeatCycle = false;