Making existing paginiation implementations a bit more consistent.

For Blog (posts) and Search.

--HG--
branch : dev
This commit is contained in:
Nathan Heskew
2010-10-31 23:54:37 -07:00
parent dba8ea4b92
commit d34cae6ac4
20 changed files with 148 additions and 156 deletions

View File

@@ -370,6 +370,7 @@
<Content Include="Routable\Views\Routable.HomePage.cshtml" />
<Content Include="Localization\Views\Parts\Localization.ContentTranslations.SummaryAdmin.cshtml" />
<Content Include="Contents\Views\Items\Content.Summary.cshtml" />
<Content Include="Shapes\Views\Pager.cshtml" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />

View File

@@ -0,0 +1,39 @@
@{
var nextText = HasText(Model.NextText) ? Model.NextText : T("Older").Text;
var previousText = HasText(Model.PreviousText) ? Model.PreviousText : T("Newer").Text;
var routeData = new RouteValueDictionary(ViewContext.RouteData.Values);
var queryString = ViewContext.HttpContext.Request.QueryString;
if (queryString != null) {
foreach (string key in queryString.Keys) {
if (!routeData.ContainsKey(key)) {
var value = queryString[key];
routeData[key] = queryString[key];
}
}
}
if (routeData.ContainsKey("id") && !HasText(routeData["id"])) {
routeData.Remove("id");
}
Model.Classes.Add("pager");
Model.Classes.Add("group");
var tag = Tag(Model, "ul");
}
@if (Model.HasNextPage || Model.Page > 1) {
@tag.StartElement
if(Model.HasNextPage) {
routeData["page"] = Model.Page + 1;
<li class="older">
@Html.ActionLink((string)nextText, (string)routeData["action"], (string)routeData["controller"], routeData, null)
</li>
}
if(Model.Page > 1) {
routeData["page"] = Model.Page - 1;
<li class="newer">
@Html.ActionLink((string)previousText, (string)routeData["action"], (string)routeData["controller"], routeData, null)
</li>
}
@tag.EndElement
}

View File

