Replacing Bootstrap Dropdown with Dropup (Different activity on two near identical implementations) - javascript

I'm working on a project over at github pages, which I replace a bootstrap .dropdown with .dropup if the div's overflow-y: scroll will cause the dropdown menu to be cutoff / overflow. You can see the function working properly at this jsfiddle. Notice if you click on the ellipsis icon to the right on the top rows, it will drop down, if you click on the icon on the bottom rows, it will drop up.
Now, my actual implementation (github page), the code is exactly the same (below), but it wants to replace all .dropdown classes with .dropup when opened, including the top-most row which gets cut off, seen in the photo below.
I've been struggling with this for a week and can't quite figure it out. I've tried a few different things that I thought fixed it but ended up just being a hack and didn't work on mobile, or replaced some but not all etc.
Here is the Javascript / jQuery I'm using, which can be seen in the jsfiddle and my github source here.
$(document).on("shown.bs.dropdown", ".dropdown", function () {
// calculate the required sizes, spaces
var $ul = $(this).children(".dropdown-menu");
var $button = $(this).children(".song-menu");
var ulOffset = $ul.offset();
// how much space would be left on the top if the dropdown opened that direction
var spaceUp = (ulOffset.top - $button.height() - $ul.height()) - $('#playlist').scrollTop();
// how much space is left at the bottom
var spaceDown = $('#playlist').scrollTop() + $('#playlist').height() - ((ulOffset.top + 10) + $ul.height());
// switch to dropup only if there is no space at the bottom AND there is space at the top, or there isn't either but it would be still better fit
if (spaceDown < 0 && (spaceUp >= 0 || spaceUp > spaceDown))
$(this).addClass("dropup");
}).on("hidden.bs.dropdown", ".dropdown", function() {
// always reset after close
$(this).removeClass("dropup");
});
Edit:
To clear up any confusion, here's an example of the behavior without my added .dropup function. jsfiddle Notice when you click the last menu item, it opens the menu but requires scrolling. I specifically want to remove the .dropdown class and add .dropup in this case, so no scrolling is required.

It took some basic math, but I managed to figure out what you desired to do. This code changes the bootstrap classes between dropup and dropdown depending on the room available for a normal dropdown.
I calculated this by detracting the height of the button, dropdownmenu and how far the button was scrolled down in the scrollContainer from the height of the scrollContainer. I got the value how much the div was scrolled down by using the buttons offset and detracting the offset from the scrollContainer.
Here is my jQuery (I selected the .playlist class because this was attached to your scrollContainer, but you should replace it by an id or select it by other means):
$(".dropdown, .dropup").click(function(){
var dropdownClassCheck = $(this).hasClass('dropdown');
var buttonOffset = $(this).offset().top;
var scrollboxOffset = $('.playlist').offset().top;
var buttonHeight = $(this).height();
var scrollBoxHeight = $('.playlist').height();
var dropDownButtonHeight = $(this).children('ul').height();
dropdownSpaceCheck = scrollBoxHeight>buttonOffset-scrollboxOffset+buttonHeight+dropDownButtonHeight;
if(dropdownClassCheck && !dropdownSpaceCheck){
$(this).removeClass('dropdown').addClass('dropup');
}
else if(!dropdownClassCheck && dropdownSpaceCheck){
$(this).removeClass('dropup').addClass('dropdown');
}
});
A working JSFiddle
Let me know if there are parts of the code that could be improved/done easier or if there are any problems with my solution.

