setting all protected methods on WizardStepPage as virtual
[castle.git] / MonoRail / Castle.MonoRail.Framework / ViewComponents / TreeMakerComponent.cs
blob8e92f3888212daab133ad514fafe75a781051540
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
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
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
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;
19 using System.IO;
20 using System.Reflection;
21 using System.Text;
23 /// <summary>
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"/>
27 /// </summary>
28 ///
29 /// <example>
30 /// The following example uses nvelocity view engine syntax.
31 /// <code>
32 /// <![CDATA[
33 /// #blockcomponent(TreeMakerComponent "rootnode=$sitemapnode,CollectionPropertyName='ChildNodes'")
34 ///
35 /// #text
36 /// $currentnode.data.Name
37 /// #end
38 ///
39 /// #end
40 /// ]]>
41 /// </code>
42 /// </example>
43 ///
44 /// <remarks>
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.
49 ///
50 /// <para>
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/>
56 /// </para>
57 /// </remarks>
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;
67 /// <summary>
68 /// Gets or sets the root node.
69 /// </summary>
70 /// <value>The root node.</value>
71 [ViewComponentParam(Required = true)]
72 public object RootNode
74 get { return rootNode; }
75 set { rootNode = value; }
78 /// <summary>
79 /// Gets or sets the name of the collection property.
80 /// </summary>
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; }
89 /// <summary>
90 /// Gets or sets a value indicating whether [use inline style].
91 /// </summary>
92 /// <value><c>true</c> if [use inline style]; otherwise, <c>false</c>.</value>
93 [ViewComponentParam]
94 public bool UseInlineStyle
96 get { return useInlineStyle; }
97 set { useInlineStyle = value; }
100 /// <summary>
101 /// Gets or sets the inline style images dir.
102 /// </summary>
103 /// <value>The inline style images dir.</value>
104 [ViewComponentParam]
105 public string InlineStyleImagesDir
107 get { return inlineStyleImagesDir; }
108 set { inlineStyleImagesDir = value; }
111 /// <summary>
112 /// Called by the framework once the component instance
113 /// is initialized
114 /// </summary>
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);
121 base.Initialize();
124 /// <summary>
125 /// Called by the framework so the component can
126 /// render its content
127 /// </summary>
128 public override void Render()
130 Node processedNode = BuildNodeHierarchy(rootNode, 0);
132 StringBuilder sb = new StringBuilder();
134 RecursiveRenderNode(processedNode, sb);
136 RenderText(sb.ToString());
139 /// <summary>
140 /// Recursively renders the specified node.
141 /// </summary>
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));
151 else
153 WriteNodeDiv(node, sb);
156 foreach(Node child in node.Children)
158 RecursiveRenderNode(child, sb);
162 /// <summary>
163 /// Writes a div element for the specified node.
164 /// </summary>
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);
171 if (node.Level != 0)
173 if (node.HasNext)
175 RenderBranchLine(sb);
177 else
179 RenderEndOfBranchLine(sb);
183 if (HasSection("text"))
185 PropertyBag["currentnode"] = node;
186 RenderSection("text", new StringWriter(sb));
188 else
190 sb.Append(node.Data);
193 sb.AppendLine("<div style=\"clear: both\"></div>");
196 /// <summary>
197 /// Recursively writes the lines for the tree nodes.
198 /// </summary>
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);
208 if (node.HasNext)
210 RenderPipeLine(sb);
212 else
214 RenderLinePlaceholder(sb);
218 /// <summary>
219 /// Renders a pipe kind of line for the tree.
220 /// </summary>
221 /// <param name="sb">The string builder.</param>
222 protected virtual void RenderPipeLine(StringBuilder sb)
224 if (useInlineStyle)
226 sb.AppendFormat(
227 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/i.gif)\"></div>", inlineStyleImagesDir);
228 sb.AppendLine();
230 else
232 sb.AppendLine("<div class=\"tree-pipe\"></div>");
236 /// <summary>
237 /// Renders a line placeholder.
238 /// </summary>
239 /// <param name="sb">The string builder.</param>
240 protected virtual void RenderLinePlaceholder(StringBuilder sb)
242 if (useInlineStyle)
244 sb.AppendFormat(
245 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/noexpand.gif)\"></div>", inlineStyleImagesDir);
246 sb.AppendLine();
248 else
250 sb.AppendLine("<div class=\"tree-blank\"></div>");
254 /// <summary>
255 /// Renders the end of branch line.
256 /// </summary>
257 /// <param name="sb">The string builder.</param>
258 protected virtual void RenderEndOfBranchLine(StringBuilder sb)
260 if (useInlineStyle)
262 sb.AppendFormat(
263 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/l.gif)\"></div>", inlineStyleImagesDir);
264 sb.AppendLine();
266 else
268 sb.AppendLine("<div class=\"tree-branchend\"></div>");
272 /// <summary>
273 /// Renders the branch line.
274 /// </summary>
275 /// <param name="sb">The string builder.</param>
276 protected virtual void RenderBranchLine(StringBuilder sb)
278 if (useInlineStyle)
280 sb.AppendFormat(
281 "<div style=\"float: left; width: 19px; height: 20px; background: transparent url({0}/t.gif)\"></div>", inlineStyleImagesDir);
282 sb.AppendLine();
284 else
286 sb.AppendLine("<div class=\"tree-branchline\"></div>");
290 /// <summary>
291 /// Builds the node hierarchy.
292 /// </summary>
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;
315 /// <summary>
316 /// Represents a tree node
317 /// </summary>
318 public class Node
320 private bool hasNext = true;
321 private Node parent;
322 private readonly object data;
323 private readonly int level;
324 private readonly List<Node> children = new List<Node>();
326 /// <summary>
327 /// Initializes a new instance of the <see cref="Node"/> class.
328 /// </summary>
329 /// <param name="data">The data.</param>
330 /// <param name="level">The level.</param>
331 public Node(object data, int level)
333 this.data = data;
334 this.level = level;
337 /// <summary>
338 /// Gets or sets the parent.
339 /// </summary>
340 /// <value>The parent.</value>
341 public Node Parent
343 get { return parent; }
344 set { parent = value; }
347 /// <summary>
348 /// Gets a value indicating whether this instance has children.
349 /// </summary>
350 /// <value>
351 /// <c>true</c> if this instance has children; otherwise, <c>false</c>.
352 /// </value>
353 public bool HasChildren
355 get { return children.Count != 0; }
358 /// <summary>
359 /// Gets the level.
360 /// </summary>
361 /// <value>The level.</value>
362 public int Level
364 get { return level; }
367 /// <summary>
368 /// Gets a value indicating whether this instance has a next sibling.
369 /// </summary>
370 /// <value><c>true</c> if this instance has next; otherwise, <c>false</c>.</value>
371 public bool HasNext
373 get { return hasNext; }
376 /// <summary>
377 /// Gets the data.
378 /// </summary>
379 /// <value>The data.</value>
380 public object Data
382 get { return data; }
385 /// <summary>
386 /// Gets the children.
387 /// </summary>
388 /// <value>The children.</value>
389 public List<Node> Children
391 get { return children; }
394 /// <summary>
395 /// Marks the last child.
396 /// </summary>
397 public void MarkLastChild()
399 if (HasChildren)
401 children[children.Count - 1].hasNext = false;