PageRenderTime 29ms CodeModel.GetById 12ms app.highlight 11ms RepoModel.GetById 2ms app.codeStats 0ms

/BlogEngine/BlogEngine.NET/App_Code/Controls/MonthList.cs

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