Quantcast
Channel: Yet Another Tridion Blog
Viewing all articles
Browse latest Browse all 215

A DD4T.net Implementation - Pagination (Models)

$
0
0
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:

    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:

publicabstractclassBuilderBase<ModelType>
{
public ModelType Model { get; protectedset; }

publicBuilderBase(ModelType model)
{
Model = model;
}

publicabstractvoidBuild();
}

Next, we define base class AjaxBase, another generic that provides some common properties for AJAX calls, such as controller name, view, partialView, partialUrl:

publicabstractclassAjaxBase<ModelType>
    : BuilderBase<ModelType> where ModelType : ModelBase
{
publicstring Controller { get; privateset; }
publicbool IsPostback { get; protectedset; }
publicstring View { get; privateset; }
publicstring PartialView { get; privateset; }
publicstring PartialUrl
{
get
{
Uri uri = HttpContext.Current.Request.Url;
TcmUri modelId = new TcmUri(Model.Id);

returnstring.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);
}
}

publicAjaxBase(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.

publicabstractclassPaginationBase<ModelType, PagedType> : AjaxBase<ModelType>
where ModelType : ModelBase
where PagedType : ModelBase
{
public IPagedList<PagedType> PagedItems { get; protectedset; }
publicint PageNumber { get; set; }
publicint PageSize { get { return10; } } // just an example

publicPaginationBase(ModelType model, string controller, string view,
string partialView, int pageNumber)
: base(model, controller, view, partialView)
{
PageNumber = Math.Max(1, pageNumber);
}

publicstringGetPagerUrl(int pageNumber)
{
returnstring.Format("{0}/{1}.ajax", PartialUrl, pageNumber);
}

protectedvoidToPagedList(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.

publicclassDocumentPartial : PaginationBase<Device, Document>
{
publicDocumentPartial(Device device, int pageNumber)
: base(device, "Device", "Device", "_PartialDocument", pageNumber)
{ }

publicoverridevoidBuild()
{
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.

publicclassDeviceController
{
public ActionResult Index(int? publication, int? component,
string view, stringpartial, 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();
returnView(partial, documentPartial);
}
}

returnView(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 later post, I will tackle the server-side view logic.



Viewing all articles
Browse latest Browse all 215

Trending Articles