65.9K
CodeProject 正在变化。 阅读更多。
Home

表单的客户端分页

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.69/5 (13投票s)

2005年1月19日

6分钟阅读

viewsIcon

116847

downloadIcon

4020

一种非常简单、跨浏览器客户端分页的方法。

再介绍

在本文的第一个版本中,我演示了如何使用客户端 HTML 表格来实现一个非常简单的客户端分页解决方案。我从一些人那里听说,他们指出了大量数据在性能方面存在问题。我同意。该解决方案最适合处理固定量的数据。它适用于通常有八到十二页,每页约有五“行”或嵌入表格的商业应用程序。

唯一的影响性能的仍然是页面加载。客户端处理非常快,即使在较慢的机器上。在 P-III 700 Mhz 和 256 MB RAM 的机器上,它的响应速度与我的 1.8 Ghz P-4 和 512 MB RAM 的机器一样好。

ASP.NET 的 DataGrid 以及第三方的商业扩展在很多方面都使开发者的生活变得更加轻松。但 DataGrid 是一种拐杖,虽然它可能让你能够行走,但它会阻碍你奔跑。有时,会出现一个项目,其技术和用户需求使得 DataGrid 既过于沉重又过于有限。正是这样一个项目迫使我暂时离开了拖放式开发世界,转而使用能够满足我所有客户需求的代码。

背景

我最近为一家知名公司的一个项目,需要显示相当多的相似数据集,涉及营销计划。基本上,用户需要查看他们的计划列表,并提供按钮让他们跳转到编辑页面,按钮可以在表单中内联显示上下文帮助,以及每个计划的几行数据。此外,客户要求尽量减少回发,并明确拒绝了我最初的回发以更改页面和基于其他下拉列表选择来更改下拉列表内容的计划。

我通过扩展 Al Alberto 的 Master Detail DDL 项目解决了前一个问题,该项目我在 Code Project 上找到了。但分页仍然是个问题。我尝试了各种使用 DataGridRepeater 的技术,但它们都太复杂了,需要比我所知道的解决方案所需的代码多几百行甚至上千行。在其中一些提供了关于分页的精彩见解的文章中,有 Andrew Merlino 的文章和代码

我意识到 HTML 表格可以像 div 层一样工作。通过设置表格的 style=”display:none” 属性,我可以在客户端隐藏该表格。我只需要为每个页面创建一个表格。这些表格包含一个带有单个单元格的标题行,以及一个带有单个单元格的内容行。在内容单元格中,我可以注入任何内容——在我演示的例子中,是一系列包含数据和格式信息的表格。剩下要做的就是创建一些动态 JavaScript 来处理客户端的页面更改请求。

我最初的文章比较朴素。在这个版本中,我专注于使这种方法更受欢迎的一些用户功能。

首先,我将数据库从 Northwind 切换到了 pubs。这使我能够使用更丰富、更详细的数据。为了使应用程序更具可移植性,我将连接字符串添加到了 web.config 文件中。

接下来,我为每个“行”添加了进度条,这些进度条会汇总到主页上的总体进度条。此外,我还添加了一个更新字段,允许用户设置销售目标。

最后,为了加快更新速度,我为编辑模式添加了一个伪旋钮控件,该控件可以将编辑窗口向上或向下移动一行。当您到达页面末尾时,它会自动将您带到下一页的第一行或上一页的最后一行。

使用代码

我的演示虽然承认有点简陋,但包含一个 aspx 页面及其代码隐藏,代码行数少于 250 行。步骤和解决方案本身一样简单:

  • 获取包含单个 DataTableDataSet
  • 根据用户定义的页面大小和我表中行的数量来确定所需的页数。
  • 为每一页创建一个容器表。
  • 为每一行数据创建一个子表。
  • 将子表添加到容器表中。
  • 将容器表添加到页面,在本例中,通过将其添加到设置为 runat=”server” 的表格单元格的 Controls 元素中。
  • 将 JavaScript 添加到页面。

对于这个演示,我使用了 SQL Server 中的 Northwind 数据库。您可以轻松更改连接字符串/查询字符串以使用您想要的任何 SQL Server 数据库。