I have not thoroughly checked, but .scrollTop() is probably why the code fails when combined with other elements in the DOM, so here is a solution without it:
function checkHeights(){
// LOOP through each dropdown
$('.dropdown,.dropup').each(function(index,element){
var $dropDown = $(element),
$dropDownMenu = $dropDown.find('.dropdown-menu'),
dropDownTop = $dropDown.offset().top,
visibleHeight = $dropDown.height(),
hiddenHeight = $dropDownMenu.height(),
ddTop = dropDownTop - hiddenHeight,
ddBottom = dropDownTop + visibleHeight + hiddenHeight;
// LOOP through all parents
$dropDown.parents().each(function(ix,el){
var $el = $(el);
// CHECK if any of them have overflow property set
if( $el.css('overflow') !== 'visible' ){
var limitTop = $el.offset().top,
limitBottom = limitTop + $el.height();
// CHECK if parent is better fit when dropped upside
if( limitBottom < ddBottom && ( ddTop - limitTop ) > ( limitBottom - ddBottom ) )
$dropDown.removeClass('dropdown').addClass('dropup');
else
$dropDown.removeClass('dropup').addClass('dropdown');
// BREAK LOOP
return false;
}
});
});
}
$(document).ready(function() {
checkHeights();
$('.playlist').scroll(checkHeights);
});
JS Fiddle here.
This one does not require any class or id given to it except for dropdown,dropdown-menu, and dropup (all of which are Bootstrap defaults) and would work fine even if there are multiple playlists on page.
UPDATE
The code is modified and wrapped in a function in order to allow being called when scroll event fires.

I think that the problem it's that you have a big header, and the jsFiddle don't. So ulOffset.top it's always big, and spaceDown is always negative

Replace parent div.dropdown with div.dropup.

Related

How to change the element class when the last one targets, and stays over other element?

The project has the sticky menu with the white svg logo. When the sticky menu over the black section menu should stay white, if over the black section the logo should convert to the black. I tried a lot of variant for goal this. But all of them does not work properly. I olso tried to use old jQuery library: background-check but it does not work at all. Here is the last variant I created:
$(window).scroll(function() {
let headerLogo = $("#header_logo");
let overlaySection = $("#overlay-section");
let headerLogo_top = headerLogo.offset().top;
let headerLogo_bottom = headerLogo_top + headerLogo.height();
let overlaySection_top = overlaySection.offset().top;
let overlaySection_bottom = overlaySection_top + overlaySection.height();
let services = $("#services");
let services_top = services.offset().top;
let services_bottom = services_top + services.height();
if (headerLogo_bottom >= overlaySection_top && headerLogo_top < overlaySection_bottom) {
headerLogo.addClass('black_active')
}else if(headerLogo_bottom >= services_top && headerLogo_top < services_bottom){
headerLogo.removeClass('black_active')
}
else{
headerLogo.removeClass('black_active')
}
});
And it almost works but not properly because the class changed to early and logo color changed from black to whit when it does not target the next black block. Maybe there is some old but good works jQuery library please advise I need to figure out in this issue.
Instead of addClass and removeClass, have you tried the following?
headerLogo.classList.toggle('black_active');
For some reason I also always get stuck with the other two, but this always seems to work.

making multiple sliders with one set of jQuery code

I've made a slider with jQuery using html tags which have classes instead of IDs so that I will be able to use the same jQuery code for other duplicated sliders.
The problem is, I want the width of my ul to be calculated based on the number of lis, instead of setting it manually in CSS. When there is only one slider I can set my vars outside of the function, but when I have to use it though event attributes on my html parts so that I will be able to use them for multiple sliders, I have to move the vars inside of my function, which sets the wrong width.
This is my code:
function OLAR(direction,span) {
var OurNexNPrv = $(span);
var Parent_OLAR = OurNexNPrv.parents('.OLAR');
var UL_OF_OLAR = Parent_OLAR.find('.OLAR_CONTENT ul');
var LI_OF_OLAR = UL_OF_OLAR.find('li');
var LI_OF_OLAR_LENGTH = LI_OF_OLAR.length;
var Quantity_OF_OLAR_PAGES = LI_OF_OLAR_LENGTH / 3;
var Max_Margin_LEFT = -(Quantity_OF_OLAR_PAGES - 1) * 576;
UL_OF_OLAR.css('width',LI_OF_OLAR_LENGTH*192);
var AffectedLeftMargin;
var CurrentLeftMargin = UL_OF_OLAR.css('margin-left');
CurrentLeftMargin = parseFloat(CurrentLeftMargin);
if (direction == 'right') {
AffectedLeftMargin = CurrentLeftMargin - 576;
}
if (direction == 'left') {
AffectedLeftMargin = CurrentLeftMargin + 576;
}
if (AffectedLeftMargin < Max_Margin_LEFT) {
AffectedLeftMargin = 0;
}
if (AffectedLeftMargin > 0) {
AffectedLeftMargin = Max_Margin_LEFT;
}
UL_OF_OLAR.animate({'marginLeft': AffectedLeftMargin}, 1000);
}
$('.CIRCLE_LOAD_RIGHT').click(function () {
OLAR('right');
});
$('.CIRCLE_LOAD_LEFT').click(function () {
OLAR('left');
});
How can I set the width for each of my uls individually, through css commands outside of that function?
Okay, my ul tag did not have the absolute position; after giving it the right position and left:0 and bottom:0 or right:0 and bottom:0 (based on your language)the given width which comes from JQuery code can rhyme perfectly with everything else. I've tried it with multiple sliders and it works perfectly :)
So, we need an anchor point in order to make this work. And there is nothing wrong with this JQuery code which has been mentioned above!
Have a great day.

