12/20/2009

SharePoint: DataView Group By Expand / Collapse bug fixed!

 

One of the more popular articles I’ve written in this blog  is “Group By on more than 2 columns in a view”. As it has been over a year now, I decided to update the article and clean it up a bit. One of the problems many people had with it was not my solution, but how SharePoint handled the clicks on the Expand and Collapse buttons in the web part.  When you clicked Expand at the top level it also expanded all the detail levels.

As background, SharePoint Views support grouping, but to only two levels. My article shows how to convert a View into a DataView web part and how to add as many levels of grouping you want. Many people thought that was cool, but complained that clicking the expand button expanded way too much.

After converting to a DataView and grouping (and adding a bit of color), the list looks like this:

image 

Clicking on the second item expanded all of the detail below it, which made drill down painful.

image

After some research I found that the expand/collapse buttons call a JavaScript routine named ExpGroupBy that is found in CORE.JS. This routine was written to use the same code to expand and to collapse. When you collapse a higher level you also want to collapse all of the lower levels. Well, that then meant that expanding also expanded all lower levels!

 

The Fix…

I started with part of the ExpGroupBy code and added my own code to hand one level at a time expansion.

The DataView still starts out like this:

image 

But now it expands only one level at a time:

image

And you can then drill down and only expose what you need!

image

 

The Code…

First… where do you want to put this code?  I only needed it on the page with the grouped view, so I added a Content Editor Web Part to that page and used the Source Editor button to add the JavaScript. Do not modify CORE.JS! Besides being a generally bad idea, it will probably be overwritten on the next service pack update. You could add this to a linked JavaScript file in the master page that is loaded after the CORE.JS library, or even add the code to the master page itself.

Like all of my tricks (hacks?), you are on your own on this… and the safest place for this kind of code is in a Content Editor Web Part.

 

Note: Only tested with SharePoint 2007 and Internet Explorer (so far).

(If you have problems with Copy and Paste, copy and then paste in to Notepad, then copy and paste from there.)

 

 

 
<script>
// More JavaScript tricks from TechTrainingNotes.blogspot.com!
// Fix the way a DataView expands/collapse 
 
//wait until all of the SharePoint stuff is loaded...
_spBodyOnLoadFunctionNames.push("CustomExpGroupByFix"); 
 
 
function CustomExpGroupByFix() {
  //Replace the function in CORE.JS with our custom function
  window.ExpGroupBy=function(x){CustomExpGroupBy(x)}
}
 
 
// The customized version of ExpGroupBy
// based on ExpGroupBy found in CORE.JS
function CustomExpGroupBy(formObj)
{
  if (browseris.nav)
    return;
 
  if ((browseris.w3c) && (!browseris.ie)) {
    document.all=document.getElementsByTagName("*");
  }
 
  docElts=document.all;
  numElts=docElts.length;
  images=formObj.getElementsByTagName("IMG");
  img=images[0];
  srcPath=img.src;
  index=srcPath.lastIndexOf("/");
  imgName=srcPath.slice(index+1);
 
 
  if (imgName=='plus.gif')
  {
    fOpen=true;
    displayStr="block";
    img.src='/_layouts/images/minus.gif';
  } 
  else
  {
    fOpen=false;
    displayStr="none";
    img.src='/_layouts/images/plus.gif';
  }
  oldName=img.name;
  img.name=img.alt;
  img.alt=oldName;
  spanNode=img;
 
  //scan up to find TR with GROUPx ID
  var groupid=""
 
  while(spanNode !=null)
  {
    spanNode=spanNode.parentElement;
    if (spanNode !=null &&
        spanNode.id !=null &&
        spanNode.id.length > 5 &&
        spanNode.id.substr(0, 5)=="group")
      break;
  }
 
  groupid=spanNode.id;
 
  var startrow = spanNode.rowIndex;
 
  //scan up farther to find TABLE
 
  parentNode=spanNode;
  while(parentNode !=null)
  {
    parentNode=parentNode.parentElement;
    if (parentNode !=null &&
        parentNode.tagName.toUpperCase()=="TABLE")
      break;
  }
 
  trs = parentNode.rows;
 
  var datarowsnext = false;
  if (trs[startrow+1].id=="")
  {
    datarowsnext = true;
  }
 
  groupidnext = "group" + (parseInt(groupid.substr(5,1)) + 1)
 
  for (i=startrow+1;i<trs.length;i++)
  {
    if ( ((trs[i].id == groupidnext) || datarowsnext || !fOpen) 
           & (trs[i].id != groupid) )
    {
      trs[i].style.display = displayStr;
      if (trs[i].id == groupidnext)
      {
        img = trs[i].getElementsByTagName("IMG");
        img[0].src = '/_layouts/images/plus.gif';
      }
 
    }
 
    if ( trs[i].id == groupid) 
    {
      //then we have hit the next group at this level
      return;
    }
 
  }
 
}
</script>
 

