Friday, 30 December 2011

Christmas Lists - legacy ASP.Net, JQuery and database constraints.

Letters to Santa

Mr and Mrs Geek have trained their kids, nephews and nieces and grandchildren to write to Santa using a home grown system, developed over a number of years. It saves the letter using the person's name. If people want to edit the letter (if they think of new presents they would like) they can see a list and choose one. Of course, all people on the list think they been good!

The database has been set up with a unique constraint on the person's name. It would be terrible if Santa mixed up Mr Geek's brother's son John G and Mrs Geek's sister's boy John J. Also, the kids want completely different items each year - no babyish stuff for them - so overwrite last year's letter completely. The adults on the other hand are delighted to add each year to their collection of jokey T-shirts.

Well, what with all these new great-nephews and great-nieces added to the tribe, the system has to be a bit better at telling which are new letters and which are edits of old ones. People get stuck when they have written a new letter, try to save it, and find there is already one with the same name.
In Christmases past, the parents have tried supplying one save button for a new letter and one for an old - it was tough on the old grey memory cells. They've let the server come back with an exception - that was fine for a genuine conflict, but the interface was clunky when the youngster really did want to replace the old list.

So this year they're adding a new feature 'The letter is already written. Are you sure you want to replace it?' They didn't want to ask their family to click a replace-please button, then click again to actually do the replace.

While they would love to rewrite the whole thing in the shiny new MVC technology, there are puddings to stir, cakes to ice, mince pies to cut out...

Playing with the new feature

They toyed with the idea of using the exactly the same hidden field mechanism as ASP.Net, something like the following snippet
  function __doPostBack(eventTarget, eventArgument) {
    var form = $(document).forms[0];
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
      theForm.__EVENTTARGET.value = eventTarget;
      theForm.__EVENTARGUMENT.value = eventArgument;
      theForm.submit();
    }
  }
and set the button OnClientClick="__doPostBack('resubmitme', 'update');"

Santa was watching. Stealing ASP.Net's fields is not being good. So the two senior Geeks went to play with JQuery instead.

Their system still lets the database throw an exception if someone tries to insert a duplicate. Doing an attempted retrieve before an attempted insert just didn't feel right, and might put more strain on an  ancient network and database server.

So it now makes some changes to the form, asks the question, and resubmits itself if that's confirmed.

Cutting the aspx

They made sure the JQuery library was included. Then added:

a javascript function at the top of the file (easier to debug that way) that asks the question. If the user is happy, it changes a hidden field, to be read by the server when the javascript resubmits.
  function ConfirmUpdate(duplicateNameMessage, btnAddClientID) {
    var element = $(document).find('.message-error')[0];
    if ($.trim(element.innerText)== duplicateNameMessage) {
      if (confirm(duplicateNameMessage +'. Do you want to replace it?')){
        var txtShouldUpdate = $(document).find('#txtShouldUpdate')[0];
        txtShouldUpdate.value = 'true';
        var btnAdd = $(document).find('#' + btnAddClientID)[0];
        btnAdd.value = 'Replacing...';
        btnAdd.click();
      }
    }
  }
a text box for a name
              <asp:TextBox runat="server" ID="txtNewName" Visible="true"> </asp:TextBox>
a button
              <asp:Button runat="server" ID="btnAdd" CssClass="seasonalbutton"
                      OnClick="btnAdd_Click" 
                         ToolTip="some Help" />
a hidden field
             <input id="txtShouldUpdate" name="txtShouldUpdate" type="hidden"  /> 
and an error block
           <div class="message-error">
                <asp:Literal runat="server" ID="ctlAddError" EnableViewState="true"></asp:Literal>
            </div>

The code behind

btnAdd_Click() inspects the hidden field to see if it should update or insert. TryInsert() usually 'just works'. However a duplicate name exception on calls AddJQueryScriptToConfirmReplace(), which wires up the document.ready event and triggers our client side dialogue.

    protected void btnAdd_Click(object sender, EventArgs e)
        {
          string name = txtNewName.Text.Trim();
          if (string.IsNullOrEmpty((string)Request.Params.Get("txtShouldUpdate")))
          {
            TryInsert(name);
          }
          else
          {
            TryUpdate(name);
          }
        }

    private void TryInsert(string name) //TryUpdate() is left as a puzzle for your stocking
    {
      try
      {
        var saved = InsertWithName(name);
        if (saved != null)
        {
          RedirectToSavedLists();
        }
      }
      catch (DuplicateNameException dupExc)
      {
        var message = dupExc.Message;
        ctlAddError.Text = Server.HtmlEncode(message);
        AddJQueryScriptToConfirmReplace(DuplicateNameMessage(name));
      }
      catch (Exception exc)
     {
        var message = exc.Message; // any other exception
        ctlAddError.Text = Server.HtmlEncode(message);
        }
    }

    private void AddJQueryScriptToConfirmReplace(string duplicateNameMessage)
        {
          BindJQuery();
          StringBuilder jsStart = new StringBuilder();
          jsStart.AppendLine("<script type=\"text/javascript\">");
          jsStart.AppendLine("$(document).ready(function() {");
          jsStart.AppendLine("ConfirmUpdate('" + duplicateNameMessage + "','" + btnAdd.ClientID  + "');");
          jsStart.AppendLine("});");
          jsStart.AppendLine("</script>");
          Page.ClientScript.RegisterClientScriptBlock(GetType(),
               "yourscriptblock",
                jsStart.ToString());
        }
 private string DuplicateNameMessage(SQLException exc,string name)
    {
      return "The name " + name + " already exists";
 // Why? because its the server's responsibility to work out what the exception message should be.
    }

Notes: Edited to remove first moves towards discussing database structures and what if there are child elements, say a PresentRequest table. That needs a blog entry of its own, another time.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.