Inline editing with DB write : 'REAL' AJAX with Asp.Net Series

Writing to the database inline without a postback, simple and effective

This is a continuation of my 'REAL' AJAX with Asp.Net (not Asp.Net AJAX) series posts for those of us trying to stop relying on Asp.Net 'AJAX'.

This is the first example of ajax I am showing with a write to the DB, and this will show just how easy it really is. I am building on the last post (GridView Sorting/Paging : 'REAL' AJAX with Asp.Net Series) which will explain where/how the data and display is set up.

Also, you will notice that the CSS changed a bit, that is because this plugin has a hover property that resets the backgorund of the td, and that gave a funky interface; now this can be taken care of with some jQuery magic pretty easily, but that is outside the scope of what this is trying to show.

load all of your js

I am still using jQuery and the extension dataTables but for this lesson I am using an additional plugin: inPlace. So now our script loading looks like this (notice I moved the script refs into the masterpage in this lesson):

demo.master

<script type="text/javascript"
  src="js/jquery.js"></script>
<script type="text/javascript"
  src="js/jquery.dataTables.js"></script>
<script type="text/javascript"
  src="js/jquery.inplace.js"></script>


decide what you want to be editable

Now, since we are using our Repeater table from the last example, we just modify if ever-so-slightly to make it editable, to do that, we just add the class 'editable' (or whatever you want it to be) to each td that we want to be editable. In addition to adding a class, we are going to add meaningful ids to each of the cells, each ID will contain:
  • First 4 Letters - What field is being edited
  • Following digits - 'person_id' that is being edited

if you are unsure as to why we are doing this, keep reading, it will become clear.

inline_edit.aspx

<asp:Repeater ID="rpt" runat="server" DataSourceID="lds">
  <HeaderTemplate>
    <table id="sort_table">
      <thead>
        <tr><th>First</th><th>Last</th><th>Age</th></tr>
      </thead>
      <tbody>
  </HeaderTemplate>
  <ItemTemplate>
    <tr>
      <td id="frst<%#Eval("person_id")%>" class="editable">
        <%#Eval("first_name")%>
      
</td>
      <td id="last<%#Eval("person_id")%>" class="editable">
        <%#Eval("last_name")%>
      
</td>
      <td id="age_<%#Eval("person_id")%>" class="editable">
        <%#Eval("age")%>
      
</td>
    </tr>
  </ItemTemplate>
  <FooterTemplate>
    </tbody></table>
  </FooterTemplate>
</asp:Repeater>
<asp:LinqDataSource ID="lds" runat="server"
  ContextTypeName="demoDataContext" TableName="persons" />


make them editable

Now just add in a a little jQuery magic, and the fields will be editable:

inline_edit.aspx
$(function() {
    $("#sort_table").dataTable();
    $(".editable").editInPlace({
        url: "ajax_functions/update.aspx",
        params: "ajax=yes",
        value_required: true,
        default_text: "click to edit"
    });
});

You can look on the documentation for jquery.inplace.js for a further explanation, but what we declared there is that all items that have class 'editable' will now be editable; also, requests will get sent to 'ajax_functions/update.aspx' (which we haven't made yet) and we are requiring a value.

handling the ajax

Ok, so what is happening now, is if you click on a cell you will be able to edit it, and when you click off, or hit enter (you can edit these behaviors if you want), a POST will be sent to 'ajax_functions/update.aspx'. What is sent to the page is the following values:
  • update_value - the value you edited
  • element_id - the element id that was edited
  • original_html - value from before your edit

Now we only care about the first two in this example, but the third value could also be useful given the situation; possibly returning the old value if an error occurs? Also, it may be easier to see why we included both the field and the person_id in the ID of the <td>.

For handling, you could easily have seperate pages that only handle a certain element, i.e. ajax_functions/first_name.aspx for first_name, ajax_functions/last_name.aspx for last_name, etc. but I found that if you did it that way, you ended up writing the same code over and over - so what I did was combine it into a single update handling page. That is why I include 'what' is being updated in the id.

When you make your handling page, it is important that there is no html in it to start with - as the page text itself will be what is stuffed back into the area that you edited. This is the ENTIRE ajax_functions/update.aspx page:

ajax_functions/update.aspx

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


Anything we want to appear we will just dump out a Response.Write() for. Here is the handling code, I will explain afterwards:

ajax_functions/update.aspx.cs
using System;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;

public partial class ajax_functions_update : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        demoDataContext db = new demoDataContext();
        // this is unneccessary, but more efficient since there will 
        // be more than 1 db call, this uses the same connection
        SqlConnection con = new SqlConnection(
            ConfigurationManager
            .ConnectionStrings["demoConnectionString"].ConnectionString);

        try
        {
            // get the new value
            string newValue = Request.Form[0]; 
            // figure out what is being edited (remember, its the first 4 chars in the id)
            string what = Request.Form[1].Substring(0, 4);
            // get the person_id of the changing value (the remaining digits)
            int person_id = Convert.ToInt32(
                Request.Form[1].Substring(4, Request.Form[1].Length - 4));

            con.Open();
            // get the record you want to deal with
            person p = db.persons.First(x => x.person_id == person_id);

            // change the value depending on 'what' was sent
            switch (what)
            {
                case "frst": p.first_name = newValue; break;
                case "last": p.last_name = newValue; break;
                case "age_":
                    try { p.age = Convert.ToInt32(newValue); }
                    catch { throw new Exception("not an int"); }
                    break;
                default: throw new Exception("nothing saved");
            }
            db.SubmitChanges();
            con.Close();
            Response.Write(newValue);
        }
        // this will catch all the errors and output them to the edited field
        catch (Exception ex) { Response.Write("Error: " + ex.Message); }
        finally { con.Close(); }
    }
}