Adding and removing classes at different heights on page using jQuery

I want to remove/add classes when the user is at different distances from the top by using jQuery.
I have successfully done it, and it works fine, but I think I'm doing it wrong, and I would like your help to optimize the code.
The html is simple, basically the sections(including the header), have 100% width. and different colors. I want to make the header change color when its over the first section(for aesthetical purposes).
And I also want it to have a shadow when the page has been scrolled more than 1 pixel.
I'm doing it by adding/removing classes.
When I use one big else if statement it doesn't work well because whenever any any condition is matched js stops checking for other matches, so it doesn't apply all the classes needed.
The next code works, however as I said, I think that it's not optimal/bad written.
Here is the HTML markup:
<header class="dark no-shadow">
Header
</header>
<section class="blue">
Please Scroll Down to see the header changes...
</section>
<section>
The header color Should change when you pass through me.
</section>
And here is the jQuery code:
var header = $('header'),
blueSection = $('section.blue'),
// Calculate when to change the color.
offset = blueSection.offset().top + blueSection.height() - header.height();
$(window).scroll(function(){
var scroll = $(window).scrollTop();
// Remove Class "dark" after scrolling over the dark section
if (scroll >= offset) {
header.removeClass('dark');
} else {
header.addClass('dark');
}
// Remove Class "no-shadows" whenever not on the top of the page.
if (scroll >= 1) {
header.removeClass('no-shadow');
} else {
header.addClass('no-shadow');
}
});
And for those of you who like to use jsfiddle(like me!):
https://jsfiddle.net/shock/wztdt077/6/
Thanks ahead guys!
Here is what I've come up with:
var header = $('header'),
blueSection = $('section.blue'),
// Calculate when to change the color.
offset = blueSection.offset().top + blueSection.height() - header.height();
var add = function(obj, cls) {obj.addClass(cls);}
var remove = function(obj, cls) {obj.removeClass(cls);}
var stylePoints = [offset, 1, 100, 200];
var styleTo = ['dark', 'no-shadow', 'blue', 'tall'];
var styleType = [add, add, remove, remove];
$(window).scroll(function() {
var scroll = $(window).scrollTop();
for (i = 0; i < stylePoints.length; i++) {
var func = styleType[i];
if (scroll >= stylePoints[i])
(styleType[i] == add) ? remove(header, styleTo[i]) : add(header, styleTo[i]);
else func(header, styleTo[i]);
}
});
It's not that much longer than your current jQuery, and allows for (theoretically) an infinite number of style changes without having to add a million long if/else statements. To add a new style change, you have to add a value to the end of each of the three arrays. stylePoints specifies the scrollTop() value at which a style should either be added or removed. styleTo specifies the class to be added or removed. styleType specifies whether this class should be added or removed when the user is scrolled above the corresponding stylePoints value. The opposite will occur when the user is scrolled below or at the corresponding stylePoints value. For instance, you can see from the code that the tall class will be removed from the header when the user is scrolled above 200, and added when the user is scrolled below or at 200.

jQuery infinite scrolling scrolling in fixed div