private void BuildTables ()
{
    // Determine total number of records

    int NumItems  = ds.Tables[0].Rows.Count;

    // Set number of records per page

    int PageSize  =    Int32.Parse(txtPageSize.Text);
    // Determine    number of pages minus any leftover records

    long Pages  = (NumItems    / PageSize);
    // Save this number for future reference


    long WholePages =  NumItems / PageSize;
    // Determine number of leftover records


    int Leftover =  NumItems  % PageSize;
    //If there are leftover records, increase page count by one


    if (Leftover >  0)
    { 
        Pages += 1; 
    } 

    int StartOfPage = 0; 
    int EndOfPage = PageSize -1;
    this.lblPages.Text = Pages.ToString() + " Pages";
    this.lblRecords.Text =    NumItems.ToString() + "Records";

    for(intp=1;p<=Pages;p++)
    {
        //Create Page Tables

        HtmlTable tblPage = new HtmlTable();
        HtmlTableRow trow = new HtmlTableRow();
        HtmlTableRow hrow = new HtmlTableRow();
        HtmlTableCell hcell = new HtmlTableCell();
        hcell.InnerHtml = "<b>Page " +p.ToString()+"</b>";
        hrow.Cells.Add(hcell);
        tblPage.Rows.Add(hrow);
        HtmlTableCell tcell = new HtmlTableCell();
        tblPage.ID = "Page"+p;
        if(p==1)
            tblPage.Style.Add("Display","block");
        else
            tblPage.Style.Add("Display","none");
        for(int i = StartOfPage;i<=EndOfPage;i++)
        {
            if(i < ds.Tables[0].Rows.Count)
            {
                tcell.Controls.Add(FillPages(ds.Tables[0].Rows[i],i,p));
                tcell.Controls.Add(BuildEditTable(ds.Tables[0].Rows[i],i,p));
            }
        }
        trow.Cells.Add(tcell);
        tblPage.Rows.Add(trow);
        HtmlTableCell tdPages = (HtmlTableCell)FindControl("tdPages");
        tdPages.Controls.Add(tblPage);
        StartOfPage = EndOfPage+1;
        EndOfPage = EndOfPage+PageSize;
    }

    OverallProgress = (OverallProgress/NumItems);
    this.hdnOverallPercent.Value=OverallProgress.ToString()+"%";
    this.ovl.InnerHtml = OverallProgress.ToString()+"%";

    this.RenderScript(Convert.ToInt32(Pages), Convert.ToInt32(NumItems));
}

上面的方法会迭代调用 FillPages (int Record) 方法,传入当前行值并返回一个表。还有其他更优雅的方法可以确定 DataSet 表中有多少行,但同样,此项目的目标是简洁。

这个版本新增了计算进度并显示 GIF 图像一部分(与进度成比例)的逻辑。同样,原理非常简单:图像为 100px。我计算完成百分比(实际/目标),并将图像宽度设置为等于完成百分比。如果实际值为 30,目标值为 100,则图像宽度为 30px,即完整图像的 30%。

我还为总体进度维护一个运行的百分比总数。为了保持简单和在客户端,我使用一个隐藏的 HTML 字段 (HtmlInputHidden) 来存储运行总数。在构建页面后,我将记录数除以 PercentComplete 的总和来计算总体进度。

private HtmlTable FillPages(DataRow Record, int tableNumber, int pageNumber)
{
    HtmlTable tblNew = new HtmlTable();
    HtmlTableRow r1 = new HtmlTableRow();
    HtmlTableRow r2 = new HtmlTableRow();
    HtmlTableCell c1 = new HtmlTableCell();
    HtmlTableCell c2 = new HtmlTableCell();
    HtmlTableCell c3 = new HtmlTableCell();
    HtmlTableCell c4 = new HtmlTableCell();
    HtmlInputButton b1 = new HtmlInputButton();

    b1.Value="Edit";
    b1.ID="b"+tableNumber.ToString();
    b1.Attributes.Add("onclick","doEdit('"+b1.ID+"')");
    c4.Controls.Add(b1);
    //format


    tblNew.Style.Add("DISPLAY","block");
    tblNew.ID="#vw"+tableNumber.ToString();
    tblNew.Attributes.Add("Page",pageNumber.ToString());
    tblNew.Border=1;
    tblNew.CellSpacing=0;
    tblNew.CellPadding=3;
    tblNew.Width = "520px";

    c1.BgColor="silver";
    c1.Style.Add("FONT-COLOR","WHITE");
    c2.Width="80%";
    c3.Width = "20%";
    //fill cells

    int intProgress = 
      Convert.ToInt32(Double.Parse(Record[4].ToString())/
      Double.Parse(Record[5].ToString())*100);
    if(intProgress>100)
        intProgress=100;
    this.OverallProgress += intProgress;
    c2.InnerHtml = "Title: "+Record[1].ToString() + 
            "<br>Sales: $" +Record[4] + "     Target: $" + Record[5];
    c1.InnerHtml = "<b>" + Record[0].ToString() + 
      "</b>     Progress" + 
      "   <span id='Label7' " + 
      "class='TaskProgress' ></span>" + 
      " "+intProgress.ToString()+ "%" + b1;

    //c2.InnerHtml = "Title: "+Record[1].ToString() 

    //   + "<br>Sales: $" +Record[4] + "     Target: $" + Record[5];

    c3.InnerHtml = "Category: " +Record[2].ToString();
    //assign cells to rows

    r1.Cells.Add(c1);
    r1.Cells.Add(c4);
    r2.Cells.Add(c2);
    r2.Cells.Add(c3);
    //assign rows to table

    tblNew.Rows.Add(r1);
    tblNew.Rows.Add(r2);
    return tblNew;
}