First off, the code simply sets the DataContext and opens a SqlConnection (it is explained why in the code). Then, we pull from the POST values from the Request. We pull 3 things from this:
  • person_id - 'person_id' that is being edited
  • what - what field is being edited
  • newValue - the new value for the update

Now is the easy part, get the 'person' to edit, run a switch on 'what' and and update it to 'newValue'. I caught all errors and output them to Response.Write() as that is all the user will be able to see. Also, whatever is written is returned so it integrated seamlessly.  What the user will see is 'Saving...' (unless you change the text or substitute an image) and when it is complete, they will see the new value in place of the old value (which is now written to the DB) as that is what was put in the Response.Write() (if there are no errors, otherwise they'll see the error message).

There you have it! A 'real' ajax inline update with asp.net, and it was as painless as can be! There are all sorts of other things you can do like apply a js function after the update has completed, add in a nice loading graphic instead of the text, etc. - fun to play around with.

Keep in mind with this, if a user (hacker) realizes how it works, they would be able to fabricate a POST to send to your page and update it how they see fit; so be sure you test, protect against that. In one of my apps that uses this. I make sure that the record being edited belongs to the user currently logged in before they can update.

Next will be deleting a row in this table with ajax.



but...

If I have 3 'people', with last names, 'a', 'b', and 'c', and edit 'b' to now be 'z', it will not sort correctly; 'z' will not be the last one in the table, it will still be 'c'. That is because the table data that does the sorting is not held within the table itself, but in an array of 'nodes'.

I left that as a seperate download because this part dives a little deeper into the relation of the sort/edit. What needs to be done is we have to go into the array that holds the nodes, and change the related data along with the represented data in the table.

how do I do that?

This one might require reading a few times, so bear with me. First you need to set the global variables we will be using:
var oTable;
var aPos;
var editedCell;

Then, you will need to store your table in memory:
oTable = $("#sort_table").dataTable();

Now you need to intercept the click in any editable field, and get the position of it in memory; also, we will store the edited cell into a variable as well:
$(".editable").click(function() {
    aPos = oTable.fnGetPosition(this);
    editedCell = $(this);
})

What is now in aPos is a 2-element array [int row_index, int cell_index]; editedCell is the cell that you just clicked. At this point we have all the necessary values to plug the data back in. So next we tack the '.editable()' on to the end of the previous 'click()' call and add a 'success' parameter that will fire on success of the edit. Here is the completed js:

inline_edit.aspx
var oTable;
var aPos;
var editedCell;
$(function() {
    $(".editable").click(function() {
        aPos = oTable.fnGetPosition(this);
        editedCell = $(this);
    }).editInPlace({
        url: "ajax_functions/update.aspx",
        params: "ajax=yes",
        value_required: true,
        default_text: "click to edit",
        success: function() {
            var data = oTable.fnGetData(aPos[0]); 
            data[aPos[1]] = editedCell.html();
        }
    });
    oTable = $("#sort_table").dataTable();
});

Now, the table will re-sort correctly, it's a little extra work, but the added benefit is worth it IMO. Here is the download with the extended functionality (it also has the next demo in it):

Shout it

Comments (6) -

  • Another great article,

    How can we validate the edited field ? I require client side validation ...

    Thanks
  • realy great post. Thanks.

Pingbacks and trackbacks (1)+

Comments are closed