Background
I am trying to create an infinite scrolling table inside a fixed position div. The problem is that all the solutions I come across use the window height and document scrollTop to calculate if the user has scrolled to the bottom of the screen.
Problem
I have tried to create a jQuery plugin that can calculate if a user has scrolled to the bottom of a fixed div with overflow: scroll; set.
My approach has been to create a wrapper div (the div with a fixed position and overflow: scroll) that wraps the table, I also place another div at the bottom of the table. I then try calculate if the wrapper.scrollTop() is greater than the bottom div position.top every time the wrapper is scrolled. I then load the new records and append them to the table body.
$.fn.isScrolledTo = function () {
var element = $(this);
var bottom = element.find('.bottom');
$(element).scroll(function () {
if (element.scrollTop() >= bottom.position().top) {
var tableBody = element.find("tbody");
tableBody.append(tableBody.html());
}
});
};
$('.fixed').isScrolledTo();
See Example http://jsfiddle.net/leviputna/v4q3a/
Question
Clearly my current example is not correct. My question is how to I detect when a user has scrolled to the bottom of a fixed div with overflow:scroll set?
Using the bottom element is a bit clunky, I think. Instead, why not use the scrollHeight and height to test once the scrollable area has run out.
$.fn.isScrolledTo = function () {
var element = this,
tableBody = this.find("tbody");
element.scroll(function(){
if( element.scrollTop() >= element[0].scrollHeight-element.height()){
tableBody.append(tableBody.html());
}
});
};
$('.fixed').isScrolledTo();
EDIT (12/30/14):
A DRYer version of the plugin might be much more re-usable:
$.fn.whenScrolledToBottom = function (cback_fxn) {
this.on('scroll',this,function(){
if( ev.data.scrollTop() >= ev.data[0].scrollHeight - ev.data.height()){
return cback_fxn.apply(ev.data, arguments)
}
});
};
Plugin Usage:
var $fixed = $('.fixed'),
$tableBody = $fixed.find("tbody");
$fixed.whenScrolledToBottom(function(){
// Load more data..
$tableBody.append($tableBody.html());
});
I have modified your code to handle the scroll event with a timer threshold:
$.fn.isScrolledTo = function () {
var element = $(this);
var bottom = element.find('.bottom');
$(element).scroll(function(){
if (this.timer) clearTimeout(this.timer);
this.timer=setTimeout(function(){
if( element.scrollTop() >= bottom.position().top){
var tableBody = element.find("tbody");
tableBody.append(tableBody.html());
}
},300);
});
};
$('.fixed').isScrolledTo();
The issue you are having is that as you scroll, new scroll event is being generated. Your code might have other issues, but this is a start.

Get divs within viewport inside a wrapper div

Is there a way to get elements which is:
Inside a div with overflow: scroll
Is in viewport
Just like the following picture, where active div (5,6,7,8,9) is orange, and the others is green (1-4 and >10) :
I just want the mousewheel event to add "active" class to div 5,6,7,8,9 (currently in viewport). View my JSFiddle
$('.wrapper').bind('mousewheel', function (e) {
//addClass 'active' here
});
You could do something like this. I would have re-factored it, but only to show the concept.
Firstly I would attach this to scroll event and not mousewheel. There are those among us that likes to use keyboard for scrolling, and you also have the case of dragging the scrollbar. ;) You also have the case of touch devices.
Note that with this I have set overflow:auto; on wrapper, thus no bottom scroll-bar.
With bottom scrollbar you would either have to live with it becoming tagged as in-view a tad to early, or tumble into the world of doing a cross-browser calculating of IE's clientHeight. But the code should hopefully be OK as a starter.
»»Fiddle««
function isView(wrp, elm)
{
var wrpH = $(wrp).height(),
elmH = $(elm).height(),
elmT = $(elm).offset().top;
return elmT >= 0 &&
elmT + elmH < wrpH;
}
$('.wrapper').bind('scroll', function (e) {
$('div.box').each(function(i, e) {
if (isView(".wrapper", this)) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
});
Note that you should likely refactor in such a way that .wrapper height is only retrieved once per invocation, or if it is static, at page load etc.
Update; a modified version of isView(). Taking position of container into account. This time looking at dolphins in the pool.
»»Fiddle««
function isView(pool, dolphin) {
var poolT = pool.offset().top,
poolH = pool.height(),
dolpH = dolphin.height(),
dolpT = dolphin.offset().top - poolT;
return dolpT >= 0 && dolpT + dolpH <= poolH;
}

Categories