Как стать автором
Поиск
Написать публикацию
Обновить

Делаем свой Pager для ASP.NET Web Forms

Предыстория


Думаю, каждый кто хоть раз реализовал пэйджизацию на asp.net(не MVC) плевался и задавал простой вопрос. Неужели нельзя было сделать по-людски. К примеру, стандартный GridView генерирует ссылки вида
«javascript:__doPostBack('ctl00$ctl00$MainContent$LiveExample$GridView1','Page$2')»>. Думаю не нужно объяснять, почему это плохо. В версии 3.5 появился DataPager, в котором можно передать номер страницы через GET запрос. Но всеобщие счастье опять не настало Необходимо, чтобы контрол реализовал интерфейс IPageableItemContainer, а его реализует только ListView. Это как я понял новый сверхмощный контрол на замену DataList GridView. Соответственно если нужно использовать новый пэйджер со старыми контролами нужно реализовать интерфейс. Как это сделать можно прочитать к примеру тут. Основной минус данного подхода — при связывании данных будет извлекаться вся(если не использовать LINQ DataSource) коллекция и исходя из этого строится грид и пэйджер. Обычно при постраничном отображении мы имеем коллекцию объектов для отображения на текущей странице, плюс мы знаем общее число страниц и текущую страницу. Эти предпосылки плюс не желание в 3-раз (все же 2 раза я это сделал) писать пэйджер под каждый конкретный грид послужило поводом сделать что-то более общее.

Требования


  • Идентификатор страницы при пейджизации должен передаваться в строке GET запроса
  • Контролу должен знать количество страниц для отображения
  • визуальные элементы отображения (ссылки следующая, предыдущая, первая, последняя и тд) должны быть шаблонными, что бы при использовании можно было легко править стил пэйджера под текущую страницу
  • естественно свойства для имени параметра идентифицирующего номер странице в строке запроса

Да и вообще модифицировать контрол нужно практически декларативно(разметкой и заданием свойств). Максимум что можно стерпеть так это связывание данных кодом.

Реализация


При реализации возник вопрос, что делать чистый серверный контрол или User Control. Был выбран второй. Почему? ну так фишка легла. Всегда можно сделать по-другому.
Если кто не в курсе как писать шаблонные user контролы, то это хорошо описано тут, а еще лучше тут.

Для начала нужно создать класс для шаблона.
public class PageContainer : Control, INamingContainer
{
  private int pageIndex;

  private string destinationURL;

  public string DestinationURL
  {
    get
    {
      return destinationURL;
    }
  }

  public int PageIndex
  {
    get
    {
      return pageIndex;
    }
  }

  public PageContainer(int index,string url)
  {
    pageIndex = index;
    destinationURL = url;
  }
}


* This source code was highlighted with Source Code Highlighter.



Далее создаем несколько шаблонов
private ITemplate currentPageTemplate; // текущая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate CurrentPageTemplate
  {
    get
    {
      return currentPageTemplate;
    }
    set
    {
      currentPageTemplate = value;
    }
  }

  private ITemplate nextPageTemplate; // следующая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate NextPageTemplate
  {
    get { return nextPageTemplate; }
    set { nextPageTemplate = value; }
  }

  private ITemplate prevPageTemplate; // предыдущая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate PrevPageTemplate
  {
    get { return prevPageTemplate; }
    set { prevPageTemplate = value; }
  }

  private ITemplate numericalPageTemplate; // шаблон для отображения номера страницы

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate NumericalPageTemplate
  {
    get { return numericalPageTemplate; }
    set { numericalPageTemplate = value; }
  }

  private ITemplate firstPageTemplate; // первая страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate FirstPageTemplate
  {
    get { return firstPageTemplate; }
    set { firstPageTemplate = value; }
  }

  private ITemplate lastPageTemplate; // последняя страница

  [TemplateContainer(typeof(PageContainer))]
  [PersistenceMode(PersistenceMode.InnerProperty)]
  public ITemplate LastPageTemplate
  {
    get { return lastPageTemplate; }
    set { lastPageTemplate = value; }
  }


* This source code was highlighted with Source Code Highlighter.



