Minor changes to improve testability of helpers
[castle.git] / MonoRail / Castle.MonoRail.Framework / ViewComponents / ColumnRenderer.cs
blob056a6f43a9e0b248247e63b9e2114bafbc9d71c6
1 // Copyright 2004-2007 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;
18 using System.Collections;
20 /// <summary>
21 /// Renders a table where each nested content is rendered on a cell.
22 /// <para>
23 /// For example, suppose you have a dynamic list of items and what to display
24 /// them side by side, in four columns. As the number of elements in unknown
25 /// in development time, you can use the ColumnRenderer to
26 /// create the table and cells.
27 /// </para>
28 /// </summary>
29 ///
30 /// <example>
31 /// The following example uses nvelocity view engine syntax.
32 /// <code>
33 /// <![CDATA[
34 /// #blockcomponent(ColumnRenderer with "items=$interests")
35 ///
36 /// #firstelement
37 /// Custom first element
38 /// #end
39 ///
40 /// #item
41 /// Content is $item
42 /// #end
43 ///
44 /// #end
45 /// ]]>
46 /// </code>
47 /// <para>
48 /// Which should render something like:
49 /// </para>
50 /// <code>
51 /// <![CDATA[
52 /// <table>
53 /// <tr>
54 /// <td>
55 /// Custom first element
56 /// </td>
57 /// <td>
58 /// Content is Tennis
59 /// </td>
60 /// <td>
61 /// Content is Soccer
62 /// </td>
63 /// </tr>
64 /// <tr>
65 /// <td>
66 /// Content is Voleyball
67 /// </td>
68 /// </tr>
69 /// </table>
70 /// ]]>
71 /// </code>
72 /// </example>
73 ///
74 /// <remarks>
75 /// The following sections are supported. Only the <c>item</c> section must be always provided. <br/>
76 ///
77 /// <para>
78 /// <c>start</c>: override it in order to create the table yourself <br/>
79 /// <c>endblock</c>: override it in order to end the table <br/>
80 ///
81 /// <c>startrow</c>: override it in order to start the column <br/>
82 /// <c>endrow</c>: override it in order to end the column <br/>
83 ///
84 /// <c>startcolumn</c>: override it in order to start the cell <br/>
85 /// <c>endcolumn</c>: override it in order to end the cell <br/>
86 ///
87 /// <c>item</c>: must be overriden in order to display the item content (unless it's something trivial like a primitive) <br/>
88 /// <c>empty</c>: section used when the <see cref="Items"/> is empty <br/>
89 /// <c>firstelement</c>: if provided, will be rendered before any cells <br/>
90 /// </para>
91 ///
92 /// <para>
93 /// The number of columns defaults to three.
94 /// </para>
95 /// </remarks>
96 public class ColumnRenderer : ViewComponent
98 private int cols = 3;
99 private IEnumerable enumerable;
100 private bool dontRenderUneededTableForEmptyLists = false;
102 /// <summary>
103 /// Gets or sets the number of columns to display.
104 /// </summary>
105 /// <value>The cols.</value>
106 [ViewComponentParam]
107 public int Cols
109 get { return cols; }
110 set { cols = value; }
113 /// <summary>
114 /// Gets or sets the items to show.
115 /// </summary>
116 /// <value>The items.</value>
117 [ViewComponentParam(Required = true)]
118 public IEnumerable Items
120 get { return enumerable; }
121 set { enumerable = value; }
124 /// <summary>
125 /// Gets or sets a value indicating whether the component should render a table
126 /// even if there are no elements on the <see cref="Items"/>.
127 /// </summary>
128 /// <value>
129 /// <c>true</c> if it should not render; otherwise, <c>false</c>.
130 /// </value>
131 [ViewComponentParam("emptyness")]
132 public bool DontRenderUneededTableForEmptyLists
134 get { return dontRenderUneededTableForEmptyLists; }
135 set { dontRenderUneededTableForEmptyLists = value; }
138 /// <summary>
139 /// Called by the framework once the component instance
140 /// is initialized
141 /// </summary>
142 public override void Initialize()
144 if (cols <= 0)
146 throw new ViewComponentException("ColumnRenderer: 'cols' parameter must be greater than zero");
150 /// <summary>
151 /// Called by the framework so the component can
152 /// render its content
153 /// </summary>
154 public override void Render()
156 if (IsEmptyAndShouldNotRenderBrokenTableTags())
158 NoElements();
159 return;
161 StartTable();
163 bool hasElements = false;
164 int itemIndex = 0;
166 if (enumerable != null)
168 int toFinishRow = 0;
170 if (Context.HasSection("firstelement"))
172 StartRow();
174 StartColumn();
176 Context.RenderSection("firstelement");
178 EndColumn();
180 toFinishRow = itemIndex++ + cols;
183 foreach(object item in enumerable)
185 hasElements = true;
187 bool writeRow = itemIndex++ % cols == 0;
189 if (toFinishRow == itemIndex) EndRow();
191 if (writeRow)
193 StartRow();
196 StartColumn();
198 WriteElement(item);
200 EndColumn();
202 if (writeRow)
204 toFinishRow = itemIndex + cols;
208 if (itemIndex < toFinishRow)
210 for(; itemIndex < toFinishRow - 1; itemIndex++)
212 StartColumn();
213 RenderText("&nbsp;");
214 EndColumn();
216 EndRow();
220 if (!hasElements)
222 NoElements();
225 EndTable();
228 /// <summary>
229 /// Implementor should return true only if the
230 /// <c>name</c> is a known section the view component
231 /// supports.
232 /// </summary>
233 /// <param name="name">section being added</param>
234 /// <returns><see langword="true"/> if section is supported</returns>
235 public override bool SupportsSection(string name)
237 return name == "start" || name == "endblock" ||
238 name == "startcolumn" || name == "endcolumn" ||
239 name == "startrow" || name == "endrow" ||
240 name == "item" || name == "firstelement" ||
241 name == "empty";
244 private bool IsEmptyAndShouldNotRenderBrokenTableTags()
246 if (dontRenderUneededTableForEmptyLists)
248 if (enumerable == null || !enumerable.GetEnumerator().MoveNext())
250 return true;
253 return false;
256 private void WriteElement(object item)
258 if (Context.HasSection("item"))
260 PropertyBag["item"] = item;
261 Context.RenderSection("item");
263 else
265 RenderText(item.ToString());
269 private void StartColumn()
271 if (Context.HasSection("startcolumn"))
273 Context.RenderSection("startcolumn");
275 else
277 RenderText("<td>");
281 private void EndColumn()
283 if (Context.HasSection("endcolumn"))
285 Context.RenderSection("endcolumn");
287 else
289 RenderText("</td>");
293 private void StartRow()
295 if (Context.HasSection("startrow"))
297 Context.RenderSection("startrow");
299 else
301 RenderText("<tr>");
305 private void EndRow()
307 if (Context.HasSection("endrow"))
309 Context.RenderSection("endrow");
311 else
313 RenderText("</tr>");
317 private void StartTable()
319 if (Context.HasSection("start"))
321 Context.RenderSection("start");
323 else
325 RenderText("<table cellpadding='5'>");
329 private void NoElements()
331 if (Context.HasSection("empty"))
333 Context.RenderSection("empty");
335 else
337 if (dontRenderUneededTableForEmptyLists)
339 RenderText("Empty");
341 else
343 StartRow();
344 StartColumn();
345 RenderText("Empty");
346 EndColumn();
347 EndRow();
352 private void EndTable()
354 if (Context.HasSection("endblock"))
356 Context.RenderSection("endblock");
358 else
360 RenderText("</table>");