Need Help Convert jQuery Code to Pure Javascript

I want someone to please convert my jQuery Code to Pure Javascript Code.

I tried myself but I stuck somewhere and can’t be done. That’s why I come here.

The Code is actually for Menu Which Merges on resize window . It works perfectly with jquery but my client demanded that he wants this code in pure javascript. I shall be very thankful for you support.

Here is the jquery Code that needs to be converted to javascript.

$(document).ready(function () {
var item_width = $('#menu_ul li').width();
var item_count = ($("#menu_ul li").length);
var nav_width_og = $('.menu').width();
var nav_width = $('.menu').width();

$('#more').hide();
if ((item_width * (item_count + 1)) > nav_width) {
    $('#more').appendTo('body');
    $('#more').hide();
}

for (var i = 0; i < item_count; i++) {
    nav_width = $('.menu').width();
    item_width = $('#menu_ul li').width();
    item_count = ($("#menu_ul li").length);

    if (nav_width < (item_width * item_count)) {
        $('#menu_ul li').not('#more').last().appendTo($('.overflow')); //i mostly stuck here

        $('#more').appendTo($('#menu_ul'));
        $('#more').show();
    }
}


$(window).resize(function () {

    nav_width = $('.menu').width();
    item_width = $('#menu_ul li').width();
    item_count = ($("#menu_ul li").length);

    if (nav_width < (item_width * item_count)) {
        $('#menu_ul li').not('#more').last().appendTo($('.overflow'));

        $('#more').appendTo($('#menu_ul'));
        $('#more').show();
    }

    if (nav_width > (item_width * item_count) + (item_width - 1)) {
        $('.overflow li').last().appendTo($('#menu_ul'));

        $('#more').appendTo($('#menu_ul'));
    }

    if (nav_width == nav_width_og) {
        $('#more').appendTo('body');
        $('#more').hide();
    }

});

$('#more').click(function () {
    $('.overflow').slideToggle();
});  });

Also Here is the HTML Code (Just to understand you Structure)

<div class="submenu__container menu">

        <ul class="submenu__nav" id="menu_ul">
            <li class="nav-item">
                <a class="nav-link" href="#">Item 1</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">Item 2</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#">Item 3</a>
            </li>
            <li class="nav-item" id="more">
                <a class="nav-link" href="#">More <i class="fa fa-caret-right" aria-hidden="true"></i></a>
            </li>
        </ul>
        <ul class="overflow">
            
        </ul>
    </div>

I shall be very thankful if you help me convert this code into javascript.

I doubt anyone will do it for you, but I’m sure people will be willing to help, if you show how far you’ve managed to get on your own.

Have you read the detailed guide @Paul_Wilkins wrote?

1 Like

i think maybe… ok i post what i have done. so you guide me… Thanks for answering.

let menu = document.querySelector('.menu');
let menu_ul = document.getElementById("menu_ul");
let menuLi = document.querySelector("#menu_ul li");
let moreLink = document.getElementById('more');

let item_width = menuLi.offsetWidth;
let item_count = menu_ul.getElementsByTagName("li").length;

let nav_width_og = menu.offsetWidth;
let nav_width = menu.offsetWidth;

// hidding more li link 
moreLink.style.visibility = 'hidden';
if ((item_width * (item_count + 1)) > nav_width) {
    moreLink.style.visibility = 'hidden';    
}

if (nav_width < (item_width * item_count)) {
    let body = document.getElementsByTagName('body');
    body.appendChild(moreLink);
}

let menuNodeList = document.querySelectorAll('#menu_ul li:not(#more)');

//and i need to convert them into array so 
let arrMenuList = Array.from(menuNodeList);

//Now i have a list of LI elements in array
arrMenuList.forEach(element => {
    let text = element.textContent;
    let markup = `
        <li class="nav-item">
            <a class="nav-link" href="#">${text}</a>
        </li>
    `;
   let overflow = document.querySelector('.overflow');
   overflow.insertAdjacentHTML('beforeend', markup);
});

This is what i have done for now…

i’m confused what i do next and how i do.

because i’m getting all the elements instead i get one by one.

It seems that you don’t have any tests for your code, so you’ve had no feedback for what went wrong or when. Due to that lack of tests, you’ll need to do manual tests much more frequently.

