I'm hoping the Daniweb community can help where other forums have been unable to!

I am attempting to have my code count how many top level menu items there are and then determine what position each menu item is in. So, if it is the first, it will append the class of that item to "first" and if it is the last, it will append the class to "last."

I did the same thing with other variables and it worked. For example, I have the code set to append the class to "parent" if there are subitems and also "active" if it is the current link. However, I am running into difficulty with first/last. I've been through several attempts. My first incarnation was:

<?php
/*
    Class: MenuDefault
        Menu base class
*/
class MenuDefault extends Menu {
    /*
        Function: process
        Returns:
            Object
    */   
    public function process($module, $element) {
        self::_process($module, $element->first('ul:first'));
        return $element;
    }
    /*
        Function: _process
        Returns:
            Void
    */
    protected static function _process($module, $element, $level = 0) {
        if ($level == 0) {
            $element->attr('class', 'menu '.$module->menu_style);
        } else {
            $element->addClass('level'.($level + 1));
        }
        foreach ($element->children('li') as $li) {
             // is active ?
            if ($active = $li->attr('data-menu-active')) {
                $active = $active == 2 ? ' active current' : ' active';
            }
             // is parent ?
            $ul = $li->children('ul');
            $parent = $ul->length ? ' parent' : null;
            // is first or last ? -- PROBLEM AREA
            $lis = $element->children("li");
             for($forl=0,$imax=$lis;
             $forl<$imax;$forl++){
             if ($forl==0) $position_n = 'first';
             elseif ($forl==$imax-1) $position_n = 'last';
             else $position_n = null;}
              // set class in li
            $li->attr('class', sprintf('level%d item%s '. $position_n .$parent.$active, $level + 1, $li->attr('data-id')));
            // set class in a/span
            foreach ($li->children('a,span') as $child) {
                // get title
                $title = $child->first('span:first');
                // set subtile
                $subtitle = $title ? explode('||', $title->text()) : array();
                if (count($subtitle) == 2) {
                    $li->addClass('hassubtitle');
                    $title->html(sprintf('<span class="title">%s</span><span class="subtitle">%s</span>', trim($subtitle[0]), trim($subtitle[1])));
                }
                // set image
                if ($image = $li->attr('data-menu-image')) {
                    $title->prepend(sprintf('<span class="icon" style="background-image: url(\'%s\');"> </span>', $image));
                }
                $child->addClass(sprintf('level%d'.$parent.$active, $level + 1));
            }
            // process submenu
            if ($ul->length) {
                self::_process($module, $ul->item(0), $level + 1);
            }
        }
   }
}

However, that generated "first" for every menu item. See below for example:

<ul class="menu menu-dropdown">
  <li class="level1 item24 first">...Subcode...</li>
  <li class="level1 item22 first parent">...Subcode...</li>
  <li class="level1 item23 first active current">...Subcode...</li>
  <li class="level1 item20 first">...Subcode...</li>
  <li class="level1 item19 first">...Subcode...</li>
</ul>

What I wanted was:

<ul class="menu menu-dropdown">
  <li class="level1 item24 first">...Subcode...</li>
  <li class="level1 item22 parent">...Subcode...</li>
  <li class="level1 item23 active current">...Subcode...</li>
  <li class="level1 item20">...Subcode...</li>
  <li class="level1 item19 last">...Subcode...</li>
</ul>

Then in another forum, it was suggested to me that " Your basic logic for determining first and last is correct, but you should not add a new loop to do it; instead you need to make it part of the loop that is already looping over the <li>'s (ie: the foreach loop at the top of the snippet I posted). Because of the use of the foreach statement, you need to do a little extra work in order to obtain numerically useful index values.

// is first or last ? -- PROBLEM AREA
$children_elements = array_values($element->children('li')); [/U]
$last_child_index = count($children_elements)-1; 
foreach($children_elements as $child_index => $li) {
    if ($forl==0) $position_n = 'first';
    elseif ($forl==$imax-1) $position_n = 'last';
    else $position_n = null;}

That brought a bunch of errors:

*Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59

Warning: Invalid argument supplied for foreach() in ... on line 61

Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59

Warning: Invalid argument supplied for foreach() in ... on line 61

Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59

Warning: Invalid argument supplied for foreach() in ... on line 61

Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59

Warning: Invalid argument supplied for foreach() in ... on line 61

Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59

Warning: Invalid argument supplied for foreach() in ... on line 61

Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59

Warning: Invalid argument supplied for foreach() in ... on line 61

Warning: array_values() [
function.array-values
]: The argument should be an array in ... on line 59*