现在,我们将构建编辑表格。因为我使用的是 Pubs 数据库,所以我不会将页面上所做的任何更改存储到数据库中,所以请不要期望您的更改能够持久。

private HtmlTable BuildEditTable(DataRow Record, int tableNumber, int pageNumber)
{
    HtmlTable tblNew = new HtmlTable();
    HtmlTableRow r1 = new HtmlTableRow();
    HtmlTableRow r2 = new HtmlTableRow();
    HtmlTableCell c1 = new HtmlTableCell();
    HtmlTableCell c2 = new HtmlTableCell();
    HtmlTableCell c3 = new HtmlTableCell();
    HtmlTableCell c4 = new HtmlTableCell();
    HtmlInputText txtAuthor = new HtmlInputText();
    HtmlInputText txtTitle = new HtmlInputText();
    HtmlInputText txtCategory = new HtmlInputText();
    HtmlInputText txtTarget = new HtmlInputText();
    HtmlInputButton bSave = new HtmlInputButton();
    tblNew.Attributes.Add("Page",pageNumber.ToString());
    bSave.ID = "bSave"+tableNumber;
    HtmlInputButton bPrev = new HtmlInputButton();
    HtmlInputButton bNext = new HtmlInputButton();
    int iprev = tableNumber-1;
    int inext = tableNumber+1;

    bPrev.ID = "bPrev"+iprev;
    bNext.ID = "bNext"+inext;
    bPrev.Value = "^ Prev";
    bNext.Value = "Next v";
    bPrev.Attributes.Add("onclick",
       "doEdit('b"+ iprev+"','"+pageNumber+"')");
    bNext.Attributes.Add("onclick",
       "doEdit('b"+inext +"','"+pageNumber+"')");

    //format

    txtTarget.Size=10;
    tblNew.Style.Add("DISPLAY","none");
    tblNew.ID="#edit"+tableNumber.ToString();
    tblNew.Border=1;
    tblNew.CellSpacing=0;
    tblNew.CellPadding=3;
    tblNew.Width = "520px";
    c1.BgColor="silver";
    c1.Style.Add("FONT-COLOR","WHITE");
    c2.Width="80%";

    c3.Width = "20%";
    //fill cells

    int intProgress = 
        Convert.ToInt32(Double.Parse(Record[4].ToString())/
        Double.Parse(Record[5].ToString())*100);
    if(intProgress>100)
        intProgress=100;
    this.OverallProgress += intProgress;
    txtTarget.Value = Record[5].ToString();
    c2.InnerHtml = "Title: "+Record[1].ToString() + "<br>Sales: $" +Record[4] ;
    c1.InnerHtml = Record[0].ToString();
    c3.Controls.Add(bPrev);
    c3.Controls.Add(bNext);// "Category: " +Record[2].ToString();

    Literal l = new Literal();
    l.Text = "New Target:";

    c4.Controls.Add(l);
    c4.Controls.Add(txtTarget);
    //assign cells to rows

    r1.Cells.Add(c1);
    r1.Cells.Add(c4);
    r2.Cells.Add(c2);
    r2.Cells.Add(c3);
    //assign rows to table

    tblNew.Rows.Add(r1);
    tblNew.Rows.Add(r2);
    return tblNew;
}

BuildTables 还调用 RenderScript(int Pages, int Items),该方法需要页数,以及(此版本新增的)项目总数,然后将相应的 JavaScript 注入页面。我添加了几个函数来处理进度条和旋转编辑窗口。项目总数用于计算总体进度。

此方法可以分解为四个独立的方法——每个操作一个。我为简洁起见将它们保留在一个函数中,但未来的需求可能需要更离散的一组方法。

