This came up this week on a project I am working on and now it will transcend to other projects. I needed to rewrite some functionality utilizing client-side code. Well, I could've done it server-side but I'm not a fan of unnecessary postbacks (or UpdatePanel "partial postbacks") so I decided to utilize JavaScript.
So I removed all the AutoPostBack="true"'s from the codebase and utilized my favorite property (Control.ClientID) to set the css display style to "none" or "" depending on which option was selected. After all that was done I decided to test. Uh-oh, my RequiredFieldValidators were firing off since I was only using CSS* to hide / unhide my Panels. Instead of adding values to these controls to satisfy the expressions (which doesn't work all of the time (i.e. input type='file' or asp:FileUpload which don't allow the setting of a posted file as it is a security risk). I found out how to turn off validation. It only took a little bit to add via codebehind.cs.
For this example, I have a CheckBox for checks that when clicked needs to disable a RequiredFieldValidator for CreditCard. Stupid example I know.
check.Attributes.Add("onclick", string.Format("disable('{0}')", regexCreditCard.ClientID));
That will add the appropriate handler for client-side work. Now that that is out of the way, time for the actual disabling of the validator.
function
disable(validatorId)
{
var validator = document.getElementById(validatorId);
ValidatorEnable(validator, false);
}
Easy. Now you can use .NET to implement tamper-proof validation and disable / enable via JavaScript for a richer user experience.
* Using "display: none" is much better than simply using "visibility: hidden" for what's it's worth (at least it was for me). With "visibility: hidden" you can tell that something is supposed to be there. With "display: none" you can't. So in summary, "visibility: hidden" results in the page calculating this div as part of the flow. With "display: none", it's not even accounted for. It's as if it were never there!
The more you know ;-)
Tags:
AJAX,
JavaScript

