Sometimes a design calls for the first and last items in a list to be styled differently for the others, for example a line or column of overlapping tabs.
For most modern browsers this is trivial and can be done using the list-class:first-child and list-class:last-child psuedoclasses. It's just that Internet Explorer doesn't support these very well.
If you are hardcoding the list or writing the generation routine it is also simple to generate 'first-item' and 'last-item' classes in the appropriate place.
<ul id="list-id">
<li class="ie-list-first list-type-item">Stuff</li>
<li class="list-type-item">Stuff</li>
<li class="list-type-item">Stuff</li>
<li class="ie-list-last-last list-type-item">Stuff</li>
</ul>When templating for an existing CMS it may not be desirable to dig deeply enough to customise the code. For example WordPress's wp_list_pages() function (with appropriate parameters) returns something like this:
<li class="page_item page-item-4 current_page_item">Page link</li>
<li class="page_item page-item-9">Page link</li>
<li class="page_item page-item-13">Page link</li>
<li class="page_item page-item-15">Page link</li>
<li class="page_item page-item-34">Page link></li>
<li class="page_item page-item-25">Page link</li>I spent some time following the function trail before deciding it was better to add the classes after the string was generated. Fortunately if you set 'echo=0' wp_list_pages() will return the string to a variable. After that it's just a matter of string substitution - first and last incidence of class=" . (This might get a bit trickier if you're using nested lists, but I try and keep depth=1 if possible).
It turned out the easiest way to do this was with a preg_replace - but it still requires a little trickery because you can't tell preg_replace to start from the end of a string. Instead you have to reverse everything, do the replacement, and then reverse everything again.
//get li string
$pages = wp_list_pages('title_li=&depth=1&echo=0' );
//add styling identifiers for first item
$pages = preg_replace('/class="/','class="ie-list-first ', $pages, 1); //note space on end of replacement string
//add styling identifiers for last item
$reversedString = strrev($pages);
$reversedSearch = '/'.strrev('class="').'/';
$endClass = strrev('class="ie-list-last '); //note space on end
$pages = strrev(preg_replace($reversedSearch,$endClass, $reversedString, 1));
//output
echo $pages;I make sure the ie-list class is first because it makes it easier to call in your IE-specific stylesheet without worrying that another CMS generated class will get precedence.

19 comments:
I think you're missing a double quote:
$reversedSearch = '/'.strrev('class="').'/';
By jove, I think you might be right. Edited to fix, thank you very much :)
Hi Robert
This piece of code was just what I was looking for to style the first and last elements of my wordpress menu but I'm getting an error on this line:
$endClass = strrev('class='"ie-list-last '); //note space on end
Everything after that in the editor is not showing the correct syntax highlighting which indicates that something is not quite right and the browser is throwing a "Parse error" at that line in the code. If I comment it out everything works fine but no last item class... any idea's/help would be greatly appreciated :-)
Thanks.
Hi Simon,
you're right, a close inspection shows I've managed to stick an extra single quote in that line right next to the double quote.
replace it with
$endClass = strrev('class="ie-list-last '); ; //note space on end
[fixes post]
Thanks Robert, that fixed the error :-) but now I'm getting a different issue... the class being applied to the last list item is ending up like this:
<li class="last " page-item-13="" page_item="">
page-item-13 and page-item are the existing classes but it looks like the quotes are still out of sync.
Also, a question: what would I need to change (or is it even viable) to add the first and last class to the a tag withing the list element rather than the list element itself?
Cheers :-)
Hi Simon,
I think the problem is now with this line:
$reversedSearch = '/'.strrev('class=').'/';
should be
$reversedSearch = '/'.strrev('class="').'/';
If not I'll have to go dig up the project I was working on at the time and see what I was actually doing. Obviously I did not manage to copy the code perfectly into blogger :-/
It's feasible to change the <a> tag as long as you can identify a constant string around the class to work with
- if it doesn't already have a class changing out the search string for /<a/ and the replace string for /<a class="[class-name]"/ ought to do it
- I'd leave it as is and apply css as eg:
li.first a:link, li.first a:visited {[style]} myself
You're a legend! Thank you so much :-)
I had to make a couple tweeks to get the link class working but it now works a treat! The reason I want the class on the a tag rather than the list item is so that I can style the linked based on the currently selected item:
.current-page-item { default list item style }
.current-page-item .first { first list item style }
.current-page-item .last{ last list item style }
this way I can add menu ends that are current page aware. :-)
I'll flick you a link to the finished site when it rolls if you like.
Glad to be of service :) Please do send over the link when you are done.
You know you can chain-reference classnames within the same class attribute by omitting the space between them? So for example
<li class="current-page-item"><a class="first">
.current-page-item .first
is equivalent to
<li class="current-page-item first"><a>
.current-page-item.first a
This is more useful when you have multiple elements to style within your first or last item :)
Wow... I didn't know that, very handy! How browser safe is chaining though? I know we all hate IE6 but it's often a pain we have to bear ;-)
Another option that came to mind was utilising jQuery's :first-child and :last-child selectors.
Chaining is IE6 safe in my experience.
jQuery and other javascript solutions analyse the DOM to get around IE's lack of support for :first-child, :last-child (and now :nth-child). I prefer to use css if possible because more people have javascript disabled than css, and more non-visual - actually make that non-PC (eg mobile) - browsers are css-aware than javascript aware.
Great tip!
Is it possible to use this on list_categories?
I have this string;
wp_list_categories('orderby=id&show_count=0&title_li=&use_desc_for_title=1&child_of='.$this_category->cat_ID);
but i can't get it to add to the class names.
Hi stein™
as with wp_list_pages you need to tell wp_list categories to return the html as a string for further processing rather than echoing it to the browser directly. Use 'echo=0' for this.
$categories =
wp_list_categories('orderby=id&show_count=0&title_li=&use_desc_for_title=1&child_of='.$this_category->cat_ID&echo=0);
See http://codex.wordpress.org/Template_Tags/wp_list_categories for a list of all the available parameters.
Oops. Missed the concatenation.
wp_list_categories('orderby=id&show_count=0&title_li=&use_desc_for_title=1&child_of='.$this_category->cat_ID.'&echo=0');
Perfect!
The perfect solution! Thank you Robert! After hours on trying different plugins and other bul..ts your simple script gave me exactly what I needed to create a working multilingual overlapping tabbed menu in half an hour.
I needed to remove the border-right off my last list item in the footer, and I could not for the life of me figure out why I was having trouble manipulating the returned value of the wp_list_pages() function. I tried assigning it to a variable so I could search the $output string with other functions, I tried while/if statements, I tried CSS :last-child and adjacent selectors... nothing. Before I read your solution I did try using the echo=0 argument a few times but the Codex doesn't make it clear enough what it actually does (prevents automatic echo of $output), so I stopped using it until I read this. Thank you for this solution, this problem kept me up for many hours!
I too have been trying to conquer this little lingering item. I can see that the code here works. However, I don't quite get how to make it apply to my existing pages list menu. When I put the code in my functions.php file, it the creates its own list at the very top of my page. My normal menu remains with the styles unapplied.
What am I missing here?
Hi Squidz,
You need to put at least the line
//output
echo $pages;
in your template in place of the current page generation function (wherever it calls wp_list_pages() normally, that's header.php in the site I'm looking at right now).
I personally put the entire chunk of code at that location to make the transformation obvious.
great bit of code cheers :)
Post a Comment