Google

Thursday, November 29, 2007

How to refer a server side control inside client side script

Referencing a control inside a user control / master page on the client side is a little complicated because Asp.Net "fudges" the ID of all controls rendered as part of a user control, or a master page template. We'll look at the "why" of this a little later in the article, but let us look at the "how" of it with an example. Create a simple User Control which has a single button btnInput :

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="UsrCtrl.ascx.cs" Inherits="A_1_UsrCtrl" %>
<script language="javascript" type="text/javascript">
    function btnClicked(btnID)
    {
        var obj = document.getElementById("<%=btnInput.ClientID %>");
        alert('There is a button named : ' + obj.id + ' on this user control');
    }
</script>

<input type="button" runat="server" id="btnInput" value="Click Me" onclick="btnClicked()" />
Pay attention to the javascript function - we're getting the object using document.getElementById( ... ), and displaying an alert box with the ID of our button. Embed this on an Aspx page, and run it. When the button is clicked, you get the following error : So what happened ? To understand, view the source of your page (you can do this by right click on the page --> View Source). Look at our button control in the rendered HTML, it looks something like this :
<input name="UsrCtrl1$btnInput" type="button" id="UsrCtrl1_btnInput" value="Click Me" onclick="btnClicked()" />
What happened ? The ID of the User Control has been prepended to the front of the button's ID. You might be wondering, "why on earth would asp.net fudge the ID of controls". To understand why, imagine a situation where you have created five different user controls, each having a label named "btnInput". When you put all these five user controls on a single page, and if Asp.Net renders all the controls as is, we would have five buttons on the same webpage, having the ID "btnInput". This would not be acceptable, since each HTML element on a web page should have a unique ID. But in this case, when the page renders, there would be no way to distinguish which button is from which usercontrol, since all of them have the same ID. That is the reason for the following rule in Asp.Net : "If a control is embedded in a user control / master page, it's ID property is prepended with the parent user control's ID when the page is rendered". That is the reason that the user control's ID has been prepended to the button's ID in our above example. But this behavior creates problems in client side script. You would normally try to refer to a control as
document.getElementById([control id]
But in our case, you can't just use the control's ID. You have to use the actual "fudged" ID of the control. To account for this behavior in our client side javascript, we can use a handy asp.net control property called "ClientID". This property is always set to the actual ID of the control on the rendered page. Which means that if the control is not inside a user control, the ClientID would be same as the ID, but if the control is inside a user control, the ClientID will be [User Control ID]_[Control ID]. For e.g. in our case, ClientID will be set to "UsrCtrl1_btnInput". Here's how to use it to get the actual ID of a page webcontrol :
    function btnClicked(btnID)
    {
        var obj = document.getElementById("<%=btnInput.ClientID %>");
        alert('There is a button named : ' + obj.id + ' on this user control');
    }
Look at the highlighed code. The magic here is the <%= ... %> block. What this does is that when the page is rendered(as html), it executes the code between the
 <%= ... %> 
, and places the result of the embedded code in its place. Please note the fact that the code blocks are evaluated after the Render page method fires. So, if you are changing the server side variable in different methods, (say, page_load, preRender, and some event handler), only the most recent value will be reflected on the client side (in our case, the value set in the preRender method). The above highlighted line looks like the following when the page is rendered :
var obj = document.getElementById("UsrCtrl1_btnInput");
Once you have this, you can access the control on the client side just like any other normal control. Interestingly, the <%= %> blocks are not limited to only control properties. You can even call the page's public methods, and the returned values will be substituted in the output HTML. For e.g., let's say we have a C# method codeBehindMethod() that always returns "TEST". The following line is gonna show an alert box with the message "TEST".
alert(<%= codeBehindMethod() %>);

Until now, we saw how to access a server side variables in client side script. However, it is also possible to run your C# code on the client side. This can be put to use to achieve some pretty sleek functionality. In my next post, we'll see how to execute C# code on the client side to achieve convenient results.

Friday, November 23, 2007

How to pass values from popup window to calling page

Passing values from popup window to parent page can get tricky sometimes, especially if you are attempting to do heavy duty stuff in the popup window. The best way to do this is to create hidden variables on the parent page, and populate these from the popup window with whatever values need to be passed to the parent. As usual, let us take an example. Suppose we have the following requirement : 1. Create an .aspx page, that opens a popup window on a button click. 2. The popup window that opens up should have a user information form that collects UserName, Address, Phone No. 3. Upon filling up the details, the user shall click a button that closes the window and passes the information to the main page. 4. The user information from the popup window should be displayed on the main page now. So basically, we need to pass values from the popup window to the parent page. Let us look at the parent page code first :

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

<!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>Untitled Page</title>

    <script language="javascript" type="text/javascript">
        function openWindow()
        {
            window.open('/website3/testpage.aspx', null, 'height=200,width=400,status=yes,toolbar=no,menubar=no,location=no');
            return false;
        }
    </script>

</head>
<body>
    <form id="form1" runat="server">
        <div>
            <input type="button" runat="server" onclick="openWindow();" value="Click Me" />
            <b>Information from the popup window :</b>
            <br />
            <label>
                UserName :
            </label>
            <label id="lblUName">
            </label>
            <br />
            <label>
                Address :
            </label>
            <label id="lblAddress">
            </label>
            <br />
            <label>
                Phone no :
            </label>
            <label id="lblPhone">
            </label>
        </div>
    </form>
</body>
</html
Run the page and click on the button to open a popup window that shows the a page TestPage.aspx. This is the aspx page for our popup window. As per the requirements, this should have some data entry fields in which the user can enter values, and also a button that closes the window. Upon window close, the user-entered values should be persisted to the main page. Look at the code below :
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="TestPage.aspx.cs" Inherits="TestPage" %>
<!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>Popup Window</title>
    <script language="javascript" type="text/javascript">
        function passValues()
        {
            ;
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <label>
                UserName :
            </label>
            <input type="text" id="txtUName" />
            <br />
            <label>
                Address :
            </label>
            <input type="text" id="txtAddress" />
            <br />
            <label>
                Phone No. :
            </label>
            <input type="text" id="txtPhone" />
            <br />
                        <input type="submit" onclick="passValues(); window.close()" value="Submit" />
        </div>
    </form>
</body>
</html>
Note that we have created the function passValues but have not yet implemented it. Now run the main page, you should see something like this : So much for laying the groundwork. The tough task now is to propagate the values that the user enters in the popup window, to the parent page. As you can guess, we'll have to first access the popup window's parent window, and once we have the parent window reference, we can get the required elements by doing the following : [parentWindow Object].document.getElementById('lblUName'); To access an element on the parent window, you can do the following : window.opener.document.getElementById('lblPhone').innerText = 'xyz'; Look at the code below to get a better idea of what we are talking about :
        function passValues()
        {
            window.opener.document.getElementById('lblUName').innerText = document.getElementById('txtUName').value;
            window.opener.document.getElementById('lblAddress').innerText = document.getElementById('txtAddress').value;
            window.opener.document.getElementById('lblPhone').innerText = document.getElementById('txtPhone').value;
        }
This function grabs the window's parentWindow's document object, and searches for the required elements (lblUName, lblAddress, lblPhone) therein. It assigns these objects the values from the popup window textboxes. Now to complicate things a little bit further, let us try to access elements of the parent page that are in an iFrame. Here's the (modified) parent page :
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default3.aspx.cs" Inherits="Default3" %>

<!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>Untitled Page</title>
.
.
.
.
            <label>
                Phone no :
            </label>
            <label id="lblPhone">
            </label>
        </div>
        <iframe src="HTMLPage.htm" width="400px" height="200px"></iframe>
    </form>
Confused ? Review the screenshot below to jog your memory : Our goal here is this : -- When the user clicks the Submit button on the popup window, the UserName, Address and Phone no. fields should be displayed on the parent page. In addition to that, the UserName should also be propagated to the iFrame's page's [lblPopupValue] label control. To achieve this, we would have to access the [frames] collection of the parent window object. To cut a long story short, here's the magical line of code : window.opener.frames(0).document.getElementById('lblPopupValue').innerText = 'blah blah'; Pardon the blah blah. Here's the complete code :
        function passValues()
        {
            window.opener.document.getElementById('lblUName').innerText = document.getElementById('txtUName').value;
            window.opener.document.getElementById('lblAddress').innerText = document.getElementById('txtAddress').value;
            window.opener.document.getElementById('lblPhone').innerText = document.getElementById('txtPhone').value;   
            window.opener.frames(0).document.getElementById('lblPopupValue').innerText = document.getElementById('txtUName').value;
        }
` And that's it. To summarize, we've successfully opened / closed a popup window, passed values to the parent window, and passed values to an iFrame embedded in the parent window.

Monday, November 19, 2007

How to call a javascript method from the server side code

Calling a javascript method from a server side control requires adding an attribute to the control. Let us take an example -- Suppose we have a simple asp.net button on our web page, and want to display a javascript alert when this button is clicked. Our first instinct would be to put the script in the .aspx page itself, like this :

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

<!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>Javascript Experiment</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:Button ID="btnClickMe" Text="Click Me" runat="server"     OnClick="alert('Clicked');" />
    </div>
    </form>
</body>
</html>
Try compiling it (in VS 2005), and you get the following error : error CS1012: Too many characters in character literal Why ? Because the [OnClick] here refers to the serverside OnClick event(the one you define in your code-behind). It does not refer to the javascript onclick event of the button. So how do you "tell" the server-side button control to call the client side onclick event ? By writing the following in your code-behind :
btnClickMe.Attributes.Add("onclick", "alert('Clicked');");
Recall that each asp.net server control is rendered as an HTML element on the page. So before including the above line in our code, here's how our button is rendered :
<input type="submit" name="btnClickMe" value="Click Me" id="btnClickMe" />
As you can see, our button [btnClickMe] is rendered as an <input /> html element. It has four attributes : type, name, value, and ID. When we add the Attributes.Add method to our code-behind, here's how the button looks :
<input type="submit" name="btnClickMe" value="Click Me" onclick="alert('Clicked');" id="btnClickMe" />
Note the [onclick] attribute rendered here. This basically causes the "alert" to be shown when the button's onclick event is raised (in other words, when the button is clicked). You can further extend this example to call a javascript method that does something meaningful on the button click. Guess it can look something like this :
btnClickMe.Attributes.Add("onclick", "someMeaningfulMethod();");
So to summarize, we don't directly call the javascript method from the server side code. Instead, we add attributes to the control so that when the control is rendered (as html) on the browser, it also contains the attributes (event handlers, in this case) we have added.

Saturday, November 17, 2007

How to access a client-side control in asp.net code behind file

Accessing client-side variables/elements on the server side is not a straightforward affair. This is because the elements you create using javascript are not "server" controls, so you cannot directly access them in your code behind code like regular asp.net server controls (by the way, server controls are controls that have the runat="server" attribute set.) To achieve this functionality, we use hidden controls. Hidden controls are HTML controls that remain "hidden" on the page. In other words, you can assign a text value to the hidden controls (just like label, textbox etc.), but their contents are not rendered on the screen. By creating hidden controls with runat="server" set, we can access them both on client-side and server-side code. Let us take an example. Suppose we have a simple .aspx page which has three elements : 1. An Asp.Net ListBox control, 2. A button for adding new options to the listbox, and 3. A button for posting back the page. Here's what it looks like :

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

<!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>Untitled Page</title>
    <script language="javascript" type="text/javascript">
        function myMethod()
        {
            var listbox = document.getElementById('ListBox1');
            var newElement = document.createElement('option');
            newElement.text = 'Test';
            listbox.options.add(newElement);
            window.event.returnValue = false;
        }       
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <input id="Submit1" onclick="myMethod();" type="submit"
                value="Add Element" />
            <asp:ListBox ID="ListBox1" runat="server"></asp:ListBox>
            <input id="Submit2" type="submit" value="PostBack Page" />
        </div>
    </form>
</body>
</html>
Run the above page. Observe that on clicking on the first button, our javascript method "myMethod()" successfully adds new elements to the listbox. However, as soon as you postback the page using the second button, the elements that you added are lost. Why ? The reason for this is that although we have added the controls on the client-side, the viewstate variable on our page hasn't changed at all. It still says that the page has only one server control - the Listbox. So while processing the page upon postback, when asp.net constructs the "tree" of all controls on the page, it does not realize that there have been a few listbox elements added to the page on the client side (because the viewstate doesn't say so). Hence, when it renders back the page upon postback, it just renders an empty listbox. To get around this, we would have to make an "entry" for the new elements in the viewstate, which is not possible because viewstate is encrypted. The second option is to use a hidden variable with runat="server", and put a comma seperated list of the new listbox elements added in this hidden variable. That way, when the page posts back, we read the value in the hidden variable and accordingly add new elements to the Listbox. Here's the modified .Aspx page for this :
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!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>Untitled Page</title>
    <script language="javascript" type="text/javascript">
        function myMethod()
        {
            var listbox = document.getElementById('ListBox1');
            var newElement = document.createElement('option');
            newElement.text = 'Test';
            listbox.options.add(newElement);
            document.getElementById('hdnListElements').value = document.getElementById('hdnListElements').value + ',' + newElement.text;
            window.event.returnValue = false;
        }         
    </script>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <input id="Submit1" onclick="myMethod();" type="submit"
                value="Add Element" />
            <asp:ListBox ID="ListBox1" runat="server"></asp:ListBox>
            <input id="Submit2" type="submit" value="PostBack Page" />
            <input type="hidden" runat="server" id="hdnListElements" />
        </div>
    </form>
</body>
</html>
Observe carefully the highlighted line. We are basically creating a comma-seperated list of all the elements added to the listbox on the clientside. On the server-side, we read these values, and populate the listbox with the newly added child elements. Here's the C# code :
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
            string str = hdnListElements.Value;
            // If the hidden variable is empty, no new elements added.
            if (!String.IsNullOrEmpty(str))
            {
                // Split the comma-seperated list into an array of strings.
                string[] strArray = str.Split(",".ToCharArray(),StringSplitOptions.RemoveEmptyEntries);
                foreach (string sTemp in strArray)
                {
                    // foreach newly added element, add a ListItem to the listbox control
                    ListBox1.Items.Add(new ListItem(sTemp, sTemp));
                }
                // Clear the hidden variable, so it does not contain the currently added elements.
                hdnListElements.Value = String.Empty;
            }
    }
}
And that is it. That's a pure and simple way to access client-side variables / controls in server-side code.

Wednesday, November 14, 2007

Adding Rows dynamically to a gridview [Part 2]

In our last post, we saw how to add a row dynamically to a gridview. But we still have to see how to persist values added on the client side to the server side. We shall not be using the recent AJAX based methodologies, instead we'll be using the good old fashioned way of persisting client side data to the server-side -- hidden variables. Before we move further, we need to make two changes to the gridview we created in our last post: 1) Add a hidden variable with runat="server", and id="hdnRowCount". This will contain the number of rows in the gridview when the gridview was created, so that we can identify which rows have been dynamically added by the user. 2) Add a hidden variable with runat="server" and id ="hdnPostAddedRows". This variable is responsible for persisting the changes we made on the client side to the server side. More on how to do this later in the article. Here is how the updated page looks like :

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        <div>
            <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="WorkOrderID"
                DataSourceID="SqlDataSource1">
                <Columns>
                    <asp:BoundField DataField="WorkOrderID" HeaderText="WorkOrderID" InsertVisible="False"
                        ReadOnly="True" SortExpression="WorkOrderID" />
                    <asp:BoundField DataField="ProductID" HeaderText="ProductID" SortExpression="ProductID" />
                    <asp:BoundField DataField="OrderQty" HeaderText="OrderQty" SortExpression="OrderQty" />
                    <asp:BoundField DataField="StockedQty" HeaderText="StockedQty" ReadOnly="True" SortExpression="StockedQty" />
                    <asp:BoundField DataField="ScrappedQty" HeaderText="ScrappedQty" SortExpression="ScrappedQty" />
                    <asp:BoundField DataField="StartDate" HeaderText="StartDate" SortExpression="StartDate" />
                    <asp:BoundField DataField="EndDate" HeaderText="EndDate" SortExpression="EndDate" />
                    <asp:BoundField DataField="DueDate" HeaderText="DueDate" SortExpression="DueDate" />
                    <asp:BoundField DataField="ScrapReasonID" HeaderText="ScrapReasonID" SortExpression="ScrapReasonID" />
                    <asp:BoundField DataField="ModifiedDate" HeaderText="ModifiedDate" SortExpression="ModifiedDate" />
                </Columns>
            </asp:GridView>
            <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:AdventureWorksConnectionString %> "
                SelectCommand="select top 10 * from Production.WorkOrder"></asp:SqlDataSource>
        </div>
        <input type="hidden" runat="server" id="hdnRowCount" />
        <input type="hidden" runat="server" id="hdnPostAddedRows" />
    </form>
</body>
</html>
Next, let us revise the problem we're trying to solve here : We are adding new rows to a gridview using client side script. These rows are not accessible on the server side, and there are no events being raised for these new rows on the server side. Our objective is to persist the rows that have been added, to the database. So, the question here is, "How do we access controls added to the page on the client side using Javascript, in our server side (or C# / VB.Net) code ?" To solve this, we are going to create a pipe-delimited string for all the values inserted on client side, and put that string in the hidden variables created above. For e.g., if the following three rows were added to our gridview Saurabh 45 Testing Data Description Swati 89 Some more description TestName 2322 End of new rows We shall convert this into the following string : Saurabh45Testing Data Description~~Swati89Some more description~~TestName2322End of new rows In the above case, the character "" is the column seperator, while the character combination "~~" is the row seperator. Once we put this string in one of the hidden variables created above, and post back the page. Upon postback, on the server side, we can still access the hidden variable, since we declared it with runat="server". We shall read values from the hidden variable, and run an insert query for each row inserted into the gridview. First, in your Page_Load method, populate the number of rows in the gridview in the hidden variable we created above (hdnRowCount)
protected void Page_Load(object sender, EventArgs e)
{
    hdnRowCount.Value = GridView1.Rows.Count;
}
Next, add a button "Save" to the page that triggers the save process :
<asp:Button ID="btnSave" runat="server" OnClick="btnSave_Click" Text="Save" />
Also create a server side event handler btnSave_Click. Next, create a javascript function that will construct the pipe-delimited string we discussed above. Here's a short pseudocode :
{
    1. Get the number of rows originally present in the gridview using the hidden variable hdnRowCount
    2. Create a variable strExpression, and set it to blank ""
    2. Loop through all the newly added rows
    {
        Note : Ignore the first cell, since this is blank.
        3. Read the second cell, append a "" character at the end
        4. Read the third cell, append a "" character at the end
        .
        .
        .
        .
        8. Read the eigth cell, append a "" character at the end
        9. Append a row seperator character, "~~" at the end of the row
        10. Append the string created above to strExpression variable.
    }
    10. strExpression is the string we want. Assign this to the hidden variable "hdnPostAddedRows" so we can access this on the server side.
}
Before we dive into the code, let us ponder over one question. How do we access the values the user has inserted in the table cells. For this, we need to access the individual textboxes in the table cells. We can do this using : table.rows([rowcount]).cells([cell number]).childNodes(0).value This would first access the cell using table.rows([rowcount]).cells([cell number]), and extract the value of the first child of the cell (childNodes(0).value). Again, if one of the columns is a checkbox, you cannot do a .childNodes(0).value to get its value. The reason being, a checkbox does not have a "value" property. Instead, it has the "checked" property. So, to access a checkbox in a table, we need to use : table.rows([rowcount]).cells([cell number]).childNodes(0).checked Let us look at the code now :
            function fn_BuildPostback()
            {
                var rowCount = 0;
                var strExpression = '';
            var table = document.getElementById("GridView1");

    //        1. Get the number of rows originally present in the gridview using the     //        hidden variable hdnRowCount
                var originalRowCount = parseInt(document.getElementById("hdnRowCount").value);
           
            if ( originalRowCount == table.rows.length )
            {
                // No new rows have been added
                return "";
            }
 
    //    2. Loop through all the newly added rows            
                for ( rowCount = originalRowCount - 1; rowCount < table.rows.length ; rowCount++ )
                {
                    var str = '';
                    var operator = '';
                 
                    var tr = table.rows(rowCount);
        // Ignore the first column, move to the second one.
                    str = str + tr.cells(1).childNodes(0).value + '';                
                    str = str + tr.cells(2).childNodes(0).value + '';                
                    str = str + tr.cells(3).childNodes(0).value + '';                
                    str = str + tr.cells(4).childNodes(0).value + '';                
                    str = str + tr.cells(5).childNodes(0).value + '';                
                    str = str + tr.cells(6).childNodes(0).value + '';                
                    str = str + tr.cells(7).childNodes(0).value + '';                
                    str = str + tr.cells(8).childNodes(0).value + '';                

               strExpression = strExpression + str + '~~';
                }

                10. strExpression is the string we want. Assign this to the hidden variable "hdnPostAddedRows" so we can access this on the server side.
                document.getElementById('hdnPostAddedRows').value = strExpression;
            }

The above function just reads the dynamically added rows of the table one by one, and consolidates the contents of the rows into a single, pipe-delimited string. We assign this string to the hidden variable 'hdnPostAddedRows', which is also accessible on the server side in your c# code. The next thing that needs to be done is to call this function when the user clicks the button "btnSave". That is easy, just add the following line at the end of your page_load method : btnSave.Attributes.Add("onclick", "fn_BuildPostback();") If you run the code now, here's the sequence of events : 1. You dynamically add rows to the gridview. Enter some test values, and hit "Save" button. 2. That would create the pipe-delimited string, assign it to the hidden variable, and finally post back the page. 3. Upon postback, the btnSave_Click event handler is called. Inside the event handler, we can write code to split the string into individual rows, and fire insert statements (or stored procedures, as in this case) for each row. Here's a sample code that you might want to build upon :
    protected void btnSave_Click(object sender, EventArgs e)
    {
        string hdnExpr = hdnPostAddedRows.Value;
        int count = 0;
        int originalRowCount = Convert.ToInt32(hdnRowCount.Value.ToString().Trim());
     
        string [] exprArray = hdnExpr.Split("~~".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
        string [] rowArgs;
        SqlConnection conn = new SqlConnection(@"Data Source=wwwddsd331337\SQLExpress;Initial Catalog=TestDB;User Id=sd;Password=xyz;");
        conn.Open();

        foreach (string expression in exprArray)
        {
            // If this is an extra row added on client side
            if (count + 1 > originalRowCount)
            {
                SqlCommand command = new SqlCommand("re_InsertRow", conn);
                command.CommandType = CommandType.StoredProcedure;
                SqlParameter param;
                rowArgs = expression.Split("".ToCharArray());

                param = new SqlParameter("@workorderid", rowArgs[0]);
                param.Direction = ParameterDirection.Input;
                param.DbType = (DbType)SqlDbType.Int;
                command.Parameters.Add(param);

                param = new SqlParameter("@productid", rowArgs[1]);
                param.Direction = ParameterDirection.Input;
                param.DbType = (DbType)SqlDbType.Int;
                command.Parameters.Add(param);

                param = new SqlParameter("@orderqty", rowArgs[2]);
                param.Direction = ParameterDirection.Input;
                param.DbType = (DbType)SqlDbType.VarChar;
                param.Size = 30;
                command.Parameters.Add(param);

                param = new SqlParameter("@stockedqty", rowArgs[3]);
                param.Direction = ParameterDirection.Input;
                param.DbType = (DbType)SqlDbType.VarChar;
                param.Size = 7;
                command.Parameters.Add(param);
            .
            .
            .
            .
            .
            // Add parameters for all columns
                }

                command.ExecuteNonQuery();
            }
            count++;
        }
    // Call a method to bind the gridview again.
        LoadGridData();
    }
That's it !! You've created a gridview with client-side insert functionality. Give yourself a pat on the back, and be prepared for all the good comments ur gonna receive from your website users for making their life easier.

Sunday, November 11, 2007

Adding client side insert update delete functionality to a Gridview

All of us have, at some point or the other, worked with providing Insert / Update / Delete functionality in a datagrid, and you can find quite a few good articles on this topic. Each approach uses some sort of server side code when modifying rows of a datagrid. There is nothing wrong with this as such, but it could become frustrating for the user because of the continous round trips to the server when modifying rows. We have decided to start a series of tutorials explaining the task of building up an client side insert / update / delete gridview.

We have split the operation of dynamically adding rows to a gridview into two phases : 1. Create a javascript function that dynamically adds a new row to the gridview. 2. Create a button on your page that calls this function when clicked.

Simple, huh. To get started, create an aspx page as below, and populate the gridview with records from the AdventureWorks.Production.WorkOrder table (You can use any other database, but then you would have to modify the code below to work with the columns in your database). Please be sure of the following factors when you create the gridview : a) The query for your SQLDataSource should be the following : "select top 10 * from Production.WorkOrder" This will limit the number of rows returned, and save you time unnecessarily waiting for the page to load, in addition to putting less burden on your database. b) Disable paging for the gridview, so that we do not have a pager at the bottom of our gridview. c) Make sure that you are not using master pages, or the gridview is not part of a user control. This is because when a control is placed inside a parent control, user control, or a master page, it's HTML id is prepended with some string identifying the parent. More on this in a later post. If you do not know how to do this, please refer to this documentation on msdn to make sure the above simple conditions are met. Note that we have also added an html button named "btnAddRow" with runat = "server" set. Once done, here's what our page looks like.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default2.aspx.cs" Inherits="Default2" %>

<!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>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <input type="button" id="btnAddRow" value="Add" runat="server" />
    <div>
        <asp:GridView ID="myGridView" runat="server" DataSourceID="SqlDataSource1">
            <Columns>
                <asp:TemplateField>
                    <HeaderTemplate>
                        Modify
                    </HeaderTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>
        <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:AdventureWorksConnectionString %>"
            SelectCommand="SELECT top 10 * FROM [Production].[WorkOrder]"></asp:SqlDataSource>
    </div>
    </div>
    </form>
</body>
</html>

Test the page by running it once, the output should show 10 rows from the AdventureWorks.Production.WorkOrder database table.

Note that I have added a template column as the first column, so that the first column always shows empty when the gridview is rendered on browser. This column has been intentionally left blank so we can show the insert / update / delete buttons.

Next, take a moment to inspect the HTML code of your application. You can do this by right clicking on your page and choosing "View Source". Our gridview now looks like this (text clipped for brevity) :

<div>
    <table cellspacing="0" rules="all" border="1" id="myGridView" style="border-collapse:collapse;">
        <tr>
            <th scope="col">
                        Modify
                    </th><th scope="col">WorkOrderID</th><th scope="col">ProductID</th><th scope="col">OrderQty</th><th scope="col">StockedQty</th><th scope="col">ScrappedQty</th><th scope="col">StartDate</th><th scope="col">EndDate</th><th scope="col">DueDate</th><th scope="col">ScrapReasonID</th><th scope="col">ModifiedDate</th>
        </tr><tr>
            <td>&nbsp;</td><td>1</td><td>722</td><td>8</td><td>8</td><td>0</td><td>7/4/2001 12:00:00 AM</td><td>7/14/2001 12:00:00 AM</td><td>7/15/2001 12:00:00 AM</td><td>&nbsp;</td><td>7/14/2001 12:00:00 AM</td>
    </table>
</div>
This reiterates the fact that our gridview is actually rendered as an html table in the user's browser. It also means that if we want to dynamically add some rows to the gridview, we are actually adding rows to an HTML table. Our goal is that when the user clicks on the "Add" button that we created in our aspx page, a new row should be dynamically added to the gridview. For this, I have created a short pseudocode which we could base our code upon.
{
    // 1. Get the <table> element corresponding to the gridview from the page's DOM.

    // 2. Insert a new row in the table fetched in step 1.

    // 3. Insert an empty cell for the first "Modify" column

    // 4. Insert an empty cell for "WorkOrderID" in the row created in step 2. above.
        // In the cell created above, add a textbox in which the user will input the workorderID value he wishes to insert.
    // 5. Insert an empty cell for ProductID in the row created above.
        // In the cell created above, add a textbox in which the user will input the ProductID value he wishes to insert.
    // 6. Insert an empty cell for OrderQty in the row created above.
        // In the cell created above, add a textbox in which the user will input the OrderQty value he wishes to insert.
    .
    .
    .
    .
    // N. Insert cells similar to the above for the remaining columns (StockedQty,ScrappedQty,StartDate, EndDate, DueDate, ScrapReasonID, ModifiedDate)
}

So, basically we're getting the table object from the DOM, and adding a new row and new cells dynamically using javascript. Before you look at the code, recall that you can add a child element to a parent element using the parentElement.appendChild(childElement) method. But if you are adding a new table row (child), to a table (parent), you can directly use the tableName.insertRow() method. Similarly, to add a new table cell to a row, use the tableRow.insertCell() method. Let us see the code now :

            function fn_AddFilterRow()
            {
        // 1. Get the <table> element corresponding to the gridview from the document
            var table = document.getElementById('myGridView');

        // 2. Insert a new row in the table fetched in step 1.
                var newRow = table.insertRow();

        // 3. Insert an empty cell for the first "Modify" column
                // Column 1 : An empty cell for the "Modify" column
                var newCell = newRow.insertCell();
                newCell.appendChild(btnDelete);

        // 4. Insert an empty cell for "WorkOrderID" in the row created in step 2. above.                                    
                // Column 1 : WorkOrderID
                newCell = newRow.insertCell();
        // In the cell created above, add a textbox in which the user will input the  workorderID value he wishes to insert.
            var newTextBox = document.createElement('input');
            newTextBox.type = 'text';
            newCell.appendChild(newTextBox);
    
        // 5. Insert an empty cell for ProductID in the row created above.
                // Column 2 : ProductID 
                newCell = newRow.insertCell();
        // In the cell created above, add a textbox in which the user will input the ProductID value he wishes to insert.
            var newTextBox = document.createElement('input');
            newTextBox.type = 'text';
            newCell.appendChild(newTextBox);

        // 6. Insert an empty cell for OrderQty in the row created above.                                
                // Column3 : OrderQty
                newCell = newRow.insertCell();
        // In the cell created above, add a textbox in which the user will input the OrderQty value he wishes to insert.
            var newTextBox = document.createElement('input');
            newTextBox.type = 'text';
            newCell.appendChild(newTextBox);
        .
        .
        .
        .
        .
        .
        .
        // Do the same for the remaining columns.

        }

The last step here is to write some code so that when the button "btnAddRow" gets clicked , our javascript function fn_AddFilterRow gets called. This is easy. Go to your code behind page. In the Page_Load method, add the following line

 btnAddRow.Attributes.Add("onclick", "fn_AddFilterRow();");

That's it. Let it rip, and see for yourself. Launch the page in IE, and click on the button to add a new row to the gridview.

Note that each time you click on the button, you'd get a new row added to the gridview, which is pretty cool if users want to add multiple rows at a time.

Okay, that's half the battle won. The other half is accessing the contents of the newly inserted row on the server side, and eventually inserting the row into the database. We shall discuss that in our next post.

Just so you know, the above is a very basic implementation of the client side editing capability. Notably, you might want to experiment a little bit to fix the following flaws 1. The new row always comes in as a standard with no formatting applied. But if the table has some formatting applied, your new row should also have the same color, font size, alignment etc. as the other gridview rows. 2. Currently we're adding textboxes for all columns in the new row. But there may be situations where you would want the user to choose from a dropdown list, or a checkbox (e.g. a column for sex can have values Male / Female / Yes, Please ;) ). Our gridview does not handle this. 3. If there was a footer/pager present, the code would have to be adjusted. The reason being the new row is always added after the last row of the <table&gr;. The new row should ideally be added just above the footer, not below it. 4. Code does not handle the situation where the gridview is empty, i.e. no rows are being displayed.

Nevertheless, this is a good starting point and you can definitely build up on the fundamentals you learnt here. In the next post, we'll complete the circle by persisting the row entries we added here to the database.

Wednesday, November 7, 2007

How to add HTML controls / elements dynamically to a page

How to add HTML controls / elements dynamically to a page ------------------------------------------------------------------------------------------------ Hmm .... now there's a familiar javascript problem. You know what javascript is ... know how it works ... know that you can manipulate controls, but how do you actually add new elements to the page using JS. To revise our principles and beliefs -- The javascript hierarchy looks like this : Document --> HTML --> Body --> Page Elements. The above expression omits one essential fact though - "Controls on a page can be children of other controls on the page". Confused ? Let's look at an example :

<html>
<head>
<title>Untitled Page</title>
</head>
<body>
<div id="myDiv">
<input type="button" id="btn" value="Click Me" />
</div>
</body>
</html>
In the above html, the <input> element with id = "btn", is a CHILD OF the <div> element.Getting the hang of it ??! Let's fudge up things a little bit
<html>
<head>
<title>Untitled Page</title>
</head>
<body>
<div id="myDiv">
<input type="button" id="btn" value="Click Me" />
<div id="childDiv">
<h id="childH"> Child </h>
</div>
</div>
</body>
</html>
Again, the hierarchy we have is : Meaning, myDiv element has two children - btn, and childDiv. In turn, childDiv has one child of its own, called childH. Now do you get the hang of it ??!!. If not, look at my earlier post on debugging javascript, and try debugging this piece of HTML script. Pay close attention to the "children" properties of the elements. Please note that the parent-child relationship determines how elements are displayed on the page. In the above case, the button "myBtn" will be shown "inside" the myDiv element's area on screen. Again, the childH element will be shown inside the childDiv area, which in turn falls inside the area for myDiv, which in turn falls in the area for <body> . When an element is child of the <body> element, it is not shown as a part of any other control, because <body> tag stands for the entire browser area. More on this in a later post. All right, now about adding elements dynamically. There are roughly two situations where you would be adding an element to an html page : 1) Adding an element directly to the page's <body>. 2) Adding an element as a child of some other element. Let us look at case # 1 first - Adding an element to the <body> tag. Theoretically, this is the same as the case # 2, with the only difference that the element that you add is a child of the <body> element. First, we need to define an event. An event is (usually) some kind of action that the user will perform on the browser while viewing a web page. The action can be a button click, or moving the mouse or even typing something in a textbox. The reason we're associating an event here is to allow the user to trigger the process of adding a control dynamically. Meaning, the javascript that dynamically adds the controls to the page should be called only when the user performs that action.
<html>
<head>
    <title>Untitled Page</title>
</head>
<body>
    <div id="myDiv">
        <input type="button" id="btn" value="Click Me" />
        <div id="childDiv">
            <h id="childH"> Child </h>
            <input type="button" id="addCtrl" value="Add a new Elt" onclick="var elt = document.getElementById('childDiv');
var newElt = document.createElement('LABEL');
newElt.innerText = 'I was just added';
elt.appendChild(newElt);" />
        </div>
    </div>
</body>
</html>
Ok, so here we go. First, look at the line beginning with "onclick". This is like telling the browser, "Whenever the user does a click on this button (that is, clicks the button), run this piece of javascript". Simple, huh. And lets dissect the javascript -- var elt = document.getElementById('childDiv'); You should be familiar with this. If not, look at my post on javascript debugging. This is fetching the object for childDiv, and assigning to the variable 'elt'. Next up, var newElt = document.createElement('LABEL'); Here, we are creating a new element of type 'label'. Usually, when you need to display some plain text on the screen, we use this type of element. The new element is now stored in the 'newElt' variable. Next, newElt.innerText = 'I was just added'; This is basically setting a property for that control, so that when the object is shown on the browser, it writes "I was just added" on screen, and we know that it exists. elt.appendChild(newElt); Now this is the most critical part. We are adding the element we created above using the 'createElement' method, to the element that elt refers to. Meaning, we are adding the newly created element 'newElt' to the 'childDiv' element. Once done, the hierarchy would look like this : You can verify this by running the page. Initially you will not see any label text. As soon as you click the button, the text "I was just added" will appear. I would also advise that you look at what is happening by debugging the javascript. In case you are not familiar with debugging javascript, you can refer to my earlier post. Hmm ... that was fun and easy. Next time, we will look at a related problem - dynamically building html tables. Keep learning.

To add value in Hashtable from SQLReader

If you want to add the values to HashTable which you are reading through SQLDataReader , then this is what you have to write, Hashtable myHash =new Hashtable(); while(reader.Read()) { string type = reader.GetString(0).Trim(); int num = reader.GetInt32(1).Trim(); myhash.Add(num,type); } Now if you want to add more than one values to Hashtable while reading it from a SQLDataReader, you will have to put the values in a class and make an object of it to insert in the value for Hashtable. It goes like this : public class myClass { private string sType; private int sNumber; private Datetime stimeType; public myClass(string Type,int Number,Datetime timeType) { sType = Type; sNumber = Number; stimeType = timeType; } public string getType() { return sType; } public int getNumber() { return sNumber; } public Datetime gettimeType() { return stimeType; } } public void readVal() { Hashtable myHash =new Hashtable(); while(reader.Read()) { string type = reader.GetString(0).Trim(); int num = reader.GetInt32(1).Trim(); Datetime time= reader.GetDateTime(2); myClass myObj = new myClass(type,num,time); myhash.Add(num,myObj); } }

Tuesday, November 6, 2007

How to debug the javascript DOM [Part 2]

Well, let me summarize what we understood until now

A] Whatever you see on the browser (including this page), is all pure HTML

B] When the browser "draws" the HTML as buttons, textboxes, colors etc., it also creates a hierarchy of objects called the DOM model.

C] The elements are in this order : HTML --> BODY --> Other Elements

You might be saying "All right, I get it ... but is there a way I can physically see this hierarchy ??". The answer is Yes. But, you should have either Visual Studio or Microsoft Script Editor installed on you machine. Let us see how.

First, create a simple test page called HelloWorld.html. Here's the text

<html> <head> <title>Untitled Page</title> </head> <body> <div id="myDiv"> <input type="button" id="btn" value="Click Me"> </div> </body> </html>

Type the above in a simple notepad and save it as HelloWorld.html. Next, open this page in internet explorer by double clicking on it. You'll see something like this : For debugging purposes, we would have to add some javascript to this page. Let's tweak the HTML slightly :

<html> <head> <title>Untitled Page</title> </head> <body onload="var theDiv = document.getElementById('myDiv');"> <div id="myDiv"> <input type="button" id="btn" value="Click Me"> </div> </body> </html>

As you can see, I have added some javascript to the page (now you know what it looks like). Let's dissect it a little bit before diving into how to debug it. <body onload="XXXXXX" .....

The above line is like effectively telling the browser that "When you start drawing this <body> element, that is, when you "load" this body element, before doing anything else, execute the statements given by XXXXXXX". So, as soon as the browser starts drawing the <body> element on the page, it stops the drawing and first starts executing the javascript. Let's move to the Javascript now. var theDiv = document.getElementById('myDiv'); var -- whenever you create a variable in javascript, the type has to be var only. No matter if it's an int, float, date, even an object, the Type is always VAR. So we declare a variable called "theDiv". Before you wonder what document.getElementById(...) means, let me remind you the hierarchy. HTML --> BODY --> Other Elements

Keep in mind all the page elements (including HTML, and BODY), are part of the page's document. Thats one of the reasons the DOM stands for Document Object Model. So effectively, here's the object hierarchy diagram for any HTML page : So what document.getElementById('myDiv') is doing is searching for an element called "myDiv" in the document object's children. That is gonna return the <div> element that we created in our HTML, and this value is assigned to the variable theDiv.

Whew, so far so good. Now let's get our hands dirty with some debugging stuff. Open the page in a browser, go to Tools -- > Internet Options --> Advanced --> Browsing section. Uncheck the "Disable Script Debugging (Internet Explorer)" and "Disable Script Debugging (Other)" checkboxes. Next, go to your page, from menu choose view --> Script Debugger --> Break at next statement Once done, refresh the page. Immediately you should see the a window similar to the one below :

Choose "Yes", a new session of Visual Studio / Script debugger will open. Once open, you'll see a window like this :

The yellow arrow on the left tells you on which line the debugger is currently at. --Select the text "document.getElementById('myDiv') as shown above. --Right click on the text and choose "Add Watch". The add watch command is used to add the chosen expression to a Watch window, where you can actually see what it's value is. As the program runs, you can see the value change on the fly. On choosing "Add Watch", you'll see a window like this (it usually pops up at the bottom of the Visual Script Debugger window) :

Expand the selection by clicking on the [+] symbol, and voila, you're looking at the internals of the "myDiv" object. Remember, this is actually the Object Representation of the page's myDiv element. If you change this object, the page myDiv will also change, and vice versa.

Let's make sense of some of the more important properties -->

-- id : The id of the element (in our case, "myDiv")

-- tagName : The TYPE of the element, whether it's a <div> or an <input> element, or is it <document>, or <body> ... get the point ?!!

-- parentElement : The parent of the current element. In our case, myDiv is a child of the <body> element object (remember the hierarchy ?). So the parentElement should be the document. To confirm, expand the parentElement by clicking the [+] sign, and check out the tagName property of the parent Element. Sure enough, it says "BODY".

Before going further, scroll down to the "document" element and expand it. Here's what you see : This is the page's document object, which houses all other page element object (including <html>, <body> etc. If you look carefully, you'll see it also contains Scripts, images, links, frames etc. page elements. More on this later.

Next up, OffsetLeft, offsetTop, offsetWidth, offsetHeight : Determine the position of the element on the page.

InnerHTML : The html inside this element

InnerText : If you wanna write some text in an element, you use this.


.
.
.
.

There's a hundred other properties that I'd like to go through, but let's stop here for lack of space and time. For more info, you can always checkout the description for <div> element on the MSDN site http://msdn2.microsoft.com/en-us/library/ms535240.aspx.

To cut a long story short, if you want to look at the internals of the various objects on an HTML page, you can do so by starting to debug the page and looking at various page objects. In my next post, I'll look at the internals of some of the most common operations that you need to perform in javascript while creating websites.