In the last post I introduced the functionality to create a spiffy drag-and-drop / Reorder List control using C#, XML, and JavaScript. I didn't divulge all the code yet and will explain a little more about what I did in this post. First I will start with the creation of the textboxes and labels.
private
void CreateControls()
{
ProductGroup.RetrieveAll().ForEach(
delegate(ProductGroup productGroup)
{
int counter = 0;
Label productGroupName = new Label();
productGroupName.Text = productGroup.ProductGroupName;
productGroupName.CssClass = "labelText";
productGroupName.Width = 120;
productGroupName.ID = string.Format("display{0}", productGroup.ProductGroupId);
Label productGroupTotals = new Label();
productGroupTotals.Text = "0";
productGroupTotals.CssClass = "labelText";
productGroupTotals.ID = string.Format("total{0}", productGroup.ProductGroupId);
ph.Controls.Add(new LiteralControl(string.Format("\t\t<tr>{0}", Environment.NewLine)));
ph.Controls.Add(new LiteralControl(string.Format(string.Format("\t\t\t<td valign=\"top\">{0}\t\t\t", Environment.NewLine))));
ph.Controls.Add(productGroupName);
ph.Controls.Add(new LiteralControl(string.Format("{0}\t\t\t", Environment.NewLine)));
ph.Controls.Add(productGroupTotals);
ph.Controls.Add(new LiteralControl(string.Format("{0}\t\t\t</td>{0}", Environment.NewLine)));
foreach (Product product in productGroup.Products)
ph.Controls.Add(new LiteralControl(string.Format("\t\t\t\t<td class=\"product\"><input type=\"text\" readonly=\"readonly\" id=\"productgroup_{0}_product_{1}\" onfocus=\"javascript:selectAll('productgroup_{0}_product_{1}');\" ondragenter=\"window.event.returnValue = false\" ondragover=\"window.event.returnValue = false\" ondrop=\"onDropEvent({0}, 'productgroup_{0}_product_{1}', '{2}')\" ondragstart=\"onDragStartEvent({1}, {0}, 'productgroup_{0}_product_{1}')\" class=\"productText\" value=\"{2}\" /></td>{3}", productGroup.ProductGroupId, ++counter, product.ProductId, Environment.NewLine)));
ph.Controls.Add(new LiteralControl(string.Format("\t\t</tr>{0}", Environment.NewLine)));
});
}
This looks pretty intimidating but it really isn't at all. I choose to create my controls programmatically because I like having control of all the attributes (i.e. onblur, ondragstart, ondrop, onfocus, etc...). Enumerating a collection of objects gives me direct access to every property that I could possibly need. If this functionality were part of a page that could be posted back I would have overridden CreateChildControls(). That way these controls would have been added to the Page's control tree and maintained state during postbacks. I'll use this opportunity to plug how cool the CreateChildControls override can be.
The labels are created to display the ProductGroup name as well as (for this first example) the sum of the Product prices.
Side note: I add a ton of tabs (\t) as well as Environment.NewLine because I like the output HTML to look neat and tidy. This is a personal preference and you can get rid of this part of the code if you want. I will say that when doing complex functionality it makes troubleshooting a LOT easier.
JavaScript Variables
var
productNode = null; // product being dragged
var productGroupNode = null; // productgroup for productNode
var globalProductGroupId = null; // productgroup being dragged
var globalProductGroupIdDrop = null; // productgroup dropped into
var selectedProductControlId = null; // control that is selected
var droppedProductControlId = null; // control that is dropped into
var xmlHttpRequest = null; // for database interaction later on
Pertinent JavaScript Functions & Methods
function
selectAll(controlId)
{
document.getElementById(controlId).focus();
document.getElementById(controlId).select();
selectedProductControlId = controlId;
}
The selectAll function simply selects the value in the textbox and enables it for dragging. This is here for nothing more than providing ease-of-use to the end-user. You could just have a free-form textbox and allow them to select the values. This can be problematic if you have multiple digit values.
function
clearTextBoxes(productGroupId)
{
for (i = 0; i < 4; ++i)
{
var id = (i+1) * 1;
var controlId = "productgroup_" + productGroupId + "_product_" + id;
document.getElementById(controlId).value = "";
}
}
This method simply clears the old values out of the textboxes. There are 4 textboxes in each row (hence the 4 in the for-loop). Inside the for-loop I am concatenating the productGroupId (which happens to be the textbox) to my existing naming convention for the textboxes we created above. I then set this textbox's value to empty.
function
populateTextBoxes(productGroupId, productGroupNode)
{
var products = productGroupNode.getElementsByTagName("product");
for (i = 0; i < products.length; ++i)
{
var id = (i+1) * 1;
var controlId = "productgroup_" + productGroupId + "_product_" + id;
document.getElementById(controlId).value = products[i].attributes[0].nodeValue;
}
}
Kind of like the method above except for now we are filling the textboxes with the new value from the ProductGroup node. The productGroupId tells me what row it is on. I then enumerate the contents of the products node and perform the same motions as above, except that now I am filling the contents with an actual value.
function
onDragStartEvent(productId, productGroupId, controlId)
{
globalProductGroupId = productGroupId*1;
productNode = xmlData.selectSingleNode("//productGroup/product[@productId='" + document.getElementById(controlId).value + "']");
}
Start of the drag event. I set my global variable (so I can access it later) as well as my ProductNode (also used later). Notice the XPath syntax.
function
onDropEvent(productGroupId, controlId, productIdDrop)
{
productGroupNode = xmlData.selectSingleNode("//productGroup[@productGroupId='" + productGroupId + "']");
var productGroupNodeOld = xmlData.selectSingleNode("//productGroup[@productGroupId='" + globalProductGroupId + "']");
var productNodeOld = xmlData.selectSingleNode("//productGroup/product[@productId='" + document.getElementById(controlId).value + "']");
productGroupNode.appendChild(productNode);
productGroupNodeOld.appendChild(productNodeOld);
reorderProducts(productGroupNode);
reorderProducts(productGroupNodeOld);
clearTextBoxes(globalProductGroupId);
clearTextBoxes(productGroupId);
populateTextBoxes(globalProductGroupId, productGroupNodeOld);
populateTextBoxes(productGroupId, productGroupNode);
productNode = null;
calculateProductGroupData();
}
This is where it gets really fun (complicated for some). So earlier we set the Product node in the drag event. Here we select the ProductGroup and Product node that were dropped into as well as the old ProductGroup (dragged from). I guess I could've added the dragged ProductGroup node in the dragstart but that's neither here nor there in this basic tutorial. Now I am appending the children to the appropriate parent nodes in the Xml document (xmlData). For a better user-experience I call my reorderProducts function so that the products will be numerically sorted lowest to highest. This is optional, but usually a requirement (has been) every time I add functionality like this to a client's project.
Reset the textboxes (clearTextBoxes) and then repopulate them (populateTextBoxes) which are both explained earlier in this entry.
function numericalSort(a, b) { return (a - b); }
Simple way to sort numerically (lowest to highest). This can be reversed (b - a) or not even used at all. Like I said earlier, most people (clients) required this in the past.
function
calculateProductGroupData()
{
var products = null;
var productId = -1;
var productPrice = -1;
var productGroupId = -1;
var productGroups = xmlData.getElementsByTagName("productGroup");
for ( i = 0; i < productGroups.length; i++ )
{
globalSubTotal = 0;
productGroupId = productGroups[i].attributes[0].nodeValue;
if ( productGroups[i].hasChildNodes )
{
products = productGroups[i].getElementsByTagName("product");
for ( p = 0; p < products.length; p++ )
globalSubTotal += products[p].attributes[2].nodeValue*1;
document.getElementById("total" + productGroupId).innerHTML = "$" + globalSubTotal;
}
}
}
The meat-and-potatoes of the application if you will. This lets users know that something is happening. Upon each successfull drop these values will change. This method will enumerate the newly-changed Xml document and recalculate everything.
For each ProductGroup node in the Xml document we are seeing if there any child nodes available. If there are (for this example there ALWAYS will be) we are then enumerating the child nodes and calculating the data, in this case the subtotal (price).
This is pretty straight-forward.
function
reorderProducts ( productGroupNode )
{
var productGroupNodeId = productGroupNode.attributes[0].nodeValue;
var products = productGroupNode.getElementsByTagName("product");
var productItems = new Array(products.length);
for ( p = 0; p < products.length; p++ )
productItems[p] = products[p].attributes[0].nodeValue;
productItems.sort(numericalSort);
for ( i = 0; i < productItems.length; i++ )
{
var node = xmlData.selectSingleNode("//productGroup/product[@productId='" + productItems[i] + "']");
productGroupNode.appendChild(node);
}
}
This looks crazy but it's rather simple. All I'm doing is passing a ProductGroup node and creating a new array that happens to be the length of that node (in our case 4). I then add the items from the node to said array in a for-loop. The JavaScript array has a sort method but defaults (I believe) to alphanumeric sorting. Since we are ONLY dealing with integers we could use this but I felt I should provide this in the event that you actually need it.
After the array is sorted to our liking we have another for-loop where we create the nodes (children) and append back to the appropriate parent.
Sorry for not posting this earlier. Again, VERY busy with both work and play. Hopefully this post coupled with the last will keep you busy for a little while. Feel free to post questions and I will try my best to answer them.
In the next entry relating to this I will introduce database interaction making it a complete AJAX application.
Related
An Actual Reorder List Control with Substance Part 1
Tags:
AJAX,
C#,
JavaScript
Just as the title says. I can't even begin to tell you how much I absolutely hate the Ajax Toolkit Reorder List control. I've blogged about this many times. It sucks. Big time. I've told many colleagues (real-life and internet-life) the story about how I got tired of trying to strong-arm the Reorder List control and after fruitless efforts I decided to learn a little JavaScript and roll-my-own functionality.
I figure it would be fun to prove this and share some code with everyone. The task is very involved so I will publish in installments. When all is said-and-done I will give away the code along with a sample project and you can all do what you will. Again, since I've been extremely busy both at work and at play this will take some time but should be fun nonetheless.
So. The example will focus on reordering products within product groups. Each product group has products attributed to it. It's not rocket science. This is also a very real possibility and something that could benefit from having a drag-and-drop interface. Our project will use the following 2 objects: Product and ProductGroup.
Product
public class Product
{
private int _productId = 0;
private string _productName = string.Empty;
private decimal _productPrice = 0m;
public int ProductId
{
get { return _productId; }
set { _productId = value; }
}
public string ProductName
{
get { return _productName; }
set { _productName = value; }|
}
public decimal ProductPrice
{
get { return _productPrice; }
set { _productPrice = value; }
}
}
ProductGroup
using System.Collections.Generic;
public class ProductGroup
{
private int _productGroupId = 0;
private string _productGroupName = string.Empty;
private List<Product> _products = new List<Product>();
public int ProductGroupId
{
get { return _productGroupId; }
set { _productGroupId = value; }
}
public string ProductGroupName
{
get { return _productGroupName; }
set { _productGroupName = value; }
}
public List<Product> Products
{
get { return _products; }
set { _products = value; }
}
}
Pretty straight-forward. For this project I will use one Default.aspx and a javascript file which I won't publish all the code for right now as it won't make sense. What I will do is give some snippets and show an animated demo of the code in action, then spend some time in the future dissecting it / answering questions that come to me in the form of comments, etc...
So what I like to do with drag-and-drop web applications is create an xml document representing our objects. I like to think of drag and drop applications as a sort of Xml parent-child node issue. If you ever find yourself needing to program functionality that allows the moving of nodes then drag-and-drop might be a viable option. For this first installment I am simply creating an xml document and binding it to an Xml object that we can reference using the JavaScript DOM (aka XPath queries!). For the following demo I am using this Xml format:
<?
xml version="1.0" encoding="utf-8"?>
<root>
<productGroup productGroupId="1">
<product id="1" name="Product-1" price="1" />
<product id="2" name="Product-2" price="2" />
<product id="3" name="Product-3" price="3" />
<product id="4" name="Product-4" price="4" />
</productGroup>
<productGroup productGroupId="2">
<product id="5" name="Product-5" price="5" />
<product id="6" name="Product-6" price="6" />
<product id="7" name="Product-7" price="7" />
<product id="8" name="Product-8" price="8" />
</productGroup>
<productGroup productGroupId="3">
<product id="9" name="Product-9" price="9" />
<product id="10" name="Product-10" price="10" />
<product id="11" name="Product-11" price="11" />
<product id="12" name="Product-12" price="12" />
</productGroup>
</root>
This file is created with this code:
private void CreateXmlFile(string xmlFilePath)
{
int productGroupId = 1;
using (XmlTextWriter xml = new XmlTextWriter(Server.MapPath(xmlFilePath), new UTF8Encoding(false)))
{
xml.Formatting = Formatting.Indented;
xml.Indentation = 5;
xml.WriteStartDocument();
xml.WriteStartElement("root"); // <root>
ProductGroup.RetrieveAll().ForEach(
delegate(ProductGroup productGroup)
{
xml.WriteStartElement("productGroup"); // <productGroup>
xml.WriteAttributeString("productGroupId", productGroupId.ToString());
productGroup.Products.ForEach(
delegate(Product product)
{
xml.WriteStartElement("product"); // <product>
xml.WriteAttributeString("id", product.ProductId.ToString());
xml.WriteAttributeString("name", product.ProductName);
xml.WriteAttributeString("price", product.ProductPrice.ToString());
xml.WriteEndElement(); // </product>
});
xml.WriteEndElement(); // </productGroup>
++productGroupId;
});
xml.WriteEndElement(); // </root>
}
xmlData.Attributes.Add("src", xmlFilePath);
}
You see that I add xmlData src attribute to this file? That is simply an xml object in my .aspx that looks like this:
<
xml id="xmlData" runat="server" />
Before I get an influx of inquiries asking why I just didn't use an ASP:XmlDataSource control I don't like the xml being displayed in my source. I'm a little picky and a little old school at the same time.
I'm going to skip all the JavaScript explanation because I just got done splitting and stacking over a cord and a half of firewood after working 60+ hours this week. The JavaScript is a whole other post in and of itself.
Demo
A little preface. I have 3 rows of 4 products. A row is a product group and a textbox is representing a product. The numbers in the textbox represent the product price (I know, cheesy) and the label with the $ represent the total of the numbers in the textbox. Again, this is only to pique your interest. If you are interested then stay tuned. If not, then still stay tuned and let me know how much of an idiot I am for not being able to do this with the AJAX Toolkit. One major advantage to rolling your own is that instead of dragging the ENTIRE object up one row, you can pick and choose and have a TRUE drag-and-drop interface. When I first rolled this out to production I added empty textboxes that could be used as well. This way one parent could have 5 children, one could have 1 and another could have 0 (I had functionality to delete nodes as well).
I tried to go pretty slow and only showed 3 moves to give you time to count and verify that this is actually working. Again, I haven't showed yet how I'm doing the javascript. Right now the javascript file is about 75 lines or so. In the end, depending on how ambitious I am, we'll be at about 200 or so as I'd like to give you all the ability to swap parent nodes as well as child nodes. The JavaScript might be a little heavy but in my opinion, it is a small price to pay to get the ACTUAL functionality that you want! Animated demo below.

