1 // Copyright 2004-2007 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
69 /// The parameter key that the helper looks for on the request
71 public const string PageParameterName
= "page";
73 #region CreatePageLink
76 /// Creates a link to navigate to a specific page
78 /// <param name="page">Page index</param>
79 /// <param name="text">Link text</param>
80 /// <returns>An anchor tag</returns>
81 public String
CreatePageLink(int page
, String text
)
83 return CreatePageLink(page
, text
, null, null);
87 /// Creates a link to navigate to a specific page
89 /// <param name="page">Page index</param>
90 /// <param name="text">Link text</param>
91 /// <param name="htmlAttributes">Attributes for the anchor tag</param>
92 /// <returns>An anchor tag</returns>
93 public String
CreatePageLink(int page
, String text
, IDictionary htmlAttributes
)
95 return CreatePageLink(page
, text
, htmlAttributes
, null);
99 /// Creates a link to navigate to a specific page
101 /// <param name="page">Page index</param>
102 /// <param name="text">Link text</param>
103 /// <param name="htmlAttributes">Attributes for the anchor tag</param>
104 /// <param name="queryStringParams">Query string entries for the link</param>
105 /// <returns>An anchor tag</returns>
106 public String
CreatePageLink(int page
, String text
, IDictionary htmlAttributes
, IDictionary queryStringParams
)
108 String filePath
= CurrentContext
.Request
.FilePath
;
110 if (queryStringParams
== null)
112 queryStringParams
= new Hashtable();
114 else if (queryStringParams
.IsReadOnly
|| queryStringParams
.IsFixedSize
)
116 queryStringParams
= new Hashtable(queryStringParams
);
119 queryStringParams
[PageParameterName
] = page
.ToString();
121 return String
.Format("<a href=\"{0}?{1}\" {2}>{3}</a>",
122 filePath
, BuildQueryString(queryStringParams
), GetAttributes(htmlAttributes
), text
);
126 /// Creates a link to navigate to a specific page
128 /// <param name="page">Page index</param>
129 /// <param name="text">Link text</param>
130 /// <param name="htmlAttributes">Attributes for the anchor tag</param>
131 /// <returns>An anchor tag</returns>
132 public String
CreatePageLinkWithCurrentQueryString(int page
, String text
, IDictionary htmlAttributes
)
134 NameValueCollection queryStringParams
= Controller
.Request
.QueryString
;
135 IDictionary dictionary
= null;
136 if (queryStringParams
!= null && queryStringParams
.Count
> 0)
138 dictionary
= new Hashtable(queryStringParams
.Count
);
139 foreach(string key
in queryStringParams
.Keys
)
143 dictionary
[key
] = queryStringParams
.GetValues(key
);
147 return CreatePageLink(page
, text
, htmlAttributes
, dictionary
);
152 #region CreatePagination
155 /// Creates a <see cref="Page"/> which is a sliced view of
158 /// <param name="controller">the current controller</param>
159 /// <param name="datasource">Data source to be used as target of the pagination</param>
160 /// <param name="pageSize">Page size</param>
161 /// <returns>A <see cref="Page"/> instance</returns>
162 public static IPaginatedPage
CreatePagination(Controller controller
, IList datasource
, int pageSize
)
164 return CreatePagination(datasource
, pageSize
, GetCurrentPageFromRequest(controller
));
168 /// Creates a <see cref="Page"/> which is a sliced view of
171 /// <param name="datasource">Data source to be used as target of the pagination</param>
172 /// <param name="pageSize">Page size</param>
173 /// <param name="currentPage">current page index (1 based)</param>
174 /// <returns>A <see cref="Page"/> instance</returns>
175 public static IPaginatedPage
CreatePagination(IList datasource
, int pageSize
, int currentPage
)
177 if (currentPage
<= 0) currentPage
= 1;
179 return new Page(datasource
, currentPage
, pageSize
);
184 #region CreatePagination<T>
187 /// Creates a <see cref="Page"/> which is a sliced view of
190 /// <param name="controller">the current controller</param>
191 /// <param name="datasource">Data source to be used as target of the pagination</param>
192 /// <param name="pageSize">Page size</param>
193 /// <returns>A <see cref="Page"/> instance</returns>
194 public static IPaginatedPage CreatePagination
<T
>(Controller controller
, ICollection
<T
> datasource
, int pageSize
)
196 return CreatePagination
<T
>(datasource
, pageSize
, GetCurrentPageFromRequest(controller
));
200 /// Creates a <see cref="Page"/> which is a sliced view of
203 /// <param name="datasource">Data source to be used as target of the pagination</param>
204 /// <param name="pageSize">Page size</param>
205 /// <param name="currentPage">current page index (1 based)</param>
206 /// <returns>A <see cref="Page"/> instance</returns>
207 public static IPaginatedPage CreatePagination
<T
>(ICollection
<T
> datasource
, int pageSize
, int currentPage
)
209 if (currentPage
<= 0) currentPage
= 1;
211 return new GenericPage
<T
>(datasource
, currentPage
, pageSize
);
216 #region CreateCachedPagination
219 /// Creates a <see cref="Page"/> which is a sliced view of
220 /// the data source. This method first looks for the datasource
221 /// in the <see cref="System.Web.Caching.Cache"/> and if not found,
222 /// it invokes the <c>dataObtentionCallback</c> and caches the result
223 /// using the specifed <c>cacheKey</c>
227 /// CreateCachedPagination is quite dangerous exactly because the cache is
228 /// shared. If the results vary per logged user, then the programmer must
229 /// rather pay a lot of attention when generating the cache key.
231 /// It is preferable to have caching happen at a lower level of the stack, for example the NHibernate query cache.
234 /// <param name="controller">the current controller</param>
235 /// <param name="cacheKey">Cache key used to query/store the datasource</param>
236 /// <param name="pageSize">Page size</param>
237 /// <param name="dataObtentionCallback">Callback to be used to populate the cache</param>
238 /// <returns>A <see cref="Page"/> instance</returns>
239 public static IPaginatedPage
CreateCachedPagination(Controller controller
, String cacheKey
, int pageSize
,
240 DataObtentionDelegate dataObtentionCallback
)
242 IList datasource
= (IList
) GetCache(controller
).Get(cacheKey
);
244 if (datasource
== null)
246 datasource
= dataObtentionCallback();
248 GetCache(controller
).Store(cacheKey
, datasource
);
251 return CreatePagination(controller
, datasource
, pageSize
);
256 #region CreateCustomPage
259 /// Creates a <see cref="Page"/> which is a sliced view of
262 /// Assumes that the slicing is managed by the caller.
265 /// <param name="controller">the current controller</param>
266 /// <param name="datasource">Data source to be used as target of the pagination</param>
267 /// <param name="pageSize">Page size</param>
268 /// <param name="total">The total of items in the datasource</param>
269 /// <returns>A <see cref="Page"/> instance</returns>
270 public static IPaginatedPage
CreateCustomPage(Controller controller
, IList datasource
, int pageSize
, int total
)
272 return CreateCustomPage(datasource
, pageSize
, GetCurrentPageFromRequest(controller
), total
);
276 /// Creates a <see cref="Page"/> which is a sliced view of
279 /// Assumes that the slicing is managed by the caller.
282 /// <param name="datasource">Data source to be used as target of the pagination</param>
283 /// <param name="pageSize">Page size</param>
284 /// <param name="total">The total of items in the datasource</param>
285 /// <param name="currentPage">The current page index (1 based).</param>
286 /// <returns></returns>
287 public static IPaginatedPage
CreateCustomPage(IList datasource
, int pageSize
, int currentPage
, int total
)
289 if (currentPage
<= 0) currentPage
= 1;
291 return new Page(datasource
, currentPage
, pageSize
, total
);
295 /// Creates a <see cref="Page"/> which is a sliced view of
298 /// Assumes that the slicing is managed by the caller.
301 /// <param name="datasource">Data source to be used as target of the pagination</param>
302 /// <param name="pageSize">Page size</param>
303 /// <param name="total">The total of items in the datasource</param>
304 /// <param name="currentPage">The current page index (1 based).</param>
305 /// <returns></returns>
306 public static IPaginatedPage CreateCustomPage
<T
>(IList
<T
> datasource
, int pageSize
, int currentPage
, int total
)
308 if (currentPage
<= 0) currentPage
= 1;
310 return new GenericCustomPage
<T
>(datasource
, currentPage
, pageSize
, total
);
315 private static ICacheProvider
GetCache(Controller controller
)
317 return controller
.Context
.Cache
;
320 private static int GetCurrentPageFromRequest(Controller controller
)
322 String currentPage
= GetParameter(controller
, PageParameterName
);
326 if (currentPage
!= null && currentPage
!= String
.Empty
)
328 curPage
= Int32
.Parse(currentPage
);
331 return curPage
<= 0 ? 1 : curPage
;
334 private static string GetParameter(Controller controller
, string parameterName
)
336 return controller
.Context
.Request
.Params
[parameterName
];
341 /// Represents the sliced data and offers
342 /// a few read only properties to create a pagination bar.
345 public class Page
: AbstractPage
347 private readonly IList slice
= new ArrayList();
348 private int startIndex
;
349 private int endIndex
;
352 /// Initializes a new instance of the <see cref="Page"/> class.
354 /// <param name="curPage">The desired page index</param>
355 /// <param name="pageSize">The desired page size</param>
356 /// <param name="total">The total of items in the data source.</param>
357 protected Page(int curPage
, int pageSize
, int total
)
359 startIndex
= (pageSize
* curPage
) - pageSize
;
360 endIndex
= Math
.Min(startIndex
+ pageSize
, total
);
362 CalculatePaginationInfo(startIndex
, endIndex
, total
, pageSize
, curPage
);
366 /// Constructs a Page using the specified parameters
368 /// <param name="list">The whole set</param>
369 /// <param name="curPage">The desired page index</param>
370 /// <param name="pageSize">The desired page size</param>
371 public Page(IList list
, int curPage
, int pageSize
) : this(curPage
, pageSize
, list
.Count
)
373 CreateSlicedCollection(startIndex
, endIndex
, list
);
377 /// Initializes a new instance of the <see cref="Page"/> class.
379 /// <param name="slice">The sliced list.</param>
380 /// <param name="curPage">The desired page index</param>
381 /// <param name="pageSize">The desired page size</param>
382 /// <param name="total">The total of items (not in the list, but on the original source).</param>
383 public Page(IList slice
, int curPage
, int pageSize
, int total
) : this(curPage
, pageSize
, total
)
389 /// Populates the sliced view of the whole set
391 /// <param name="startIndex">Index to start to</param>
392 /// <param name="endIndex">Last index</param>
393 /// <param name="list">Source set</param>
394 private void CreateSlicedCollection(int startIndex
, int endIndex
, IList list
)
396 for(int index
= startIndex
; index
< endIndex
; index
++)
398 slice
.Add(list
[index
]);
403 /// Creates an enumerator for the
406 /// <returns>An enumerator instance</returns>
407 public override IEnumerator
GetEnumerator()
409 return slice
.GetEnumerator();
414 /// Represents the sliced data and offers
415 /// a few read only properties to create a pagination bar.
418 public class GenericPage
<T
> : AbstractPage
420 private readonly int sliceStart
, sliceEnd
;
421 private readonly ICollection
<T
> sourceList
;
424 /// Initializes a new instance of the <see cref="GenericPage<T>"/> class.
426 /// <param name="list">The list.</param>
427 /// <param name="curPage">The cur page.</param>
428 /// <param name="pageSize">Size of the page.</param>
429 public GenericPage(ICollection
<T
> list
, int curPage
, int pageSize
)
431 // Calculate slice indexes
432 int startIndex
= sliceStart
= (pageSize
* curPage
) - pageSize
;
433 int endIndex
= sliceEnd
= Math
.Min(startIndex
+ pageSize
, list
.Count
);
437 CalculatePaginationInfo(startIndex
, endIndex
, list
.Count
, pageSize
, curPage
);
441 /// Returns a enumerator for the contents
442 /// of this page only (not the whole set)
444 /// <returns>Enumerator instance</returns>
445 public override IEnumerator
GetEnumerator()
447 if (sourceList
is IList
<T
>)
449 IList
<T
> list
= (IList
<T
>) sourceList
;
450 for(int i
= sliceStart
; i
< sliceEnd
; i
++)
452 yield return list
[i
];
455 else if (sourceList
is IList
)
457 IList list
= (IList
) sourceList
;
458 for(int i
= sliceStart
; i
< sliceEnd
; i
++)
460 yield return list
[i
];
465 IEnumerator en
= sourceList
.GetEnumerator();
466 for(int i
= 0; i
< sliceEnd
; i
++)
474 yield return en
.Current
;
481 /// Represents the sliced data and offers
482 /// a few read only properties to create a pagination bar.
485 public class GenericCustomPage
<T
> : AbstractPage
, IEnumerable
<T
>
487 private readonly IList
<T
> sourceList
;
490 /// Initializes a new instance of the <see cref="GenericCustomPage<T>"/> class.
492 /// <param name="list">The list.</param>
493 /// <param name="curPage">The cur page.</param>
494 /// <param name="pageSize">Size of the page.</param>
495 /// <param name="total">The total.</param>
496 public GenericCustomPage(IList
<T
> list
, int curPage
, int pageSize
, int total
)
498 int startIndex
= (pageSize
* curPage
) - pageSize
;
499 int endIndex
= Math
.Min(startIndex
+ pageSize
, total
);
503 CalculatePaginationInfo(startIndex
, endIndex
, total
, pageSize
, curPage
);
507 /// Returns a enumerator for the contents
508 /// of this page only (not the whole set)
510 /// <returns>Enumerator instance</returns>
511 public override IEnumerator
GetEnumerator()
513 for(int i
= 0; i
< sourceList
.Count
; i
++)
515 yield return sourceList
[i
];
520 /// Returns an enumerator that iterates through the collection.
523 /// A <see cref="T:System.Collections.Generic.IEnumerator`1"></see> that can be used to iterate through the collection.
525 IEnumerator
<T
> IEnumerable
<T
>.GetEnumerator()
527 for(int i
= 0; i
< sourceList
.Count
; i
++)
529 yield return sourceList
[i
];
535 /// Abstract implementation of <see cref="IPaginatedPage"/>
536 /// which performs the standard calculations on
537 /// <see cref="CalculatePaginationInfo"/>
540 public abstract class AbstractPage
: IPaginatedPage
542 private int firstItem
, lastItem
, totalItems
, pageSize
;
543 private int previousIndex
, nextIndex
, lastIndex
, curIndex
;
544 private bool hasPrev
, hasNext
, hasFirst
, hasLast
;
547 /// Calculate the values of all properties
548 /// based on the specified parameters
550 /// <param name="startIndex">Start index</param>
551 /// <param name="endIndex">Last index</param>
552 /// <param name="count">Total of elements</param>
553 /// <param name="pageSize">Page size</param>
554 /// <param name="curPage">This page index</param>
555 protected void CalculatePaginationInfo(int startIndex
, int endIndex
, int count
, int pageSize
, int curPage
)
557 firstItem
= count
!= 0 ? startIndex
+ 1 : 0;
561 hasPrev
= startIndex
!= 0;
562 hasNext
= count
== -1 || (startIndex
+ pageSize
) < count
;
563 hasFirst
= curPage
!= 1;
564 hasLast
= count
> curPage
* pageSize
;
567 previousIndex
= curPage
- 1;
568 nextIndex
= curPage
+ 1;
569 lastIndex
= count
== -1 ? -1 : count
/ pageSize
;
571 this.pageSize
= pageSize
;
573 if (count
!= -1 && count
/ (float) pageSize
> lastIndex
)
582 public int FirstIndex
588 /// The index this page represents
590 public int CurrentIndex
592 get { return curIndex; }
596 /// The last index available on the set
600 get { return lastIndex; }
604 /// The previous index (from this page)
606 public int PreviousIndex
608 get { return previousIndex; }
612 /// The next index (from this page)
616 get { return nextIndex; }
620 /// The first element (index + 1)
624 get { return firstItem; }
628 /// The last element in the page (count)
632 get { return lastItem; }
636 /// The count of all elements on the set
638 public int TotalItems
640 get { return totalItems; }
644 /// Gets the size of the page.
646 /// <value>The size of the page.</value>
649 get { return pageSize; }
653 /// Returns true if a previous page
654 /// is accessible from this page
656 public bool HasPrevious
658 get { return hasPrev; }
662 /// Returns true if a next page is
663 /// accessible from this page
667 get { return hasNext; }
671 /// Returns true if a first page
676 get { return hasFirst; }
680 /// Returns true if a last page
685 get { return hasLast; }
689 /// Checks whether the specified page exists.
690 /// Useful for Google-like pagination.
692 /// <param name="pageNumber">The page number</param>
693 public bool HasPage(int pageNumber
)
695 return pageNumber
<= LastIndex
;
699 /// Returns a enumerator for the contents
700 /// of this page only (not the whole set)
702 /// <returns>Enumerator instance</returns>
703 public abstract IEnumerator
GetEnumerator();