Casting Off

I've always loved the simplicity of this idea.  Standing n (meters) from a wall, move 1/2n towards that wall.  Repeat... You shall never actually reach the wall, making wall the limit of your position.  Two things appeal to me about this truth.  First, the ability of mathematics to describe an infinite series, using limit , and second, the inability of perception to reconcile the physical reality of that problem with its underlying truth.

It seems to be a common struggle to reconcile the easily computable with the functional.  As a developer you see the opposing forces of perception and logic pitted against each other time and time again.  The rub here, is that often perception is far more important than fact.  What makes an illusion such a fundamentally enjoyable form of entertainment, is its ability to manipulate fact in favor of perception.  To push an idea so far into conceptual being, that it must be truth. 

To catch myself back up to where I was going in this interrupted post stream I'm going to jump ahead to code.  Over the next few posts I'm going to show some examples of different approaches to the same problem, and when those are done, I want to pair those solutions to possible use cases.  Starting this line up is a simple multi-row multi-UpdatePanel grid.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="UpdatePanelGrid.aspx.cs" Inherits="UpdatePanelGrid" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Grid Demos</title>
    <script type="text/javascript">
      //Queue object
      function Queue(){
         this.__items = new Array();
         this.Count = 0;  

         this.Enqueue = function(item){
            this.__items[this.Count] = item;
            this.Count++;
         };       

         this.Dequeue = function(){
            if (this.Count <= 0){throw ('No items in Queue');}
            var item = this.__items[0];
            this.Count--;
            for (var i=0; i<this.Count; i++){
               this.__items[i] = this.__items[i+1];
            }
            return item;
         };

         this.Peek = function(){
            if (this.Count <= 0){throw ('No items in Queue');}
            var item = this.__items[0];
            return item;
         }
      }

      var requestQueue = new Queue();
      var prm = null;

      function setColored(oid, sid){
         document.getElementById(oid).style.backgroundColor = "#FF0033";
         document.getElementById(sid).disabled = true;
      }

      function Init(sender){
         prm = Sys.WebForms.PageRequestManager.getInstance();
         if (!prm.get_isInAsyncPostBack()){
            prm.add_initializeRequest(InitRequest);
            prm.add_endRequest(EndRequest);
         }
      }

      function InitRequest(sender,args){
         sleep(50);
         if (prm.get_isInAsyncPostBack()){
            //let current request finish, cancel this request, and reschedule
            args.set_cancel(true);
            requestQueue.Enqueue(args.get_postBackElement().id);
         }
      }

      function EndRequest(sender, args) {
         // throw out aborted request responses
         if (args.get_response().get_aborted()){ return; }

         if (requestQueue.Count > 0){
            var pbk = setTimeout("DoAsync()",100);
         }
      }

      function sleep(ms){
         var wait = true;
         var start = new Date().getTime();
         var check;
         while(wait){
            check = new Date().getTime();
            if(check - start >= ms){wait = false;}
         }     
      }

      function DoAsync(){
         if (requestQueue.Count <= 0){
            return;
         }

         if (prm.get_isInAsyncPostBack()){
            var pbk = setTimeout("DoAsync()",100);
            return;
         }

         var id = requestQueue.Dequeue();
         var sub = $get(id);

         if (sub){
            //this is a cheat, as it's always a button, but this could be handled by checking the elType and then
            //eval-ing the correct attribute
            eval(sub.attributes['onclick'].value);

         }
         return;
      }
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="UDCoreScriptManager" runat="server" EnablePartialRendering="true" />
        <div>
           <asp:UpdatePanel ID="WrapperPanel" runat="server" ChildrenAsTriggers="False" UpdateMode="conditional">
               <ContentTemplate>
                  <div id="mainpanel" style="width:702px; height:auto; background-color:#cccccc; border: solid 1px #000000;">

                     <asp:Repeater ID="RowRepeater" runat="Server" OnItemDataBound="RowRepeater_ItemDataBound" OnItemCommand="RowRepeater_ItemCommand"  >
                        <HeaderTemplate>
                           <div id="header" style="width:700px; height:25px; text-align:center; vertical-align:middle; background-color:#fcfcfc; border: solid 1px #000000;">
                              <center>
                                 <span>Header</span>
                              </center>
                           </div>
                        </HeaderTemplate>
                        <ItemTemplate>
                            <asp:UpdatePanel ID="ItemPanel" runat="server" ChildrenAsTriggers="True" UpdateMode="conditional">
                                <ContentTemplate>
                                   <div id="aRow" runat="server"  style="width:700px; text-align:center; vertical-align:middle; height:25px; background-color:#f0f0f0; border: solid 1px #000000;">
                                       <asp:HiddenField ID="ID" runat="server"  />
                                       <span style="width:100px;"><asp:TextBox ID="Col1" runat="server" /></span>
                                       <span style="width:100px;"><asp:TextBox ID="Col2" runat="server" /></span>
                                       <span style="width:100px;"><asp:TextBox ID="Col3" runat="server" /></span>
                                       <span style="width:100px;"><asp:TextBox ID="Col4" runat="server" /></span>
                                       <span style="width:100px;"><asp:Button ID="Submit" Text="Save" runat="Server" UseSubmitBehavior="false" /></span>
                                    </div>
                                 </ContentTemplate>
                            </asp:UpdatePanel>
                        </ItemTemplate>
                        <FooterTemplate>
                         <div id="footer" style="width:700px; height:25px; text-align:center; vertical-align:middle; background-color:#fcfcfc; border: solid 1px #000000;">
                              <center>
                                 <span>Footer</span>
                              </center>
                           </div>
                        </FooterTemplate>
                     </asp:Repeater>
                  </div>
               </ContentTemplate>
            </asp:UpdatePanel>
        </div>
    </form>
    <script type="text/javascript">
            Sys.Application.add_init(Init);
    </script>
