diff --git a/gts/gts.csproj b/gts/gts.csproj
index a1619adb..36f1a0aa 100644
--- a/gts/gts.csproj
+++ b/gts/gts.csproj
@@ -3539,6 +3539,7 @@
RoomLeaders.aspx
+
@@ -3550,6 +3551,7 @@
+
diff --git a/gts/src/DependencyNode.cs b/gts/src/DependencyNode.cs
new file mode 100644
index 00000000..8a44733e
--- /dev/null
+++ b/gts/src/DependencyNode.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+
+namespace PkmnFoundations.Web
+{
+ public class DependencyNode
+ {
+ public DependencyNode()
+ {
+ Dependencies = new HashSet();
+ }
+
+ public DependencyNode(TKey key, TValue value)
+ : this()
+ {
+ Key = key;
+ Value = value;
+ }
+
+ public DependencyNode(TKey key, TValue value, HashSet dependencies)
+ : this(key, value)
+ {
+ Dependencies = dependencies;
+ }
+
+ public TKey Key { get; set; }
+ public TValue Value { get; set; }
+ public HashSet Dependencies { get; private set; }
+ }
+}
diff --git a/gts/src/RequireLinkBase.cs b/gts/src/RequireLinkBase.cs
new file mode 100644
index 00000000..878ad443
--- /dev/null
+++ b/gts/src/RequireLinkBase.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Web;
+using System.Web.UI;
+using System.Web.UI.HtmlControls;
+
+namespace PkmnFoundations.Web
+{
+ public abstract class RequireLinkBase : System.Web.UI.Control
+ {
+ protected RequireLinkBase()
+ : base()
+ {
+ this.Init += RequireLinkBase_Init;
+ this.Load += RequireLinkBase_Load;
+ this.PreRender += RequireLinkBase_PreRender;
+ }
+
+ void RequireLinkBase_Init(object sender, EventArgs e)
+ {
+ // todo: only bind once
+ Page.PreRender += Page_PreRender;
+ }
+
+ void RequireLinkBase_Load(object sender, EventArgs e)
+ {
+ }
+
+ void RequireLinkBase_PreRender(object sender, EventArgs e)
+ {
+ DependencyGraph graph = GetDependencyGraph();
+
+ DependencyNode node = new DependencyNode(Key, this, ParseDependencies(After ?? ""));
+ if (!graph.Graph.Any(n => n.Key == Key))
+ graph.Graph.Add(node);
+ }
+
+ void Page_PreRender(object sender, EventArgs e)
+ {
+ DependencyGraph graph = GetDependencyGraph();
+ if (graph.Page == null)
+ {
+ Page.Header.Controls.Add(new RequireLinkRenderer(GetDependencyGraph()));
+ graph.Page = Page;
+ }
+ }
+
+ public String Key { get; set; }
+ public String After { get; set; }
+
+ public abstract void RenderHeader(System.Web.UI.HtmlTextWriter writer);
+
+ ///
+ /// Obtain a dependency graph which is specific to the Page instance
+ /// and the control class
+ ///
+ private DependencyGraph GetDependencyGraph()
+ {
+ Type myType = this.GetType();
+
+ Dictionary> all_graphs;
+
+ if (!Page.Items.Contains("pkmncfDependencyGraphs"))
+ {
+ all_graphs = new Dictionary>();
+ Page.Items.Add("pkmncfDependencyGraphs", all_graphs);
+ }
+ else all_graphs =
+ (Dictionary>)Page.Items["pkmncfDependencyGraphs"];
+
+ if (all_graphs.ContainsKey(myType)) return all_graphs[myType];
+
+ DependencyGraph myGraph = new DependencyGraph();
+ all_graphs.Add(myType, myGraph);
+ return myGraph;
+ }
+
+ private HashSet ParseDependencies(String dependencies)
+ {
+ return new HashSet(dependencies.Split(',').Select(s => s.Trim()).Where(s => s.Length > 0));
+ }
+
+ }
+
+ internal class RequireLinkRenderer : System.Web.UI.Control
+ {
+ public RequireLinkRenderer(DependencyGraph graph)
+ : base()
+ {
+ m_graph = graph;
+ }
+
+ private DependencyGraph m_graph;
+
+ protected override void Render(System.Web.UI.HtmlTextWriter writer)
+ {
+ List links = m_graph.Resolve();
+
+ foreach (RequireLinkBase link in links)
+ link.RenderHeader(writer);
+ }
+ }
+
+ internal class DependencyGraph
+ {
+ public DependencyGraph()
+ {
+ Graph = new List>();
+ }
+
+ public DependencyGraph(List> graph)
+ {
+ Graph = graph;
+ }
+
+ public List> Graph;
+ public Page Page = null;
+
+ public List Resolve()
+ {
+ // fixme: we should clone the DependencyNodes so they don't get modified.
+ LinkedList> nodesList = new LinkedList>(Graph);
+ List result = new List(nodesList.Count);
+
+ while (nodesList.Count > 0)
+ {
+ LinkedListNode> nodeLinked = nodesList.First;
+
+ while (nodeLinked != null)
+ {
+ if (nodeLinked.Value.Dependencies.Count == 0) break;
+ nodeLinked = nodeLinked.Next;
+ }
+
+ if (nodeLinked == null)
+ {
+ // we hit the end of the list without finding a dependency-less node.
+ // this means there has to be a circular dependency.
+ // ie. every remaining node in the list depends on some other remaining
+ // node in the list.
+
+ // todo: display the actual cycle (or an example if there's more than one)
+ // in this exception.
+ // algo:
+ // Keep a collection of found nodes.
+ // Start at the first node, adding it to the collection.
+ // Move to the node of the first dependency of this node and add it to the collection.
+ // Repeat until you reach a node which is already in the collection.
+ // Output the collection of nodes, starting at the node which was already found.
+ throw new Exception("Circular dependency found in your links.\n" +
+ "Keys:\n" +
+ String.Join("\n", nodesList.Select(n => n.Key).ToArray()));
+ }
+
+ // add this node to output, remove it as a dependency from the remaining nodes
+ DependencyNode nodeOutput = nodeLinked.Value;
+ nodesList.Remove(nodeLinked);
+
+ foreach (DependencyNode nodeValue in nodesList)
+ nodeValue.Dependencies.Remove(nodeOutput.Key);
+
+ result.Add(nodeOutput.Value);
+ }
+ return result;
+ }
+ }
+}