I'm hoping the DigitalPoint 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); } } } } PHP: 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> HTML: 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> HTML: 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;} PHP: That brought a bunch of errors 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; } PHP: 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.
Hi Nick, I see here teh definition of your class and methods, but not how you initialize the objecst and how you call the "proceess" function , maybe the problem is there. BR, Marcel
Correct me if I am wrong, but if it were how I was calling the "process" function, then other parts of this page would not work either. Yet they do. All except that area labeled // is first or last ? -- PROBLEM AREA In fact, if I delete that one section, everything works fine. It just doesn't add first or last to any of the li's.
Honestly, if I had markup outputting that many classes, I'd put a bullet in my head... Whatever you are doing and/or how you are storing the menu seems like a needlessly convoluted mess; could you post the actual structure here, as all this recursion and checking of values seems pretty pointless... and two to three times as much code as should be needed for such simple output.
It is very hard to tell you a solution without looking at the $element object definition first. Can you post the object type, methods and any extends/implementation on it. It doesn't look like a DOM element (specifically with the addClass() method).
I'm guessing wildly, but I suspect this is what you are trying to do: <?php $menu = [ 'Home' => [ 'url' => '\home' ], 'Pages' => [ 'url' => '\pages', 'children' => [ 'Page 1' => [ 'url' => '\page1' ], 'Page 2' => [ 'url' => '\page2' ] ] ], 'Forums' => [ 'url' => '\forums' ], 'Register' => [ 'url' => '\register' ], 'Log In' => [ 'url' => '\login' ] ]; function template_menu($menu,$id = '', $depth=0) { $t = 0; $last = count($menu) - 1; $indent = "\r\n" . str_pad('',$depth,"\t"); echo $indent,'<ul',( empty($id) ? '' : ' id="' . $id . '"' ),( $depth > 0 ? '' : ' class="menu"' ),'>'; $indent .= "\t"; foreach ($menu as $name => $data) { $classes=[]; if ($t==0) $classes[] = 'first'; if ($t==$last) $classes[] = 'last'; if ($hasKids = ( isset($data['children']) && count($data['children']) > 0 )) $classes[] = 'hasKids'; echo $indent,'<li',( count($classes) == 0 ? '' : ' class="' . implode(' ', $classes) .'"' ),'>',$indent,"\t", '<a href="', $data['url'], '">', $name, '</a>'; if ($hasKids) template_menu($data['children'], '', $depth+2); echo $indent,'</li>'; $t++; } $indent = subStr($indent, 0, -1); echo $indent,'</ul>'; } // template_menu template_menu($menu); ?> Code (markup): Though with a few less unnecessary classes. Honestly with CSS3 on the table I'd be using :last-child and :first-child instead of first/last classes. Doesn't work in IE7/lower, OH WELL. Neither would most of the visual effects I'd be applying with those classes anyways
The abundance of classes is something I minimized. Wordpress automatically adds the item # to the class which I have since stripped. And current and active were duplicates so they are now just active. I need level#, parent, active, and first/last to make it look the way I want. As for CSS3 and the pseudo elements :last-child, etc, I am aware of them. But I do stay away because of the IE incompatibility. When possible. Yes, I maybe shouldn't care about ppl who are using outdated browsers BUT I figure there is already alot of things they miss on my site. So when I can come up with a solution that makes some of their experience right on my site, I want to go that direction. I ended up getting it working the way I wanted. Below is the solution I created for the benefit of anyone reading. 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. // 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 ); PHP:
Between the 'insecure by design' codebase, with such idiocies as storing the SQL un/pw/host in DEFINE, multiple entry points with ZERO attempts to restrict output on direct calls, idiotic endless classes you can't control from the skin without defeating the entire point of using a templating system and throwing even more bloated code at an already bloated train wreck of how not to write PHP or build a website... The steaming pile of manure that is most every result I've EVER seen from it really makes it so I cannot fathom how anyone is DUMB ENOUGH to use it by choice. Which me underestimating the stupidity of my fellow man is a bit like Micheal Moore making a movie about the benefits of corporate growth. It truly earned that pwnie back in '08. Just saying.
Looking at the code in your last post, I've got little clue what that first foreach mess is even there to accomplish, but the second one I'd suspect sets 'last' on everything; it seems a pointless loop when you have perfectly good END and RESET functions. $keys = array_keys($objects); $objects[end($keys)]->classes[] = 'last'; $objects[reset($keys)]->classes[] = 'first'; Code (markup): Sets the first and last classes on the $objects array... without some slow loop, making multiple copies of the entire table, etc, etc... Though I'm guessing wildly... WAIT -- are the menu items not nested, and instead just dumped in there any old way with only a 'parent' field to indicate that it's a submenu item?!?
Ok, I THINK I figured out what you are doing... not sure though. // set first/last class function nb_first_last_menu_class( $objects, $args ) { $keyList = array(); foreach ($objects as $key => $data) { if (empty($data->menu_item_parent)) $keyList[] = $key; } $objects[end($keyList)]->classes[] = 'last'; $objects[reset($keyList)]->classes[] = 'first'; return $objects; } add_filter( 'wp_nav_menu_objects', 'nb_first_last_menu_class', 10, 2 ); Code (markup): Most everything else in that last bit of code you posted seems to serve no purpose. I mean things like: $objects[$i]->classes[] = ''; Why add a blank item to it's class list?!? But then I'm from the old school that says if you have to use 'continue' you're probably asking the wrong questions the wrong way.
Actually, thinking on it I'd be setting first and last on every menu 'level'... using a dummy value you'd likely never use as a menu item key for those without parents could be used to build a list of keylists for each submenu. Assuming they are all just crapped into one array flat and rely on an extra value for saying if it's a submenu item or not (which is REALLY farking stupid), That would go something like this: function nb_first_last_menu_class( $objects, $args ) { $keyList = [ '~~~~~~' => [] ]; foreach ($objects as $key => $data) { $keyList[ empty($data->menu_item_parent)) ? '~~~~~~' : $data->menu_item_parent ][] = $key; } foreach ($keyList as $data) { $objects[end($data)]->classes[] = 'last'; $objects[reset($$data)]->classes[] = first; } return $objects; } Code (markup): Basically making an array of arrays to handle it, something they should have done in the first blasted place... though collisions would screw the whole thing up BAD.