На странице отображатся будет в следующей последовательности
[PrevPageTemplate][FirstPageTemplate][NumericalPageTemplate][CurrentPageTemplate][NumericalPageTemplate][LastPageTemplate][NextPageTemplate]

Вся генерация происходить в методе
public override void DataBind()
  {

    if( CurrentPageIndex >0)
    {
      if(PrevPageTemplate!=null)
      {
        AddTemplateAsControl(PrevPageTemplate, new PageContainer(CurrentPageIndex-1,GetDestinationURL(CurrentPageIndex-1)));
      }
      if(FirstPageTemplate!=null)
      {
        AddTemplateAsControl(FirstPageTemplate, new PageContainer(0, GetDestinationURL(0)));
      }
    }
    for(int i=CurrentPageIndex-NumericRange;i<CurrentPageIndex; i++)
    {
      if(i>=0 && NumericalPageTemplate!=null)
      {
        AddTemplateAsControl(NumericalPageTemplate, new PageContainer(i,GetDestinationURL(i)));
      }
    }

    if(CurrentPageTemplate!=null)
    {
      AddTemplateAsControl(CurrentPageTemplate, new PageContainer(CurrentPageIndex, GetDestinationURL(CurrentPageIndex)));
    }
    for(int i=CurrentPageIndex+1;i<CurrentPageIndex+NumericRange; i++)
    {
      if(i<=TotalPageCount && NumericalPageTemplate!=null)
      {
        AddTemplateAsControl(NumericalPageTemplate, new PageContainer(i,GetDestinationURL(i)));
      }
    }
    if(CurrentPageIndex!=TotalPageCount )
    {

      if(LastPageTemplate!=null)
      {
        AddTemplateAsControl(LastPageTemplate, new PageContainer(TotalPageCount, GetDestinationURL(TotalPageCount)));
      }
      if(NextPageTemplate!=null)
      {
        AddTemplateAsControl(NextPageTemplate, new PageContainer(currentPageIndex+1, GetDestinationURL(currentPageIndex+1)));
      }

    }
    base.DataBind();
  }


* This source code was highlighted with Source Code Highlighter.



А теперь просто добавляем контрол на любую страницу


 <uc:pager ID="super_pager" TotalPageCount="10" NumericRange="3" QueryStringParameter="p" runat="server" >
      <NumericalPageTemplate>
          <a href='<%# Container.DestinationURL %>'><%# Container.PageIndex %></a>      
      </NumericalPageTemplate>   
      <CurrentPageTemplate>
        <span style="background-color:Red; color:Black;">
         current page <b> <%# Container.PageIndex %></b>
        </span>      
      </CurrentPageTemplate>
      <FirstPageTemplate>
        <a href='<%# Container.DestinationURL %>'><b>First</b></a>
      </FirstPageTemplate>
      <LastPageTemplate>
       <a href='<%# Container.DestinationURL %>'><b>Last</b></a>
      </LastPageTemplate>
      <NextPageTemplate>
       <a href='<%# Container.DestinationURL %>'>--></a>      
      </NextPageTemplate>
      <PrevPageTemplate>
       <a href='<%# Container.DestinationURL %>'><--</a>
      </PrevPageTemplate>    
    </uc:pager>

* This source code was highlighted with Source Code Highlighter.



Правя шаблон можно сделать пейджизацию digg or Habra style

Еще немного занудства
  • TotalPageCount количестьво страниц
  • NumericRange сколько страниц(слева и справа) будет отображатся от текущей в числомов шаблоне
  • QueryStringParameter параметр в котором хранится номер страницы


Исходный код можна скачать
тут

Заключение


Если вы используете URL Rewriting, то придется вместо свойства контейнера DestinationURL формировать ссылку самому. Если кто-то как выполнить обратную операцию для URL Rewriting, то будет весьма полезно.
К слову сказать по аналогии можно реализовать систему табов(разделов) на сайте. Фактически вместо номера страницы будет использоваться идентификатор таба. Можно разместить на странице несколько таких контролов и организовать «cross paging», а скорее cross tabing. Например первый контрол отвечает за бренд продукции второй за категорию или один за период дат (месяц года или день) другой за тэг
Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.