If I were you I would undo half of your changes and retest the code. If you still have problems then undo all of your changes so that things still work, then test after making each small removal of jQuery to learn what caused your problem.

1 Like

In a separate attempt to convert the code, I’m going to start from the end of the code and work towards the start.

Document ready

And finally the document ready wrapper can be removed.

// $(document).ready(function () {
  ...
// });

Click More

The more click part at the end is easily converted:

// $('#more').click(function () {
document.querySelector('#more').addEventListener("click", function() {
  $('.overflow').slideToggle();
});

SlideToggle

For the slideToggle I’ve used some CSS transition code, but the CSS people are likely to have better ways to achieve this:

.overflow {
    max-height: 1000px;
    -webkit-transition: all 1s ease-in-out;
    transition: all 1s ease-in-out;
}
.toggled {
    overflow: hidden;
    max-height: 0;
}
  // $('.overflow').slideToggle();
  document.querySelector(".overflow").classList.toggle("toggled");

Show/Hide

The next things to convert working from the bottom of the code, is the hide/show code. Those are as easy as having a hide class.

.hide {
  display: none;
}
    // $('#more').show();
    document.querySelector("#more").classList.remove("hide");

    // $('#more').hide();
    document.querySelector("#more").classList.add("hide");

That is a partial conversion that results in the following code: https://jsfiddle.net/9myfjrs4/

Do you have CSS to make the menu items more presentable, to make further work easier to achieve?

I have found the code that you started with from stackoverflow: https://stackoverflow.com/questions/52268907/menu-design-add-more-menu-option

I’ve made the changes from my previous post to the code, resulting in the following partly converted code:

var item_width = $('#menu_ul li').width();
var item_count = ($("#menu_ul li").length);
var nav_width_og = $('.menu').width();
var nav_width = $('.menu').width();

if ((item_width * (item_count + 1)) > nav_width) {
  $('#more').appendTo('body');
  document.querySelector("#more").classList.add("hide");
}

for (var i = 0; i < item_count; i++) {
  nav_width = $('.menu').width();
  item_width = $('#menu_ul li').width();
  item_count = ($("#menu_ul li").length);

  if (nav_width < (item_width * item_count)) {
    $('#menu_ul li').not('#more').last().appendTo($('.overflow'));

    $('#more').appendTo($('#menu_ul'));
    document.querySelector("#more").classList.remove("hide");
  }
}

$(window).resize(function() {

  nav_width = $('.menu').width();
  item_width = $('#menu_ul li').width();
  item_count = ($("#menu_ul li").length);

  if (nav_width < (item_width * item_count)) {
    $('#menu_ul li').not('#more').last().appendTo($('.overflow'));

    $('#more').appendTo($('#menu_ul'));
    document.querySelector("#more").classList.remove("hide");
  }

  if (nav_width > (item_width * item_count) + (item_width - 1)) {
    $('.overflow li').last().appendTo($('#menu_ul'));

    $('#more').appendTo($('#menu_ul'));
  }

  if (nav_width == nav_width_og) {
    $('#more').appendTo('body');
    document.querySelector("#more").classList.add("hide");
  }

});

document.querySelector("#more").addEventListener("click", function() {
  document.querySelector(".overflow").classList.toggle("toggled");
});

The partly converted example can be found at https://jsfiddle.net/1ztkdcm9/2/

Next up on the conversion is the append commands.

Append

JavaScript appends starting with the parent, and instructing what to append. To help improve the success of our conversion, we should first change appendTo to append, and switching around the terms.

    $("#more").appendTo("body");
    $("body").append("#more");

To help with simplicity, we can move that more element out to a separate variable:

const more = $("#more");
    ...
    $("body").append(more);

We can now convert those jQuery references to standard JavaScript ones instead:

const more = document.querySelector("#more");
    ...
    document.querySelector("body").appendChild(more);

Replace menu_ul

Currently #menu_ul is used a lot in the jQuery code. We can easily assign references to the menu as variables and get it from there instead:

const menu = document.querySelector(".menu");
const menuList = document.querySelector("#menu_ul");

We can now replace occurrences of #menu_ul in the code with menuList instead:

// var item_width = $('#menu_ul li').width();
var item_width = $("li", menuList).width();
// var item_count = ($("#menu_ul li").length);
var item_count = $("li", menuList).length;
// var nav_width_og = $(".menu").width();
var nav_width_og = $(menu).width();
// var nav_width = $(".menu").width();
var nav_width = $(menu).width();