24 comments:

Anonymous said...

It's good solution but unfortunately when you start expand the nodes get intermixed! Is there a fix for this issue? Thank you.

Mike Smith said...

Anonymous,

> the nodes get intermixed

I have not run across that. Data is showing in the wrong rows?

Mike

Anonymous said...

Hey, basically what happened was when i expanded a level 3 node it would go ahead and expand lower nodes in the list (including other levels like level 1, 2). I added this code to make sure that it only expands the level it is on...


// More JavaScript tricks from TechTrainingNotes.blogspot.com!
// Fix the way a DataView expands/collapse
//wait until all of the SharePoint stuff is loaded...

_spBodyOnLoadFunctionNames.push("CustomExpGroupByFix");
function CustomExpGroupByFix()
{
//Replace the function in CORE.JS with our custom function
window.ExpGroupBy=function(x)
{
CustomExpGroupBy(x);
}
}

// The customized version of ExpGroupBy
// based on ExpGroupBy found in CORE.JS
function CustomExpGroupBy(formObj)
{
if (browseris.nav)
return;
if ((browseris.w3c) && (!browseris.ie))
{
document.all=document.getElementsByTagName("*");
}

docElts=document.all;
numElts=docElts.length;
images=formObj.getElementsByTagName("IMG");
img=images[0];
srcPath=img.src;
index=srcPath.lastIndexOf("/");
imgName=srcPath.slice(index+1);

if (imgName=='plus.gif')
{
fOpen=true;
displayStr="block";
img.src='/_layouts/images/minus.gif';
}
else
{
fOpen=false;
displayStr="none";
img.src='/_layouts/images/plus.gif';
}

oldName=img.name;
img.name=img.alt;
img.alt=oldName;
spanNode=img;
//scan up to find TR with GROUPx ID
var groupid="";

while(spanNode !=null)
{
spanNode=spanNode.parentElement;
if (spanNode !=null && spanNode.id !=null && spanNode.id.length > 5 && spanNode.id.substr(0, 5)=="group")
break;
}
groupid=spanNode.id;
var startrow = spanNode.rowIndex; //start row is the first row of the group (the one below it is the first row we need to expand)
//scan up farther to find TABLE

parentNode=spanNode;
while(parentNode !=null)
{
parentNode=parentNode.parentElement;
if (parentNode !=null && parentNode.tagName.toUpperCase()=="TABLE")
break;
}

trs = parentNode.rows;
var datarowsnext = false;

if (trs[startrow+1].id=="")
{
datarowsnext = true;
}

groupidnext = "group" + (parseInt(groupid.substr(5,1)) + 1);
groupidlevel3 = "group" + (parseInt(groupid.substr(5,1)) + 2);
// trs.length is the total amount of nodes in the list
//trs is an array of all the nodes in the list
//groupidnext is the groupid of the current node
for (i=startrow+1;i<trs.length;i++)
{
if ( ((trs[i].id == groupidnext) || datarowsnext || !fOpen) & (trs[i].id != groupid) )
{

var idAsString = new String(trs[i].id);
//alert(idAsString);
//alert(groupidnext);
if(trs[i].id != groupidnext && trs[i].id != groupidlevel3 && idAsString.indexOf("group") != -1)
{
continue;
}
//alert('done');
trs[i].style.display = displayStr;

if (trs[i].id == groupidnext)
{
img = trs[i].getElementsByTagName("IMG");
img[0].src = '/_layouts/images/plus.gif';
}
}

if ( trs[i].id == groupid)
{
//then we have hit the next group at this level
return;
}
}
}

Anonymous said...

Awesome, it seems working fine now. Thank you very much for your help!

Anonymous said...

I have updated the script to work with unlimited amount of rows. The current scripts provided didn't seem to collapse correctly. I have also tried to clean up some of the items.
-------------------------
// More JavaScript tricks from TechTrainingNotes.blogspot.com!
// Fix the way a DataView expands/collapse
//wait until all of the SharePoint stuff is loaded...