</body>
</html>



Points of interest:
  • Queue javascript object, this is used to queue up UpdatePanel requests.  Since the UpdatePanel control does not support multiple simultaneaous async requests, this page queues them up and fires them one by one.  If the requests are not queued, previous UpdatePanel requests will be "canceled" basically meaning that nothing will be listening for a response, and no update will ever take place on the client.
  • Of course each row is it's own UpdatePanel.  This gives the user the ability to interact with each data row independently.
public partial class UpdatePanelGrid : System.Web.UI.Page
{
   private List<DataClass> Data
   {
      get
      {
         List<DataClass> data = Session["Data"] as List<DataClass>;
         if (null == data)
         {
            data = new DataClassHelper().GetData();
            Session["Data"] = data;
         }
         return data;
      }
      set
      {
         Session["Data"] = value;
      }
   }

   protected void Page_Load(object sender, EventArgs e)
   {
      if (!IsPostBack)
      {
         bindData(Data);
      }
   }

   protected void RowRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
   {
      if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem )
      {
         HiddenField id = (HiddenField)e.Item.FindControl("ID");
         TextBox col1 = (TextBox)e.Item.FindControl("Col1");
         TextBox col2 = (TextBox)e.Item.FindControl("Col2");
         TextBox col3 = (TextBox)e.Item.FindControl("Col3");
         TextBox col4 = (TextBox)e.Item.FindControl("Col4");
         Button sub = (Button)e.Item.FindControl("Submit");
         HtmlGenericControl div = (HtmlGenericControl)e.Item.FindControl("aRow");

         DataClass data = (DataClass)e.Item.DataItem;
         id.Value = data.ID.ToString();
         col1.Text = data.Col1;
         col2.Text = data.Col2;
         col3.Text = data.Col3;
         col4.Text = data.Col4;
         sub.OnClientClick += "setColored('" + div.ClientID + "','" + sub.ClientID +"');";
       
      }
   }

   protected void RowRepeater_ItemCommand(object source, RepeaterCommandEventArgs e)
   {
      System.Threading.Thread.Sleep(500);
      HiddenField id = (HiddenField)e.Item.FindControl("ID");
      TextBox col1 = (TextBox)e.Item.FindControl("Col1");
      TextBox col2 = (TextBox)e.Item.FindControl("Col2");
      TextBox col3 = (TextBox)e.Item.FindControl("Col3");
      TextBox col4 = (TextBox)e.Item.FindControl("Col4");

      int iid = -1;
      if (int.TryParse(id.Value, out iid))
      {
         foreach (DataClass dataItem in Data)
         {
            if (dataItem.ID == iid)
            {
               dataItem.Col1 = col1.Text;
               dataItem.Col2 = col2.Text;
               dataItem.Col3 = col3.Text;
               dataItem.Col4 = col4.Text;
               break;
            }
         }
      }

      bindData(Data);
   }

   protected void bindData( List<DataClass> data)
   {
      RowRepeater.DataSource = data;
      Page.DataBind();
   }

}

Points of interest:
  • The Thread.Sleep() to give the appearance of the server doing some work.
  • The entire row is updated with the data in the post.  If multiple posts could occur at once, this approach could fall victim to requests being received out of order.

Lastly there's the supporting data classes


public class DataClass
{
   private string col1;
   private string col2;
   private string col3;
   private string col4;
   private int id;

   public int ID
   {
      get { return id; }
      set { id = value; }
   }

   public string Col1
   {
      get { return this.col1; }
      set { this.col1 = value; }
   }

   public string Col2
   {
      get { return this.col2; }
      set { this.col2 = value; }
   }

   public string Col3
   {
      get { return this.col3; }
      set { this.col3 = value; }
   }

   public string Col4
   {
      get { return this.col4; }
      set { this.col4 = value; }
   }

   public DataClass()
   {
   }

   public DataClass(int id, string col1, string col2, string col3, string col4)
    {
      this.id = id;
      this.col1 = col1;
      this.col2 = col2;
      this.col3 = col3;
      this.col4 = col4;       
    }
}


public abstract class DataClassHelperBase<T>
{
   public List<T> GetData()
   {
      List<T> data = new List<T>();
      for (int i = 1; i <= 12; i++)
      {
         data.Add(GetItem(i));
      }
      return data;
   }
   protected abstract T GetItem(int index);
}


public class DataClassHelper : DataClassHelperBase<DataClass>
{
   public DataClassHelper()
   {
   }

   protected override DataClass GetItem(int index)
   {
      return new DataClass(index, "Column 1 : Row " + index, "Column 2 : Row " + index, "Column 3 : Row " + index, "Column 4 : Row " + index);
   }
}


Okay that's it for now.  The next posts will mostly be code listings as well, and then I'll try to figure out what I was trying to do when I wrote them.
Print | posted on Wednesday, June 20, 2007 10:10 PM
Comments have been closed on this topic.