Pages

Showing posts with label SPTreeView. Show all posts
Showing posts with label SPTreeView. Show all posts

December 18, 2013

Expand SPTreeView Node Automatically When Selected


Before:
After:


The standard behavior of SharePoint tree view control (SPTreeView) is that when you select a folder node it will not expand its children nodes. You must click the expand icon [+] in order to show the sub folders. Also, that is not exclusive, so when you click the expand [+] icons of another node folder, the tree will show it's children without collapsing the previous node folder.


The objective:

I wanted to customize the tree view navigation to combine the functionality of clicking the node title and clicking the expand icon so that user can expand nodes with less hassle. The user should only click a node folder title and the tree will automatically expand to show child nodes while other nodes will collapse.


How:

I'll use JQuery. The idea is when a node title is clicked then a post back occurs and there will be a RootFolder parameter in page Url indicating selected folder node, so we will call the javascript function associated with its exapnd [+] icon.

This is the markup for the SPTreeView in the page:

<SharePoint:SPHierarchyDataSourceControl id="doclibDataSource" runat="server" RootListId="a65a6e37-de92-496a-b896-5b976dff7b61"
RootWebId="277d98b7-be33-451f-9ed8-6691a5a72736" ShowFolderChildren="true" EnableViewState="false">

</SharePoint:SPHierarchyDataSourceControl>
<SharePoint:SPRememberScroll runat="server" id="TreeViewRememberScrollV4" onscroll="javascript:_spRecordScrollPositions(this);" style="overflow: auto">
<SharePoint:SPTreeView ID="doclibtreeview" runat="server" DataSourceID="doclibDataSource" EnableViewState="false" ExpandDepth="1" SelectedNodeStyle-CssClass="SelectedItemStyle">
</SharePoint:SPTreeView>
</SharePoint:SPRememberScroll>


Inject the following script in the master page or in CEWP. I added the SelectedItemStyle CSS style to make selected nodes more visible.


<style>
.SelectedItemStyle {
BORDER-TOP: 1px dotted; BORDER-RIGHT: 1px dotted; BORDER-BOTTOM: 1px dotted; BORDER-LEFT: 1px dotted; BACKGROUND-COLOR: #dddddd
}</style>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

<script>

_spBodyOnLoadFunctionNames.push("customIBRTreeNav");

function customIBRTreeNav(){
var SelectedFolder = unescape(getUrlParameter('RootFolder'));
var ItemTitle = SelectedFolder.substring(SelectedFolder.lastIndexOf("/") + 1);
var LinkItem = jQuery('a[title="' + ItemTitle + '"]:not(:has(img))');
var IconItem = LinkItem.closest('td').prev('td').prev('td').find('a');// [+] & [-] icon
var IconItemHref = IconItem.attr("href");
var CustomTreeViewPopulate = new Function(IconItemHref); // create the function from string
CustomTreeViewPopulate.call(this);
}

function getUrlParameter(name)
{
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}</script>


Note: remember to set the ExpandDepth parameter to value of '1' in the SPTreeView control. 
This is necessary to get the intended functionality. I didn't have time to implement the logic when ExpandDepth is 2 or more.. maybe later!


November 14, 2013

How to make SPTreeView work on a custom page with 2 or more list views


The issue

I was reading the other day an article here on how to add an SPTreeView control to a list view page using SharePoint Designer 2010. This works fine on a library view page (for example: AllItems.aspx).
However, if you add another list view web part into the same list view page, or if you want to use the SPTreeView in a custom page then an issue appears. When user clicks on folder node the SPTreeView control will NOT stay in the same page but will redirect into the library root page (AllItems.apsx).



Analysis

I found very little info about this in the Internet, so decided to dig this up. I'll show here the details with an example. If you are interested keep reading, or you can go directly to solution section below.

The example setup:
  • Suppose we want a custom page to show 2 list views of the same library using different filters, and we want a tree view navigation to work for both of them whenever we click a folder.
  • I created a document library named 'Test Library Doc' and put some files and folders.


  • Created 2 list views for 'Test Doc Lib':
  1. Local view: Content Type is not Folder, and the Scope field is equal to Global
  2. Global view: Content Type is not Folder, and the Scope field is equal to Local
  • Added a custom web part page 'TestTreeView.aspx', added an SPTreeView control with its SPHierarchyDataScourceControl to the page using SPD2010. Make sure the RootListID and RootWebId are relevant.



Tests: (both on custom page TestTreeView.aspx)

Case 1:
Using SPTreeView with only 1 list view web part. Upon user click, each folder node will redirect to same page TestTreeView.aspx with corresponding folder level. Here is how 'Folder A' node is rendered:

<a title="Folder A" class="ctl00_PlaceHolderMain_doclibtreeview_0" id="ctl00_PlaceHolderMain_doclibtreeviewt1" href="javascript:_spNavigateHierarchy(this,'doclibDataSource','30:FolderNode:ef072b87-28a5-4db4-b82f-097c7ba329d3','\u002fTest Library Doc\u002fFolder A',true,'FolderNode')">

