Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework / Helpers / PaginationHelper.cs
blob673623bc1055b4602d2ab4579f7b4a152cdd980d
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.Helpers
17 using System;
18 using System.Collections;
19 using System.Collections.Generic;
20 using System.Collections.Specialized;
22 /// <summary>
23 /// Used as callback handler to obtain the items
24 /// to be displayed.
25 /// </summary>
26 public delegate IList DataObtentionDelegate();
28 /// <summary>
29 /// This helper allows you to easily paginate through a data source
30 /// -- anything that implements <see cref="IList"/>.
31 /// </summary>
32 ///
33 /// <remarks>
34 /// With the pagination you expose a <see cref="Page"/> instance to your view,
35 /// that can be used to created a very detailed page navigator.
36 ///
37 /// <para>
38 /// You can use up to three approaches to pagination:
39 /// </para>
40 ///
41 /// <list type="bullet">
42 /// <item>
43 /// <term>CreatePagination</term>
44 /// <description>Uses a whole data set and creates a <see cref="Page"/> with a slice of it. </description>
45 /// </item>
46 /// <item>
47 /// <term>CreateCachedPagination</term>
48 /// <description>Caches the dataset and creates a <see cref="Page"/> with a slice.
49 /// As the cache is shared, you must be very careful on creating a cache key that uniquely represents the
50 /// cached dataset.
51 /// </description>
52 /// </item>
53 /// <item>
54 /// <term>CreateCustomPage</term>
55 /// <description>
56 /// In this case, you are handling the slicing. The <see cref="Page"/> is created with your
57 /// actual dataset and information about total. It calculates the other information based on that.
58 /// </description>
59 /// </item>
60 /// </list>
61 ///
62 /// <para>
63 /// Performance wise, the best choice is the <see cref="CreateCustomPage(IList,int,int,int)"/>
64 /// </para>
65 /// </remarks>
66 public class PaginationHelper : AbstractHelper
68 #region Constructors
69 /// <summary>
70 /// Initializes a new instance of the <see cref="PaginationHelper"/> class.
71 /// </summary>
72 public PaginationHelper() { }
73 /// <summary>
74 /// Initializes a new instance of the <see cref="PaginationHelper"/> class.
75 /// setting the Controller, Context and ControllerContext.
76 /// </summary>
77 /// <param name="engineContext">The engine context.</param>
78 public PaginationHelper(IEngineContext engineContext) : base(engineContext) { }
79 #endregion
81 /// <summary>
82 /// The parameter key that the helper looks for on the request
83 /// </summary>
84 public const string PageParameterName = "page";
86 #region CreatePageLink
88 /// <summary>
89 /// Creates a link to navigate to a specific page
90 /// </summary>
91 /// <param name="page">Page index</param>
92 /// <param name="text">Link text</param>
93 /// <returns>An anchor tag</returns>
94 public String CreatePageLink(int page, String text)
96 return CreatePageLink(page, text, null, null);
99 /// <summary>
100 /// Creates a link to navigate to a specific page
101 /// </summary>
102 /// <param name="page">Page index</param>
103 /// <param name="text">Link text</param>
104 /// <param name="htmlAttributes">Attributes for the anchor tag</param>
105 /// <returns>An anchor tag</returns>
106 public String CreatePageLink(int page, String text, IDictionary htmlAttributes)
108 return CreatePageLink(page, text, htmlAttributes, null);
111 /// <summary>
112 /// Creates a link to navigate to a specific page
113 /// </summary>
114 /// <param name="page">Page index</param>
115 /// <param name="text">Link text</param>
116 /// <param name="htmlAttributes">Attributes for the anchor tag</param>
117 /// <param name="queryStringParams">Query string entries for the link</param>
118 /// <returns>An anchor tag</returns>
119 public String CreatePageLink(int page, String text, IDictionary htmlAttributes, IDictionary queryStringParams)
121 String filePath = CurrentContext.Request.FilePath;
123 if (queryStringParams == null)
125 queryStringParams = new Hashtable();
127 else if (queryStringParams.IsReadOnly || queryStringParams.IsFixedSize)
129 queryStringParams = new Hashtable(queryStringParams);
132 queryStringParams[PageParameterName] = page.ToString();
134 return String.Format("<a href=\"{0}?{1}\" {2}>{3}</a>",
135 filePath, BuildQueryString(queryStringParams), GetAttributes(htmlAttributes), text);
138 /// <summary>
139 /// Creates a link to navigate to a specific page
140 /// </summary>
141 /// <param name="page">Page index</param>
142 /// <param name="text">Link text</param>
143 /// <param name="htmlAttributes">Attributes for the anchor tag</param>
144 /// <returns>An anchor tag</returns>
145 public String CreatePageLinkWithCurrentQueryString(int page, String text, IDictionary htmlAttributes)
147 NameValueCollection queryStringParams = Context.Request.QueryString;
148 IDictionary dictionary = null;
149 if (queryStringParams != null && queryStringParams.Count > 0)
151 dictionary = new Hashtable(queryStringParams.Count);
152 foreach(string key in queryStringParams.Keys)
154 if (key != null)
156 dictionary[key] = queryStringParams.GetValues(key);
160 return CreatePageLink(page, text, htmlAttributes, dictionary);
163 #endregion
165 #region CreatePagination
167 /// <summary>
168 /// Creates a <see cref="Page"/> which is a sliced view of
169 /// the data source
170 /// </summary>
171 /// <param name="engineContext">The engine context.</param>
172 /// <param name="datasource">Data source to be used as target of the pagination</param>
173 /// <param name="pageSize">Page size</param>
174 /// <returns>A <see cref="Page"/> instance</returns>
175 public static IPaginatedPage CreatePagination(IEngineContext engineContext, IList datasource, int pageSize)
177 return CreatePagination(datasource, pageSize, GetCurrentPageFromRequest(engineContext));
180 /// <summary>
181 /// Creates a <see cref="Page"/> which is a sliced view of
182 /// the data source
183 /// </summary>
184 /// <param name="datasource">Data source to be used as target of the pagination</param>
185 /// <param name="pageSize">Page size</param>
186 /// <param name="currentPage">current page index (1 based)</param>
187 /// <returns>A <see cref="Page"/> instance</returns>
188 public static IPaginatedPage CreatePagination(IList datasource, int pageSize, int currentPage)
190 if (currentPage <= 0) currentPage = 1;
192 return new Page(datasource, currentPage, pageSize);
195 #endregion
197 #region CreatePagination<T>
199 /// <summary>
200 /// Creates a <see cref="Page"/> which is a sliced view of
201 /// the data source
202 /// </summary>
203 /// <param name="engineContext">The engine context.</param>
204 /// <param name="datasource">Data source to be used as target of the pagination</param>
205 /// <param name="pageSize">Page size</param>
206 /// <returns>A <see cref="Page"/> instance</returns>
207 public static IPaginatedPage CreatePagination<T>(IEngineContext engineContext,
208 ICollection<T> datasource, int pageSize)
210 return CreatePagination<T>(datasource, pageSize, GetCurrentPageFromRequest(engineContext));
213 /// <summary>
214 /// Creates a <see cref="Page"/> which is a sliced view of
215 /// the data source
216 /// </summary>
217 /// <param name="datasource">Data source to be used as target of the pagination</param>
218 /// <param name="pageSize">Page size</param>
219 /// <param name="currentPage">current page index (1 based)</param>
220 /// <returns>A <see cref="Page"/> instance</returns>
221 public static IPaginatedPage CreatePagination<T>(ICollection<T> datasource,
222 int pageSize, int currentPage)
224 if (currentPage <= 0) currentPage = 1;
226 return new GenericPage<T>(datasource, currentPage, pageSize);
229 #endregion
231 #region CreateCachedPagination
233 /// <summary>
234 /// Creates a <see cref="Page"/> which is a sliced view of
235 /// the data source. This method first looks for the datasource
236 /// in the <see cref="System.Web.Caching.Cache"/> and if not found,
237 /// it invokes the <c>dataObtentionCallback</c> and caches the result
238 /// using the specifed <c>cacheKey</c>
239 /// </summary>
240 /// <param name="engineContext">The engine context.</param>
241 /// <param name="cacheKey">Cache key used to query/store the datasource</param>
242 /// <param name="pageSize">Page size</param>
243 /// <param name="dataObtentionCallback">Callback to be used to populate the cache</param>
244 /// <returns>A <see cref="Page"/> instance</returns>
245 /// <remarks>
246 /// CreateCachedPagination is quite dangerous exactly because the cache is
247 /// shared. If the results vary per logged user, then the programmer must
248 /// rather pay a lot of attention when generating the cache key.
249 /// It is preferable to have caching happen at a lower level of the stack, for example the NHibernate query cache.
250 /// </remarks>
251 public static IPaginatedPage CreateCachedPagination(IEngineContext engineContext, String cacheKey, int pageSize,
252 DataObtentionDelegate dataObtentionCallback)
254 ICacheProvider cacheProvider = engineContext.Services.CacheProvider;
255 IList datasource = (IList) cacheProvider.Get(cacheKey);
257 if (datasource == null)
259 datasource = dataObtentionCallback();
261 cacheProvider.Store(cacheKey, datasource);
264 return CreatePagination(engineContext, datasource, pageSize);
267 #endregion
269 #region CreateCustomPage
271 /// <summary>
272 /// Creates a <see cref="Page"/> which is a sliced view of
273 /// the data source
274 /// <para>
275 /// Assumes that the slicing is managed by the caller.
276 /// </para>
277 /// </summary>
278 /// <param name="engineContext">The engine context.</param>
279 /// <param name="datasource">Data source to be used as target of the pagination</param>
280 /// <param name="pageSize">Page size</param>
281 /// <param name="total">The total of items in the datasource</param>
282 /// <returns>A <see cref="Page"/> instance</returns>
283 public static IPaginatedPage CreateCustomPage(IEngineContext engineContext, IList datasource, int pageSize, int total)
285 return CreateCustomPage(datasource, pageSize, GetCurrentPageFromRequest(engineContext), total);
288 /// <summary>
289 /// Creates a <see cref="Page"/> which is a sliced view of
290 /// the data source
291 /// <para>
292 /// Assumes that the slicing is managed by the caller.
293 /// </para>
294 /// </summary>
295 /// <param name="datasource">Data source to be used as target of the pagination</param>
296 /// <param name="pageSize">Page size</param>
297 /// <param name="total">The total of items in the datasource</param>
298 /// <param name="currentPage">The current page index (1 based).</param>
299 /// <returns></returns>
300 public static IPaginatedPage CreateCustomPage(IList datasource, int pageSize, int currentPage, int total)
302 if (currentPage <= 0) currentPage = 1;
304 return new Page(datasource, currentPage, pageSize, total);
307 /// <summary>
308 /// Creates a <see cref="Page"/> which is a sliced view of
309 /// the data source
310 /// <para>
311 /// Assumes that the slicing is managed by the caller.
312 /// </para>
313 /// </summary>
314 /// <param name="datasource">Data source to be used as target of the pagination</param>
315 /// <param name="pageSize">Page size</param>
316 /// <param name="total">The total of items in the datasource</param>
317 /// <param name="currentPage">The current page index (1 based).</param>
318 /// <returns></returns>
319 public static IPaginatedPage CreateCustomPage<T>(IEnumerable<T> datasource, int pageSize, int currentPage, int total)
321 if (currentPage <= 0) currentPage = 1;
323 return new GenericCustomPage<T>(datasource, currentPage, pageSize, total);
326 #endregion
328 private static int GetCurrentPageFromRequest(IEngineContext engineContext)
330 String currentPage = GetParameter(engineContext, PageParameterName);
332 int curPage = 1;
334 if (currentPage != null && currentPage != String.Empty)
336 curPage = Int32.Parse(currentPage);
339 return curPage <= 0 ? 1 : curPage;
342 private static string GetParameter(IEngineContext engineContext, string parameterName)
344 return engineContext.Request.Params[parameterName];
348 /// <summary>
349 /// Represents the sliced data and offers
350 /// a few read only properties to create a pagination bar.
351 /// </summary>
352 [Serializable]
353 public class Page : AbstractPage
355 private readonly IList slice = new ArrayList();
356 private readonly int startIndex;
357 private readonly int endIndex;
359 /// <summary>
360 /// Initializes a new instance of the <see cref="Page"/> class.
361 /// </summary>
362 /// <param name="curPage">The desired page index</param>
363 /// <param name="pageSize">The desired page size</param>
364 /// <param name="total">The total of items in the data source.</param>
365 protected Page(int curPage, int pageSize, int total)
367 startIndex = (pageSize * curPage) - pageSize;
368 endIndex = Math.Min(startIndex + pageSize, total);
370 CalculatePaginationInfo(startIndex, endIndex, total, pageSize, curPage);
373 /// <summary>
374 /// Constructs a Page using the specified parameters
375 /// </summary>
376 /// <param name="list">The whole set</param>
377 /// <param name="curPage">The desired page index</param>
378 /// <param name="pageSize">The desired page size</param>
379 public Page(IList list, int curPage, int pageSize) : this(curPage, pageSize, list.Count)
381 CreateSlicedCollection(startIndex, endIndex, list);
384 /// <summary>
385 /// Initializes a new instance of the <see cref="Page"/> class.
386 /// </summary>
387 /// <param name="slice">The sliced list.</param>
388 /// <param name="curPage">The desired page index</param>
389 /// <param name="pageSize">The desired page size</param>
390 /// <param name="total">The total of items (not in the list, but on the original source).</param>
391 public Page(IList slice, int curPage, int pageSize, int total) : this(curPage, pageSize, total)
393 this.slice = slice;
396 /// <summary>
397 /// Populates the sliced view of the whole set
398 /// </summary>
399 /// <param name="startIndex">Index to start to</param>
400 /// <param name="endIndex">Last index</param>
401 /// <param name="list">Source set</param>
402 private void CreateSlicedCollection(int startIndex, int endIndex, IList list)
404 for(int index = startIndex; index < endIndex; index++)
406 slice.Add(list[index]);
410 /// <summary>
411 /// Creates an enumerator for the
412 /// sliced set
413 /// </summary>
414 /// <returns>An enumerator instance</returns>
415 public override IEnumerator GetEnumerator()
417 return slice.GetEnumerator();
421 /// <summary>
422 /// Represents the sliced data and offers
423 /// a few read only properties to create a pagination bar.
424 /// </summary>
425 [Serializable]
426 public class GenericPage<T> : AbstractPage
428 private readonly int sliceStart, sliceEnd;
429 private readonly ICollection<T> sourceList;
431 /// <summary>
432 /// Initializes a new instance of the <see cref="GenericPage&lt;T&gt;"/> class.
433 /// </summary>
434 /// <param name="list">The list.</param>
435 /// <param name="curPage">The cur page.</param>
436 /// <param name="pageSize">Size of the page.</param>
437 public GenericPage(ICollection<T> list, int curPage, int pageSize)
439 // Calculate slice indexes
440 int startIndex = sliceStart = (pageSize * curPage) - pageSize;
441 int endIndex = sliceEnd = Math.Min(startIndex + pageSize, list.Count);
443 sourceList = list;
445 CalculatePaginationInfo(startIndex, endIndex, list.Count, pageSize, curPage);
448 /// <summary>
449 /// Returns a enumerator for the contents
450 /// of this page only (not the whole set)
451 /// </summary>
452 /// <returns>Enumerator instance</returns>
453 public override IEnumerator GetEnumerator()
455 if (sourceList is IList<T>)
457 IList<T> list = (IList<T>) sourceList;
458 for(int i = sliceStart; i < sliceEnd; i++)
460 yield return list[i];
463 else if (sourceList is IList)
465 IList list = (IList) sourceList;
466 for(int i = sliceStart; i < sliceEnd; i++)
468 yield return list[i];
471 else
473 IEnumerator en = sourceList.GetEnumerator();
474 for(int i = 0; i < sliceEnd; i++)
476 if (!en.MoveNext())
477 yield break;
479 if (i < sliceStart)
480 continue;
482 yield return en.Current;
488 /// <summary>
489 /// Represents the sliced data and offers
490 /// a few read only properties to create a pagination bar.
491 /// </summary>
492 [Serializable]
493 public class GenericCustomPage<T> : AbstractPage, IEnumerable<T>
495 private readonly IEnumerable<T> sourceList;
497 /// <summary>
498 /// Initializes a new instance of the <see cref="GenericCustomPage&lt;T&gt;"/> class.
499 /// </summary>
500 /// <param name="list">The list.</param>
501 /// <param name="curPage">The cur page.</param>
502 /// <param name="pageSize">Size of the page.</param>
503 /// <param name="total">The total.</param>
504 public GenericCustomPage(IEnumerable<T> list, int curPage, int pageSize, int total)
506 int startIndex = (pageSize * curPage) - pageSize;
507 int endIndex = Math.Min(startIndex + pageSize, total);
509 sourceList = list;
511 CalculatePaginationInfo(startIndex, endIndex, total, pageSize, curPage);
514 /// <summary>
515 /// Returns a enumerator for the contents
516 /// of this page only (not the whole set)
517 /// </summary>
518 /// <returns>Enumerator instance</returns>
519 public override IEnumerator GetEnumerator()
521 foreach(T item in sourceList)
523 yield return item;
527 /// <summary>
528 /// Returns an enumerator that iterates through the collection.
529 /// </summary>
530 /// <returns>
531 /// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
532 /// </returns>
533 IEnumerator<T> IEnumerable<T>.GetEnumerator()
535 foreach(T item in sourceList)
537 yield return item;
542 /// <summary>
543 /// Abstract implementation of <see cref="IPaginatedPage"/>
544 /// which performs the standard calculations on
545 /// <see cref="CalculatePaginationInfo"/>
546 /// </summary>
547 [Serializable]
548 public abstract class AbstractPage : IPaginatedPage
550 private int firstItem, lastItem, totalItems, pageSize;
551 private int previousIndex, nextIndex, lastIndex, curIndex;
552 private bool hasPrev, hasNext, hasFirst, hasLast;
554 /// <summary>
555 /// Calculate the values of all properties
556 /// based on the specified parameters
557 /// </summary>
558 /// <param name="startIndex">Start index</param>
559 /// <param name="endIndex">Last index</param>
560 /// <param name="count">Total of elements</param>
561 /// <param name="pageSize">Page size</param>
562 /// <param name="curPage">This page index</param>
563 protected void CalculatePaginationInfo(int startIndex, int endIndex,
564 int count, int pageSize, int curPage)
566 firstItem = count != 0 ? startIndex + 1 : 0;
567 lastItem = endIndex;
568 totalItems = count;
570 hasPrev = startIndex != 0;
571 hasNext = count == -1 || (startIndex + pageSize) < count;
572 hasFirst = curPage != 1;
573 hasLast = count > curPage * pageSize;
575 curIndex = curPage;
576 previousIndex = curPage - 1;
577 nextIndex = curPage + 1;
578 lastIndex = count == -1 ? -1 : count / pageSize;
580 this.pageSize = pageSize;
582 if (count != -1 && count / (float) pageSize > lastIndex)
584 lastIndex++;
588 /// <summary>
589 /// The first index
590 /// </summary>
591 public int FirstIndex
593 get { return 1; }
596 /// <summary>
597 /// The index this page represents
598 /// </summary>
599 public int CurrentIndex
601 get { return curIndex; }
604 /// <summary>
605 /// The last index available on the set
606 /// </summary>
607 public int LastIndex
609 get { return lastIndex; }
612 /// <summary>
613 /// The previous index (from this page)
614 /// </summary>
615 public int PreviousIndex
617 get { return previousIndex; }
620 /// <summary>
621 /// The next index (from this page)
622 /// </summary>
623 public int NextIndex
625 get { return nextIndex; }
628 /// <summary>
629 /// The first element (index + 1)
630 /// </summary>
631 public int FirstItem
633 get { return firstItem; }
636 /// <summary>
637 /// The last element in the page (count)
638 /// </summary>
639 public int LastItem
641 get { return lastItem; }
644 /// <summary>
645 /// The count of all elements on the set
646 /// </summary>
647 public int TotalItems
649 get { return totalItems; }
652 /// <summary>
653 /// Gets the size of the page.
654 /// </summary>
655 /// <value>The size of the page.</value>
656 public int PageSize
658 get { return pageSize; }
661 /// <summary>
662 /// Returns true if a previous page
663 /// is accessible from this page
664 /// </summary>
665 public bool HasPrevious
667 get { return hasPrev; }
670 /// <summary>
671 /// Returns true if a next page is
672 /// accessible from this page
673 /// </summary>
674 public bool HasNext
676 get { return hasNext; }
679 /// <summary>
680 /// Returns true if a first page
681 /// exists
682 /// </summary>
683 public bool HasFirst
685 get { return hasFirst; }
688 /// <summary>
689 /// Returns true if a last page
690 /// exists
691 /// </summary>
692 public bool HasLast
694 get { return hasLast; }
697 /// <summary>
698 /// Checks whether the specified page exists.
699 /// Useful for Google-like pagination.
700 /// </summary>
701 /// <param name="pageNumber">The page number</param>
702 public bool HasPage(int pageNumber)
704 return pageNumber <= LastIndex;
707 /// <summary>
708 /// Returns a enumerator for the contents
709 /// of this page only (not the whole set)
710 /// </summary>
711 /// <returns>Enumerator instance</returns>
712 public abstract IEnumerator GetEnumerator();