JQuery plugin not working when used multiple times in a page - javascript

I am trying to write a JQuery plugin called grid2carousel which takes some content in a Bootstrap-style grid on desktop devices and becomes a carousel on smaller screens.
The plugin works fine if it is the only instance of it on a page but runs into some problems if there are more than one. I have created a Codepen here to demonstrate the issue:
http://codepen.io/decodedcreative/pen/BzdBpb
Try commenting out one of the components in the HTML section of the codepen, resizing the browser til it becomes a carousel, and then repeating this process again with it uncommented
The plugin works by running an internal function called SetupPlugin every time the browser width is below a breakpoint specified in a data attribute in the HTML. If the browser width exceeds this breakpoint a function called DestroyPlugin reverts the HTML back to its original state. Like so:
checkDeviceState = function(){
if($(window).width()>breakpointValue){
destroyPlugin();
}else{
if(!$element.hasClass('loaded')){
setupPlugin();
}
}
},
Below is my plugin code in its entirety. Could someone give me a pointer as to what I'm doing wrong here?
(function (window, $){
$.grid2Carousel = function (node, options){
var
options = $.extend({slidesSelector: '.g2c-slides', buttonsSelector: '.g2c-controls .arrow'}, {},options),
$element = $(node),
elementHeight = 0,
$slides = $element.find(options.slidesSelector).children(),
$buttons = $element.find(options.buttonsSelector),
noOfItems = $element.children().length + 1,
breakpoint = $element.data("bp"),
breakpointValue = 0;
switch(breakpoint){
case "sm":
breakpointValue = 767;
break;
case "md":
breakpointValue = 991;
break;
case "lg":
breakpointValue = 1199;
break;
}
setupPlugin = function(){
// Add loaded CSS class to parent element which adds styles to turn grid layout into carousel layout
$element.addClass("loaded");
// Get the height of the tallest child element
elementHeight = getTallestInCollection($slides)
// As the carousel slides are stacked on top of each other with absolute positioning, the carousel doesn't have a height. Set its height using JS to the height of the tallest item;
$element.height(elementHeight);
// Add active class to the first slide
$slides.first().addClass('active');
$buttons.on("click", changeSlide);
},
destroyPlugin = function(){
$element.removeClass("loaded");
$element.height("auto");
$buttons.off("click");
$slides.removeClass("active");
},
checkDeviceState = function(){
if($(window).width()>breakpointValue){
destroyPlugin();
}else{
if(!$element.hasClass('loaded')){
setupPlugin();
}
}
},
changeSlide = function(){
var $activeSlide = $slides.filter(".active"),
$nextActive = null,
prevSlideNo = $activeSlide.prev().index() + 1,
nextSlideNo = $activeSlide.next().index() + 1;
if($(this).hasClass('left')){
if(prevSlideNo !== 0){
$nextActive = $activeSlide.prev();
$nextActive.addClass('active');
$slides.filter(".active").not($nextActive).removeClass("active");
}else{
$nextActive = $slides.last();
$nextActive.addClass('active');
$slides.filter(".active").not($nextActive).removeClass("active");
}
}else if($(this).hasClass('right')){
if(nextSlideNo !== 0){
$nextActive = $activeSlide.next();
$nextActive.addClass('active');
$slides.filter(".active").not($nextActive).removeClass("active");
}else{
$nextActive = $slides.first();
$nextActive.addClass('active');
$slides.filter(".active").not($nextActive).removeClass("active");
}
}
},
getTallestInCollection = function(collection){
$(collection).each(function(){
if($(this).outerHeight() > elementHeight){
elementHeight = $(this).outerHeight();
}
});
return elementHeight;
};
setupPlugin();
checkDeviceState();
$(window).on("resize", checkDeviceState);
}
$.fn.grid2Carousel = function (options) {
this.each( function (index, node) {
$.grid2Carousel(node, options)
});
return this
}
})(window, jQuery);
Many thanks,
James