_spBodyOnLoadFunctionNames.push("CustomExpGroupByFix");
function CustomExpGroupByFix() {
//Replace the function in CORE.JS with our custom function window.ExpGroupBy = function (x) {
CustomExpGroupBy(x);
}
}

// The customized version of ExpGroupBy
// based on ExpGroupBy found in CORE.JS
function CustomExpGroupBy(formObj) {
var plusIMG = '/_layouts/images/plus.gif';
var minusIMG = '/_layouts/images/minus.gif';

if (browseris.nav)
return;

// Get a list of images within the form object
// Save the properties of the first image
images = formObj.getElementsByTagName("IMG");
img = images[0];
imgName = img.src.slice(img.src.lastIndexOf("/") + 1);

// Are we currently expanded/collapsed?
if (imgName == 'plus.gif') {
// Collapsed
fOpen = true;
displayStr = "block";
img.src = minusIMG;
}
else {
// Expanded
fOpen = false;
displayStr = "none";
img.src = plusIMG;
}

// Swap the image name and alternate text
oldName = img.name;
img.name = img.alt;
img.alt = oldName;

//scan up from the image to find TR with GROUPx ID
spanNode = img;
while (spanNode != null) {
spanNode = spanNode.parentElement;
if (spanNode != null && spanNode.id != null && spanNode.id.length > 5 && spanNode.id.substr(0, 5) == "group")
break;
}

var groupid = spanNode.id;
var startrow = spanNode.rowIndex; //start row is the first row of the group (the one below it is the first row we need to expand)

//scan up farther to find TABLE
parentNode = spanNode;
while (parentNode != null) {
parentNode = parentNode.parentElement;
if (parentNode != null && parentNode.tagName.toUpperCase() == "TABLE")
break;
}

// Get a list of the table rows
trs = parentNode.rows;
var datarowsnext = false;

if (trs[startrow + 1].id == "") {
datarowsnext = true;
}

groupidnext = "group" + (parseInt(groupid.substr(5, 1)) + 1);

// trs.length is the total amount of nodes in the list
// trs is an array of all the nodes in the list
// groupidnext is the groupid of the current node
for (i = startrow + 1; i < trs.length; i++) {
if (((trs[i].id == groupidnext) || datarowsnext || !fOpen) & (trs[i].id != groupid)) {
// Ensure the group ID is greater than = otherwise skip
// Also ensure we are on a group
if ((trs[i].id < groupidnext) && (trs[i].id.indexOf("group") != -1)) {
continue;
}

// Update the display of the row to be collapsed/expanded
// and set the image accordingly
trs[i].style.display = displayStr;
img = trs[i].getElementsByTagName("IMG");
img[0].src = plusIMG;
}

// Have we have hit the next group at this level?
if (trs[i].id == groupid) {
return;
}
}
}

Robyn said...

I just tried using your newest code and I still can't get any of my groupings to collapse. Note that my DVWP was built entirely in SP Designer and by default all of the groupings were set to "Expand". What am I doing wrong in order to get your code to work. Note that I have 4 groups. Do I need to change something in the code since I have more groupings than you do? Thanks in advance for your help.

Mike Smith said...

Robyn,

Are you saying the "+" and "-" (expand/collapse) buttons don't do anything?

Mike

Robyn said...

No, what I'm saying is that everything is still expanded. I want it so that all four groupings show as follows when first opening the page.
+ 2011
+ Annual
+ Stage
+ Season

I then want for users to be able to click on each + sign and expand it (of which then the symbol will turn to '-'). Does that make sense?

Robyn said...

No, what I'm saying is that I want my first level to show as collapsed. When the user clicks the '+' sign then it should expand and show the next level of groupings with the '+' sign.

They should be able to continue to drill down by clicking on each of the '+' signs and have it expand accordingly. I am trying to get it to look (and behave) as follows:
+ 2012
+ Annual
+ Restoration
+ Spring
+ Summer
+ Fall
+ Winter
+ Stage 1A
+ Spring
+ Summer
+ Fall
+ Winter
+ Stage 1B
+ Spring
+ Summer
+ Fall
+ Winter
+ Monthly
+ January
+ February
+ March
+ 2011
+ Annual
+ Monthly

I hope that helps to clarify what I am trying to accomplish.

Thanks again for any help you can provide.

JasonY said...

When I create a group, it requires I select a default value for collapsing or expanding the groups. This is fine, but what I would like is after I expand a collapsed group, and then click on a link to view more information for a particular record, when I click the back button in my browser that the data view will remember which groups were expanded or collapsed instead of just using the default. Is there a way I can do that?