Easy. Stay tuned for HOW I did that.
Related
An Actual Reorder List Control with Substance Part 2
Tags:
AJAX,
JavaScript
Just learned something really cool about Generic Handlers (.ashx). There is a cool interface called IRequiresSessionState that you can implement to gain access to session data. This interface isn't new and a quick Google search will net you some great blog entries about it. For me, this interface opened up my eyes in that I now use these Generic Handlers for asynchronous callbacks. Now you can read & write Session-state values from static HTML pages using JavaScript.
When would you ever need to use this? High-traffic site that needs to be cached? Integrating bits of dynamic information within static pages (large existing sites), etc.. This technique could also be used to read querystring values from non .NET sites (php, asp, jsp, etc...). Again, think being able to add an ASP.NET Generic Handler to an existing site (granted it's hosted on a server that has the .NET Framework installed) and read Querystring information from non-.NET pages.
First here's the Handler.ashx file. What I'm doing here is examing the current request and checking to see if my session values of A & B (lame I know) exist. If not I'm simply going to return "none" to the client. In a real-world scenario I would probably return an empty string but will do this for the example. You need to SEE it work. :-)
I will call this page from a static HTML page (via xmlHttpRequest*) and populate a textbox with the value and also set this session variable from the same textbox's onblur event. In a nut-shell I am exchanging session data between a static (HTML) and dynamic (.aspx) page. There is a demo (animated) to show this.
* should be renamed webRequest!
<%@ WebHandler Language="C#" Class="Handler" %>
using
System.Web;
using System.Web.SessionState;
public
class Handler : IHttpHandler , IRequiresSessionState
{
public void ProcessRequest (HttpContext context)
{
context.Response.ContentType = "text/plain";
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
string item = context.Request.QueryString["item"] ?? "";
string update = context.Request.QueryString["update"] ?? "";
switch (item)
{
case "A":
if (!string.IsNullOrEmpty(update))
context.Session["A"] = update
object a = context.Session["A"];
context.Response.Write(a != null ? (string) a : "none");
break;
case "B":
if (!string.IsNullOrEmpty(update))
context.Session["B"] = update
object b = context.Session["B"];
context.Response.Write(b != null ? (string) b : "none");
break;
}
}
public bool IsReusable
{
get { return false; }
}
}
Here is the code markup for the "static" HTML page. The JavaScript actually belongs in an external .js file that is referenced but in the interest of time and for educational purposes I put the JavaScript between my head tags.
<
html>
<
head>
<title>Handler Example</title>
<
script type="text/javascript" language="javascript">
function
ajaxFunction(item, update, elementId)
{
var ajaxRequest = null;
var url = update != null ? "Handler.ashx?item=" + item + "&update=" + update : "Handler.ashx?item=" + item;
try { ajaxRequest = new XMLHttpRequest(); }
catch (e)
{
try { ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP"); }
catch (e)
{
try { ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP"); }
catch (e)
{
alert("your browser sucks!");
return false;
}
}
}
ajaxRequest.onreadystatechange = function()
{
if(ajaxRequest.readyState == 4)
{
var responseValue = ajaxRequest.responseText;
document.getElementById(elementId).value = responseValue;
}
}
ajaxRequest.open("GET", url, true);
ajaxRequest.send(null);
}
function
populateSessionValues()
{
ajaxFunction("A", null, "textA");
ajaxFunction("B", null, "textB");
}
function
onBlurEvent(item, elementId)
{
var newValue = document.getElementById(elementId).value;
ajaxFunction(item, newValue, elementId);
}
</
script>
</
head>
<
body onload="populateSessionValues()">
<
div style="margin-bottom: 10px;">
<label>A Value</label>
<input type="text" id="textA" onblur="javascript:onBlurEvent('A', 'textA')" />
</div>
<
div style="margin-bottom: 10px;">
<label>B Value</label>
<input type="text" id="textB" onblur="javascript:onBlurEvent('B', 'textB')" />
</div>
<
div>
<a href="handlerExample.aspx" target="_top">Back to dynamic page</a>
</div>
</
body>
</html>
For the demo I create a handlerExample.aspx that simply reads the values from the Session for A & B. When I press the button it resets the session values to "Will" and "Asrari" and then the link directs to the static page. On that page you should see that the session changes actually took place. The textboxes are populate with Session data via asynchronous request. Now to bring it all together with the demo!
Here's the code-behind for handlerExample.aspx.
protected
void Page_Load(object sender, EventArgs e)
{
string a = Session["A"] != null ? (string) Session["A"] : "none";
string b = Session["B"] != null ? (string) Session["B"] : "none";
Response.Write(string.Format("{0}<br />{1}", a, b));
}
protected void button1_Click(object sender, EventArgs e)
{
Session.Add("A", "Will");
Session.Add("B", "Asrari");
Response.Write("<br />Added A = Will, and B = Asrari to Session<br />");
}
Now time for the demo (seriously!).

Important
On a scale of 1-10 (1 not important and 10 being very important) setting Cacheability to NoCache is about a 75 ESPECIALLY with IE. The other option is to use POST as I am 95% positive those aren't cached.
The possibilities are endless. You are also not limited to simply displaying data in a textbox, label, alert, etc... You can context.Response.Write() HTML to display formatted HTML to the client. To do this with a div with id divHTMLGoesHere you just do the following:
document.getElementById("divHTMLGoesHere").innerHTML = "";
Easy!
Tags:
AJAX,
C#,
JavaScript
I wrote the the other day about an easier (in my opinion) solution to provide in-line text editing without having to use the ASP.NET AJAX Library. What that solution didn't include was the very thing the other articles online didn't include: actually editing the data. Let's face it, changing the value of a label is completely useless unless you are updating a data source whether it be SQL, XML, etc.. I have done just that. I added about 20 minutes to initial 6 minutes to bring us in under a half-hour. The code doesn't use stored procedures (very Hello World'esque) but at least I use paramterized queries! The Ajax page (ajax.aspx) simply Response.Writes( 0 = good, 1 = bad). To each his own. If you want an XML response simply add an XML response. It is also worth mentioning that XmlHttpRequest is rather misleading and it should be renamed to WebRequest in the future. It isn't meant to be used with XML and it doesn't use XML under the covers. FYI (I'll post the link later).
Also, please add CSS to this. This is very vanilla. I would never, EVER deploy something like this in production. Maybe beta-testing...
I have included the Visual Studio 2005 website and the database .bak (located in the App_Data folder). If you have suggestions on how I could make it better let me know. I will tell you that my next update to this code will be to dynamically generate the div's, labels, and textboxes. In order to do that (I haven't quite finished scoping it) I am guessing I will use Serialization (XML) and the HtmlTextWriter class.
Have fun!
By the way, from top to bottom: favorite color, favorite band (currently listening), favorite stand-up comedian.
Edit Favorite Color

Automatically Focus() and Select()

Changed to Blue

Successful!

Download
AjaxEditing.zip
Tags:
AJAX,
C#,
Code,
JavaScript
I've seen a lot of posts lately on my usual aggregate blog sites dealing with "using ASP.NET AJAX to accomplish in-line text editing." After reading about 5 of these posts and realizing that they are all pretty much the same in that they rely on utilizing the ASP.NET AJAX library I thought that there must be a less-convoluted method of accomplishing this functionality. I understand that there are a lot of people like me out there that prefer to the roll-their-own for reasons such as personal preference mixed with the fact that they haven't fully crossed over to ASP.NET AJAX.
My initial reaction to these posts was about the same as my reaction to the Mutually-Exclusive Checkbox(List) Extender: <sarcasm>Really? That's all it takes?</sarcasm> I told myself that I could accomplish the same functionality with less code and effort (in my opinion). Last night at about 1:00am I decided to do just that and even timed myself.
The following code is 100% functional and works in both Firefox and IE. It took me 6 minutes to finish. The CSS isn't as pretty (i.e. doesn't exist) as some of the examples but again; 6 minutes.
I will update this with database support in the next couple of days. I plan on moving over to ASP.NET AJAX (or at least the AJAX.NET library) pretty soon.
JavaScript
<
script type="text/javascript" language="javascript">
function
swap(show, hide)
{
document.getElementById(show).style.visibility = "visible";
document.getElementById(hide).style.visibility = "hidden";
var controlId = show + "Text";
var controlValue = document.getElementById(hide).innerHTML;
document.getElementById(controlId).value = controlValue;
document.getElementById(controlId).focus();
document.getElementById(controlId).select();
}
function
blurEvent(item)
{
document.getElementById(item + "Edit").style.visibility = "hidden";
var newValue = document.getElementById(item + "EditText").value;
document.getElementById(item).innerHTML = newValue;
document.getElementById(item).style.visibility = "visible";
}
</
script>
.aspx
<
body>
<form id="form1" runat="server">
<
div id="divFavoriteColor">
<div id="colorEdit" style="visibility: hidden;">
<input type="text" id="colorEditText" onblur="javascript:blurEvent('color')" />
</div>
<label id="color" onclick="javascript:swap('colorEdit', 'color')">Green</label>
</div>
</form>
</body>
Maybe using ASP.NET AJAX Library is easier but I am still content writing my own JavaScript. Personally I think it's fun. If I had someone breathing down my neck about a deadline then maybe I would use an out-of-the-box'ish solution but until then I'll keep it old-school. Remember, this only took 6 minutes. No references, web.config handler configurations, etc.. Also, none of the samples that I have seen include code for updating the database which without, would really render in-line text-editing pretty useless.
Easy.
Update
Notice the naming conventions that I am using for this example. Keeping this in mind, it would be very easy to dynamically generate these controls, div's, and labels while enumerating some List<T> returned from ... Don't forget to override CreateChildControls. Maybe I'll post something later.
Tags:
AJAX,
JavaScript
I love JavaScript. At the same time I absolutely hate it. I love how when used correctly, JavaScript can provide a pleasant and intuitive user-experience. I hate how the syntax is very similar to that of C#... but only when I make stupid coding mistakes like below. When I'm not, I love the similarity.
There is a mistake in the code below. It has been fixed. To make matters worse, when writing my own JavaScript I tend to make this error at least once every couple of months.
function
ajaxFunction()
{
var ajaxRequest = null;
var url = "http://machine/app/Albums.aspx";
try { ajaxRequest = new XMLHttpRequest(); }
catch (e)
{
try { ajaxRequest = new ActiveXObject("Msxml2.XMLHTTP"); }
catch (e)
{
try { ajaxRequest = new ActiveXObject("Microsoft.XMLHTTP"); }
catch (e)
{
alert("Your browser sucks!");
return false;
}
}
}
ajaxRequest.onreadystatechange = function()
{
if(ajaxRequest.readyState == 4)
{
var document = ajaxRequest.responseXML;
var albums = document.getElementsByTagName("albums");
for (int i = 0; i < albums.length; ++i)
{
var value = albums[i].attributes[0].nodeValue;
var text = albums[i].attributes[1].nodeValue;
// do stuff with --^
}
}
}
ajaxRequest.open("GET", url, true);
ajaxRequest.send(null);
}
Usually for onreadystatechange I will have a switch statement for readystates and responses. This is stripped down for your enjoyment.
Maybe after getting into the swing of things with Visual Studio 2008 and C# 3.0 I won't be as inclined to make this stupid mistake. If this were a C# 3.0 code I could use the var keyword and still have type-safety.
Tags:
AJAX,
JavaScript
A little while back I gave a presentation for our local .NET User`s Group on non-Microsoft AJAX. By non-Microsoft AJAX I mean roll-your-own AJAX, the useful kind. One of the demos I had showed a makeshift form with user information such as favorite color, favorite band, etc... Upon submitting the form there would be appropriate updates to the SQL tables. Well, when the user (who was just updated) would get selected again (dropdownlist) the form wouldn`t reflect what was in SQL server. I would again try to change the user information and then look in SQL to make sure it worked. It did. Some members of the group suggested it was a caching issue and I decided to explore it a little more.
It turns out that Internet Explorer caches by default. If you are going to be making a lot of XmlHttpRequest then it would probably be a good idea to turn off caching. Just put the following line in your Page_Load().
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Easy.
Tags:
AJAX,
C#,
JavaScript
A couple of days ago I wrote about sorting child nodes using JavaScript. It appears that there was a small mistake and I'd like to rectify that. The code I posted was both correct and incorrect in that it did sort and to an extent it did sort numerically. I found that it wasn't a true numerical sort when I had a parent node that contained 1, 2, 3, 10. The order was actually 10, 1, 2, 3. So the following code is a TRUE numerical sort using JavaScript.
I was going to post this awhile ago but have been extremely busy with work.
function numericalSort(a, b) { return (a - b); }
function
reOrderChildren ( productGroupNode )
{
var node = null;
var path = null;
var products = productGroupNode.getElementsByTagName("product");
var items = new Array(products.length);
for ( p = 0; p < products.length; p++ )
items[p] = products[p].attributes[0].nodeValue;
// numerically sorts array (ASC);
items.sort(numericalSort);
for ( i = 0; i < items.length; i++ )
{
path = "//productGroup/product[@productId='" + items[i] + "']"
// xml is the name of my Xml Data Island <asp:Xml>
node = xml.selectSingleNode(path);
productGroupNode.appendChild(node);
}
}
Easy.
Tags:
AJAX,
JavaScript,
XML
Man have I been busy lately. Been working on a very large project that has required working from home a lot (after working at the office or on site). The main reason being I have been doing a lot of JavaScript, XML, and now AJAX components and have become completely addicted. One of the components I am working on lately ( and having the most trouble with ) is the drag and drop screen that allows child nodes to be dragged and dropped into parent nodes. The last piece of that puzzle was to figure out how to reorder child nodes numerically within a given parent node on each onDropEvent. I only care about the onDropEvent because the Xml file is generated on the server after being pulled from SQL where the ideal ordering is computed based on configurable business logic and parameters, etc, etc...
One of the things I found while browsing the Internets is that there is some really ugly JavaScript code out there. By ugly I mean sloppy and hard to read. I figured JavaScript programmers would write neater code than what I am finding. It seems as if everyone who writes JavaScript uses Systems or Apps Hungarian notation. objObject. What's up with that? The Xml I've come across in these examples isn't the prettiest either. It doesn't follow any conventions that I've seen, been taught, or use on a daily basis. Here's a quick example:
<
root>
<productGroup productGroupId="1">
<product>
<productId>1</productId>
<productPrice>5.00</productPrice>
</product>
<product>
<productId>2</productId>
<productPrice>3.21</productPrice>
</product>
<product>
<productId>3</productId>
<productPrice>0.99</productPrice>
</product>
</productGroup>
<productGroup productGroupId="2">
<product>
<productId>4</productId>
<productPrice>3.41</productPrice>
</product>
<product>
<productId>5</productId>
<productPrice>2.97</productPrice>
</product>
<product>
<productId>6</productId>
<productPrice>3.59</productPrice>
</product>
</productGroup>
</root>
Not to tell you how to use / write Xml but for what it's worth I format mine like the example below and will reference it in the sample code later on. In my honest opinion it is much easier to read and looks more convenient and elegant.
<
root>
<productGroup productGroupId="1">
<product productId="1" productPrice="5.00" />
<product productId="2" productPrice="4.40" />
<product productId="3" productPrice="3.33" />
</productGroup>
<productGroup productGroupId="2">
<product productId="4" productPrice="2.27" />
<product productId="5" productPrice="3.10" />
<product productId="6" productPrice="4.20" />
</productGroup>
<productGroup productGroupId="3">
<product productId="7" productPrice="4.76" />
<product productId="8" productPrice="3.77" />
<product productId="9" productPrice="0.59" />
</productGroup>
</root>
So now you've implemented some drag-and-drop functionality ( for whatever reason ) and you want to order your child nodes by productId or anything else that would warrant reordering child nodes. At first I started to write this very long and complicated recursive function that took in the current node and then checked to see if the attribute in question was greater than the previous and if so it would do this and if not it would continue looping and then do that.... Well I instead went with the JavaScript Array (since it has a sort method that defaults to alpha / numerical) and populated it with the child nodes of a given Product Group. Then all you need to is sort the array and then create a node and append said node to the parent. Easy.
FYI: Xml attributes are 0-based. So in this example [0] refers to productId. If you wanted to sort by price you would use [1] as the attribute index.
function
reOrderChildren ( productGroupNode )
{
var node = null;
var path = null;
var products = productGroupNode.getElementsByTagName("product");
var items = new Array(products.length);
for ( p = 0; p < products.length; p++ )
items[p] = products[p].attributes[0].nodeValue;
// numerically sorts array (ASC);
items.sort();
for ( i = 0; i < items.length; i++ )
{
path = "//productGroup/product[@productId='" + items[i] + "']"
// xml is the name of my Xml Data Island <asp:Xml>
node = xml.selectSingleNode(path);
productGroupNode.appendChild(node);
}
}
UPDATE: See also.
Tags:
AJAX,
JavaScript,
XML