Switching Out Bootstrap Dropdown with Dropup (Varying functionality on two almost identical implementations)

In the midst of my project on Github Pages, I encountered an interesting issue involving replacing a bootstrap .dropdown with .dropup when the div's overflow-y: scroll causes the dropdown menu to be cut off or overflow. The functionality can be viewed in action at this demonstration. Pay special attention to how clicking the ellipsis icon on the top rows results in a dropdown, while clicking it on the bottom rows triggers a drop up.

In my current implementation (Github page), the code remains identical (as shown below). However, there seems to be an issue where it replaces all instances of .dropdown classes with .dropup upon opening, including the top row which is cut off as depicted in the image below. https://i.sstatic.net/Cq7MB.png This problem has been persisting for about a week, and I haven't been able to find a solution. I've attempted various approaches that seemed promising but ultimately turned out to be ineffective hacks or didn't work properly on mobile devices, leaving me frantically searching for a viable fix.

Below is the Javascript / jQuery code I'm employing, accessible through 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 avoid any confusion, consider this example without my added .dropup feature. jsfiddle. Notice the need for scrolling when clicking the last menu item opens the menu. In such cases, my objective is to eliminate the .dropdown class and replace it with .dropup to ensure seamless menu interaction without requiring scrolling.

Answer №1

After some careful calculations, I was able to determine the best approach for achieving your desired functionality. This script dynamically switches between bootstrap classes dropup and dropdown based on available space within a standard dropdown menu.

I arrived at this solution by subtracting the height of the button, dropdown menu, and the button's scroll position within the designated container from the total height of the container. To determine the scroll distance, I calculated the difference between the button's offset position and that of the scroll container.

The jQuery code snippet below is tailored to target elements with the .playlist class, which reflects the specified scrollContainer. You may need to adjust this targeting logic according to your specific setup:

$(".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');
 }
});

You can access a functional demonstration on JSFiddle

Please feel free to provide feedback on any aspects of the code that could be optimized or simplified, as well as any issues you encounter with this solution.

Answer №2

Without deeply examining, it seems that the issue arises when using .scrollTop() in conjunction with other elements in the DOM. As a resolution, here is an alternative approach:

function checkHeights(){
  // Iterate 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;
    
    // Traverse all parent elements
    $dropDown.parents().each(function(ix,el){
      var $el = $(el);
    
      // Check if any of them have the overflow property set
      if( $el.css('overflow') !== 'visible' ){
        var limitTop = $el.offset().top,
            limitBottom = limitTop + $el.height();
      
        // Determine if dropping upside-down fits better within the parent element
        if( limitBottom < ddBottom && ( ddTop - limitTop ) > ( limitBottom - ddBottom ) )
          $dropDown.removeClass('dropdown').addClass('dropup');
        else
          $dropDown.removeClass('dropup').addClass('dropdown');
      
        // Exit loop
        return false;
      }
    });
  });
}

$(document).ready(function() {
  checkHeights();
  $('.playlist').scroll(checkHeights);
});

View JS Fiddle here.

This implementation does not depend on having specific class or id attributes assigned except for dropdown,dropdown-menu, and dropup (which are Bootstrap defaults) and can function smoothly even with multiple playlists on the page.


UPDATE

The code has been updated and encapsulated within a function to enable execution upon scroll events.

Answer №3

It seems to me that the issue is related to having a large header on your end while jsFiddle does not. This causes ulOffset.top to consistently be large, resulting in spaceDown being constantly negative.

Answer №4

Swap out the parent div.dropdown with div.dropup.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Enquire.js does not compute accurately at first glance, but only after adjustments are made during

Currently, I am working on incorporating media queries using Enquire.js and Vue.js. The functionality seems to be in good shape when manually resizing the browser window. However, upon loading the document, no match is detected. This peculiar behavior beco ...

Discovering the XPath of an element

Can anyone help me locate the XPath for the hamburger navigation icon on the website , specifically in the upper left corner? I am developing a Python script using selenium and need it to be able to click this particular button. Here's what I have tr ...

`Problem with data representation in PDF regarding page dimensions and layout`

I am currently using the datatable plugin to enable exporting to PDF, CSV, XLS, and for printing purposes. However, I am facing an issue with the PDF view. When I attempt to download the PDF file by clicking on the button, the data in the PDF does not app ...

Unable to insert values into database using PHP