Mike Smith said...

JasonY,

That can probably be done by storing a cookie after each expand/collapse and then checking that cookie on page to auto expand/collapse. I don't have a sample of JavaScript for this.

Mike

rgwinn said...

Has anyone else had a problem with this not working in Firefox 4? When trying to use this code on a page and render it with Firefox 4, it shows the '=' signs but when clicking on them to expand, it doesn't do anything other than change to a 'minus' sign but it doesn't ever expand.

Any help on this would be appreciated. Thanks in advance for your help.

Anonymous said...

Hy!

I have the same issue as rgwinn, but with built in grouping.

Somebody met this problem before?

Thanks.

robwscott said...

Sharepoint List when grouped and default view is collapsed - will not expand.

This list is located in a sub-site. I have tested other lists within this sub-site, as well as other lists in different sub-sites and the root, and no matter how I group them, either nested or not, once I click the expand "+", the items will show.
On this particular list, we have a nested group by, meaning group by this, then by that, as well as sorting. If I change the default view to be expanded, rather than collapsed, I can collapse and expand all I want and it's no problem.
When the default is collapsed, and I click on the expand "+", it'll say "loading..." directly underneath the group, then disappear. I do have a customized css file related to the system pages. However, if I change it back to the original corev4.css file, it does that same thing. This cannot be the issue since all other lists using this customized css file work.
Once again, it is only this list, none others, even lists in the same sub-site.
Thanks in advance...

dearnemo said...

Hi Mike, do you have a fix for the defect in 2010 SharePoint?
In SPD 2010, by default,the first sort parameter say BikeType shows a checked radio button for Expandgroup by default. No matter how many times you change it to second radio button Collapse group by default and save it, it doesn't save that option. So I m clueless on how to fix this in SP 2010. Any idea?

Thanks.

Mike Smith said...

dearnemo,

I finally ran across and example that had the same problem you described. The only solution I found was to delete the web part and start over. Looks like a but in SharePoint Designer 2010.

Mike

Anonymous said...

In Mike's response to dearnemo 05-Oct I think SPD 2010 has a bug not a but.

Mike Smith said...

I'll claim "typo" as there was only one "t" in "but". :-)

Mike

Anonymous said...

Its working fine in IE, but not working in fire fox, Is there any fix to work in fore fox

Anonymous said...

Hi Mike

Firstly, thank you so much for this blog, produces wonderful results!

I wondered if you could help be at all though:-

In OOB views that are using grouping, if you edit the properties of an item then hit okay, you are returned to the library with the groups still expanded. This makes it easier to see the item you've just edited more readily.

However when i do the same on an 'amended' group view, the library is collapsed when you have finished editing the item, making it a bit of a pain to re-expand the groups if you then had to approve the item again.

I realise that i'm possibly asking for the moon on a stick here, when everything else works so smoothly, but your help would be massively appreciated.

Many thanks,

Will

Mike Smith said...

Will,

That can probably be done by storing a cookie after each expand/collapse and then checking that cookie on page to auto expand/collapse. I don't have a sample of JavaScript for this.

Depending on the content in your list, you should be able to right-click the item and select Open in New Window.

Mike

Anonymous said...

In FireFox and Chrome this does not work because of the first part of the script:
if (browseris.nav)
return;

If you remove that part then FireFox and Chrome will expand the items, but will display all the columns as close together as possible ignoring the rest of the table. This is because it is set to display:block when the rest of the table is not. To fix this change:

if (imgName=='plus.gif')
{
fOpen=true;
displayStr="block";
img.src='/_layouts/images/minus.gif';
}

to:

if (imgName=='plus.gif')
{
fOpen=true;
displayStr="";
img.src='/_layouts/images/minus.gif';
}


(Notice that the only change there is setting displayStr="block" to displayStr=""))
{
fOpen=true;
displayStr=

Anonymous said...

I am currently using SharePoint 2013.
Your script is not working for me.
Can you help me to make it work.

Thanks...

Nolan Smith said...

Hi,

It seems something strange is happening but starting with the 5 node when I close subnodes it closes all the nodes after it. I have a feeling it has to do with the if (spanNode !=null && spanNode.id !=null && spanNode.id.length > 5 && spanNode.id.substr(0, 5)=="group") but I am not sure how to correct it.

Thank you,
Nolan

Note to spammers...

Spammers, don't waste your time... all posts are moderated. If your comment includes unrelated links, is advertising, or just pure spam, it will never be seen.