Please check line #30 in your plugin code,it looks that you've just forget to add var keyword so instead of creating local variables to store functions setupPlugin, destoryPlugin and so on you've created global variables and then each new initialization of your plugin is rewriting this functions to point to a newly created slider.
setupPlugin = function(){
should be
var setupPlugin = function(){
Updated code here.

Related

how to check multi div scroll to top?

I have multiple divs on my page with different classes
how can I check which div is scrolled to the top and therefore do something.
<div class="menu-session-0">
content
</div>
<div class="menu-session-1">
content
</div>
<div class="menu-session-2">
content
</div>
I already tried this :
$(window).scroll(function() {
setTimeout(function(){
var hT = $('.session-index_1').offset().top,
hH = $('.session-index_1').outerHeight(),
wH = $(window).height(),
wS = $(this).scrollTop() + 175;
if(hT <= (wS + 250)){
$('.menu-item').removeClass('menu-item_active');
$('.item-index-1').addClass('menu-item_active');
mySwiper.slideTo(1);
mySwiper.update();
}
},1050);
});
But it did not work as I expected...
Ok.. I get it now, I think.. :P
This should (in theory) work just fine if you copy and paste instead of your code.
If you really want to use JQuery just replace the element reference getters and class writers
const menuItems = document.getElementsByClassName('menu-item');
const menuSessions = document.getElementsByClassName('menu-session');
var nextSession, activeSession;
// do this to get menu-session offsets.. rerun function on viewport resize
function getSessionOffset() {
for( let session of menuSessions ) {
// store position and dataset.index directy on the element reference
session.y = session.offsetTop;
session.indx = session.dataset.index;
}
// define active elements which position we listen for
// these are correct if the window scroll offset is 0 (or at the top)
activeSession = menuSessions[0];
nextSession = menuSessions[1];
onScroll(window.pageYOffset); // so we check and set the correct active elements
}
getSessionOffset();
// page scroll listener
window.addEventListener( 'scroll' , ScrollHandler );
var lastScrollPos = 0; // last recorded window scroll offset
var scrollTick = false; // the tick is used to throttle code execution
function ScrollHandler (ev) {
lastScrollPos = ev.target.scrollTop;
if (!scrollTick) {
window.requestAnimationFrame(() => { // <- read up on this if you are not familiar. Very usefull
onScroll(lastScrollPos);
scrollTick = false;
});
scrollTick = true;
}
}
function onScroll(scrollY) {
if (scrollY > nextSession.y) { // if lower offset is less than scrollPos
removeActiveClass(activeSession.indx); // we remove the active class from menu item
activeSession = menuSessions[nextSession.indx]; // define new elements to listen for
nextSession = menuSessions[nextSession.indx + 1];
addActiveClass(activeSession.indx); // and add an active class to the new menu item
} else if (scrollY < activeSession.y) { // do the same here only in reverse
removeActiveClass(activeSession.indx);
nextSession = menuSessions[activeSession.indx];
activeSession = menuSessions[nextSession.indx - 1];
addActiveClass(activeSession.indx);
}
}
function removeActiveClass(indx) {
menuItems[indx].classList.remove('menu-item_active');
}
function addActiveClass(indx) {
menuItems[indx].classList.add('menu-item_active');
}
We listen only for the current and next values.
This may look like a lot but can be shortened to a third of the size.
Hope this helps :)

Scroll bottom in JavaScript

I have a working bottom function in JavaScript to detect if the user scrolls at the bottom. However, a problem comes when the user has a strange resolution (like windows scale) or when you zoom. The function is not working anymore and can't detect the bottom.
Here is what I did :
const bottom = e.target.scrollHeight - e.target.scrollTop === e.target.clientHeight;
if (bottom) {
this.props.getNewValues();
}
Is there a way to avoid that? Even when you don't zoom, this is not working for people displaying the site on a TV or something like this (like a friend of mine did)
Thanks you
EDIT : I'm applying this on a precise element and I repeat that my solution is working except by unzooming. Unzooming provides float values that made the response not really accurate (it goes from 1 to 50px of difference based on the zoom made)
I use this function (can't take credit as someone else wrote it - sorry for no credit - it was ages ago). Maybe you can adapt this to your use case:
(function($) {
//CHECK SCROLLED INTO VIEW UTIL
function Utils() {
}
Utils.prototype = {
constructor: Utils,
isElementInView: function (element, fullyInView) {
var pageTop = $(window).scrollTop();
var pageBottom = pageTop + $(window).height();
var elementTop = $(element).offset().top;
var elementBottom = elementTop + $(element).height();
if (fullyInView === true) {
return ((pageTop < elementTop) && (pageBottom > elementBottom));
} else {
return ((elementTop <= pageBottom) && (elementBottom >= pageTop));
}
}
};
var Utils = new Utils();
//END CHECK SCROLLED INTO VIEW UTIL
//USING THE ELEMENT IN VIEW UTIL
//this function tells what to do do when the element is or isnt in view.
//var inView = Utils.isElementInView(el, false); Where FALSE means the element doesnt need to be completely in view / TRUE would mean the element needs to be completely in view
function IsEInView(el) {
var inView = Utils.isElementInView(el, false);
if(inView) {
//console.log('in view');
} else {
//console.log('not in view');
}
};
//Check to make sure the element you want to be sure is visible is present on the page
var variableOfYourElement = $('#variableOfYourElement');
//if it is on this page run the function that checks to see if it is partially or fully in view
if( variableOfYourElement.length ) {
//run function on page load
IsEInView(variableOfYourElement);
//run function if the element scrolls into view
$(window).scroll(function(){
IsEInView(variableOfYourElement);
});
}
//END USING THE ELEMENT IN VIEW UTIL
})(jQuery);

Execute something while element is in view

I am using the Jquery inview plugin and I am trying to load some elements whenever the user reached the footer of the page. While doing this, I discovered a bug where if the user holds the scroll-click and drags the mouse towards the bottom, in some cases the elements will not load anymore until the footer is out of the view and then back into the view.
Here is the function that I have so far to load the elements when the footer is in the viewport:
//Infinite load function. Uses jquery.inview
$scope.addMoreElements = function(){
$scope.limitElementsPerPage += 16;
$('.footer').on('inview', function(event, isInView) {
if (isInView) {
// element is now visible in the viewport
$scope.limitElementsPerPage += 16;
} else {
// element has gone out of viewport
//do nothing
}
});
};
I am using Angularjs as well as jQuery for this project. Essentially, what I think I need is something that checks at about 1-2 seconds if the element is still in view. I am not exactly sure I should do this at the moment. This is what I tried to do to solve this issue:
$scope.$watch($('.footer'), function(){
$('.footer').on('inview', function(event, isInView) {
setTimeout(function(){
while(isInView){
console.log('test')
}
}, 1000);
});
});
This unfortunately, will crash the browser (I am not sure how I would go about doing this with the setTimeout or the other related functions).
Any help or ideas on how to do this would be greatly appreciated.
Thank you in advance.
InView adds a new event for elements, that triggers when the element enters the viewport. Probably some times you just have the footer in the viewport at all times, so that is why it fails.
I think you need to redesign the logic of the page to use the 'scroll' event on whatever element contains the added items and scrolls for the infinite view and in that event to check if the footer is in the viewport, not if it enters.
Personally I use this extension for checking if it is in the viewport:
(function($) {
$.inviewport = function(element, settings) {
var wh=$(window).height();
var wst=$(window).scrollTop();
var et=$(element).offset().top;
var eh=$(element).height();
return !(wh + wst <= et)&&!(wst >= et + eh);
};
$.extend($.expr[':'], {
"in-viewport": function(a, i, m) {
return $.inviewport(a);
}
});
})(jQuery);
Here are couple of functions you can use:
var getScrollY = function(){
var supportPageOffset = window.pageXOffset !== undefined;
var isCSS1Compat = ((document.compatMode || "") === "CSS1Compat");
var y = supportPageOffset ? window.pageYOffset : isCSS1Compat ?
document.documentElement.scrollTop : document.body.scrollTop;
return y;
}
function get_elem_y( elem ) {
var box = elem.getBoundingClientRect();
return box.top + getScrollY();
}
And then you can listen to the scroll event, assume footer is something like <div id="footer">...</div>
var footer = document.getElementById("footer"); // get footer
var b_foot_visible = false;
window.addEventListener("scroll", function() {
var y = get_elem_y(footer);
var pageHeight = ( window.innerHeight || document.body.clientHeight);
if((getScrollY() + pageHeight) > y ) {
// footer is visible
if(!b_foot_visible) {
// TODO: add something
b_foot_visible = true;
}
} else {
// footer is not visible
if(b_foot_visible) {
// TODO: remove something
b_foot_visible = false;
}
}
});
Thus, when the scrollY + pages height reaches the footer elements Y coordinate you can do something to display things for the footer.
You might also add check in the beginning to test if the footer is already visible.

Affect a div when is out of view?

Is there a way to affect a div that is out of view? Ex: when you scroll down the page and the div is no longer visible.
I have an embedded youtube video and I would like to mute it only when the video is no longer in view.
This will mute every video player that is not visible:
$(function() {
var $w = $(window), oldw = 0, oldh = 0, oldt = 0;
function checkVideoVisible() {
if (oldw !== $w.width() || oldh !== $w.height() ||
oldt !== $w.scrollTop()) {
oldw = $w.width();
oldh = $w.height();
oldt = $w.scrollTop();
var top = oldt, bottom = oldt + oldh;
$("video").each(function() {
var $this = $(this);
if ($this.offset().top + $this.height() >= top &&
$this.offset().top < bottom) {
$this.prop("muted", false);
} else {
$this.prop("muted", true);
}
});
}
}
Now to trigger the checking, you can either use a timer:
var timerId = setInterval(checkVideoVisible, 200);
}
Or handle the scroll event:
$w.on("scroll", checkVideoVisible);
}
In the latter case, you will also need to perform a check when any change is made to the dom.
Use this as its probably your best bet im guessing as you;ve posted no code that a pre-written lib will help you
JQ Visible Lib
To implement you need to give your element an id and reference it in script tags or in a js file like this:
$('#element').visible() will return true if visible.
You can then add the part to mute/pause the video based on that state.

problems setting css with jquery

I have created a slideshow with jquery. It clones an image in a container, moves it to the right, then slides it to the left and starts over. Here is the code:
$(document).ready(function() {
var slideshow = new main.slideshow();
slideshow.start({
path: 'images/slideshow/',
images: ['1', '2']
});
});
var main = new (function() {
this.slideshow = (function() {
var self = this;
var nextSlide, path, images, startLeft;
var fileExtension = 'jpg';
var container = $('#slideshow');
var currentSlide = container.children('img');
var timerlength = 4000;
var currentSlideIndex = 0;
this.start = function(args) {
path = args['path'];
images = args['images'];
if (typeof args['fileExtension'] !== 'undefined') fileExtension = args['fileExtension'];
container.css('overflow', 'hidden');
currentSlide.css('position', 'absolute');
startLeft = currentSlide.position();
startLeft = startLeft.left;
self.nextSlide();
};
this.nextSlide = function() {
nextSlide = currentSlide.clone();
nextSlide.css('left', (startLeft + currentSlide.width()) + 'px');
currentSlideIndex++;
if (currentSlideIndex >= images.length) currentSlideIndex = 0;
nextSlide.attr('src', path + images[currentSlideIndex] + '.' + fileExtension);
container.append(nextSlide);
setTimeout(function() {
self.slideToNext();
}, timerlength);
};
this.slideToNext = function() {
currentSlide.animate({
left: '-' + (currentSlide.width() - startLeft) + 'px'
}, 2000);
nextSlide.animate({
left: startLeft + 'px'
}, 2000, function() {
currentSlide.remove();
currentSlide = nextSlide;
self.nextSlide();
});
};
});
});
A link to see this in action can be found here:
https://dustinhendricks.com/breastfest/public_html/
The problem I'm having as you can see is that the second slide when first added to the dom, does not seem to be moved to the right when I call css('left', x);. After the first jQuery animation however, each cloned slide then seems to be able to be moved to the right with that call no problem. This leads me to believe that jquery's animate is setting something that allows for the object to be moved via css('left', x);, but what could it be changing? position is already being set to absolute.
This is why my example pages seems to take a while before the slides start sliding. Any idea how I can fix?
If your first image is not loaded yet when you call .start() such that currentslide.width() isn't correct, then it won't set the proper initial value for left upon initialization. You may need to set a .load() event handler so you know when that first slide is loaded and you can wait for it to be loaded before starting the slideshow.
When testing this, you must set the .load() handler before the .src value is set on the image object (or you may miss the load event in IE) and you should make sure you test both the cases where no images are cached and where all images are cached (as the timing of the load event can be different in both cases).

Categories

Resources