3

I have current list look like:

<ul>
   <li><a href="#">Menu 1</a></li>
   <li><a href="#">_Submenu a</a></li>
   <li><a href="#">_Submenu b</a></li>
   <li><a href="#">_Submenu c</a></li>
   <li><a href="#">Menu 2</a></li>
   <li><a href="#">Menu 3</a></li>
   <li><a href="#">_Submenu x</a></li>
   <li><a href="#">_Submenu y</a></li>
   <li><a href="#">Menu 4</a></li>
</ul>

How can I use Jquery to create Drop-down menu like that.

  <ul>
      <li><a href="#">Menu 1</a>
         <ul>
           <li><a href="#">_Submenu a</a></li>
           <li><a href="#">_Submenu b</a></li>
           <li><a href="#">_Submenu c</a></li>
         </ul>
      </li>
      <li><a href="#">Menu 2</a></li>
      <li><a href="#">Menu 3</a>
         <ul>
            <li><a href="#">_Submenu x</a></li>
            <li><a href="#">_Submenu y</a></li>
         </ul>
      </li>
      <li><a href="#">Menu 4</a></li>
 </ul>

It means some items after main item which have "_" will be added as dropdown items for main item. Thank for your help.

0

3 Answers 3

8
+100

You can achieve the effect you're looking for by using the following logic.

  1. Create a common cache variable to hold the previous top-level menu.
  2. Loop through all menu list items checking to see whether the text begins with an underscore.
    • If it does, append this to the previous top-level menu.
    • If it does not, append a new unordered list element to this list item and cache the new list in the previous top-level menu variable.
  3. Once the loop has finished you can select all lists that are empty and remove them (.find('ul:empty').remove()).

In the example below I have favoured Native DOM API methods instead of their jQuery counterparts in a few instances because:

  1. $(this).append('<ul></ul>') returns $(this) instead of the newly created list. The work around is to add another line of code, or just use the DOM API this.appendChild($('<ul>')[0]) which does return the newly created list. And...
  2. It just seems wasteful to use jQuery in situations where it is just as simple to use the DOM API. see: youmightnotneedjquery.com

var prev;
$('.menu li').each(function(){
    if(/^_/.test(this.textContent) && prev) {
        prev.appendChild(this);
    } else {
        prev = this.appendChild($('<ul>')[0]);
    }
}).find('ul:empty').remove();
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<ul class="menu">
   <li><a href="#">Menu 1</a></li>
   <li><a href="#">_Submenu a</a></li>
   <li><a href="#">_Submenu b</a></li>
   <li><a href="#">_Submenu c</a></li>
   <li><a href="#">Menu 2</a></li>
   <li><a href="#">Menu 3</a></li>
   <li><a href="#">_Submenu x</a></li>
   <li><a href="#">_Submenu y</a></li>
   <li><a href="#">Menu 4</a></li>
</ul>

The example above results in the following HTML structure:

<ul class="menu">
    <li><a href="#">Menu 1</a>
        <ul>
            <li><a href="#">_Submenu a</a></li>
            <li><a href="#">_Submenu b</a></li>
            <li><a href="#">_Submenu c</a></li>
        </ul>
    </li>
    <li><a href="#">Menu 2</a></li>
    <li><a href="#">Menu 3</a>
        <ul>
            <li><a href="#">_Submenu x</a></li>
            <li><a href="#">_Submenu y</a></li>
        </ul>
    </li>
    <li><a href="#">Menu 4</a></li>
</ul>
Sign up to request clarification or add additional context in comments.

1 Comment

Hi Tiny. Thank you very much. Great result.
5

// Create custom selectors
$.extend($.expr[':'], {
  startsWith: function(e, i, m) {  
    return $(e).text().trim().indexOf(m[3]) === 0;
  }
});

$("li:not(:startsWith(_))").each(function(){  // LI that are not _Sub
  if($(this).next("li:startsWith(_)").length) // If my next() is _Sub, start grouping:
  $("<ul/>", {
    html: $(this).nextUntil("li:not(:startsWith(_))"),
    appendTo: this
  });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
  <li><a href="#">Menu 1</a></li>
  <li><a href="#">_Submenu a</a></li>
  <li><a href="#">_Submenu b</a></li>
  <li><a href="#">_Submenu c</a></li>
  <li><a href="#">Menu 2</a></li>
  <li><a href="#">Menu 3</a></li>
  <li><a href="#">_Submenu x</a></li>
  <li><a href="#">_Submenu y</a></li>
  <li><a href="#">Menu 4</a></li>
</ul>

Here's the HTML result:

<ul>
  <li>
    <a href="#">Menu 1</a>
    <ul>
      <li><a href="#">_Submenu a</a></li>
      <li><a href="#">_Submenu b</a></li>
      <li><a href="#">_Submenu c</a></li>
    </ul>
  </li>
  <li>
    <a href="#">Menu 2</a>
  </li>
  <li>
    <a href="#">Menu 3</a>
    <ul>
      <li><a href="#">_Submenu x</a></li>
      <li><a href="#">_Submenu y</a></li>
    </ul>
  </li>
  <li>
    <a href="#">Menu 4</a>
  </li>
</ul>

7 Comments

Hi Roko. It seems not look like result I want.
@MinhAnh Fixed added another selector
I have not known that we can create custom jQuery selectors until reading your answer. It's really elegant.
@HieuLe I've simplified my answer - I removed the custom selector :notStartsWith() in favor of the already existent :not() :)
@Tushar jQuery uses the Sizzle engine sizzlejs.com for selectors that are not in the scope of CSS2.1/3 selectors using .find(). github.com/jquery/sizzle/wiki#extension-api. Here's an interesting reading: james.padolsey.com/javascript/…
|
2

$('.group1').wrapAll('<ul></ul>');
$('.group2').wrapAll('<ul></ul>');
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<ul>
  <li><a href="#">Menu 1</a>
  </li>
  <li class='group1'><a href="#">_Submenu a</a>
  </li>
  <li class='group1'><a href="#">_Submenu b</a>
  </li>
  <li class='group1'><a href="#">_Submenu c</a>
  </li>
  <li><a href="#">Menu 2</a>
  </li>
  <li><a href="#">Menu 3</a>
  </li>
  <li class='group2'><a href="#">_Submenu x</a>
  </li>
  <li class='group2'><a href="#">_Submenu y</a>
  </li>
  <li><a href="#">Menu 4</a>
  </li>
</ul>

Try this way

Use .wrap()

Description: Wrap an HTML structure around each element in the set of matched elements.

Use .wrapAll()

Description: Wrap an HTML structure around all elements in the set of matched elements.

6 Comments

Thank but wrapping will wrap all and cannot add them into corresponding items. I am not down vote. Can you please explain?
isnt it what you wanted?if not what you want?@MinhAnh
you can add $('a:contains(_Submenu)').parent().wrap('<ul></ul>') like this to make the parent of a to be wrapped in <ul> @MinhAnh
@MinhAnh you can give class to the li you want to group to make it easier to select them and you can use the class to form the ul for those li
Hi Guradio. The result like Roko and they are still not wrapped into <li></li> corresponding item like result I want. Any idea?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.