1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
7 // http://www.apache.org/licenses/LICENSE-2.0
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle
.MonoRail
.Framework
.ViewComponents
17 using System
.Collections
;
18 using System
.Collections
.Generic
;
20 using System
.Reflection
;
24 /// This view component deals with object and children as a tree, rendering nodes in a depth-first algorithm fashion, building
25 /// a tree using html's divs.
26 /// You must specify the name of the property that retrieve the children of an object using the <see cref="CollectionPropertyName"/>
30 /// The following example uses nvelocity view engine syntax.
33 /// #blockcomponent(TreeMakerComponent "rootnode=$sitemapnode,CollectionPropertyName='ChildNodes'")
36 /// $currentnode.data.Name
45 /// By default this view component will output inline styles for the div elements, such as
46 /// <c>float: left; width: 19px; height: 20px; background: transparent url(/Content/images/i.gif)</c>.
47 /// You can set the property <see cref="UseInlineStyle"/> to false so the view component will use
48 /// the following classes instead: tree-pipe, tree-blank, tree-branchend and tree-branchline.
51 /// You can use the following nested sections:
52 /// Supported sections: <br/>
53 /// <c>node</c>: when supplied, no tree will be outputted. instead the section will get a Node structure that has information like
54 /// level, data and so on, so you can build your own tree. <br/>
55 /// <c>text</c>: used to render the node content (name, description?) <br/>
58 [ViewComponentDetails("TreeMakerComponent", Sections
= "node,text")]
59 public class TreeMakerComponent
: ViewComponent
61 private string collectionPropertyName
;
62 private object rootNode
;
63 private bool useInlineStyle
= true;
64 private string inlineStyleImagesDir
= null;
65 private PropertyInfo collProperty
;
68 /// Gets or sets the root node.
70 /// <value>The root node.</value>
71 [ViewComponentParam(Required
= true)]
72 public object RootNode
74 get { return rootNode; }
75 set { rootNode = value; }
79 /// Gets or sets the name of the collection property.
81 /// <value>The name of the collection property.</value>
82 [ViewComponentParam(Required
= true)]
83 public string CollectionPropertyName
85 get { return collectionPropertyName; }
86 set { collectionPropertyName = value; }
90 /// Gets or sets a value indicating whether [use inline style].
92 /// <value><c>true</c> if [use inline style]; otherwise, <c>false</c>.</value>
94 public bool UseInlineStyle
96 get { return useInlineStyle; }
97 set { useInlineStyle = value; }
101 /// Gets or sets the inline style images dir.
103 /// <value>The inline style images dir.</value>
105 public string InlineStyleImagesDir
107 get { return inlineStyleImagesDir; }
108 set { inlineStyleImagesDir = value; }
112 /// Called by the framework once the component instance
115 public override void Initialize()
117 collProperty
= rootNode
.GetType().GetProperty(collectionPropertyName
, BindingFlags
.Public
| BindingFlags
.Instance
);
119 inlineStyleImagesDir
= inlineStyleImagesDir
?? string.Format("{0}/content/images", EngineContext
.ApplicationPath
);
125 /// Called by the framework so the component can
126 /// render its content
128 public override void Render()
130 Node processedNode
= BuildNodeHierarchy(rootNode
, 0);
132 StringBuilder sb
= new StringBuilder();
134 RecursiveRenderNode(processedNode
, sb
);
136 RenderText(sb
.ToString());
140 /// Recursively renders the specified node.
142 /// <param name="node">The node.</param>
143 /// <param name="sb">The string builder.</param>
144 protected virtual void RecursiveRenderNode(Node node
, StringBuilder sb
)
146 if (HasSection("node"))
148 PropertyBag
["currentnode"] = node
;
149 RenderSection("node", new StringWriter(sb
));
153 WriteNodeDiv(node
, sb
);
156 foreach(Node child
in node
.Children
)
158 RecursiveRenderNode(child
, sb
);
163 /// Writes a div element for the specified node.
165 /// <param name="node">The node.</param>
166 /// <param name="sb">The string builder.</param>
167 protected virtual void WriteNodeDiv(Node node
, StringBuilder sb
)
169 RecursiveWriteLine(node
.Parent
, sb
);
175 RenderBranchLine(sb
);
179 RenderEndOfBranchLine(sb
);
183 if (HasSection("text"))
185 PropertyBag
["currentnode"] = node
;
186 RenderSection("text", new StringWriter(sb
));
190 sb
.Append(node
.Data
);
193 sb
.AppendLine("<div style=\"clear: both\"></div>");
197 /// Recursively writes the lines for the tree nodes.
199 /// <param name="node">The node.</param>
200 /// <param name="sb">The string builder.</param>
201 protected virtual void RecursiveWriteLine(Node node
, StringBuilder sb
)
203 if (node
== null) return;
204 if (node
.Level
== 0) return;
206 RecursiveWriteLine(node
.Parent
, sb
);
214 RenderLinePlaceholder(sb
);
219 /// Renders a pipe kind of line for the tree.
221 /// <param name="sb">The string builder.</param>
222 protected virtual void RenderPipeLine(StringBuilder sb
)
227 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/i.gif)\"></div>", inlineStyleImagesDir
);
232 sb
.AppendLine("<div class=\"tree-pipe\"></div>");
237 /// Renders a line placeholder.
239 /// <param name="sb">The string builder.</param>
240 protected virtual void RenderLinePlaceholder(StringBuilder sb
)
245 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/noexpand.gif)\"></div>", inlineStyleImagesDir
);
250 sb
.AppendLine("<div class=\"tree-blank\"></div>");
255 /// Renders the end of branch line.
257 /// <param name="sb">The string builder.</param>
258 protected virtual void RenderEndOfBranchLine(StringBuilder sb
)
263 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/l.gif)\"></div>", inlineStyleImagesDir
);
268 sb
.AppendLine("<div class=\"tree-branchend\"></div>");
273 /// Renders the branch line.
275 /// <param name="sb">The string builder.</param>
276 protected virtual void RenderBranchLine(StringBuilder sb
)
281 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/t.gif)\"></div>", inlineStyleImagesDir
);
286 sb
.AppendLine("<div class=\"tree-branchline\"></div>");
291 /// Builds the node hierarchy.
293 /// <param name="node">The node.</param>
294 /// <param name="level">The level.</param>
295 /// <returns></returns>
296 protected virtual Node
BuildNodeHierarchy(object node
, int level
)
298 Node processedNode
= new Node(node
, level
);
300 IEnumerable children
= (IEnumerable
) collProperty
.GetValue(node
, null);
302 foreach(object child
in children
)
304 Node childNode
= BuildNodeHierarchy(child
, level
+ 1);
305 childNode
.Parent
= processedNode
;
307 processedNode
.Children
.Add(childNode
);
310 processedNode
.MarkLastChild();
312 return processedNode
;
316 /// Represents a tree node
320 private bool hasNext
= true;
322 private readonly object data
;
323 private readonly int level
;
324 private readonly List
<Node
> children
= new List
<Node
>();
327 /// Initializes a new instance of the <see cref="Node"/> class.
329 /// <param name="data">The data.</param>
330 /// <param name="level">The level.</param>
331 public Node(object data
, int level
)
338 /// Gets or sets the parent.
340 /// <value>The parent.</value>
343 get { return parent; }
344 set { parent = value; }
348 /// Gets a value indicating whether this instance has children.
351 /// <c>true</c> if this instance has children; otherwise, <c>false</c>.
353 public bool HasChildren
355 get { return children.Count != 0; }
361 /// <value>The level.</value>
364 get { return level; }
368 /// Gets a value indicating whether this instance has a next sibling.
370 /// <value><c>true</c> if this instance has next; otherwise, <c>false</c>.</value>
373 get { return hasNext; }
379 /// <value>The data.</value>
386 /// Gets the children.
388 /// <value>The children.</value>
389 public List
<Node
> Children
391 get { return children; }
395 /// Marks the last child.
397 public void MarkLastChild()
401 children
[children
.Count
- 1].hasNext
= false;