Adding Expand/Collapse Logic to your Data View Web Parts (DVWPs) –The Easy Way
Live and learn. Maybe that’s my mantra. One thing I can tell you is that the first time I come up with a way to do something, it’s rarely the best. If I look at some old code of mine and think it’s perfect, I worry. We all are learning all the time, and it’s usually the case that we can see a better way to do things the next time we look.
Adding expand/collapse logic to a Data View Web Part (DVWP) is a great example. I’ve done this many, many times, and suddenly last week, I looked at a DVWP and saw it all again for the first time.
Part of the reason I saw it differently was that I was looking at the document Object Model (DOM) in the context of doing some jQuery spiffiness. Because of that, I was looking at how SharePoint itself does the expand/collapse stuff. You know what I mean:
When I set the view above to group by Navigation Area, I get different sections in it for each value of Navigation Area, in this case [Meats, Vegetables].
So how does it work? Well, simply, SharePoint lists the data in table rows (TRs) beneath the Group By values with the style set to display:none; (Assuming you have the view set to collapse by default, which is probably the most common setting.) That’s right, SharePoint just shows each group and item in a table row, no fancy wrapping DIVs logic or anything (which is always what I’d tried to do in the past, successfully, but it was usually pretty messy).
What SharePoint does (yes, SharePoint is anthropomorphic to me and I’m sticking to that) is give the table rows which are group headers IDs like this:
<tr id="group0{generate-id()}">
The generate-id() bit is what SharePoint Designer adds for you to make the group IDs unique; the generated IDs look something like:
Each of the group rows contain markup that looks something like this:
<TD class=ms-gb colSpan=100 noWrap> <IMG alt="" src="/_layouts/images/blank.gif" width=0 height=1> <A onclick="javascript:ExpCollGroup('1-2_','img_1-2_');return false;" href="javascript:"> <IMG id=img_1-2_ border=0 alt=Expand/Collapse src="/_layouts/images/minus.gif"> </A> <A onclick="javascript:ExpCollGroup('1-2_','img_1-2_');return false;" href="javascript:">Navigation Area</A> : <A href="/Intranet/JQueryLib/Lists/Regions/DispForm.aspx?ID=7&RootFolder=*">Meats</A> <SPAN style="FONT-WEIGHT: lighter">‎(1)</SPAN> </TD>
If you emit markup similar to this in your DVWP, you can take advantage of the JavaScript functions that SharePoint makes available in core.js. (If you dig into the DOM for different examples of the grouping behavior, you’ll see some variations in all of this. SharePoint seems to make some determinations about how to piece it all together which are different based on some rules I haven’t quite figured out.)
In a DVWP I built the other day, I emitted markup like this in my DVWP’s rowview template:
<xsl:template name="Targets.rowview"> <xsl:variable name="NewSortValue" select="ddwrt:NameChanged(string(@Target), 0)"/> <xsl:if test="string-length($NewSortValue) > 0"> <tr id="group0{generate-id()}"> <td class="ms-gb" colspan="99"> <a onclick="javascript:ExpGroupBy(this);return false;" href="javascript:"> <img border="0" name="expand" alt="expand" src="/_layouts/images/plus.gif"/> </a> Target: <xsl:value-of select="@Target" disable-output-escaping="yes"/> </td> </tr> </xsl:if> <tr class="" style="display:none;"> <td class="ms-vb"> <xsl:value-of select="@Title"/> </td> </tr> </xsl:template>
What this does is:
- In line 2, I use the ddwrt:NameChanged function to determine if the value of the @Target column has changed. ddwrt:NameChanged returns the new value if there has been a change and nothing if there hasn’t been.
- In line 3, I test whether there has been a change, and it there has been, I emit a group row.
- Whether there has been a change or not, I emit each item starting in line 13, in this case showing only the value of the @Title column (just to keep the example simple).
The really cool part of this is that I can use the ExpGroupBy JavaScript function just like SharePoint does. I did look into exactly how it works, but I’ll keep it simple here. Basically, it does the expand/collapse behavior you want on all of the rows (items) under the row which has the ExpGroupBy function in the link. It even swaps the plus and minus images appropriately for you. No fuss, little muss.
I added this code to a project I was working on today and it worked. However, I did notice if you are grouping using a people picker, you’ll need to strip the generateid() from the id. If you don’t, you’ll experience mixed results with how the expand/collapse feature works.
Do you know of a way to automatically collapse the rows? I’m using the code above and I’ve noticed that all of the rows are expanded by default.
Matt:
Line 13 ought to be “collapsing” the detail lines with the style=”display:none;”.
M.
I missed that bit of code… Works as is. Excellent post!
Can you use something like this to create a view that is categorized by a multivalue People Picker field? Is that even possible?
L01$Lan3:
The quick answer is almost definitely “yes”. With XSL in a DVWP, it’s hard to come up with something which is “unbuildable”.
M.
If you’ve started off with groups set to expanded and want to change it back. go back to your data view, use the common tasks wizard to select “sort and group”. Choose the grouping you want and the type of collapse to ‘collapsed’.
Now cuss a little bit as some vital little change is made and erase the duplicate ‘breakout’ and ‘groupheader’ templates.
go back and find $imagesrc, name=imagesrc and switch the minus.gif with plus.gif.
poof, all better. Yes, the javascript of ExpGroupBy looks for the image as it’s individual expand or collapse variable. Sorta clever if you think about it.
I’m coming a little late to this post, but I’m really hoping you can help me. I would like to know if there is a way for a certain group to be expanded, while the rest are collapsed.
I have a filter built from a data view in a blog, which groups post by months. Is there a way to expand the month of the post you are looking at, while leaving the rest collapsed as default.
I’m using a custom ExpGroupBy, just because I’ve changed the plus and minus icons.
I hope this makes sense, can you possibly point me in the right direction?
Jo:
You should be able to do this with some script. If you know which section you want to expland, you can trigger the click event on that group’s + icon.
M.
Hi Marc,
I have used DWP and i have grouped some columns and i have expanded/collapse.
But how can i keep the group selected when i click on a link i created under that group.
Thanksn in advance
Patrick:
I’m not sure what you mean, but it sounds like you might need a cookie to remember what the user has expanded previously.
M.
Yes how can i apply a cookie or maybe use an ID to keep the selected node.
Cheers and Thanks
Patrick:
It’s hard to answer your question generally. I’d suggest that you post the specifics to http://SharePoint.stackexchange.com. The more details you can provide, along with whatever code you already have, the better the answers will be.
M.
Thanks
I posted it there already :)
This is what i’m trying to do:
http://stackoverflow.com/questions/7889730/calling-expgroupby-using-jquery-for-specific-group
But don’t have the CustomExpGroupBy script.
Basically how can i keep a specific group expanded when i click on a hyperlink when the page reloads.What happens now is that it collapses back.
Cheers
The question on Stack Exchange is different. That’s a DVWP and the poster wants to determine what to expand based on a value in the page.
From what I understand, you want the page to “remember” what groups are expanded. To do that, you would need to update a cookie with the currently expanded groups and expand them again on page load based on the cookie contents.
M.
Hi Mark,
I’m using DVWP with Expand/Collapse feature and i used your code as well.
Regarding the HTML hierarchy, i’m trying to wrap the items under the group header to one component.
for example, I have:
and I want it to be, which is not so big wish:
Thank you!
Dolev:
I think you can do this just with CSS. Simply add some padding-left to the first TD.
M.
Oh, my tags have been rendered to HTML…
I meant:
so without the tags, only their names:
tr GroupHeaderIte tr
tr Childrenitem tr
tr Childrenitem tr
tr Childrenitem tr
and I want it to be, which is not so big wish:
tr GroupHeaderItem tr
td Childrenitem td
td Childrenitem td
td Childrenitem td
It’s not a CSS thing..
Thanks.
Dolev:
I’m not sure what you’re aiming at. If you want different markup, you can just change what you already have in the DVWP. However, in your second example, you don’t have valid HTML, as the TDs aren’t enclosed in a TR.
M.
Thanks for the code. Does anyone have a trick that allows you to expand all or collapse all onclick? For example, I want all of the group headings to show by default, collapsed. But I’d like to provide users with a way to click one link and get all of the groups to expand at one time, rather than having to manually click the little plus sign in front of each one individually. Ideas?
I’ve done this many times. You can simply bind to the click event for something else (I usually add a bigger plus/minus up top) and then click all of the individual +/- images in your script. Easy-peasy.
M.