PageRenderTime 512ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/BlogEngine/BlogEngine.NET/AppCode/Controls/MonthList.cs

#
C# | 381 lines | 211 code | 64 blank | 106 comment | 27 complexity | 3245510838242263423a7330dde7d21b MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0, BSD-3-Clause
  1. // --------------------------------------------------------------------------------------------------------------------
  2. // <summary>
  3. // Builds a category list.
  4. // </summary>
  5. // --------------------------------------------------------------------------------------------------------------------
  6. namespace App_Code.Controls
  7. {
  8. using System;
  9. using System.Collections.Generic;
  10. using System.ComponentModel;
  11. using System.Linq;
  12. using System.Web;
  13. using System.Web.Caching;
  14. using System.Web.UI;
  15. using Resources;
  16. using BlogEngine.Core;
  17. /// <summary>
  18. /// Displays a list of hyperlinks each of which points at a page
  19. /// containing blog posts written within a particular month.
  20. /// </summary>
  21. public class MonthList : Control
  22. {
  23. #region Nested Classes
  24. /// <summary>
  25. /// Specifies the way <see cref="App_Code.Controls.MonthList" /> control is displayed.
  26. /// </summary>
  27. [Flags]
  28. public enum MonthListDisplayOptions
  29. {
  30. /// <summary>
  31. /// A convenience constant that corresponds to default <see cref="App_Code.Controls.MonthList" />
  32. /// behavior where grouping by year is performed.
  33. /// </summary>
  34. Grouping_ByYear = 0,
  35. /// <summary>
  36. /// When set, the <see cref="App_Code.Controls.MonthList" /> control renders
  37. /// a flat month list, without grouping by year.
  38. /// </summary>
  39. Grouping_Flat = 1,
  40. /// <summary>
  41. /// A convenience constant that corresponds to default <see cref="App_Code.Controls.MonthList" />
  42. /// behavior where more recent dates appear at the bottom of the list.
  43. /// </summary>
  44. Sorting_RecentAtBottom = 0,
  45. /// <summary>
  46. /// When set, the the <see cref="App_Code.Controls.MonthList" /> control renders
  47. /// a month list where more recent dates appear at the top of the list.
  48. /// </summary>
  49. Sorting_RecentAtTop = 2
  50. }
  51. /// <summary>
  52. /// Represents a <see cref="System.DateTime" /> comparer that performs
  53. /// direct date comparison.
  54. /// </summary>
  55. class DateComparer_Asc : IComparer<DateTime>
  56. {
  57. int IComparer<DateTime>.Compare(DateTime x, DateTime y)
  58. { return x.CompareTo(y); }
  59. }
  60. /// <summary>
  61. /// Represents a <see cref="System.DateTime" /> comparer that performs
  62. /// inverse date comparison.
  63. /// </summary>
  64. class DateComparer_Desc : IComparer<DateTime>
  65. {
  66. int IComparer<DateTime>.Compare(DateTime x, DateTime y)
  67. { return -x.CompareTo(y); }
  68. }
  69. #endregion
  70. #region Constants and Fields
  71. /// <summary>
  72. /// The cache key.
  73. /// </summary>
  74. private const string CacheKey = "BE_MonthListCacheKey";
  75. /// <summary>
  76. /// The cache timeout in hours.
  77. /// </summary>
  78. private const double CacheTimeoutInHours = 1;
  79. /// <summary>
  80. /// The global instance of the direct date comparer.
  81. /// </summary>
  82. private static readonly IComparer<DateTime> ourComparer_Asc = new DateComparer_Asc();
  83. /// <summary>
  84. /// The global instance of the inverse date comparer.
  85. /// </summary>
  86. private static readonly IComparer<DateTime> ourComparer_Desc = new DateComparer_Desc();
  87. /// <summary>
  88. /// Stores the current year so that it remains constant while the control is being rendered.
  89. /// </summary>
  90. private int myCurrentYear;
  91. #endregion
  92. #region Constructors and Destructors
  93. /// <summary>
  94. /// Initializes static members of the <see cref="MonthList" /> class.
  95. /// </summary>
  96. static MonthList()
  97. {
  98. Post.Saved += Handler_Post_Saved;
  99. }
  100. /// <summary>
  101. /// Initializes a new instance of the <see cref="MonthList" /> class.
  102. /// </summary>
  103. public MonthList()
  104. {
  105. myCurrentYear = DateTime.Now.Year;
  106. }
  107. #endregion
  108. #region Properties
  109. /// <summary>
  110. /// Specifies whether the month lit should be render hierarchically, grouped by year.
  111. /// </summary>
  112. [DefaultValue(true)]
  113. public bool GroupByYear
  114. {
  115. get
  116. {
  117. return (DisplayFlags & (int)MonthListDisplayOptions.Grouping_Flat) == 0;
  118. }
  119. set
  120. {
  121. if (value) DisplayFlags &= ~(int)MonthListDisplayOptions.Grouping_Flat;
  122. else DisplayFlags |= (int)MonthListDisplayOptions.Grouping_Flat;
  123. }
  124. }
  125. /// <summary>
  126. /// Specifies the way months should be sorted: recent dates at top or at bottom.
  127. /// </summary>
  128. [DefaultValue(false)]
  129. public bool RecentDatesAtTop
  130. {
  131. get
  132. {
  133. return (DisplayFlags & (int)MonthListDisplayOptions.Sorting_RecentAtTop) != 0;
  134. }
  135. set
  136. {
  137. if (value) DisplayFlags |= (int)MonthListDisplayOptions.Sorting_RecentAtTop;
  138. else DisplayFlags &= ~(int)MonthListDisplayOptions.Sorting_RecentAtTop;
  139. }
  140. }
  141. /// <summary>
  142. /// Stores the controls display options in a compact manner.
  143. /// </summary>
  144. public virtual int DisplayFlags
  145. {
  146. get
  147. {
  148. object aVal = ViewState["DisplayFlags"];
  149. if (aVal == null)
  150. return 0;
  151. else
  152. return (int)aVal;
  153. }
  154. set { ViewState["DisplayFlags"] = value; }
  155. }
  156. #endregion
  157. #region Public Methods
  158. /// <summary>
  159. /// Outputs server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/>
  160. /// object and stores tracing information about the control if tracing is enabled.
  161. /// </summary>
  162. /// <param name="writer">
  163. /// The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.
  164. /// </param>
  165. public override void RenderControl(HtmlTextWriter writer)
  166. {
  167. SortedDictionary<DateTime, int> months = GetPostsPerMonth(RecentDatesAtTop);
  168. if (months.Keys.Count == 0)
  169. {
  170. writer.RenderBeginTag(HtmlTextWriterTag.P);
  171. writer.Write(Resources.labels.none);
  172. writer.RenderEndTag();
  173. return;
  174. }
  175. writer.AddAttribute("id", "monthList");
  176. writer.RenderBeginTag(HtmlTextWriterTag.Ul);
  177. if (GroupByYear)
  178. {
  179. int current = 0;
  180. foreach (KeyValuePair<DateTime, int> aIt in months)
  181. {
  182. if (current != aIt.Key.Year)
  183. {
  184. if (current != 0)
  185. RenderYearGroupEnd(writer);
  186. RenderYearGroupBegin(writer, aIt.Key.Year);
  187. current = aIt.Key.Year;
  188. }
  189. RenderMonth(writer, aIt.Key, aIt.Value, false);
  190. }
  191. if (current != 0)
  192. RenderYearGroupEnd(writer);
  193. }
  194. else
  195. {
  196. foreach (KeyValuePair<DateTime, int> aIt in months)
  197. RenderMonth(writer, aIt.Key, aIt.Value, true);
  198. }
  199. writer.RenderEndTag();
  200. }
  201. #endregion
  202. #region Implementation-Specific Methods
  203. /// <summary>Obtains a collection of months with a corresponding month post count.</summary>
  204. /// <returns>
  205. /// A <see cref="T:System.Collections.Generic.SortedDictionary<DateTime, int>" />
  206. /// whose keys represent the first day of a month and values contain the month post count.
  207. /// </returns>
  208. private static SortedDictionary<DateTime, int> GetPostsPerMonth(bool theSortDesc)
  209. {
  210. var months = Blog.CurrentInstance.Cache[CacheKey] as SortedDictionary<DateTime, int>;
  211. IComparer<DateTime> aComparer = theSortDesc ? ourComparer_Desc : ourComparer_Asc;
  212. if (months != null)
  213. {
  214. if (months.Comparer != aComparer)
  215. {
  216. Blog.CurrentInstance.Cache.Remove(CacheKey);
  217. months = null;
  218. }
  219. }
  220. if (months == null)
  221. {
  222. months = new SortedDictionary<DateTime, int>(aComparer);
  223. // let dictionary expire after 1 hour
  224. Blog.CurrentInstance.Cache.Insert(
  225. CacheKey, months, null, DateTime.Now.AddHours(CacheTimeoutInHours), Cache.NoSlidingExpiration);
  226. foreach (
  227. var month in Post.ApplicablePosts.Where(post => post.IsVisibleToPublic).Select(
  228. post => new DateTime(post.DateCreated.Year, post.DateCreated.Month, 1)))
  229. {
  230. int count;
  231. // if the date is not in the dictionary, count will be set to 0
  232. months.TryGetValue(month, out count);
  233. ++count;
  234. months[month] = count;
  235. }
  236. }
  237. return months;
  238. }
  239. /// <summary>
  240. /// Handles the Saved event of the Post control.
  241. /// </summary>
  242. /// <param name="sender">The source of the event.</param>
  243. /// <param name="e">The <see cref="BlogEngine.Core.SavedEventArgs"/> instance containing the event data.</param>
  244. private static void Handler_Post_Saved(object sender, SavedEventArgs e)
  245. {
  246. // invalidate cache whenever a post is modified
  247. Blog.CurrentInstance.Cache.Remove(CacheKey);
  248. Blog siteAggregationBlog = Blog.SiteAggregationBlog;
  249. if (siteAggregationBlog != null)
  250. {
  251. siteAggregationBlog.Cache.Remove(CacheKey);
  252. }
  253. }
  254. #region Rendering Methods
  255. /// <summary>
  256. /// Renders the beginning of HTML code corresponding to a specific year.
  257. /// </summary>
  258. /// <param name="theWriter">The text writer that receives rendered data.</param>
  259. /// <param name="theYear">The year being rendered</param>
  260. private void RenderYearGroupBegin(HtmlTextWriter theWriter, int theYear)
  261. {
  262. // 1. Year List Item
  263. string aYearStr = theYear.ToString();
  264. theWriter.AddAttribute("class", "year");
  265. theWriter.AddAttribute("onclick", "BlogEngine.toggleMonth('year" + aYearStr + "')");
  266. theWriter.RenderBeginTag(HtmlTextWriterTag.Li);
  267. theWriter.Write(aYearStr);
  268. // 2. Nested Month List
  269. theWriter.AddAttribute("id", "year" + aYearStr);
  270. if (theYear == myCurrentYear)
  271. theWriter.AddAttribute("class", "open");
  272. theWriter.RenderBeginTag(HtmlTextWriterTag.Ul);
  273. }
  274. /// <summary>
  275. /// Renders the end of HTML code corresponding to a year.
  276. /// </summary>
  277. /// <param name="theWriter">The text writer that receives rendered data.</param>
  278. private void RenderYearGroupEnd(HtmlTextWriter theWriter)
  279. {
  280. // 2. Nested Month List
  281. theWriter.RenderEndTag();
  282. // 1. Year List Item
  283. theWriter.RenderEndTag();
  284. }
  285. /// <summary>
  286. /// Renders HTML code corresponding to a specific month/year combination.
  287. /// </summary>
  288. /// <param name="theWriter">The text writer that receives rendered data.</param>
  289. /// <param name="theDate">The date (year and month) being rendered.</param>
  290. /// <param name="thePostCount">The number of posts published within the specified month.</param>
  291. /// <param name="theShowYear">True, if the year must be rendered; otherwise, false.</param>
  292. private void RenderMonth(HtmlTextWriter theWriter, DateTime theDate, int thePostCount, bool theShowYear)
  293. {
  294. theWriter.RenderBeginTag(HtmlTextWriterTag.Li);
  295. {
  296. string aHref = string.Format
  297. (
  298. "{0}{1}/{2}/default{3}",
  299. Utils.RelativeOrAbsoluteWebRoot,
  300. theDate.Year,
  301. theDate.ToString("MM"),
  302. BlogConfig.FileExtension
  303. );
  304. theWriter.AddAttribute("href", aHref);
  305. theWriter.RenderBeginTag(HtmlTextWriterTag.A);
  306. theWriter.Write(theShowYear ? theDate.ToString("MMMM yyyy") : theDate.ToString("MMMM"));
  307. theWriter.RenderEndTag();
  308. }
  309. theWriter.Write(" (");
  310. theWriter.Write(thePostCount.ToString());
  311. theWriter.Write(")");
  312. theWriter.RenderEndTag();
  313. }
  314. #endregion
  315. #endregion
  316. }
  317. }