@@ -140,6 +140,12 @@ namespace Orchard.Blogs.Controllers {
if (blogPart == null)
return HttpNotFound();
//() => {
// var list = shapeHelper.List();
// list.AddRange(_blogPostService.Get(part, VersionOptions.Latest)
// .Select(bp => _contentManager.BuildDisplay(bp, "SummaryAdmin")));
// return shapeHelper.Parts_Blogs_BlogPost_List_Admin(ContentPart: part, ContentItems: list);
//})
var model = Services.ContentManager.BuildDisplay(blogPart, "DetailAdmin");
return View(model);

View File

@@ -3,13 +3,15 @@ using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Xml.Linq;
using Orchard.Blogs.Extensions;
using Orchard.Blogs.Models;
using Orchard.Blogs.Routing;
using Orchard.Blogs.Services;
using Orchard.ContentManagement;
using Orchard.Core.Feeds;
using Orchard.DisplayManagement;
using Orchard.Logging;
using Orchard.Themes;
using Orchard.UI.Navigation;
namespace Orchard.Blogs.Controllers {
[Themed]
@@ -18,6 +20,7 @@ namespace Orchard.Blogs.Controllers {
private readonly IBlogService _blogService;
private readonly IBlogPostService _blogPostService;
private readonly IBlogSlugConstraint _blogSlugConstraint;
private readonly IFeedManager _feedManager;
private readonly RouteCollection _routeCollection;
public BlogController(
@@ -25,12 +28,14 @@ namespace Orchard.Blogs.Controllers {
IBlogService blogService,
IBlogPostService blogPostService,
IBlogSlugConstraint blogSlugConstraint,
IFeedManager feedManager,
RouteCollection routeCollection,
IShapeFactory shapeFactory) {
_services = services;
_blogService = blogService;
_blogPostService = blogPostService;
_blogSlugConstraint = blogSlugConstraint;
_feedManager = feedManager;
_routeCollection = routeCollection;
Logger = NullLogger.Instance;
Shape = shapeFactory;
@@ -51,10 +56,7 @@ namespace Orchard.Blogs.Controllers {
return View(viewModel);
}
//TODO: (erikpo) Should move the slug parameter and get call and null check up into a model binder
public ActionResult Item(string blogSlug, int page) {
const int pageSize = 10;
public ActionResult Item(string blogSlug, Pager pager) {
var correctedSlug = _blogSlugConstraint.FindSlug(blogSlug);
if (correctedSlug == null)
return HttpNotFound();
@@ -63,21 +65,18 @@ namespace Orchard.Blogs.Controllers {
if (blogPart == null)
return HttpNotFound();
var blogPosts = _blogPostService.Get(blogPart, (page - 1) * pageSize, pageSize)
_feedManager.Register(blogPart);
var blogPosts = _blogPostService.Get(blogPart, pager.GetStartIndex(), pager.PageSize)
.Select(b => _services.ContentManager.BuildDisplay(b, "Summary"));
blogPart.As<BlogPagerPart>().Page = page;
blogPart.As<BlogPagerPart>().PageSize = pageSize;
blogPart.As<BlogPagerPart>().BlogSlug = correctedSlug;
blogPart.As<BlogPagerPart>().ThereIsANextPage = _blogPostService.Get(blogPart, (page) * pageSize, pageSize).Any();
var blog = _services.ContentManager.BuildDisplay(blogPart);
var list = Shape.List();
list.AddRange(blogPosts);
blog.Content.Add(Shape.Parts_Blogs_BlogPost_List(ContentItems: list), "5");
var hasNextPage = _blogPostService.Get(blogPart, pager.GetStartIndex(pager.Page + 1), 1).Any();
blog.Content.Add(Shape.Pager(pager).HasNextPage(hasNextPage), "Content:after");
return View(blog);
}

View File

@@ -1,11 +0,0 @@
using Orchard.Blogs.Models;
using Orchard.ContentManagement.Drivers;
namespace Orchard.Blogs.Drivers {
public class BlogPagerPartDriver : ContentPartDriver<BlogPagerPart> {
protected override DriverResult Display(BlogPagerPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_Blogs_Blog_Pager",
() => shapeHelper.Parts_Blogs_Blog_Pager(ContentPart: part, Page: part.Page, PageSize: part.PageSize, BlogSlug: part.BlogSlug, ThereIsANextPage: part.ThereIsANextPage));
}
}
}

View File

@@ -41,24 +41,25 @@ namespace Orchard.Blogs.Drivers {
ContentShape("Parts_Blogs_Blog_Description",
() => shapeHelper.Parts_Blogs_Blog_Description(ContentPart: part, Description: part.Description)),
ContentShape("Parts_Blogs_Blog_BlogPostCount",
() => shapeHelper.Parts_Blogs_Blog_BlogPostCount(ContentPart: part, PostCount: part.PostCount)),
() => shapeHelper.Parts_Blogs_Blog_BlogPostCount(ContentPart: part, PostCount: part.PostCount))
//,
// todo: (heskew) implement a paging solution that doesn't require blog posts to be tied to the blog within the controller
ContentShape("Parts_Blogs_BlogPost_List",
() => {
_feedManager.Register(part);
return null;
//ContentShape("Parts_Blogs_BlogPost_List",
// () => {
// _feedManager.Register(part);
// var list = shapeHelper.List();
// list.AddRange(_blogPostService.Get(part)
// .Select(bp => _contentManager.BuildDisplay(bp, "Summary")));
// return shapeHelper.Parts_Blogs_BlogPost_List(ContentPart: part, ContentItems: list);
}),
ContentShape("Parts_Blogs_BlogPost_List_Admin",
() => {
var list = shapeHelper.List();
list.AddRange(_blogPostService.Get(part, VersionOptions.Latest)
.Select(bp => _contentManager.BuildDisplay(bp, "SummaryAdmin")));
return shapeHelper.Parts_Blogs_BlogPost_List_Admin(ContentPart: part, ContentItems: list);
})
// }),
//ContentShape("Parts_Blogs_BlogPost_List_Admin",
// () =>
// {
// var list = shapeHelper.List();
// list.AddRange(_blogPostService.Get(part, VersionOptions.Latest)
// .Select(bp => _contentManager.BuildDisplay(bp, "SummaryAdmin")));
// return shapeHelper.Parts_Blogs_BlogPost_List_Admin(ContentPart: part, ContentItems: list);
// })
);
}

View File

@@ -1,5 +1,4 @@
using System.Data;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData;
using Orchard.Data.Migration;
namespace Orchard.Blogs {
@@ -40,7 +39,6 @@ namespace Orchard.Blogs {
.WithPart("BlogPart")
.WithPart("CommonPart")
.WithPart("RoutePart")
.WithPart("BlogPagerPart")
);
ContentDefinitionManager.AlterTypeDefinition("BlogPost",

View File

@@ -1,10 +0,0 @@
using Orchard.ContentManagement;
namespace Orchard.Blogs.Models {
public class BlogPagerPart : ContentPart {
public int Page { get; set; }
public int PageSize { get; set; }
public string BlogSlug { get; set; }
public bool ThereIsANextPage { get; set; }
}
}

View File

@@ -68,14 +68,12 @@
<ItemGroup>
<Compile Include="AdminMenu.cs" />
<Compile Include="Drivers\BlogArchivesPartDriver.cs" />
<Compile Include="Drivers\BlogPagerPartDriver.cs" />
<Compile Include="Drivers\RemoteBlogPublishingDriver.cs" />
<Compile Include="Drivers\RecentBlogPostsPartDriver.cs" />
<Compile Include="Handlers\BlogArchivesPartHandler.cs" />
<Compile Include="Handlers\RecentBlogPostsPartHandler.cs" />
<Compile Include="Models\BlogArchivesPart.cs" />
<Compile Include="Models\BlogArchivesPartRecord.cs" />
<Compile Include="Models\BlogPagerPart.cs" />
<Compile Include="Models\RecentBlogPostsPart.cs" />
<Compile Include="Models\RecentBlogPostsPartRecord.cs" />
<Compile Include="ResourceManifest.cs" />
@@ -120,7 +118,6 @@
<Content Include="Scripts\archives.js" />
<Content Include="Styles\admin.css" />
<Content Include="Styles\archives.css" />
<Content Include="Styles\pagination.css" />
<Content Include="Views\BlogAdmin\Create.cshtml" />
<Content Include="Views\BlogAdmin\Edit.cshtml" />
<Content Include="Views\BlogAdmin\Item.cshtml" />
@@ -164,7 +161,6 @@
<Content Include="Views\Items\Content-Blog.Edit.cshtml" />
<Content Include="Views\Items\Content-BlogPost.Editor.cshtml" />
<Content Include="Views\Parts\Blogs.BlogPost.List.cshtml" />
<Content Include="Views\Parts\Blogs.Blog.Pager.cshtml" />
<Content Include="Views\Parts\Blogs.RecentBlogPosts.cshtml" />
<Content Include="Views\Parts\Blogs.Blog.BlogPostCount.cshtml" />
</ItemGroup>

View File

@@ -4,7 +4,6 @@
Parts_Blogs_Blog_Manage
Parts_Blogs_Blog_Description
Parts_Blogs_Blog_BlogPostCount
Parts_Blogs_Blog_Pager
Parts_Blogs_BlogPost_List -> when in the blog detail display the blog post list is currently hard-coded to Content:5 to enable the current state of blog paging
Parts_Blogs_BlogPost_List_Admin
-->
@@ -23,7 +22,6 @@
...placing it in in the Content zone as it's currently implemented to light up the RSS feed for the blog... -->
<!-- Parts_Blogs_RemotePublishing is made available with the "Remote Blog Publishing" feature -->
<Place Parts_Blogs_BlogPost_List="Content"
Parts_Blogs_Blog_Pager="Content:after"
Parts_Blogs_Blog_Description="Content:before"
Parts_Blogs_RemotePublishing="Content"/>
</Match>

View File

@@ -261,32 +261,13 @@ namespace Orchard.Blogs {
Priority = 11,
Route = new Route(
"{blogSlug}",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "Blog"},
{"action", "Item"},
{"page", 1}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary {
{"area", "Orchard.Blogs"}
},
new MvcRouteHandler())
},
new RouteDescriptor {
Priority = 11,
Route = new Route(
"{blogSlug}/Page/{*page}",
new RouteValueDictionary {
{"area", "Orchard.Blogs"},
{"controller", "Blog"},
{"action", "Item"}
},
new RouteValueDictionary {
{"blogSlug", _blogSlugConstraint},
{"page", @"^\d+$"}
{"blogSlug", _blogSlugConstraint}
},
new RouteValueDictionary {
{"area", "Orchard.Blogs"}

View File

@@ -1,19 +0,0 @@
ul.pagination
{
list-style-type:none;
}
ul.pagination li a
{
font-size:13px;
}
ul.pagination li.newer
{
float:left;
}
ul.pagination li.older
{
float:right;
}

View File

@@ -1,18 +0,0 @@
@using Orchard.Blogs.Extensions;
@{
Style.Include("pagination.css");
}
@if (Model.ThereIsANextPage || Model.Page > 1) {
<ul class="blog-pagination group">
@if(Model.ThereIsANextPage) {
<li class="older">
@Html.ActionLink(T("Older Posts").Text, "Item", new { Area = "Orchard.Blogs", blogSlug = Model.BlogSlug, page = Model.Page + 1 })
</li>
}
@if(Model.Page > 1) {
<li class="newer">
@Html.ActionLink(T("Newer Posts").Text, "Item", new { Area = "Orchard.Blogs", blogSlug = Model.BlogSlug, page = Model.Page - 1 })
</li>
}
</ul>
}

View File

@@ -8,5 +8,5 @@ Features:
Orchard.ContentQueries:
Name: Queried Content Lists
Description: Use simple queries to create orderd lists of content items with optional paging support.
Dependencies: Contents
Dependencies: Contents, Common
Category: Content

View File

@@ -1,13 +1,15 @@
using System.Web.Mvc;
using System.Web.Query.Dynamic;
using System.Linq;
using System.Web.Mvc;
using JetBrains.Annotations;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.Indexing;
using Orchard.Localization;
using Orchard.Search.Services;
using Orchard.Search.ViewModels;
using Orchard.Settings;
using Orchard.Search.Models;
using Orchard.UI.Navigation;
using Orchard.UI.Notify;
using System.Collections.Generic;
using Orchard.Collections;
@@ -22,21 +24,23 @@ namespace Orchard.Search.Controllers {
public SearchController(
IOrchardServices services,
ISearchService searchService,
IContentManager contentManager) {
IContentManager contentManager,
IShapeFactory shapeFactory) {
Services = services;
_searchService = searchService;
_contentManager = contentManager;
T = NullLocalizer.Instance;
Shape = shapeFactory;
}
private IOrchardServices Services { get; set; }
public Localizer T { get; set; }
dynamic Shape { get; set; }
protected virtual ISite CurrentSite { get; [UsedImplicitly] private set; }
public ActionResult Index(string q, int page = 1, int pageSize = 10) {
public ActionResult Index(string q, Pager pager) {
var searchFields = CurrentSite.As<SearchSettingsPart>().SearchedFields;
IPageOfItems<ISearchHit> searchHits;
@@ -46,38 +50,33 @@ namespace Orchard.Search.Controllers {
Services.Notifier.Error(T("'*' or '?' not allowed as first character in WildcardQuery"));
}
else {
searchHits = _searchService.Query(q, page, pageSize,
searchHits = _searchService.Query(q, pager.Page, pager.PageSize,
CurrentSite.As<SearchSettingsPart>().Record.FilterCulture,
searchFields,
searchHit => searchHit);
}
var searchResultViewModels = new List<SearchResultViewModel>();
foreach(var searchHit in searchHits) {
var contentItem = _contentManager.Get(searchHit.ContentItemId);
var list = Shape.List();
foreach (var contentItem in searchHits.Select(searchHit => _contentManager.Get(searchHit.ContentItemId))) {
// ignore search results which content item has been removed or unpublished
if(contentItem == null){
searchHits.TotalItemCount--;
continue;
}
searchResultViewModels.Add(new SearchResultViewModel {
Content = _contentManager.BuildDisplay(contentItem, "SummaryForSearch"),
SearchHit = searchHit
});
list.Add(_contentManager.BuildDisplay(contentItem, "Summary"));
}
var pageOfItems = new PageOfItems<SearchResultViewModel>(searchResultViewModels) {
PageNumber = page,
PageSize = searchHits.PageSize,
TotalItemCount = searchHits.TotalItemCount
};
var hasNextPage = searchHits.TotalPageCount > pager.Page;
var pagerShape = Shape.Pager(pager).HasNextPage(hasNextPage);
var searchViewModel = new SearchViewModel {
Query = q,
DefaultPageSize = 10, // TODO: sebastien <- yeah, I know :|
PageOfResults = pageOfItems
TotalItemCount = searchHits.TotalItemCount,
StartPosition = (pager.Page - 1) * pager.PageSize + 1,
EndPosition = pager.Page * pager.PageSize > searchHits.TotalItemCount ? searchHits.TotalItemCount : pager.Page * pager.PageSize,
ContentItems = list,
Pager = pagerShape
};
//todo: deal with page requests beyond result count

View File

@@ -1,9 +1,10 @@
using Orchard.Collections;
namespace Orchard.Search.ViewModels {
namespace Orchard.Search.ViewModels {
public class SearchViewModel {
public string Query { get; set; }
public int DefaultPageSize { get; set; }
public IPageOfItems<SearchResultViewModel> PageOfResults { get; set; }
public int TotalItemCount { get; set; }
public int StartPosition { get; set; }
public int EndPosition { get; set; }
public dynamic ContentItems { get; set; }
public dynamic Pager { get; set; }
}
}

View File

@@ -1,23 +1,26 @@
@model Orchard.Search.ViewModels.SearchViewModel
@{ Style.Require("Search"); }
@{
Style.Require("Search");
IEnumerable<object> searchResults = Model.ContentItems;
Model.ContentItems.Classes.Add("content-items");
Model.ContentItems.Classes.Add("search-results");
}
<h1>@Html.TitleForPage(T("Search").Text)</h1>
@using(Html.BeginForm("index", "search", new { area = "Orchard.Search" }, FormMethod.Get, new { @class = "search" })) {
@using(Html.BeginForm("index", "search", new { area = "Orchard.Search" }, FormMethod.Get, new { @class = "search group" })) {
<fieldset>
@Html.TextBox("q", Model.Query)
<button type="submit">@T("Search")</button>
</fieldset>
}
@if (!string.IsNullOrWhiteSpace(Model.Query)) {
if (Model.PageOfResults.Count() == 0) {
<p class="search-summary">@T.Plural("the <em>one</em> result", "<em>zero</em> results", Model.PageOfResults.Count())</p>
@if (HasText(Model.Query)) {
if (searchResults.Count() == 0) {
<p class="search-summary">@T.Plural("the <em>one</em> result", "<em>zero</em> results", searchResults.Count())</p>
} else {
<p class="search-summary">@T.Plural("the <em>one</em> result", "<em>{1} - {2}</em> of <em>{0}</em> results", Model.PageOfResults.TotalItemCount, Model.PageOfResults.StartPosition, Model.PageOfResults.EndPosition)</p>
<p class="search-summary">@T.Plural("the <em>one</em> result", "<em>{1} - {2}</em> of <em>{0}</em> results", Model.TotalItemCount, Model.StartPosition, Model.EndPosition)</p>
}
}
@if (Model.PageOfResults != null && Model.PageOfResults.Count() > 0) {
@Html.UnorderedList(Model.PageOfResults.Where(hit => hit.Content != null), (r, i) => Display(r.Content), "search-results contentItems")
@Html.Pager(Model.PageOfResults, Model.PageOfResults.PageNumber, Model.DefaultPageSize, new {q = Model.Query})
@if (searchResults != null && searchResults.Count() > 0) {
@Display(searchResults)
@Display(Model.Pager)
}

View File

@@ -343,11 +343,6 @@ nav ul
.blog-post-title {}
.meta {}
.blog-pagination { list-style: none; padding: 0; margin: 12px 0 0 0; }
.blog-pagination li { float: left; padding: 0 12px 0 0; margin: 0; }
.blog-pagination a { font-size: 1.077em; display: block; background-color: #dbdbdb; padding: 6px 6px; color: #434343;}
.blog-pagination a:hover { background-color: #434343; color: #fff; }
/* Comments */
#comments { margin: 24px 0 0 0; padding: 0; }
.comment-form { margin: 24px 0 0 0; padding: 0; }
@@ -491,6 +486,16 @@ nav ul
/* Pager
***************************************************************/
.pager { list-style: none; padding: 0; margin: 12px 0 0 0; }
.pager li { float: left; padding: 0 12px 0 0; margin: 0; }
.pager a { font-size: 1.077em; display: block; background-color: #dbdbdb; padding: 6px 6px; color: #434343;}
.pager a:hover { background-color: #434343; color: #fff; }
/* Misc
***************************************************************/

View File

@@ -186,6 +186,7 @@
<Compile Include="Settings\CurrentSiteWorkContext.cs" />
<Compile Include="Settings\ResourceDebugMode.cs" />
<Compile Include="UI\FlatPositionComparer.cs" />
<Compile Include="UI\Navigation\Pager.cs" />
<Compile Include="UI\Resources\IResourceManifestProvider.cs" />
<Compile Include="UI\Resources\ResourceManifestBuilder.cs" />
<Compile Include="UI\Widgets\IRuleManager.cs" />

View File

@@ -0,0 +1,22 @@
namespace Orchard.UI.Navigation {
public class Pager {
private const int PageDefault = 1;
private const int PageSizeDefault = 10;
private int _pageSize;
private int _size;
public int Page {
get { return _pageSize > 0 ? _pageSize : PageDefault; }
set { _pageSize = value; }
}
public int PageSize {
get { return _size > 0 ? _size : PageSizeDefault; }
set { _size = value; }
}
public int GetStartIndex(int? page = null) {
return ((page ?? Page) - 1)*PageSize;
}
}
}