protected void RenderScript(int Pages, int Items)
{
    int MaxPage = Pages+1;

    StringBuilder s = new StringBuilder("\n<script language="JavaScript">\n");
    //Previous Page

    s.Append("function __onPrevPage ()\n");
    s.Append("{\n");
    s.Append("for (var i=2; i<"+ MaxPage +"; i++) {\n");
    s.Append("if (document.getElementById" + 
             " ('Page' + i).style.display == 'block') {\n");
    s.Append("  document.all('Page' + i).style.display = 'none';\n");
    s.Append("    document.all('Page' +" + 
             " (i - 1)).style.display = 'block';\n");
    s.Append("    break;\n");
    s.Append("}\n");
    s.Append("}\n");
    s.Append("}\n");
    s.Append("\n");
    //Next Page

    s.Append("function __onNextPage ()\n");
    s.Append("{\n");
    s.Append("for (var i=1; i<"+ Pages +"; i++) {\n");
    s.Append(" if (document.getElementById" + 
             " ('Page' + i).style.display == 'block') {\n");
    s.Append("document.all('Page' + i).style.display = 'none';\n");
    s.Append(" document.all('Page' +" + 
             " (i + 1)).style.display = 'block';\n");
    s.Append(" break;\n");
    s.Append(" }\n");
    s.Append(" }\n");
    s.Append(" }\n");

    //First Page

    s.Append("function __onFirstPage ()\n");
    s.Append("{\n");
    s.Append("for (var i=2; i<"+ MaxPage +"; i++) {\n");
    s.Append(" if (document.getElementById" + 
             " ('Page' + i).style.display == 'block') {\n");
    s.Append("document.all('Page' + i).style.display = 'none';\n");
    s.Append(" document.all('Page' + (1)).style.display = 'block';\n");
    s.Append(" break;\n");
    s.Append(" }\n");
    s.Append(" }\n");
    s.Append(" }\n");

    //Jump Page

    s.Append("function __onJumpPage (iPage)\n");
    s.Append("{\n");
    s.Append("for (var i=1; i<"+ MaxPage +"; i++) {\n");
    s.Append(" if (document.getElementById ('Page'" + 
             " + i).style.display == 'block') {\n");
    s.Append("document.getElementById('Page'" + 
             " + i).style.display = 'none';\n");
    s.Append(" document.getElementById('Page'" + 
             "+iPage).style.display = 'block';\n");
    s.Append(" break;\n");
    s.Append(" }\n");
    s.Append(" }\n");
    s.Append(" }\n");

    //Last Page

    s.Append("function __onLastPage ()\n");
    s.Append("{\n");
    s.Append("for (var i=1; i<"+ MaxPage +"; i++) {\n");
    s.Append(" if (document.getElementById" + 
             " ('Page' + i).style.display == 'block') {\n");
    s.Append("document.all('Page' + i).style.display = 'none';\n");
    s.Append(" document.all('Page" + Pages + 
             "').style.display = 'block';\n");
    s.Append(" break;\n");
    s.Append(" }\n");
    s.Append(" }\n");
    s.Append(" }\n");

    //Show overall progress

    s.Append("function __doOverallProgress() \n");
    s.Append("{\n");
    s.Append("document.all('lblProgressOverall').style.width='" + 
                         this.OverallProgress.ToString()+"';\n");
    s.Append("document.all('lblProgressOverallValue').value ='" + 
                                    this.OverallProgress+"';\n");
    s.Append("}\n");

    s.Append("    function doEdit(bval,pval){\n");
    s.Append("    var record = bval.substr(1,bval.length);\n");
    s.Append("    var vTbl = document.getElementById('#vw'+record);\n");
    s.Append("    var eTbl = document.getElementById('#edit'+record);\n");

    s.Append("    vTbl.style.display='none';\n");
    s.Append("    for(var i = 0;i<" + Items +";i++){\n");
    s.Append("        var t = document.getElementById('#edit'+i);\n");
    s.Append("        var n = document.getElementById('#vw'+i);\n");
    s.Append("        if(t.style.display == 'block'){\n");
    s.Append("            t.style.display = 'none';\n");
    s.Append("            n.style.display = 'block';\n");
    s.Append("        }\n");
    s.Append("    }\n");
    s.Append("    eTbl.style.display='block';\n");
    s.Append("    __onJumpPage(eTbl.Page);\n");
    s.Append("    window.location=eTbl.id;\n");
    s.Append("    }\n");

    s.Append("</SCRIPT>");

    Page.RegisterClientScriptBlock("pagingScripts",s.ToString());
}

下一轮

接下来,我将清理代码并创建一个业务对象层来封装功能。我还将把我的 NUnit 测试集成到代码中,并对所有内容进行多次重构。最后,我将修改表格,从在后台加载的流式 XML 在客户端构建。

© . All rights reserved.