I've put together a user registration form using PHP, jQuery, and SQL. I have everything set up to send the details to the database via an AJAX request. The code is running smoothly without errors, but for some reason, the values are not being added t ...

Using ReactJS to create a Stacked Bar chart

I am encountering some challenges while trying to create a single stacked bar graph using data from an API. 1- The data I receive is not rounded, even when using % values. 2- Additionally, the total percentage does not always add up to 100%, causing the ...

Guide on how to position a single anchor at the center of a div alongside other elements

I am having trouble centering an anchor on my webpage. I have tried using text-align: center;, but it always puts the link on a new line due to the required div element. Other methods like margins and spans have not worked for me either. span.right { ...

Styles for Tab Alignment

.tabs { position: relative; margin: 20px auto; width: 1500px; } .tabs input { position: absolute; z-index: 1000; width: 120px; height: 40px; left: 0px; top: 0px; opacity: 0; -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filt ...

Utilize Typescript to aim for the most recent edition of EcmaScript

Are you looking to configure your typescript build to compile to the most recent version or the most current stable release of EcmaScript? For example, using this command: tsc --target <get latest version> Alternatively, in a tsconfig file: { & ...

toggle switch for numerical input

Whenever the "Qty" radio button is selected, I need to activate an input box that accepts numbers. Conversely, when the "rate" radio button is clicked, I want to disable this input box. This is how I designed it: <input type="radio" class="radioBtn" ...

The div does not allow elements to be centered

I am facing an issue where elements are not centering on my div. I have tried numerous solutions (so many that I finally decided to create a Stack Overflow account), but nothing seems to work. Interestingly, the elements center on PC but for some reason, ...

Error message reading "Unexpected type error in jQuery for Node. Argument 'context' required."

Currently facing an issue with deploying my node.js server online. While everything functions properly on my local machine, I encountered an error when attempting to run the server online using node: node.js:201 throw e; // process.nextTick error, ...

Assigning identical values to all properties of an object

Let's consider an object structured like this: myObject = { "a_0" : [{"isGood": true, "parameters": [{...}]}], "a_1" : [{"isGood": false, "parameters": [{...}]}], "a_2" : [{"isGood": false, "parameters": [{...}]}], ... }; The goal is ...

Transforming Json data into an Object using Angular 6

https://i.stack.imgur.com/JKUpL.png This is the current format of data I am receiving from the server, but I would like it to be in the form of an Object. public getOrder(): Observable < ORDERS > { return this._http.get < ORDERS > (`${thi ...

The method to extract data from a <td> element using BeautifulSoup

There is a page I'm working with that contains tables in its source code: <td class="ng-binding">9:20 AM</td>, <td class="ng-binding"><span class="ng-binding" ng-bind-html="objFlight.flight.stat ...

Every time I attempt to execute this piece of code in "node.js", an error pops up

const express = require('express'); const request = require('request'); const bodyParser = require('body-parser'); const https = require('https'); const app = express(); app.use(express.static('public')); ...

Uploading files with ExpressJS and AngularJS via a RESTful API

I'm a beginner when it comes to AngularJS and Node.js. My goal is to incorporate file (.pdf, .jpg, .doc) upload functionality using the REST API, AngularJS, and Express.js. Although I've looked at Use NodeJS to upload file in an API call for gu ...

Top method for combining several external JavaScript libraries into a single one using webpack

Recently, I was diving into the world of webpack tutorial and it struck me that in order to combine everything into one module, scripts need to use require('./xyz'). Until now, I've always written individual scripts and loaded them in HTML u ...

5 Simple Steps for Adding a Value to a Popup Textbox Automatically

I want to send a value to another php page and then display the values. How can I achieve this? Here is the PHP code snippet: if(!empty($_POST["mytext"])) { for ($x=1; $x<=$a; $x++) { echo $txtLine[$x] = $_POST['mytext'.$x]; } } B ...

What is the solution to resolving the warning about the router no longer defaulting the history prop to hash history?

I have implemented browser history in my code within routes.js export default ( <Router history={browserHistory}> <Route path="/" component={Main}> <Route path="home/:username" component={Home}> <IndexRoute co ...

Can $.ajax be used as a replacement for $(document).ready(function()?

After conducting an extensive search, I am still unable to find a clear answer to my assumption. The code I used is as follows: <?php session_start(); if (isset($_SESSION['valid_user']) && $_SESSION['from']==1) { ?> ...