This post adds pagination capabilities to an AJAX Dynamic Component Presentation (DCP) presented in an earlier post Rendering Only a Partial for AJAX.
The use case is the following: we have a controller that displays a list of DCPs in a paginated way. Due to the dynamic nature of this list, the list of DCPs should be rendered as a partial using AJAX. This implies that an actual AJAX request is made to the controller, which triggers only the list of DCPs be recalculated and served back. The paginated AJAX URL produces HTML directly, therefore the Javascript logic can simply replace the container div 'innerHTML' with the markup received from pagination. A whole page reload is thus not necessary. Additionally the list of DCPs can be sorted before being paginated, but about that in a later post.
This post descries the server-side model builder that generated the paginated list of models. In a subsequent post I will present the server-side views that render the same and the client-side Javascript performing the AJAX handling.
In RouteConfig.cs, I modified the partial AJAX route, by adding the lines highlighted below. They basically accept a new URL path parameter 'page' of type integer:
An example URL for calling this partial paginated URL would be:
The URL above calls controller Device, action method Index on Component with id tcm:123-456 and uses view FullDetail to retrieve the DD4T strong model. The controller dispatches to view _PartialDocument.cshtml and displays page 2 of the list of documents that the controller built.
The models that make all this possible and their builders are as follows. We start with a very simple BaseBuilder class that holds a generic type 'ModelType' representing the actual model we build from. The class is abstract because it simply defines the signature of a Build() method to be implemented in one of the specific subclasses of it:
Next, we define base class AjaxBase, another generic that provides some common properties for AJAX calls, such as controller name, view, partialView, partialUrl:
Next, we define the PaginationBase class. In order not to reinvent the wheel, this class makes use of the package PagedList, which "makes it easier for .Net developers to write paging code", and "allows you to take any IEnumerable(T) and by specifying the page size and desired page index, select only a subset of that list". The class defines properties such as PageNumber and PageSize that will be used later on to determine which sub-set of the paged list to display. The class also helps us construct the paged partial URL that we call from client-side Javascript.
Next, the actual implementation of the paginated model is class DocumentPartial. This class defines the Build() method that creates the list of Document object and then paginates it. Notice the use of IsPostback property -- this implies the method Build is only invoked during a postback from AJAX, otherwise this model partial does not actually build the paginated list.
Finally, the calling code sits inside the controller. In our case, the Device controller, action Index has the following simplified logic. The DocumentPartial class is used to build the list of Document only if the partial variable is actually specified and has value '_PartialDocument'; otherwise, the DocumentPartial is created, but not built. The controller either dispatches directly to the _PartialDocument partial view with the corresponding paginated list of Document, or it attaches the empty DocumentPartial to the ViewBag for later processing during the Device view.
The mechanism above is quite flexible and can be used in many situations time and time again, by simply providing implementation classes for the abstract PaginationBase and wiring them up in the Component Controller.
The pagination, ajax, or base models are quite versatile and can be used together or individually depending on the requirement to display simple embedded lists of DCPs, or AJAX lists, or paginated AJAX lists, or any combination thereof.
In a follow-up post Pagination (Views), I tackled the server-side view logic.
The use case is the following: we have a controller that displays a list of DCPs in a paginated way. Due to the dynamic nature of this list, the list of DCPs should be rendered as a partial using AJAX. This implies that an actual AJAX request is made to the controller, which triggers only the list of DCPs be recalculated and served back. The paginated AJAX URL produces HTML directly, therefore the Javascript logic can simply replace the container div 'innerHTML' with the markup received from pagination. A whole page reload is thus not necessary. Additionally the list of DCPs can be sorted before being paginated, but about that in a later post.
This post descries the server-side model builder that generated the paginated list of models. In a subsequent post I will present the server-side views that render the same and the client-side Javascript performing the AJAX handling.
In RouteConfig.cs, I modified the partial AJAX route, by adding the lines highlighted below. They basically accept a new URL path parameter 'page' of type integer:
routes.MapRoute( name: "DCP", url: "{controller}/{action}/{publication}/{component}/{view}/{partial}/{page}.ajax", defaults: new { partial = UrlParameter.Optional, page = UrlParameter.Optional }, constraints: new { controller = @"\w{1,50}", action = @"\w{1,50}", publication = @"\d{1,3}", component = @"\d{1,6}", view = @"\w{1,50}", partial = @"_[\w\.]{1,50}", page = @"\d{0,3}" } );
An example URL for calling this partial paginated URL would be:
http://my.server.com/Device/Index/123/456/FullDetail/_PartialDocument/2.ajax
The URL above calls controller Device, action method Index on Component with id tcm:123-456 and uses view FullDetail to retrieve the DD4T strong model. The controller dispatches to view _PartialDocument.cshtml and displays page 2 of the list of documents that the controller built.
The models that make all this possible and their builders are as follows. We start with a very simple BaseBuilder class that holds a generic type 'ModelType' representing the actual model we build from. The class is abstract because it simply defines the signature of a Build() method to be implemented in one of the specific subclasses of it:
public abstract class BuilderBase<ModelType> { public ModelType Model { get; protected set; } public BuilderBase(ModelType model) { Model = model; } public abstract void Build(); }
Next, we define base class AjaxBase, another generic that provides some common properties for AJAX calls, such as controller name, view, partialView, partialUrl:
public abstract class AjaxBase<ModelType>
: BuilderBase<ModelType> where ModelType : ModelBase { public string Controller { get; private set; } public bool IsPostback { get; protected set; } public string View { get; private set; } public string PartialView { get; private set; } public string PartialUrl { get { Uri uri = HttpContext.Current.Request.Url; TcmUri modelId = new TcmUri(Model.Id); return string.Format("http://{0}{1}/{2}/Index/{3}/{4}/{5}/{6}", uri.Host, uri.Port == 80 ? string.Empty : string.Format(":{0}", uri.Port), Controller, modelId.PublicationId, modelId.ItemId, View, PartialView); } } public AjaxBase(ModelType model, string controller,
string view, string partialView) : base(model) { Controller = controller; View = view; PartialView = partialView; } }
Next, we define the PaginationBase class. In order not to reinvent the wheel, this class makes use of the package PagedList, which "makes it easier for .Net developers to write paging code", and "allows you to take any IEnumerable(T) and by specifying the page size and desired page index, select only a subset of that list". The class defines properties such as PageNumber and PageSize that will be used later on to determine which sub-set of the paged list to display. The class also helps us construct the paged partial URL that we call from client-side Javascript.
public abstract class PaginationBase<ModelType, PagedType> : AjaxBase<ModelType> where ModelType : ModelBase where PagedType : ModelBase { public IPagedList<PagedType> PagedItems { get; protected set; } public int PageNumber { get; set; } public int PageSize { get { return 10; } } // just an example public PaginationBase(ModelType model, string controller, string view, string partialView, int pageNumber) : base(model, controller, view, partialView) { PageNumber = Math.Max(1, pageNumber); } public string GetPagerUrl(int pageNumber) { return string.Format("{0}/{1}.ajax", PartialUrl, pageNumber); } protected void ToPagedList(IEnumerable<PagedType> items) { PagedItems = items.ToPagedList(PageNumber, PageSize); } }
Next, the actual implementation of the paginated model is class DocumentPartial. This class defines the Build() method that creates the list of Document object and then paginates it. Notice the use of IsPostback property -- this implies the method Build is only invoked during a postback from AJAX, otherwise this model partial does not actually build the paginated list.
public class DocumentPartial : PaginationBase<Device, Document> { public DocumentPartial(Device device, int pageNumber) : base(device, "Device", "Device", "_PartialDocument", pageNumber) { } public override void Build() { IsPostback = true; IEnumerable<Document> documents = // fetch the documents... ToPagedList(documents); } }
Finally, the calling code sits inside the controller. In our case, the Device controller, action Index has the following simplified logic. The DocumentPartial class is used to build the list of Document only if the partial variable is actually specified and has value '_PartialDocument'; otherwise, the DocumentPartial is created, but not built. The controller either dispatches directly to the _PartialDocument partial view with the corresponding paginated list of Document, or it attaches the empty DocumentPartial to the ViewBag for later processing during the Device view.
public class DeviceController { public ActionResult Index(int? publication, int? component, string view, string partial, int? page) { Device device; string viewName; ResolveModelAndView<Device>(publication, component, view, out device, out viewName); if (partial == null || partial == "_PartialDocument") { var documentPartial = new DocumentPartial(device, page ?? 1);
if (partial == null) { ViewBag.DocumentPartial = documentPartial; } else { documentPartial.Build(); return View(partial, documentPartial); } } return View(viewName, device); } }
The mechanism above is quite flexible and can be used in many situations time and time again, by simply providing implementation classes for the abstract PaginationBase and wiring them up in the Component Controller.
The pagination, ajax, or base models are quite versatile and can be used together or individually depending on the requirement to display simple embedded lists of DCPs, or AJAX lists, or paginated AJAX lists, or any combination thereof.
In a follow-up post Pagination (Views), I tackled the server-side view logic.
Comments