1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
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
7 // http://www.apache.org/licenses/LICENSE-2.0
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
18 using System
.Collections
;
19 using System
.Collections
.Generic
;
20 using System
.Collections
.Specialized
;
23 /// Used as callback handler to obtain the items
26 public delegate IList
DataObtentionDelegate();
29 /// This helper allows you to easily paginate through a data source
30 /// -- anything that implements <see cref="IList"/>.
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.
38 /// You can use up to three approaches to pagination:
41 /// <list type="bullet">
43 /// <term>CreatePagination</term>
44 /// <description>Uses a whole data set and creates a <see cref="Page"/> with a slice of it. </description>
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
54 /// <term>CreateCustomPage</term>
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.
63 /// Performance wise, the best choice is the <see cref="CreateCustomPage(IList,int,int,int)"/>
66 public class PaginationHelper
: AbstractHelper
70 /// Initializes a new instance of the <see cref="PaginationHelper"/> class.
72 public PaginationHelper() { }
74 /// Initializes a new instance of the <see cref="PaginationHelper"/> class.
75 /// setting the Controller, Context and ControllerContext.
77 /// <param name="engineContext">The engine context.</param>
78 public PaginationHelper(IEngineContext engineContext
) : base(engineContext
) { }
82 /// The parameter key that the helper looks for on the request
84 public const string PageParameterName
= "page";
86 #region CreatePageLink
89 /// Creates a link to navigate to a specific page
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);
100 /// Creates a link to navigate to a specific page
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);
112 /// Creates a link to navigate to a specific page
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
);
139 /// Creates a link to navigate to a specific page
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
)
156 dictionary
[key
] = queryStringParams
.GetValues(key
);
160 return CreatePageLink(page
, text
, htmlAttributes
, dictionary
);
165 #region CreatePagination
168 /// Creates a <see cref="Page"/> which is a sliced view of
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
));
181 /// Creates a <see cref="Page"/> which is a sliced view of
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
);
197 #region CreatePagination<T>
200 /// Creates a <see cref="Page"/> which is a sliced view of
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
));
214 /// Creates a <see cref="Page"/> which is a sliced view of
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
);
231 #region CreateCachedPagination
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>
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>
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.
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
);
269 #region CreateCustomPage
272 /// Creates a <see cref="Page"/> which is a sliced view of
275 /// Assumes that the slicing is managed by the caller.
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
);
289 /// Creates a <see cref="Page"/> which is a sliced view of
292 /// Assumes that the slicing is managed by the caller.
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
);
308 /// Creates a <see cref="Page"/> which is a sliced view of
311 /// Assumes that the slicing is managed by the caller.
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
);
328 private static int GetCurrentPageFromRequest(IEngineContext engineContext
)
330 String currentPage
= GetParameter(engineContext
, PageParameterName
);
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
];
349 /// Represents the sliced data and offers
350 /// a few read only properties to create a pagination bar.
353 public class Page
: AbstractPage
355 private readonly IList slice
= new ArrayList();
356 private readonly int startIndex
;
357 private readonly int endIndex
;
360 /// Initializes a new instance of the <see cref="Page"/> class.
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
);
374 /// Constructs a Page using the specified parameters
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
);
385 /// Initializes a new instance of the <see cref="Page"/> class.
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
)
397 /// Populates the sliced view of the whole set
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
]);
411 /// Creates an enumerator for the
414 /// <returns>An enumerator instance</returns>
415 public override IEnumerator
GetEnumerator()
417 return slice
.GetEnumerator();
422 /// Represents the sliced data and offers
423 /// a few read only properties to create a pagination bar.
426 public class GenericPage
<T
> : AbstractPage
428 private readonly int sliceStart
, sliceEnd
;
429 private readonly ICollection
<T
> sourceList
;
432 /// Initializes a new instance of the <see cref="GenericPage<T>"/> class.
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
);
445 CalculatePaginationInfo(startIndex
, endIndex
, list
.Count
, pageSize
, curPage
);
449 /// Returns a enumerator for the contents
450 /// of this page only (not the whole set)
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
];
473 IEnumerator en
= sourceList
.GetEnumerator();
474 for(int i
= 0; i
< sliceEnd
; i
++)
482 yield return en
.Current
;
489 /// Represents the sliced data and offers
490 /// a few read only properties to create a pagination bar.
493 public class GenericCustomPage
<T
> : AbstractPage
, IEnumerable
<T
>
495 private readonly IEnumerable
<T
> sourceList
;
498 /// Initializes a new instance of the <see cref="GenericCustomPage<T>"/> class.
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
);
511 CalculatePaginationInfo(startIndex
, endIndex
, total
, pageSize
, curPage
);
515 /// Returns a enumerator for the contents
516 /// of this page only (not the whole set)
518 /// <returns>Enumerator instance</returns>
519 public override IEnumerator
GetEnumerator()
521 foreach(T item
in sourceList
)
528 /// Returns an enumerator that iterates through the collection.
531 /// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
533 IEnumerator
<T
> IEnumerable
<T
>.GetEnumerator()
535 foreach(T item
in sourceList
)
543 /// Abstract implementation of <see cref="IPaginatedPage"/>
544 /// which performs the standard calculations on
545 /// <see cref="CalculatePaginationInfo"/>
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
;
555 /// Calculate the values of all properties
556 /// based on the specified parameters
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;
570 hasPrev
= startIndex
!= 0;
571 hasNext
= count
== -1 || (startIndex
+ pageSize
) < count
;
572 hasFirst
= curPage
!= 1;
573 hasLast
= count
> curPage
* pageSize
;
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
)
591 public int FirstIndex
597 /// The index this page represents
599 public int CurrentIndex
601 get { return curIndex; }
605 /// The last index available on the set
609 get { return lastIndex; }
613 /// The previous index (from this page)
615 public int PreviousIndex
617 get { return previousIndex; }
621 /// The next index (from this page)
625 get { return nextIndex; }
629 /// The first element (index + 1)
633 get { return firstItem; }
637 /// The last element in the page (count)
641 get { return lastItem; }
645 /// The count of all elements on the set
647 public int TotalItems
649 get { return totalItems; }
653 /// Gets the size of the page.
655 /// <value>The size of the page.</value>
658 get { return pageSize; }
662 /// Returns true if a previous page
663 /// is accessible from this page
665 public bool HasPrevious
667 get { return hasPrev; }
671 /// Returns true if a next page is
672 /// accessible from this page
676 get { return hasNext; }
680 /// Returns true if a first page
685 get { return hasFirst; }
689 /// Returns true if a last page
694 get { return hasLast; }
698 /// Checks whether the specified page exists.
699 /// Useful for Google-like pagination.
701 /// <param name="pageNumber">The page number</param>
702 public bool HasPage(int pageNumber
)
704 return pageNumber
<= LastIndex
;
708 /// Returns a enumerator for the contents
709 /// of this page only (not the whole set)
711 /// <returns>Enumerator instance</returns>
712 public abstract IEnumerator
GetEnumerator();