I want to ask if there is a way to stop the auto looping. My goal is to have pause as default state and when user hover over my loop (images) it starts looping.
I managed to reverse the hover effect(mousenter starting the loop, mouseleave pause the loop), which was no problem. But i cant figure how to pause loop when my page loads.
HTML:
<div data-looper="go" class="looper">
<div class="looper-inner">
<div class="item">
<img src="http://lorempixel.com/320/240/sports" alt="">
</div>
<div class="item">
<img src="http://lorempixel.com/320/240/animals" alt="">
</div>
<div class="item">
<img src="http://lorempixel.com/320/240/food" alt="">
</div>
</div>
</div>
looper.js
;(function($, window, document, undefined) {
"use strict";
// css transition support detection
var cssTransitionSupport = (function(){
// body element
var body = document.body || document.documentElement,
// transition events with names
transEndEvents = {
'transition' : 'transitionend',
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition' : 'transitionend',
'MsTransition' : 'MSTransitionEnd',
'OTransition' : 'oTransitionEnd otransitionend'
}, name;
// check for each transition type
for (name in transEndEvents){
// if transition type is supported
if (body.style[name] !== undefined) {
// return transition end event name
return transEndEvents[name];
}
}
// css transitions are not supported
return false;
})();
// class definition
var Looper = function(element, options) {
this.$element = $(element);
this.options = options;
this.looping = false;
// self-reference
var self = this;
// setup keyboard accessibility
this.$element.attr('tabindex', 0)
// use keydown, keypress not reliable in IE
.keydown(function(e){
// handle key
switch (e.which) {
// left arrow
case 37:
// go to previous item
self.prev();
break;
// right arrow
case 39:
// go to next item
self.next();
break;
// give control back to browser for other keys
default: return;
}
// prevent browser default action
e.preventDefault();
})
// ARIA
.find('.item').attr('aria-hidden', true);
// setup pause on hover
this.options.pause === 'hover' && this.$element
.on('mouseenter', $.proxy(this.loop, this))
.on('mouseleave', $.proxy(this.pause, this));
// trigger init event
this.$element.trigger('init');
};
// class prototype definition
Looper.prototype = {
/**
* Start auto-loop of items
* #param e
* #return Looper
*/
loop: function(e) {
if (!e) this.paused = false;
// check for interval
if (this.interval) {
// remove interval
clearInterval(this.interval);
this.interval = null;
}
// setup new loop interval
if (this.options.interval && !this.paused)
this.interval = setInterval($.proxy(this.next, this), this.options.interval);
// return reference to self for chaining
return this;
},
/**
* Pause auto-loop of items
* #param e
* #return Looper
*/
pause: function(e) {
if (!e) this.paused = true;
if (this.$element.find('.next, .prev').length && cssTransitionSupport) {
this.$element.trigger(cssTransitionSupport);
this.loop();
}
// remove interval
clearInterval(this.interval);
this.interval = null;
// return reference to self for chaining
return this;
},
/**
* Show next item
* #return Looper
*/
next: function() {
// return if looping
if (this.looping) return this;
// go to next item
return this.go('next');
},
/**
* Show previous item
* #return Looper
*/
prev: function() {
// return if looping
if (this.looping) return this;
// go to previous item
return this.go('prev');
},
/**
* Show item at specified position
* #param pos
* #return Looper
*/
to: function(pos) {
// return if looping
if (this.looping) return this;
// zero-base the position
--pos;
var // all items
$items = this.$element.find('.item'),
// active item
$active = $items.filter('.active'),
// active position
activePos = $items.index($active);
// return if position is out of range
if (pos > ($items.length - 1) || pos < 0) return this;
// if position is already active
if (activePos == pos)
// restart loop
return this.pause().loop();
// show item at position
return this.go($($items[pos]));
},
/**
* Show item
* #param to
* #return Looper
*/
go: function(to) {
// return if looping
if (this.looping) return this;
// all items
var $items = this.$element.find('.item');
// if no items, do nothing
if (!$items.length) return this;
// active item
var $active = $items.filter('.active'),
// active position
activePos = $items.index($active),
// next item to show
$next = typeof to == 'string' ? $active[to]() : to,
// next position
nextPos = $items.index($next),
// is there an auto-loop?
isLooping = this.interval,
// direction of next item
direction = typeof to == 'string'
? to
: ((activePos == -1 && nextPos == -1) || nextPos > activePos
? 'next'
: 'prev'),
// fallback if next item not found
fallback = direction == 'next' ? 'first' : 'last',
// self-reference
that = this,
// finish
complete = function(active, next, direction) {
// if not looping, already complete
if (!this.looping) return;
// set looping status to false
this.looping = false;
// update item classes
active.removeClass('active go ' + direction)
// update ARIA state
.attr('aria-hidden', true);
next.removeClass('go ' + direction).addClass('active')
// update ARIA state
.removeAttr('aria-hidden');
// custom event
var e = $.Event('shown', {
// related target is new active item
relatedTarget: next[0],
// related index is the index of the new active item
relatedIndex: $items.index(next)
});
// trigger shown event
this.$element.trigger(e);
};
// ensure next element
$next = $next && $next.length ? $next : $items[fallback]();
// return if next element is already active
if ($next.hasClass('active')) return this;
// custom event
var e = $.Event('show', {
// related target is next item to show
relatedTarget: $next[0],
relatedIndex: $items.index($next[0])
});
// trigger show event
this.$element.trigger(e);
// return if the event was canceled
if (e.isDefaultPrevented()) return this;
// set looping status to true
this.looping = true;
// if auto-looping, pause loop
if (isLooping) this.pause();
// if using a slide or cross-fade
if (this.$element.hasClass('slide') || this.$element.hasClass('xfade')) {
// if css transition support
if (cssTransitionSupport) {
// add direction class to active and next item
$next.addClass(direction);
$active.addClass('go ' + direction);
// force re-flow on next item
$next[0].offsetWidth;
// add go class to next item
$next.addClass('go');
// finish after transition
this.$element.one(cssTransitionSupport, function() {
// weird CSS transition when element is initially hidden
// may cause this event to fire twice with invalid $active
// element on first run
if (!$active.length) return;
complete.call(that, $active, $next, direction);
});
// ensure finish
setTimeout(function() {
complete.call(that, $active, $next, direction);
}, this.options.speed);
// no css transition support, use jQuery animation fallback
} else {
// setup animations
var active = {},
activeStyle,
next = {},
nextStyle;
// save original inline styles
activeStyle = $active.attr('style');
nextStyle = $next.attr('style');
// cross-fade
if (this.$element.hasClass('xfade')) {
active['opacity'] = 0;
next['opacity'] = 1;
$next.css('opacity', 0); // IE8
}
// slide
if (this.$element.hasClass('slide')) {
if (this.$element.hasClass('up')) {
active['top'] = direction == 'next' ? '-100%' : '100%';
next['top'] = 0;
} else if (this.$element.hasClass('down')) {
active['top'] = direction == 'next' ? '100%' : '-100%';
next['top'] = 0;
} else if (this.$element.hasClass('right')) {
active['left'] = direction == 'next' ? '100%' : '-100%';
next['left'] = 0;
} else {
active['left'] = direction == 'next' ? '-100%' : '100%';
next['left'] = 0;
}
}
$next.addClass(direction);
// do animations
$active.animate(active, this.options.speed);
$next.animate(next, this.options.speed, function(){
// finish
complete.call(that, $active, $next, direction);
// reset inline styles
$active.attr('style', activeStyle || '');
$next.attr('style', nextStyle || '');
});
}
// no animation
} else {
// finish
complete.call(that, $active, $next, direction);
}
// if auto-looping
if ((isLooping || (!isLooping && this.options.interval))
// and, no argument
&& (!to
// or, argument not equal to pause option
|| (typeof to == 'string' && to !== this.options.pause)
// or, argument is valid element object and pause option not equal to "to"
|| (to.length && this.options.pause !== 'to')))
// start/resume loop
this.loop();
// return reference to self for chaining
return this;
}
};
// plugin definition
$.fn.looper = function(option) {
// looper arguments
var looperArgs = arguments;
// for each matched element
return this.each(function() {
var $this = $(this),
looper = $this.data('looperjs'),
options = $.extend({}, $.fn.looper.defaults, $.isPlainObject(option) ? option : {}),
action = typeof option == 'string' ? option : option.looper,
args = option.args || (looperArgs.length > 1 && Array.prototype.slice.call(looperArgs, 1));
// ensure looper plugin
if (!looper) $this.data('looperjs', (looper = new Looper(this, options)));
// go to position if number
if (typeof option == 'number') looper.to(option);
// execute method if string
else if (action) {
// with arguments
if (args) looper[action].apply(looper, args.length ? args : ('' + args).split(','));
// without arguments
else looper[action]();
}
// if there's an interval, start the auto-loop
else if (options.interval) looper.loop()
// otherwise, go to first item in the loop
else looper.go();
});
};
// default options
$.fn.looper.defaults = {
// auto-loop interval
interval: 2000,
// when to pause auto-loop
pause: 'hover',
// css transition speed (must match css definition)
speed: 500
};
// constructor
$.fn.looper.Constructor = Looper;
// data api
$(function() {
// delegate click event on elements with data-looper attribute
$('body').on('click.looper', '[data-looper]', function(e) {
var $this = $(this);
// ignore the go method
if ($this.data('looper') == 'go') return;
// get element target via data (or href) attribute
var href, $target = $($this.data('target')
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')), //strip for ie7
// setup options
options = $.extend({}, $target.data(), $this.data());
// start looper plugin
$target.looper(options);
// prevent default event
e.preventDefault();
});
// auto-init plugin
$('[data-looper="go"]').each(function(){
var $this = $(this);
$this.looper($this.data());
});
});
})(jQuery, window, document);
I did not find anything useful in plugin documentation.
Link to looper.js website
Related
I am trying to modify the codedrop multilevel menu.
What I am trying to do is to take the sidebar main category and insert into topbar and there submenu should open in sidebar but I am having issues most of them I have solved but some of them I m not able to.
So where I m currently suck rightnow
modified part works fine
MLMenu.prototype._initEvents = function() {
var self = this;
console.log(self);
for(var i = 0, len = this.menusArr.length; i < len; ++i) {
this.menusArr[i].menuItems.forEach(function(item, pos) {
item.querySelector('a').addEventListener('click', function(ev) {
var submenu = ev.target.getAttribute('data-submenu'),
itemName = ev.target.innerHTML,
// subMenuEl = self.el.querySelector('ul[data-menu="' + submenu + '"]');
subMenuEl = document.getElementById('sidebar').querySelector('ul[data-menu="' + submenu + '"]');
// console.log(subMenuEl)
// console.log(self.el);
// console.log(itemName);
// console.log(document.getElementById('sidebar'));
// check if there's a sub menu for this item
if( submenu && subMenuEl ) {
ev.preventDefault();
// open it
self._openSubMenu(subMenuEl, pos, itemName);
}
else {
// add class current
var currentlink = self.el.querySelector('.menu__link--current');
if( currentlink ) {
classie.remove(self.el.querySelector('.menu__link--current'), 'menu__link--current');
}
classie.add(ev.target, 'menu__link--current');
// callback
self.options.onItemClick(ev, itemName);
}
});
});
}
// back navigation
if( this.options.backCtrl ) {
this.backCtrl.addEventListener('click', function() {
self._back();
});
}
};
Main.js (it triggers error)
main.js:288 Uncaught TypeError: Cannot read property 'menuEl' of
undefined
Below is the code whree it triggers
MLMenu.prototype._menuIn = function(nextMenuEl, clickPosition) {
var self = this,
// the current menu
currentMenu = this.menusArr[this.current_menu].menuEl,
isBackNavigation = typeof clickPosition == 'undefined' ? true : false,
// index of the nextMenuEl
nextMenuIdx = this.menus.indexOf(nextMenuEl),
nextMenu = this.menusArr[nextMenuIdx],
nextMenuEl = nextMenu.menuEl,
nextMenuItems = nextMenu.menuItems,
nextMenuItemsTotal = nextMenuItems.length;
// console.log(nextMenuIdx);
// console.log(currentMenu);
console.log(nextMenuEl);
// console.log('nm ' + nextMenu);
// console.log(nextMenuEl);
// console.log(self);
// slide in next menu items - first, set the delays for the items
nextMenuItems.forEach(function(item, pos) {
item.style.WebkitAnimationDelay = item.style.animationDelay = isBackNavigation ? parseInt(pos * self.options.itemsDelayInterval) + 'ms' : parseInt(Math.abs(clickPosition - pos) * self.options.itemsDelayInterval) + 'ms';
// we need to reset the classes once the last item animates in
// the "last item" is the farthest from the clicked item
// let's calculate the index of the farthest item
var farthestIdx = clickPosition <= nextMenuItemsTotal/2 || isBackNavigation ? nextMenuItemsTotal - 1 : 0;
if( pos === farthestIdx ) {
onEndAnimation(item, function() {
// reset classes
if( self.options.direction === 'r2l' ) {
classie.remove(currentMenu, !isBackNavigation ? 'animate-outToLeft' : 'animate-outToRight');
classie.remove(nextMenuEl, !isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
}
else {
classie.remove(currentMenu, isBackNavigation ? 'animate-outToLeft' : 'animate-outToRight');
classie.remove(nextMenuEl, isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
}
classie.remove(currentMenu, 'menu__level--current');
classie.add(nextMenuEl, 'menu__level--current');
//reset current
self.current_menu = nextMenuIdx;
// control back button and breadcrumbs navigation elements
if( !isBackNavigation ) {
// show back button
if( self.options.backCtrl ) {
classie.remove(self.backCtrl, 'menu__back--hidden');
}
// add breadcrumb
self._addBreadcrumb(nextMenuIdx);
}
else if( self.current_menu === 0 && self.options.backCtrl ) {
// hide back button
classie.add(self.backCtrl, 'menu__back--hidden');
}
// we can navigate again..
self.isAnimating = false;
// focus retention
nextMenuEl.focus();
});
}
});
// animation class
if( this.options.direction === 'r2l' ) {
classie.add(nextMenuEl, !isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
}
else {
classie.add(nextMenuEl, isBackNavigation ? 'animate-inFromRight' : 'animate-inFromLeft');
}
};
Links
main.js https://pastebin.com/sKzeqAnD (modifed)
classic.js https://pastebin.com/PERqmCSb (not modifed)
modernizr https://pastebin.com/ZUNZbgK0 ( not modifed)
and jsfiddle
in bootstrap/js/carousel.js, the slide function of Carousel has a line below:
$next[0].offsetWidth // force reflow
With this line, the transition of next slide work currently after the transition of active slide. But, without this line, the transition of next slide do not work. That make me confused.
Just want to know, how the code above make that effect. Thank you!
i paste the slide function below:
Carousel.prototype.slide = function(type, next){
var $active = this.$element.find('.item.active');
var $next = next || this.getItemForDirection(type, $active);
var isCycling = this.interval;
var direction = type == 'prev' ? 'right' : 'left'
var that = this;
if($next.hasClass('active')) return this.sliding = false;
this.sliding = true;
var relatedTarget = $next[0];
isCycling && this.pause();
if(this.$indicators.length){
this.$indicators.find('.active').removeClass('active');
var nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]);
nextIndicator && nextIndicator.addClass('active');
}
var slideEvent = $.Event('carousel.slide.after', {relatedTarget: relatedTarget, direction: direction});
if($.support.transition && this.$element.hasClass('slide')){
$next.addClass(type);
$next[0].offsetWidth // force reflow
$active.addClass(direction);
$next.addClass(direction);
$active.one($.support.transition.end, function(e){
$next.removeClass([type, direction].join(' ')).addClass('active');
$active.removeClass(['active', direction].join(' '));
that.sliding = false;
setTimeout(function(){
that.$element.trigger(slideEvent);
}, 0)
})
.emulateTransitionEnd(Carousel.TRANSITION_DURATION);
}else{
$active.removeClass('active');
$next.addClass('active');
this.sliding = false;
}
isCycling && this.cycle();
};
I'm developing an application where I load content using ajax calls. But in a certain situation, I get an ajax call on a loop but it ends in random iterations. Below is the sequential code from the a double click event to the final ajax call.
//I use the double click event to fire the function
$('.item-container').on('click', '.item', function(e) {
var posX = e.pageX;
var posY = e.pageY;
var element = $(this);
var that = this;
setTimeout(function() {
var dblclick = parseInt($(that).data('double'), 10);
if (dblclick > 0) {
$(that).data('double', dblclick-1);
} else {
showToolTip(posX,posY,element);
}
}, 300);
}).on('dblclick', '.item', function(e) {
$(this).data('double', 2);
addItemsToBill(this);
});
function addItemsToBill(selectedItem) {
var element = null;
$(".bill-item-list").each(function() {
if ($(this).css("display") == "block") {
element = $(this);
return;
}
});
if (element == null) {
getSystemMessage(BILL_SELECT_VALIDATION_MSG);
return;
}
if ($('#open-tab .bill-list').find(".bill-item-list").length == 0
&& $(element).find('.bill-item').length == 0) {
getSystemMessage(BILL_SELECT_VALIDATION_MSG);
return;
}
var bill = $('.sample-elements').find('.bill-item');
var clone = bill.clone();
var itemId = $(selectedItem).attr('id');
var billId = $(element).parent().attr('id');
if ($(selectedItem).attr('isopen') == '1') {
$('.open-item-popup-overlay').lightbox_me( {
centered : true,
destroyOnClose : false,
});
$('.itmRate').val('');
$('#itmAmt').html('0.00');
$('.itemQty').text('1');
$('.itm-cancel').click(function() {
$('.open-item-popup-overlay').trigger('close');
});
//when the first run goes it's fine, bt after that, the looping starts from here. I double checked whether there is another code segment which fires this but there are none.
$('.itm-ok').click(function(){
if ($('.itmRate').val() == "") {
getSystemMessage(AMOUNT_INPUT);
return;
}
//This function gets iterated multiple times if I run the same event for multiple times.
//The function is only used in this two locations and no any other places.
billItemDrop(itemId, billId, clone, selectedItem, null,element, 1, true);
return false;
})
} else {
billItemDrop(itemId, billId, clone, selectedItem, null, element, 0,true);
}
}
function billItemDrop(itemId, billId, billItemClone, doubleClickedItem,draggableHelper, catchElement, isOpen, isDoubleClick) {
var openItemQuantity = stringToDouble($('.itemQty').val());
var openItemRate = stringToDouble($('.itmRate').val());
var openItemAmount = openItemQuantity * openItemRate;
var ajaxObject = 'itemId=' + itemId + '&billId=' + billId + '&qty='
+ openItemQuantity + '&rate=' + openItemRate + '&openItem='
+ isOpen;
ajaxCall(ADD_ITEM_TO_BILL,AJAX_POST,ajaxObject,function(response) {
var responseObject = response.billDetail;
var billTotal = responseObject.billTotal;
var total = response.billDetail.rate * response.billDetail.qty;
// Add Items to the bill failed
if (response.status != "success") {
getSystemMessage(ADD_ITEM_TO_PRINTED_BILL);
return;
}
billItemClone.attr('itemId', responseObject.itemId);
billItemClone.attr('billDetailId', responseObject.billDetailId);
billItemClone.find('.bill-item-name').text(
responseObject.itemName);
billItemClone.find('.bill-item-rate span').text(
responseObject.rate.toFixed(2));
billItemClone.find('.bill-item-amount').text(
total.toFixed(2));
billItemClone.find('.bill-item-qty span').text(responseObject.qty);
if (responseObject.kotBotNo != null) {
billItemClone.find('.kot-bot-num').text(
responseObject.kotBotNo);
}
billItemClone.draggable( {
revert : false,
addClasses : false,
zIndex : 1,
containment : "window",
opacity : 0.5,
cursor : "move",
scroll : false,
helper : function() {
return $(this).clone().appendTo('body').show();
}
});
if (isDoubleClick == true
&& (doubleClickedItem != undefined
|| doubleClickedItem != null || doubelClickedItem != "")) {
billItemClone.find('.bill-item-img img').attr('src',
$(doubleClickedItem).css('background-image'));
} else if (isDoubleClick == false
&& (draggableHelper != undefined
|| drdraggableHelper != null || draggableHelper != "")) {
billItemClone.find('.bill-item-img img').attr('src',
draggableHelper.css('background-image'));
}
if (catchElement.height() > 300) {
catchElement.css('overflow-y', 'scroll');
catchElement.css('height', '320px');
}
if (catchElement.find('.bill-item').length == 0) {
billItemClone.insertBefore(
catchElement.find('.item-drop-point')).hide()
.fadeIn("slow");
} else {
billItemClone.insertBefore(
catchElement.find('.bill-item').first()).hide()
.fadeIn("slow");
}
catchElement.parent().find('.amount')
.html(billTotal.toFixed(2));
if (draggableHelper != undefined || draggableHelper != null) {
draggableHelper.hide('fade', function() {
draggableHelper.remove()
});
}
$('.item-drop-point').removeClass('item-drop-hover');
},false);
}
/**
*
* Function for ajax calls - url : webservice url - type : ajax call type - data :
* data that needs to be passed - callback : function that needs to be executed
*
* when ajax call succeed
*
* #param url
* #param type
* #param data
* #param callback
*/
function ajaxCall(url, type, data, callback,async){
// Assign default value to async if its not specified
if (async===undefined) async = true;
$.ajax({
url: url,
type: type,
data: data,
dataType : "json",
cache: false,
beforeSend: function(x){
if(x && x.overrideMimeType){
x.overrideMimeType("application/json;charset=UTF-8");
}
},
async : async,
success:callback
});
}
The function gets iterated so the ajax call gets iterated too. I used break points to track the iteration but from the point of the click event I couldn't locate where it get's triggered after the first click event.
Is there anything wrong in the code which makes the function iterate. Please help.
Every time you call addItemsToBill(this); you are adding new event handlers to $('.itm-ok')
Try removing the call to billItemDrop(itemId, billId, clone, selectedItem, null,element, 1, true); and replacing it with a console.log('Test' If you find that multiple console logs are being made every time you click the issue is that every click is being handled by multiple handlers. If that is the case and you really want to use this structure you will have to unbind you click handler before rebinding it.
$('.itm-ok').off('click')
$('.itm-ok').on('click',function(){
if ($('.itmRate').val() == "") {
getSystemMessage(AMOUNT_INPUT);
return;
}
//This function gets iterated multiple times if I run the same event for multiple times.
//The function is only used in this two locations and no any other places.
billItemDrop(itemId, billId, clone, selectedItem, null,element, 1, true);
return false;
})
Hi I have the following code which control the position of an image gallery (which can be seen at steven.tlvweb.com). The scroll wheel currently controls the gallery position but the keydown events don't, and I would like them to. The alerts in the code below (keyLeft and keyRight) are seen but this.parent.scroll is not being called at all.
The argument sc just needs to be a positive or negative integer - that is what event.wheelDelta is after all so I am left wondering, what is the correct way to call this prototype function please?
/* //////////// ==== ImageFlow Constructor ==== //////////// */
function ImageFlow(oCont, xmlfile, horizon, size, zoom, border, start, interval) {
this.oc = document.getElementById(oCont);
this.scrollbar = getElementsByClass(this.oc, 'div', 'scrollbar');
this.text = getElementsByClass(this.oc, 'div', 'text');
this.bar = getElementsByClass(this.oc, 'img', 'bar');
this.arL = getElementsByClass(this.oc, 'img', 'arrow-left');
this.arR = getElementsByClass(this.oc, 'img', 'arrow-right');
this.bar.parent = this.oc.parent = this;
this.arL.parent = this.arR.parent = this;
/* === handle mouse scroll wheel === */
this.oc.onmousewheel = function () {
this.parent.scroll(event.wheelDelta);
return false;
}
var pleasework = this;
/* ==== add keydown events ==== */
window.document.onkeydown=function(){
pleasework.keypress(event.keyCode);
return false;
}
}
/* //////////// ==== ImageFlow prototype ==== //////////// */
ImageFlow.prototype = {
scroll: function (sc) {
if (sc < 0) {
if (this.view < this.NF - 1) this.calc(1);
} else {
if (this.view > 0) this.calc(-1);
}
},
keypress : function (kp) {
switch (kp) {
case 39:
//right Key
if (this.view < this.NF - 1) this.calc(1);
break;
case 37:
//left Key
if (this.view > 0) this.calc(-1);
break;
}
},
}
Thanks in advance
Steven (a novice Java programmer)
Do not use that parent property on the DOM elements. Instead, just create a closure over a local variable. So, replace
this.bar.parent = this.oc.parent = this;
this.arL.parent = this.arR.parent = this;
/* === handle mouse scroll wheel === */
this.oc.onmousewheel = function () {
this.parent.scroll(event.wheelDelta);
return false;
}
/* ==== add keydown events ==== */
window.document.onkeydown=function(){
this.parent.keypress(event.keyCode);
return false;
}
with
var parent = this;
this.oc.onmousewheel = function(e) {
parent.scroll(e.wheelDelta);
e.preventDefault();
};
window.document.onkeydown = function(e) { // notice this overwrites previous listeners
parent.keypress(e.keyCode);
};
I have developed a jQuery plugin which does the job of showing and hiding content with a two sequenced animations. I am happy with the behaviour of the plugin when used on a single element, however when there are two elements on the page with the same class name used in the plugin call I get four callbacks being returned. It would seem that the plugin is not quite right and I would appreciate any hints or help that could get me closer to finishing this plugin.
I am keen to improve the quality of my code and would also appreciate any general feedback.
A working example of the plugin can be found here: http://jsfiddle.net/nijk/sVu7h/
The plugin code is as follows:
(function($){
$.fn.showHide = function(method, duration, options, callback){
//console.log(method, duration, options, callback);
var animating = false;
var defaults = {
$elem: this,
easing: "swing"
}
var _sh = this;
var init = function(){
_sh.method = method;
if("show" !== _sh.method && "hide" !== _sh.method){
_sh.method = "show";
}
if( duration < 0 || (typeof(duration) == "string" && ("slow" !== duration && "normal" !== duration && "fast" !== duration) ) ){
duration = "normal";
}
console.log( duration, typeof(duration) );
if(typeof(options) == "function"){
callback = options;
options = {};
}
_sh.config = $.extend({}, defaults, options);
if(!animating){
//return _sh.each(function(index){
//console.log("found element number: " + (index + 1));
eval(_sh.method)();
//});
}
}
var show = function(){
animating = true;
_sh.config.$elem.wrap('<div class="show-hide"/>').parent().hide();
_sh.config.$elem.css({"opacity":0, "display":"block"});
console.log("element height:", _sh.config.$elem.parent().outerHeight());
_sh.config.$elem.parent().slideDown(duration, _sh.config.easing, function(){
_sh.config.$elem.animate({"opacity": 1}, duration, _sh.config.easing, function(){
console.log("show final cleanup called");
_sh.config.$elem.addClass("visible").unwrap();
$.isFunction(callback) && callback();
animating = false;
});
});
};
var hide = function(){
animating = true;
_sh.config.$elem.wrap('<div class="show-hide"/>');
_sh.config.$elem.animate({"opacity":0}, duration, _sh.config.easing, function(){
_sh.config.$elem.slideUp(duration, _sh.config.easing, function(){
console.log("hide final cleanup called");
_sh.config.$elem.removeClass("visible").hide().unwrap();
$.isFunction(callback) && callback();
animating = false;
});
});
}
init();
return this;
}
})(jQuery);
#david.mchonechase: Thank you very much for your explanation and the code example.
I have made some tweeks on the callbacks so that the correct context for 'this' is returned. Any suggestions to improve to code would be greatly appreciated.
Working code updated here: http://jsfiddle.net/nijk/sVu7h/ and as follows:
(function($){
$.fn.showHide = function(method, duration, options, callback){
var animating = false;
var defaults = { easing: "swing" };
var _sh = this;
_sh.method = show;
if("hide" === method){
_sh.method = hide;
}
if( duration < 0 || (typeof(duration) == "string" && ("slow" !== duration && "normal" !== duration && "fast" !== duration) ) ){
duration = "normal";
}
if(typeof(options) == "function"){
callback = options;
options = {};
}
_sh.config = $.extend({}, defaults, options);
function show(elem){
animating = true;
elem.wrap('<div class="show-hide"/>').parent().hide();
elem.css({"opacity":0, "display":"block"});
elem.parent().slideDown(duration, _sh.config.easing, function(){
elem.animate({"opacity": 1}, duration, _sh.config.easing, function(){
elem.addClass("visible").unwrap();
$.isFunction(callback) && callback.call(this);
animating = false;
});
});
};
function hide(elem){
animating = true;
elem.wrap('<div class="show-hide"/>');
elem.animate({"opacity":0}, duration, _sh.config.easing, function(){
elem.slideUp(duration, _sh.config.easing, function(){
elem.removeClass("visible").hide().unwrap();
$.isFunction(callback) && callback.call(this);
animating = false;
});
});
};
if(!animating){
// loop through each element returned by jQuery selector
return this.each(function(){
_sh.method($(this));
});
}
}
})(jQuery);
The problem is the mixture of global variables and the parent() call. The _sh.$elem variable contains two elements (one per jQuery selector result). The _sh.config.$elem.parent().slideDown call in the show function is called twice. Once complete, it then runs _sh.config.$elem.animate once per "showMe" element. So, the parent().slideDown is called twice, which then calls _sh.config.$elem.animate twice.
I usually try avoid global variables within jQuery plug-ins for functions like your show and hide, but the critical part is elements. (The animating global variable makes sense, though.)
I think something like this would work:
(function($){
$.fn.showHide = function(method, duration, options, callback){
//console.log(method, duration, options, callback);
var animating = false;
var defaults = {
//$elem: this,
easing: "swing"
}
var _sh = this;
var init = function(){
var methodFn = show; // reference actual function instead of string, since eval is evil (usually)
if("hide" === method){
methodFn = hide;
}
if( duration < 0 || (typeof(duration) == "string" && ("slow" !== duration && "normal" !== duration && "fast" !== duration) ) ){
duration = "normal";
}
console.log( duration, typeof(duration) );
if(typeof(options) == "function"){
callback = options;
options = {};
}
_sh.config = $.extend({}, defaults, options);
if(!animating){
// loop through each element returned by jQuery selector
_sh.each(function(){
methodFn($(this)); // pass the single element to the show or hide functions
});
}
}
var show = function(elem){
animating = true;
elem.wrap('<div class="show-hide"/>').parent().hide();
elem.css({"opacity":0, "display":"block"});
console.log("element height:", elem.parent().outerHeight());
elem.parent().slideDown(duration, _sh.config.easing, function(){
elem.animate({"opacity": 1}, duration, _sh.config.easing, function(){
console.log("show final cleanup called");
elem.addClass("visible").unwrap();
$.isFunction(callback) && callback();
animating = false;
});
});
};
var hide = function(elem){
animating = true;
elem.wrap('<div class="show-hide"/>');
elem.animate({"opacity":0}, duration, _sh.config.easing, function(){
elem.slideUp(duration, _sh.config.easing, function(){
console.log("hide final cleanup called");
elem.removeClass("visible").hide().unwrap();
$.isFunction(callback) && callback();
animating = false;
});
});
}
init();
return this;
}
})(jQuery);