I have some Managed Metadata in SharePoint which is in the following format:
Managed Metadata Service
People
Job Title
And I have a list of people, each of whom have a name and a job title, called MyTestList
.
What I needed to do was display these people in a TreeView
, sorted by their job titles, where some job titles are children of others:
Here is the main class of the SharePoint WebPart:
public partial class MyWebPartUserControl : UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
var url = "http://sharepointdev/";
var subsite = "";
var listName = "MyTestList";
var termStore = "Managed Metadata Service";
var termGroup = "People";
var termSet = "Job Title";
var targetList = SharePoint.BuildTargetList(url, subsite, listName);
var treeBuilder = new MMTreeBuilder(url, termStore, termGroup, termSet, termSet, targetList);
var tree = treeBuilder.Build();
var nodeRemover = new NodeRemover(testTreeView, tree);
nodeRemover.RemoveNodes(ref tree);
}
}
Basic structure of the WebPart:
- Get list of users from SharePoint
- Build a TreeNode containing Managed Metadata, with 'Job Title' as the top-level node
- Remove any nodes which do not contain an amployee
SharePoint
A simple class - we only need 1 method here and that is to retrieve the list of employees.
static class SharePoint
{
public static SPListItemCollection BuildTargetList(string url, string subsite, string listName)
{
using (var spSite = new SPSite(url))
{
using (var web = spSite.OpenWeb())
{
var list = web.Lists[listName];
return list.Items;
}
}
}
}
MMTreeBuilder
This class uses the Build
method to construct a TreeNode
based on Managed Metadata. We can select where the tree starts in relation to the Metadata with the topNode
parameter.
RecursiveTreeNodeBuilder
will build all of the Managed MetaData nodes one by one, and also calls AddUsersToNode
for each node built.
AddUsersToNode
iterates around our SharePoint list and checks if any of the users belong to the department that the current node represents. Note that ths checks against $"{termName}|{termId}"
, as we may have some nodes which share the same name (E.g. Consultant -> Good
or Doctor -> Good
), so we will need to differentiate.
class MMTreeBuilder
{
const string TermGroupString = "Microsoft.SharePoint.Taxonomy.Group";
const string TermStoreString = "Microsoft.SharePoint.Taxonomy.TermStore";
public readonly string Url;
public readonly string TermStoreName;
public readonly string TermGroupName;
public readonly string TermSetName;
public readonly string TopNode;
public readonly SPListItemCollection TargetList;
public MMTreeBuilder(string url, string termStoreName, string termGroupName, string termSetName, string topNode, SPListItemCollection targetList)
{
Url = url;
TermStoreName = termStoreName;
TermGroupName = termGroupName;
TermSetName = termSetName;
TopNode = topNode;
TargetList = targetList;
}
public TreeNode Build()
{
using (var spSite = new SPSite(Url))
{
var taxSession = new TaxonomySession(spSite);
var termStore = taxSession.TermStores[TermStoreName];
var group = termStore.Groups[TermGroupName];
var termSet = group.TermSets[TermSetName];
var topNode = new TreeNode(TermSetName);
var tree = RecursiveTreeNodeBuilder(termSet, topNode);
tree.CollapseAll();
return tree;
}
}
private TreeNode RecursiveTreeNodeBuilder(object term, TreeNode parentNode)
{
var childTerms = "Terms"; // Default
var typeSwitch = term.GetType().ToString();
switch (typeSwitch)
{
case TermStoreString:
childTerms = "Groups";
break;
case TermGroupString:
childTerms = "TermSets";
break;
default:
break;
}
var termName = GetObjectProperty(term, "Name");
var termId = GetObjectProperty(term, "Id");
var newNode = new TreeNode(termName, termId);
var termType = term.GetType();
var property = termType.GetProperty(childTerms);
var collection = (IEnumerable<object>)property.GetValue(term);
AddUsersToNode(termName, termId, newNode);
parentNode.ChildNodes.Add(newNode);
foreach (var item in collection)
{
// Recurse
RecursiveTreeNodeBuilder(item, newNode);
}
return newNode;
}
private string GetObjectProperty(object obj, string propertyName)
{
var type = obj.GetType();
var property = type.GetProperty(propertyName);
return property.GetValue(obj).ToString();
}
private void AddUsersToNode(string termName, string termId, TreeNode currentNode)
{
foreach (SPListItem listItem in TargetList)
{
var jobTitle = listItem[TermSetName].ToString();
if (jobTitle == $"{termName}|{termId}")
{
var name = $"<span style='background-color:red;'>{listItem["Title"]}</span>";
var userNode = new TreeNode(name);
currentNode.ChildNodes.Add(userNode);
}
}
}
}
NodeRemover
Once we have our TreeNode
, we need to remove any nodes which do not represent anyone on the SharePoint list.
class NodeRemover
{
TreeView Tree;
TreeNode Node;
public NodeRemover(TreeView treeView, TreeNode treeNode)
{
Tree = treeView;
Node = treeNode;
}
public void RemoveNodes(ref TreeNode tree)
{
var allNodes = new List<TreeNode>();
GetAllNodes(ref allNodes, tree);
var removalList = new List<TreeNode>();
CreateRemovalList(ref removalList, tree);
Tree.Nodes.Add(tree);
foreach (TreeNode node in allNodes)
{
if (removalList.Contains(node))
{
var deletionNode = Tree.FindNode(node.ValuePath);
node.Parent.ChildNodes.Remove(node);
}
}
}
private void GetAllNodes(ref List<TreeNode> nodeList, TreeNode parentNode)
{
nodeList.Add(parentNode);
foreach (TreeNode node in parentNode.ChildNodes)
{
GetAllNodes(ref nodeList, node);
}
}
private void CreateRemovalList(ref List<TreeNode> removalList, TreeNode currentNode)
{
var currentText = currentNode.Text;
var parent = currentNode.Parent;
var children = currentNode.ChildNodes;
if (children.Count == 0 && !currentText.Contains("color"))
{
removalList.Add(currentNode);
}
else
{
foreach (TreeNode child in children)
{
CreateRemovalList(ref removalList, child);
}
}
if (children.Count != 0)
{
var counter = 0;
foreach (TreeNode child in currentNode.ChildNodes)
{
if (removalList.Contains(child))
{
counter++;
}
}
if (counter == children.Count)
{
removalList.Add(currentNode);
}
}
}
}
I haven't implemented any error-handling yet, as I get the feeling that there must be a better (more concise) way of achieving what I want.
Your thoughts on structure, usage or anything else wrong with my code is much appreciated!