Case 2:
Using SPTreeView with 2 or more list view web parts. Upon user click, each folder node will redirect to the default page of library (example: AllItems.aspx) instead of our TestTreeView.aspx. Here is how 'Folder A'node is rendered:

<a title="Folder A" class="ctl00_PlaceHolderMain_doclibtreeview_0" id="ctl00_PlaceHolderMain_doclibtreeviewt1" href="javascript:_spNavigateHierarchy(this,'doclibDataSource','30:FolderNode:ef072b87-28a5-4db4-b82f-097c7ba329d3','\u002fTest Library Doc\u002fFolder A',false,'FolderNode')">



I tried to override _spNavigateHierarchy() method & inserted our custom page TestTreeView.aspx as the url parameter to force the redirect..but it didn't work. This is due to how the tree view nodes internally works as you will see below.


Digging on the front-end:


One of the arguments (highlighted above in test cases 1 & 2) caught my attention, it's called 'listInContext'. The _spNavigateHierarchy() definition is inserted by the SPHierarchyDataSourceControl in its onLoad event:

<script type=\"text/javascript\"> \r\n//<![CDATA[\r\nfunction _spNavigateHierarchy(nodeDiv, dataSourceId, dataPath, url, listInContext, type)  {\r\n   CoreInvoke('ProcessDefaultNavigateHierarchy', nodeDiv, dataSourceId, dataPath, url, listInContext, type, document.forms." + this.Page.Form.ClientID + ", \"" + SPEncode.ScriptEncode(this.ConstructSamePageQueryString()) + "\", \"" + SPEncode.ScriptEncode(str) + "\");\r\n\r\n}\r\n//]]>\r\n </script>

When we click a folder node,  the following call sequence starts:
_spNavigateHierarchy() -> CoreInvoke() -> ProcessDefaultNavigateHierarchy() in Core.js file

function ProcessDefaultNavigateHierarchy(g,e,d,b,c,j,i,h,f){
    ULSrLq:;
    if(typeof _spCustomNavigateHierarchy=="function")_spCustomNavigateHierarchy(g,e,d,b,c,j);
    else if(c==false)top.location=b;
    else{
        var a=document.createElement("INPUT");
        a.type="hidden";
        a.name="_spTreeNodeClicked";
        a.value=d;
        i.appendChild(a);
        var k="?RootFolder="+escapeProperly(b)+h+"&"+g_AdditionalNavigateHierarchyQString;
        _SubmitFormPost(f+k);
        return false
    }


Inside ProcessDefaultNavigateHierarchy(), the logic is as this:
If the 'islistInContent' flag is false then set the page location to the root page of current library and eventually redirect to the library root page (i.e AllItems.aspx). Otherwise, if the islistInContent flag is true, then eventually redirect to our custom page (TestTreeView.aspx).

Now we need to find out why listInContext parameter was set to false in test case 1.


Digging more on the back-end:


Let's have a look on how the SPTreeView is being internally rendered inside Microsoft.SharePoint.dll
In Microsoft.SharePoint.Navigation.SPHierarchyNode.cs I found NavigateUrl() method representing a tree node click. The highlighted portion is the important part.

public string NavigateUrl

        {

            {
                string str = "javascript:_spNavigateHierarchy(this,";
                return (((((str + "'" + this.DataSourceControlName + "','") + this.Path + "','") +
SPHttpUtility.EcmaScriptStringLiteralEncode("/" + this.m_url)) + "'," + ((this.m_ds.IsSingleListViewPage &&
this.IsContextListNode) ? "true" : "false")) + ",'" + this.m_type + "')");
            }
        }



According to internal code, 'IsSingleListViewPage' member returns True when there is only 1 web part (either data view or xslt list view) in current page. 'IsContextListNode' property returns True only within a list context and there is a valid list ID.
So, as long as we use 2 list view web parts or use a custom page, then listInContext parameter in NavigateUrl() will always get the value of 'False'.



Solution:

I Wrote a JQuery script to force change the listInContext flag value from 'False' to 'True' for every rendered tree view folder node, then insert this script into a CEWP in our custom TestTreeView.aspx page:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>

<script>

_spBodyOnLoadFunctionNames.push("IbrTreeView");

function IbrTreeView() {

$("a[href*='javascript:_spNavigateHierarchy']")
.each(function()
{
this.href = this.href.replace("false", "true");
});
}

</script>


And it works!
When user clicks 'Folder A' folder node, the 2 view web parts will show underlying filtered contents on the same TestTreeView.aspx page. No more redirecting to AllItems.aspx.




Note:
Remember your page should only contain list view web parts of the same library. Otherwise user will witness inconsistencies/errors when clicking on tree nodes because SPTreeView may redirect to a folder that exist in library of 1st web part but does not exist in library of the 2nd web part.


I hope people find this useful :)

Ibraheem