Line 59 being $children_elements = array_value($element->children('li'));

So it seemed like $children_elements was either empty or not an array so I tested with this:

if(empty($children_elements)) {
    echo hi;
}

Hi was generated. So it seems empty.

Sorry if this was long but I had no idea how to explain quicker. Please help. I'm stumped and if I can't get this working, a large portion of my time will go to waste as I'll have to scrap the theme design completely.

Thanks in advance.

Recommended Answers

All 6 Replies

Easiest way to determine firt and last is to use a counter. Start with zero outside the loop, and increment at the end. You can then check position zero, or length - 1.

Member Avatar for diafol

Something like this?

pseudocode

counter=0
last = count items in array - 1
do
if counter = 0 then label as first
if counter = last then label as last
increment counter
loop

pritaeas and diafol, I'm a little confused about where we are talking about implementing this. Sorry, I can be a little slow sometimes. There are two loops going on in the section of code that seems problematic so I'm unsure where you are talking about. Could you show me how you would do it in context of this code?

Member Avatar for diafol

OK, a multilevel menu. I wouldn't place the item no. as a class though. I'm confused at to 'active' and 'current' - do they mean the same thing or is one for each item in the active path and the other for the last item in the path? If not, then you could probably get away with just the one.

Do you really need all those hooks? This is a typical multilevel menu

<ul class="menu">
    <li>Item 1</li>
    <li>Parent 1
      <ul>
      <li>Item 1.1</li>
      <li>Parent 1.1
        <ul rel="active">
        <li>Item 1.1.1</li>
        <li>Item 1.1.2</li>
        <li>Parent 1.1.1
          <ul>
          <li>Item 1.1.1.1</li>
          <li>Item 1.1.1.2</li>
          <li>Item 1.1.1.3</li>
          <li>Item 1.1.1.4</li>
          </ul>
        </li>
        </ul>
      </li>
      </ul>
    </li>
    <li>Item 2</li>
</ul>

You can get all the hooks from selectors, e.g. 'ul.menu li ul'

Alternatively, you can also experiment with the PHP object called "Object Iteration" which can probably and specifically pull the first , middle, something in between, and the last item within an array. You can even count it first as suggested above and check the count pointer location, and if it is the it, do whatever you want to do with the it.

Don't have any sample codes right now, because I am currently busy with my JAVA project. Opening the PHP storage room where it resides in my brain will do me no good :).

Thanks for the help guys.

Diafol, the item# as class is a default of wordpress but on your suggestion (and the fact that I didn't like it) I went ahead and stripped it out. And the active/current meant the same thing. Current is a WP default, and active was the class I set. Took care of that too.

Veedeoo, have fun with your JAVA project (beyond me) and thanks for letting me know about "object Iteration" - I've never heard of that so I'll look into it.

I couldn't figure it out except I knew that there must be some native wordpress functionality I could use with wp_nav_menu so I created this code and it works well to have wordpress count and mark first/last (excluding child items for last) with an added function and filter.

Seems to work well. Here it is for anyone else with a similar problem:

// set first/last class 
function nb_first_last_menu_class( $objects, $args ) { 
     // set first/last class for submenu 
    $ids        = array(); 
    $parent_ids = array(); 
    $top_ids    = array(); 
    foreach ( $objects as $i => $object ) { 
        // If no parent, store the ID, skip object 
        if ( 0 == $object->menu_item_parent ) { 
            $top_ids[$i] = $object; 
            continue; 
        } 
        // don't set first class for submenu 
        if ( ! in_array( $object->menu_item_parent, $ids ) ) { 
            $objects[$i]->classes[] = ''; 
            $ids[]          = $object->menu_item_parent; 
        } 
         // If we didn't set first class for submenu, skip adding the ID 
        if ( in_array( '', $object->classes ) ) 
            continue; 
         // Store the menu parent IDs in an array 
        $parent_ids[$i] = $object->menu_item_parent; 
    } 
    // Remove dup values and pull out last menu item 
    $sanitized_parent_ids = array_unique( array_reverse( $parent_ids, true ) ); 
    // Loop IDs, set last class to the appropriate objects 
    foreach ( $sanitized_parent_ids as $i => $id ) 
        $objects[$i]->classes[] = 'last'; 
    // set classes for top level menu items 
    $objects[1]->classes[] = 'first';  
    $objects[end( array_keys( $top_ids ) )]->classes[] = 'last'; 
    return $objects; 
} 
add_filter( 'wp_nav_menu_objects', 'nb_first_last_menu_class', 10, 2 );
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.