Wish I'd seen your answer before I went down the rabbit hole. However I've been able to replicate the markup provided by our designer using an extension of the Walker class and it's working great with the jQuery UI Accordion.
class sidebar_nav_walker extends Walker_Nav_Menu {
function start_el(&$output, $item, $depth, $args) {
global $wp_query;
if (0 == $depth) {
// parent item
$output .= '<h2>';
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
$output .= '</h2>';
} else {
// child items
$class_names = $value = '';
$classes = empty( $item->classes ) ? array() : (array) $item->classes;
$classes[] = 'menu-item-' . $item->ID;
$class_names = join( ' ', apply_filters( 'nav_menu_css_class', array_filter( $classes ), $item, $args ) );
$class_names = ' class="' . esc_attr( $class_names ) . '"';
$id = apply_filters( 'nav_menu_item_id', 'menu-item-'. $item->ID, $item, $args );
$id = strlen( $id ) ? ' id="' . esc_attr( $id ) . '"' : '';
$output .= $indent . '<li' . $id . $value . $class_names .'>';
$attributes = ! empty( $item->attr_title ) ? ' title="' . esc_attr( $item->attr_title ) .'"' : '';
$attributes .= ! empty( $item->target ) ? ' target="' . esc_attr( $item->target ) .'"' : '';
$attributes .= ! empty( $item->xfn ) ? ' rel="' . esc_attr( $item->xfn ) .'"' : '';
$attributes .= ! empty( $item->url ) ? ' href="' . esc_attr( $item->url ) .'"' : '';
$item_output = $args->before;
$item_output .= '<a'. $attributes .'>';
$item_output .= $args->link_before . apply_filters( 'the_title', $item->title, $item->ID ) . $args->link_after;
$item_output .= '</a>';
$item_output .= $args->after;
$output .= apply_filters( 'walker_nav_menu_start_el', $item_output, $item, $depth, $args );
}
}
function start_lvl(&$output, $depth, $args=array()) {
$output .= "\n<div><ul>\n";
}
// Displays end of a level. E.g '</ul>'
// @see Walker::end_lvl()
function end_lvl(&$output, $depth, $args=array()) {
$output .= "</ul></div>\n";
}
/**
* @see Walker::end_el()
* @since 3.0.0
*
* @param string $output Passed by reference. Used to append additional content.
* @param object $item Page data object. Not used.
* @param int $depth Depth of page. Not Used.
*/
function end_el(&$output, $item, $depth) {
}
}
That got me pretty far. But I still had an issue with wp_nav_menu wrapping the entire thing in a UL. This broke the jQuery Accordion script. So I needed to remove it and after experimentation ended up rendering the menu with the custom Walker as follows...
<nav id="nav">
<?php
$menu = wp_nav_menu(
array(
'container' => false,
'echo' => false,
'before' => '',
'after' => '',
'link_before' => '',
'link_after' => '',
'walker' => new sidebar_nav_walker()
)
);
echo preg_replace( array( '#^<ul[^>]*>#', '#</ul>$#' ), '', $menu );
?>
</nav>
Note: I tell the menu not to echo itself in the arguments. Instead I run a regular expression on it to remove that parent level UL.
Also of note.. This will look like crap if you don't tell your jQuery Accordion NOT to autoheight.
$(function() {
$("#nav").accordion({
collapsible: true,
autoHeight: false
});
});
The result is somewhat close to the original markup from my designer, aside from the classes WP adds in.