We can do similar replacements throughout the code, so that string references are all replaced with variable references instead. That’s going to help to make the conversion a lot easier.

Replace not()

The code that uses .not() can now be updated to use the :not() pseudo-selector instead:

    // $('.overflow').append($("li", menuList).not("#more").last());
    $('.overflow').append($("li:not(#more)", menuList).last());

Replace last()

Even though CSS selectors have the :last-child pseudo selector, we can’t use that here as the :not() parts interfere with things.

Instead, we can use a getLastMenuItem function:

function getLastItem(listGroup) {
  return $("li:not(#more)", listGroup).last();
}
    ...
    const lastMenuItem = getLastItem(menuList);
    $('.overflow').append(lastMenuItem);

That way, we can convert the functions separately from the rest of the code.

Before we convert the lastMenuItem function, we need the lastGroup parameter to not be a jQuery object.

function getLastItem(listGroup) {
  if (listGroup instanceof jQuery) {
    throw new Error("jQuery object found");
  }
  return $("li:not(#more)", listGroup).last();
}

A jQuery object was found in the form of the overflow element. Updating that results in no more errors being thrown.

  if (nav_width > (item_width * item_count) + (item_width - 1)) {
    // const lastOverflowItem = getLastItem($(".overflow");
    const overflow = menu.querySelector(".overflow");
    const lastOverflowItem = getLastItem(overflow);

Now that the getLastItem function is being given elements instead of jQuery objects, we can remove jQuery from the getLastItem function too.

function getLastItem(listGroup) {
  const items = listGroup.querySelectorAll("li:not(#more)");
  return items[items.length - 1];
}

Replacing append

Now that the parts being used with append are normal JavaScript objects, we can remove the jQuery append. This is also a good time to move the overflow variable to the top of the code too, to go along with the menu ones.

const menu = document.querySelector(".menu");
const menuList = menu.querySelector("#menu_ul");
const overflow = menu.querySelector(".overflow");
    ...
    // $(menuList).append(lastOverflowItem);
    menuList.appendChild(lastOverflowItem);

Replace width and length

There’s only one other jQuery aspect that needs to be replaced now, and that’s getting the width and number of items.

  // nav_width = $(menu).width;
  nav_width = menu.clientWidth;
  // item_width = $("li", menuList).width();
  const item_width = menuList.querySelector("li").clientWidth;
  // item_count = $("li", menuList).length;
  const item_count = menuList.querySelectorAll("li").length;

Remove the jQuery library

That’s removed all of the jQuery from the code, so we don’t need the jQuery library anymore. In the jsFiddle resources section I can now remove jQuery.

Tidying things up

There’s only one significant issue remaining and that’s when the overflow code tries to add a non-existing item to the menu. A good way to deal with that is to move the code in the if statement out to a separate function.

function moveToOverflow(item) {
  overflow.appendChild(item);
  menuList.appendChild(more);
  more.classList.remove("hide");
}
  if (nav_width < (item_width * item_count)) {
    // const lastMenuItem = getLastItem(menuList);
    // overflow.appendChild(lastMenuItem);
    // menuList.appendChild(more);
    // more.classList.remove("hide");
    moveToOverflow(getLastItem(menuList));
  }

We can put the same protection in place with moveToMenu too:

function moveToMenu(item) {
  if (!item) {
    return;
  }
  menuList.appendChild(item);
  menuList.appendChild(more);
}
  ...
  if (nav_width > (item_width * item_count) + (item_width - 1)) {
    moveToMenu(getLastItem(overflow));
  }

And the code is now robust JavaScript code that does the same job.

Summary

Here’s the full JavaScript code:

function getLastItem(listGroup) {
  const items = listGroup.querySelectorAll("li:not(#more)");
  return items[items.length - 1];
}
function moveToOverflow(item) {
  if (!item) {
    return;
  }
  overflow.appendChild(item);
  menuList.appendChild(more);
  more.classList.remove("hide");
}

function moveToMenu(item) {
  if (!item) {
    return;
  }
  menuList.appendChild(item);
  menuList.appendChild(more);
}

const menu = document.querySelector(".menu");
const menuList = menu.querySelector("#menu_ul");
const overflow = menu.querySelector(".overflow");
const item_width = menuList.querySelector("li").clientWidth;
const item_count = menuList.querySelectorAll("li").length;
const nav_width_og = menu.clientWidth;
const nav_width = menu.clientWidth;
const more = document.querySelector("#more");

if ((item_width * (item_count + 1)) > nav_width) {
  document.querySelector('body').appendChild(more);
  more.classList.add("hide");
}

for (var i = 0; i < item_count; i++) {
  const nav_width = menu.clientWidth;
  const item_width = menuList.querySelector("li", menuList).clientWidth;
  const item_count = menuList.querySelectorAll("li").length;

  if (nav_width < (item_width * item_count)) {
    moveToOverflow(getLastItem(menuList));
  }
}

window.addEventListener("resize", function() {
  const nav_width = menu.clientWidth;
  const item_width = menuList.querySelector("li").clientWidth;
  const item_count = menuList.querySelectorAll("li").length;

  if (nav_width < (item_width * item_count)) {
    moveToOverflow(getLastItem(menuList));
  }

  if (nav_width > (item_width * item_count) + (item_width - 1)) {
    moveToMenu(getLastItem(overflow));
  }

  if (nav_width == nav_width_og) {
    document.querySelector("body").appendChild(more);
    more.classList.add("hide");
  }
});

more.addEventListener("click", function() {
  overflow.classList.toggle("toggled");
});

And the code can be explored at https://jsfiddle.net/y8j710dh/

Tidying things up further, I’ve turned the if statements inside of the resize event to be while statements instead.

  while (nav_width > (item_width * item_count) + (item_width - 1) && getLastItem(overflow)) {
    moveToMenu(getLastItem(overflow));
    menuList.appendChild(more);
    itemCount += 1;
  }

That way, I can remove the duplicated menu code before the resize event, and instead trigger the resize at the end of the code:

const resizeEvent = new Event('resize');
window.dispatchEvent(resizeEvent);

I also moved showing the more button into the while loop that adds items to the overflow:

  while (nav_width < (item_width * item_count) && getLastItem(menuList)) {
    moveToOverflow(getLastItem(menuList));
    menuList.appendChild(more);
    more.classList.remove("hide");
    itemCount -= 1;
  }

and placed a check at the end of the resize code to hide more when overflow is empty:

  if (!getLastItem(overflow)) {
    document.querySelector("body").appendChild(more);
    more.classList.add("hide");
  }

And lastly, the event functions were pulled up, so that the event assignments are neatly grouped together at the end.

window.addEventListener("resize", updateMenu);
more.addEventListener("click", toggleOverflow);

The code is now better structured than how it began:

const menu = document.querySelector(".menu");
const menuList = menu.querySelector("#menu_ul");
const overflow = menu.querySelector(".overflow");
const more = document.querySelector("#more");
const nav_width_og = menu.clientWidth;

function getLastItem(listGroup) {
  const items = listGroup.querySelectorAll("li:not(#more)");
  return items[items.length - 1];
}
function moveToOverflow(item) {
  if (!item) {
    return;
  }
  overflow.appendChild(item);
}
function moveToMenu(item) {
  if (!item) {
    return;
  }
  menuList.appendChild(item);
}
function updateMenu() {
  const nav_width = menu.clientWidth;
  const item_width = menuList.querySelector("li").clientWidth;
  let item_count = menuList.querySelectorAll("li").length;

  while (nav_width < (item_width * item_count) && getLastItem(menuList)) {
    moveToOverflow(getLastItem(menuList));
    menuList.appendChild(more);
    more.classList.remove("hide");
    itemCount -= 1;
  }
  while (nav_width > (item_width * item_count) + (item_width - 1) && getLastItem(overflow)) {
    moveToMenu(getLastItem(overflow));
    menuList.appendChild(more);
    itemCount += 1;
  }
  if (!getLastItem(overflow)) {
    document.querySelector("body").appendChild(more);
    more.classList.add("hide");
  }
}
function toggleOverflow() {
  overflow.classList.toggle("toggled");
}

window.addEventListener("resize", updateMenu);
more.addEventListener("click", toggleOverflow);

const resizeEvent = new Event('resize');
window.dispatchEvent(resizeEvent);

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.