/src/MuonLab.Web.Xhtml/Components/Implementations/VisibleComponent.cs

https://github.com/trullock/MuonLab.Web.Xhtml · C# · 309 lines · 210 code · 44 blank · 55 comment · 15 complexity · e4e1e84b06109fe0eda1622de015837d MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using MuonLab.Commons.Extensions;
  6. using MuonLab.Web.Xhtml.Properties;
  7. namespace MuonLab.Web.Xhtml.Components.Implementations
  8. {
  9. public abstract class VisibleComponent<TViewModel, TProperty> :
  10. Component<TViewModel, TProperty>,
  11. IVisibleComponent<TProperty>
  12. {
  13. internal event EventHandler OnPrepareForRender;
  14. protected IValidationMessageRenderer ValidationMessageRenderer;
  15. protected ComponentState state;
  16. protected IEnumerable<string> validationErrors;
  17. protected bool showLabel;
  18. protected bool showValidationMarker;
  19. protected ValidationMarkerMode showValidationMarkerMode;
  20. protected bool showValidationMessage;
  21. protected ValidationMarkerMode showValidationMessageMode;
  22. protected IDictionary<string, object> wrapperHtmlAttributes;
  23. protected string wrapperTagName;
  24. protected string helpText;
  25. protected IEnumerable<ComponentPart> renderingOrder;
  26. public string Label { get; protected set; }
  27. protected VisibleComponent()
  28. {
  29. this.validationErrors = new string[0];
  30. this.renderingOrder = new ComponentPart[0];
  31. this.showValidationMessage = true;
  32. this.ValidationMessageRenderer = new ValidationMessageRenderer();
  33. }
  34. public IVisibleComponent WithValidationMessageRenderer(IValidationMessageRenderer messageRenderer)
  35. {
  36. this.ValidationMessageRenderer = messageRenderer;
  37. return this;
  38. }
  39. public IVisibleComponent WithState(ComponentState state, IEnumerable<string> validationErrors)
  40. {
  41. this.state = state;
  42. this.validationErrors = validationErrors;
  43. return this;
  44. }
  45. /// <summary>
  46. /// Adds an HTML Label tag to the markup with text automatically determined from the property represented by the component
  47. /// </summary>
  48. /// <returns></returns>
  49. public IVisibleComponent WithLabel()
  50. {
  51. return WithLabel(this.Label);
  52. }
  53. /// <summary>
  54. /// Adds an HTML Label tag to the markup with the given text.
  55. /// </summary>
  56. /// <param name="label">The label text</param>
  57. /// <returns></returns>
  58. public IVisibleComponent WithLabel(string label)
  59. {
  60. this.showLabel = true;
  61. this.Label = label;
  62. return this;
  63. }
  64. /// <summary>
  65. /// Prevents an HTML label from being rendered
  66. /// </summary>
  67. /// <returns></returns>
  68. public IVisibleComponent WithoutLabel()
  69. {
  70. this.showLabel = false;
  71. return this;
  72. }
  73. /// <summary>
  74. /// Adds a validation marker to the markup
  75. /// </summary>
  76. /// <param name="mode"></param>
  77. /// <returns></returns>
  78. public IVisibleComponent WithValidationMarker(ValidationMarkerMode mode)
  79. {
  80. this.showValidationMarker = true;
  81. this.showValidationMarkerMode = mode;
  82. return this;
  83. }
  84. /// <summary>
  85. /// Prevents a validation marker from being displayed
  86. /// </summary>
  87. /// <returns></returns>
  88. public IVisibleComponent WithoutValidationMarker()
  89. {
  90. this.showValidationMarker = false;
  91. return this;
  92. }
  93. /// <summary>
  94. /// Adds a validation message to the markup when the field is invalid
  95. /// </summary>
  96. /// <returns></returns>
  97. public IVisibleComponent WithValidationMessage(ValidationMarkerMode mode)
  98. {
  99. this.showValidationMessage = true;
  100. this.showValidationMessageMode = mode;
  101. return this;
  102. }
  103. /// <summary>
  104. /// Prevents a validation message from being displayed
  105. /// </summary>
  106. /// <returns></returns>
  107. public IVisibleComponent WithoutValidationMessage()
  108. {
  109. this.showValidationMessage = false;
  110. return this;
  111. }
  112. /// <summary>
  113. /// Sets the help text for the component
  114. /// </summary>
  115. /// <returns></returns>
  116. public IVisibleComponent WithHelpText(string helpText)
  117. {
  118. this.helpText = helpText;
  119. return this;
  120. }
  121. /// <summary>
  122. /// set teh field as readonly
  123. /// </summary>
  124. /// <returns></returns>
  125. public virtual IVisibleComponent ReadOnly()
  126. {
  127. this.WithAttr("readonly", "readonly");
  128. return this;
  129. }
  130. /// <summary>
  131. /// Wraps all rendered tags in an outer tag with the given name
  132. /// </summary>
  133. /// <param name="tagName">the tag name, e.g. "div". Pass null for no wrapper</param>
  134. /// <returns></returns>
  135. public IVisibleComponent WithWrapper(string tagName)
  136. {
  137. this.wrapperTagName = tagName;
  138. this.wrapperHtmlAttributes = null;
  139. return this;
  140. }
  141. /// <summary>
  142. /// Wraps all rendered tags in an outer tag with the given name
  143. /// </summary>
  144. /// <param name="tagName">the tag name, e.g. "div". Pass null for no wrapper</param>
  145. /// <param name="htmlAttributes">The html attributes to apply to the wrapper</param>
  146. /// <returns></returns>
  147. public IVisibleComponent WithWrapper(string tagName, object htmlAttributes)
  148. {
  149. this.wrapperTagName = tagName;
  150. this.wrapperHtmlAttributes = htmlAttributes.ToDictionary();
  151. return this;
  152. }
  153. /// <summary>
  154. /// Sets the rendering order for this parts of the component
  155. /// </summary>
  156. /// <param name="renderingOrder"></param>
  157. /// <returns></returns>
  158. public IVisibleComponent WithRenderingOrder(params ComponentPart[] renderingOrder)
  159. {
  160. this.renderingOrder = renderingOrder;
  161. return this;
  162. }
  163. protected virtual string RenderLabel()
  164. {
  165. var htmlAttribs = new Dictionary<string, object>();
  166. htmlAttribs.Add("for", this.GetAttr("id"));
  167. var labelBuilder = new TagBuilder("label", htmlAttribs);
  168. labelBuilder.SetInnerText(this.Label);
  169. return labelBuilder.ToString();
  170. }
  171. protected virtual string RenderValidationMarker()
  172. {
  173. if (!this.showValidationMarker)
  174. return null;
  175. var firstError = this.validationErrors.FirstOrDefault();
  176. if (firstError == null)
  177. {
  178. var attribs = new Dictionary<string, object>{ {"class", "field-validation-marker"}};
  179. var builder = new TagBuilder("span", attribs);
  180. return builder.ToString();
  181. }
  182. else
  183. {
  184. var attribs = new Dictionary<string, object>
  185. {
  186. { "class", "field-validation-marker field-validation-error" },
  187. { "title", firstError }
  188. };
  189. var builder = new TagBuilder("span", attribs);
  190. builder.SetInnerText("*");
  191. return builder.ToString();
  192. }
  193. }
  194. protected virtual string RenderValidationMessage()
  195. {
  196. if (!this.showValidationMessage)
  197. return null;
  198. return this.ValidationMessageRenderer.Render(this.state, this.showValidationMessageMode, this.validationErrors);
  199. }
  200. protected virtual string RenderHelpText()
  201. {
  202. //todo: html encode this?
  203. return "<span class=\"field-help-text\">" + this.helpText + "</span>";
  204. }
  205. protected virtual string RenderWrapperEndTag()
  206. {
  207. if (!string.IsNullOrEmpty(this.wrapperTagName))
  208. return "</" + this.wrapperTagName + ">";
  209. return null;
  210. }
  211. protected virtual string RenderWrapperStartTag()
  212. {
  213. if (!string.IsNullOrEmpty(this.wrapperTagName))
  214. {
  215. var builder = new TagBuilder(this.wrapperTagName, this.wrapperHtmlAttributes);
  216. return builder.ToString(TagRenderMode.StartTag);
  217. }
  218. return null;
  219. }
  220. protected virtual void PrepareForRender()
  221. {
  222. if(this.OnPrepareForRender != null)
  223. this.OnPrepareForRender(this, new EventArgs());
  224. if (this.state == ComponentState.Invalid)
  225. this.AddClass("input-validation-error");
  226. if (this.state == ComponentState.Valid)
  227. this.AddClass("input-validation-ok");
  228. }
  229. public override string ToString()
  230. {
  231. this.PrepareForRender();
  232. var builder = new StringBuilder();
  233. foreach (var part in renderingOrder)
  234. {
  235. switch (part)
  236. {
  237. case ComponentPart.Label:
  238. if(showLabel)
  239. builder.Append(RenderLabel());
  240. break;
  241. case ComponentPart.Component:
  242. builder.Append(RenderComponent());
  243. break;
  244. case ComponentPart.HelpText:
  245. if(!string.IsNullOrEmpty(this.helpText))
  246. builder.Append(RenderHelpText());
  247. break;
  248. case ComponentPart.ValidationMarker:
  249. builder.Append(RenderValidationMarker());
  250. break;
  251. case ComponentPart.ValidationMessage:
  252. builder.Append(RenderValidationMessage());
  253. break;
  254. case ComponentPart.WrapperStartTag:
  255. builder.Append(RenderWrapperStartTag());
  256. break;
  257. case ComponentPart.WrapperEndTag:
  258. builder.Append(RenderWrapperEndTag());
  259. break;
  260. }
  261. }
  262. return builder.ToString();
  263. }
  264. }
  265. }