I'm not sure I recall the exact moment when I realized, that I would forever be much more conscious of what I do not like, that what I do like. I am not saying that there are not things I like, but rather than enjoying the bliss offered to those happy few who endure only the bright side of life; I have a predisposition to find my path by stubbing my toe.
With certainty, the fault lies squarely on the shoulders of my… Wait, wouldn't that just be recognizing the broken to crutch-up the rest of my limping along? See my problem; it's not easy to think in terms of the good of it, that would require me to forget every natural instinct I have, and… Well let's just say I know I don't like that.
Here's I'm just making a slight change in the UpdatePanel driven code I described here. For this code sample I'm going to be using WebMethods combined with some client side UI manipulation to update the data.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="AjaxPanelGrid.aspx.cs" Inherits="AjaxPanelGrid" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Grid Demos2</title>
<script type="text/javascript">
var pendingPanels = new Object();
function setColored(oid, sid){
document.getElementById(oid).style.backgroundColor = "#FF0033";
document.getElementById(sid).disabled = true;
}
function getIdRoot(obj){
var parts = obj.id.split('_');
var rs = "";
for(var i=0;i<parts.length -1;i++){
rs += parts[i] + "_";
}
return rs;
}
function DoUpdate(pEl)
{
var idRoot = getIdRoot(pEl);
var id = $get(idRoot + 'StaticID', pEl);
pendingPanels[id.value] = pEl;
var vers = $get(idRoot + 'StaticVersionID', pEl);
vers.value = parseInt(vers.value) + 1;
var col1 = $get(idRoot + 'StaticCol1', pEl);
var col2 = $get(idRoot + 'StaticCol2', pEl);
var col3 = $get(idRoot + 'StaticCol3', pEl);
var col4 = $get(idRoot + 'StaticCol4', pEl);
var data = new SmartDataClass();
data.ID = id.value;
data.VersionID = vers.value;
data.Col1 = col1.value;
data.Col2 = col2.value;
data.Col3 = col3.value;
data.Col4 = col4.value;
PageMethods.UpdateDataClass(data, OnSuccess);
}
function OnSuccess(results)
{
var pEl = pendingPanels[results.ID];
WriteUpdate(pEl, results);
}
function WriteUpdate(pEl, dataItem){
var idRoot = getIdRoot(pEl);
var vers = $get(idRoot + 'StaticVersionID', pEl);
if (vers.value < dataItem.VersionID){
vers.value = dataItem.VersionID;
$get(idRoot + 'StaticCol1', pEl).value = dataItem.Col1;
$get(idRoot + 'StaticCol2', pEl).value = dataItem.Col2;
$get(idRoot + 'StaticCol3', pEl).value = dataItem.Col3;
$get(idRoot + 'StaticCol4', pEl).value = dataItem.Col4;
}
pEl.style.backgroundColor = "#f0f0f0";
$get(idRoot + 'StaticSubmit', pEl).disabled = false;
}
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="CoreScriptManager" runat="server" EnablePageMethods="true" EnablePartialRendering="true">
</asp:ScriptManager>
<div>
<asp:Panel ID="StaticWrapperPanel" runat="server" style="width:702px; height:auto; background-color:#cccccc; border: solid 1px #000000;">
<asp:Repeater ID="StaticRowRepeater" runat="Server" OnItemDataBound="StaticRowRepeater_ItemDataBound">
<HeaderTemplate>
<div id="StaticHeader" style="width:700px; height:25px; text-align:center; vertical-align:middle; background-color:#fcfcfc; border: solid 1px #000000;">
<center>
<span>Static Header</span>
</center>
</div>
</HeaderTemplate>
<ItemTemplate>
<asp:Panel ID="StaticItemPanel" runat="server" style="width:700px; text-align:center; vertical-align:middle; height:25px; background-color:#f0f0f0; border: solid 1px #000000;">
<asp:HiddenField ID="StaticID" runat="server" />
<asp:HiddenField ID="StaticVersionID" runat="server" />
<span style="width:100px;"><asp:TextBox ID="StaticCol1" runat="server" /></span>
<span style="width:100px;"><asp:TextBox ID="StaticCol2" runat="server" /></span>
<span style="width:100px;"><asp:TextBox ID="StaticCol3" runat="server" /></span>
<span style="width:100px;"><asp:TextBox ID="StaticCol4" runat="server" /></span>
<span style="width:100px;"><asp:Button ID="StaticSubmit" Text="Save" runat="server" UseSubmitBehavior="false" OnClientClick="DoUpdate(this.parentNode.parentNode);" /></span>
</asp:Panel>
</ItemTemplate>
<FooterTemplate>
<div id="StaticFooter" 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>
</asp:Panel>
</div>
</form>
</body>
</html>
Points of interest:
- No Queue object is used to throttle the posts, this approach allows multiple simultaneous requests.
- SmartDataClass.VersionID – this is a big part of the secret here, to make sure my data is the current copy I check the versioned
- PageMethods.UpdateDataClass – this is something the Ajax framework kicks out for me, when I run this page, the framework creates the code that handles my WebMethod web service call. This is a very nice way to get the power of web services in your client side scripts. It has great application for instances where you may not want to duplicate business logic in both client script and on the server, so the use of a WebMethod gives the client a simple way to access functionality on the server.
[ScriptService]
[GenerateScriptType(typeof(SmartDataClass))]
public partial class AjaxPanelGrid : System.Web.UI.Page
{
private List<SmartDataClass> SmartData
{
get
{
List<SmartDataClass> data = Session["SmartData"] as List<SmartDataClass>;
if (null == data)
{
data = new SmartDataClassHelper().GetData();
Session["SmartData"] = data;
}
return data;
}
set
{
Session["SmartData"] = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
bindData(SmartData);
}
}
protected void StaticRowRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem)
{
HiddenField id = (HiddenField)e.Item.FindControl("StaticID");
HiddenField vers = (HiddenField)e.Item.FindControl("StaticVersionID");
TextBox col1 = (TextBox)e.Item.FindControl("StaticCol1");
TextBox col2 = (TextBox)e.Item.FindControl("StaticCol2");
TextBox col3 = (TextBox)e.Item.FindControl("StaticCol3");
TextBox col4 = (TextBox)e.Item.FindControl("StaticCol4");
Button sub = (Button)e.Item.FindControl("StaticSubmit");
Panel div = (Panel)e.Item.FindControl("StaticItemPanel");
SmartDataClass data = (SmartDataClass)e.Item.DataItem;
id.Value = data.ID.ToString();
vers.Value = data.VersionID.ToString();
col1.Text = data.Col1;
col2.Text = data.Col2;
col3.Text = data.Col3;
col4.Text = data.Col4;
sub.OnClientClick += "setColored('" + div.ClientID + "','" + sub.ClientID + "'); return false;";
}
}
protected void bindData (List<SmartDataClass> smartData)
{
StaticRowRepeater.DataSource = smartData;
Page.DataBind();
}
[WebMethod]
public static SmartDataClass UpdateDataClass(SmartDataClass RawData)
{
AjaxPanelGrid panel = new AjaxPanelGrid();
System.Threading.Thread.Sleep(500);
foreach (SmartDataClass serverData in panel.SmartData)
{
if (RawData.ID == serverData.ID && RawData.VersionID > serverData.VersionID)
{
serverData.Col1 = RawData.Col1;
serverData.Col2 = RawData.Col2;
serverData.Col3 = RawData.Col3;
serverData.Col4 = RawData.Col4;
serverData.VersionID = RawData.VersionID;
return serverData;
}
}
return null;
}
}
Points of interest:
- The UpdateDateClass WebMethod, this badly written code is what handles the request from the client, updates the data on the server and returns the updated data to the client. In the case of this app that response information will most likely be ignored, but if there server had some business logic that triggered further changes to the object that were meaningful to the client, they would be returned for client side processing.
And once again 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 class SmartDataClass : DataClass
{
private int versionID;
public int VersionID
{
get { return versionID; }
set { versionID = value; }
}
public SmartDataClass()
: base(-1, null, null, null, null){
this.versionID = 0;
}
public SmartDataClass(int id, int versionID, string col1, string col2, string col3, string col4)
: base(id, col1, col2, col3, col4)
{
this.versionID = versionID;
}
}
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 SmartDataClassHelper : DataClassHelperBase<SmartDataClass>
{
public SmartDataClassHelper()
{
}
protected override SmartDataClass GetItem(int index)
{
return new SmartDataClass(index, 1, "Column 1 : Row " + index, "Column 2 : Row " + index, "Column 3 : Row " + index, "Column 4 : Row " + index);
}
}
That's all there is to this. But here's a few words about why this works better from my perspective than the UpdatePanel version.
- Request/Repsonse size – for those of you who've battled unwieldy ViewState objects using repeaters or DataGrids, you can see the advantage having lived the plight of Sisyphus.
- Multiple simultaneous requests, no need for fancy client-side request queuing
Only 2 more versions to get through, and then we'll see if this has been a worthwhile procession.
Print | posted on Friday, June 22, 2007 10:15 PM