Amazon.com Widgets All posts tagged 'submit'

WilliaBlog.Net

I dream in code

About the author

Robert Williams is an internet application developer for the Salem Web Network.
E-mail me Send mail
Code Project Associate Logo
Go Daddy Deal of the Week: 30% off your order at GoDaddy.com! Offer expires 11/6/12

Recent comments

Archive

Authors

Tags

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.


A Virtual Form Web Custom Control

I recently ran into an issue where a site I was developing had form fields in the header area of the page (for logging in or searching) and that if I had my cursor in a form field further down the page and I hit the enter key, it it was the click event of the first button on the page (the button in the header, rather than the button I wanted it to use) that fired. The setup was a common one: the master page contains a form tag with a runat="server" attribute and that form wraps the entire content of the page. It all seemed to work fine, until I found myself on the registration page and instead of clicking the register button I just hit enter. Instead of registering me, a message appeared to explain that a username and password were required. Since those fields also exist on the registration form I was puzzled, more so when I used the mouse to click on the 'Register Now' button and it all worked. Stepping through the code I quickly realized what was happening, and rummaged around my code archives until I found some JavaScript I wrote a couple of years ago that would listen for the enter key, cancel the default behavior, and call another method. I added this snippet to the page and all was well, until I found another page with the same issue. It was at that point I thought "wouldn't it be nice if there was a control - like a panel control - that you could simply use to wrap some input controls, set a single property (to the id of the control that should be 'clicked' when the enter key is pushed), and that was all you needed to do?".

Well now there is such a control.

Edit: Actually, the standard asp Panel control has a "DefaultButton" property which implements similar functionality, however it only allows you to use asp button controls as your designated button. If you want to use an asp LinkButton control or some other type of control as your default button it does nothing for you. So if you are using asp Button Controls exclusively, I recommend you use that property. If not, then read on...

Using the control

You can register the control on a page by page basis as needed or you can add it to the <Pages> section of the web.config file to make it available to any page on the site (Listing 1).

Listing 1

  <pages enableSessionState="true" enableViewStateMac="true" enableEventValidation="true">

      <controls>

        <add tagPrefix="WBN" namespace="WilliaBlog.Net.Examples" assembly="WilliaBlog.Net.Examples"/>

      </controls>

  </pages>

Then add the control to the page in such a way that it wraps your inputs:

Listing 2

  <WBN:VirtualForm id="vf1" runat="server" SubmitButtonId="Button1" UseDebug="false">

    <asp:TextBox ID="TextBox1" runat="server"></asp:TextBox> <asp:Button ID="Button1" runat="server" Text="Button1" OnClick="Button1_Click" />

  </WBN:VirtualForm>

As you can see from Listing 2 you should use the Server side ID of the button (this will be automatically converted to the clientside ID by the virtual form control. Test it live.

How It works

The actual server side Virtual Form web custom control is really very simple. It inherits System.Web.UI.WebControls.Panel and contains a property to allow you to set the id of the button or linkbutton you want to push when hitting Enter. I chose to embed the Javascript file into the dll for the sake of portability. (Originally I had it in an external .JS file that was added to the header in the master page, but if I - or somebody else - wanted to use this control on another site, that adds an additional layer of complexity in the setup - they have to remember to load that Javascript file and while we could host it in a central location for all sites to share, embedding the file in the dll seemed the wisest choice.) The downside to this technique is that the js file has to travel over the wire every time the page is requested and cannot therefore be cached on the client, which will negatively impact any low-bandwidth users of the web application. For that reason, I have used the YUI Compressor to strip out all the comments and additional whitespace and included 2 versions of the script in the dll. When you set the property UseDebug to true, it will use the long verbose version which makes it much easier to debug in firebug, but when it is all working, use the compressed version by omitting this property from the control declaration or by setting it to false.

To make files accessible from your server control’s assembly, simply add the file to your project, go to the Properties pane, and set the Build Action to Embedded Resource. To expose the file to a web request, you need to add code like lines 7 and 8 in Listing 3 below. These entries expose the embedded resource so that the ClientScriptManager can both get to the file and know what kind of file it is. You could also embed CSS, images and other types of files in this way. Note: The project’s default namespace (as defined in the Application tab of the project's Properties page) needs to be added as a prefix to the filename of embedded resources.

So, besides exposing these properties, all the control really does is override the onPreRender event and inject some Javascript into the page. Lines 75 to 79 in Listing 3 inject the link to the embedded Javascript file, which appears in the page source as something like this:

<script src="/WebResource.axd?d=j246orv_38DeKtGbza6y6A2&amp;t=633439907968639849" type="text/javascript"></script>

Next it dynamically generates a script to create a new instance of the virtual form object, passing in the clientid of the VirtualForm Server control and the clientid of the button we want it to use, and register this as a startup script on the page.

Listing 3

    1 using System;

    2 using System.Collections.Generic;

    3 using System.ComponentModel;

    4 using System.Text;

    5 using System.Web;

    6 using System.Web.UI;

    7 using System.Web.UI.WebControls;

    8 

    9 // Script Resources

   10 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_Debug.js", "text/javascript")]

   11 [assembly: WebResource("WilliaBlog.Net.Examples.VirtualForm_min.js", "text/javascript")]

   12 

   13 namespace WilliaBlog.Net.Examples

   14 {

   15 

   16     [ToolboxData("<{0}:VirtualForm runat=server></{0}:VirtualForm>")]

   17     public class VirtualForm : System.Web.UI.WebControls.Panel

   18     {

   19         [Bindable(true), DefaultValue("")]

   20         public string SubmitButtonId

   21         {

   22             get

   23             {

   24                 string s = (string)ViewState["SubmitButtonId"];

   25                 if (s == null)

   26                 {

   27                     return string.Empty;

   28                 }

   29                 else

   30                 {

   31                     return s;

   32                 }

   33             }

   34             set { ViewState["SubmitButtonId"] = value; }

   35         }

   36 

   37         [DefaultValue(false)]

   38         public bool UseDebug

   39         {

   40             get

   41             {

   42                 string s = (string)ViewState["UseDebug"];

   43                 if (string.IsNullOrEmpty(s))

   44                 {

   45                     return false;

   46                 }

   47                 else

   48                 {

   49                     return s.ToLower() == "true";

   50                 }

   51             }

   52             set { ViewState["UseDebug"] = value; }

   53         }

   54 

   55         public VirtualForm() : base() { }

   56 

   57         protected override void OnPreRender(System.EventArgs e)

   58         {

   59             if (!string.IsNullOrEmpty(this.SubmitButtonId))

   60             {

   61                 Control theButton = this.FindControl(this.SubmitButtonId);

   62                 if ((theButton != null))

   63                 {

   64                     string resourceName;

   65                     if (this.UseDebug)

   66                     {

   67                         resourceName = "WilliaBlog.Net.Examples.VirtualForm_Debug.js";

   68                     }

   69                     else

   70                     {

   71                         resourceName = "WilliaBlog.Net.Examples.VirtualForm_min.js";

   72                     }

   73 

   74                     ClientScriptManager cs = this.Page.ClientScript;

   75 

   76                     string scriptLocation = cs.GetWebResourceUrl(this.GetType(), resourceName);

   77                     if (!cs.IsClientScriptIncludeRegistered("VirtualFormScript"))

   78                     {

   79                         cs.RegisterClientScriptInclude("VirtualFormScript", scriptLocation);

   80                     }

   81 

   82                     // New script checks for "Sys" Object, if found events will be rewired after updatepanel refresh.

   83                     StringBuilder sbScript = new StringBuilder(333);

   84                     sbScript.AppendFormat("<script type=\"text/javascript\">{0}", Environment.NewLine);

   85                     sbScript.AppendFormat("    // Ensure postback works after update panel returns{0}", Environment.NewLine);

   86                     sbScript.AppendFormat("    function ResetEventsForMoreInfoForm() {{{0}", Environment.NewLine);

   87                     sbScript.AppendFormat("        var vf_{0} = new WilliaBlog.Net.Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", this.ClientID, theButton.ClientID, Environment.NewLine);

   88                     sbScript.AppendFormat("    }}{0}", Environment.NewLine);

   89                     sbScript.AppendFormat("    if (typeof(Sys) !== \"undefined\"){{{0}", Environment.NewLine);

   90                     sbScript.AppendFormat("        Sys.WebForms.PageRequestManager.getInstance().add_endRequest(ResetEventsForMoreInfoForm);{0}", Environment.NewLine);

   91                     sbScript.AppendFormat("    }}{0}", Environment.NewLine);

   92                     sbScript.AppendFormat("    var vf_{0} = new WilliaBlog.Net.Examples.VirtualForm(document.getElementById('{0}'),'{1}');{2}", this.ClientID, theButton.ClientID, Environment.NewLine);

   93                     sbScript.AppendFormat("</script>");

   94 

   95                     string scriptKey = string.Format("initVirtualForm_" + this.ClientID);

   96 

   97                     if (!cs.IsStartupScriptRegistered(scriptKey))

   98                     {

   99                         cs.RegisterStartupScript(this.GetType(), scriptKey, sbScript.ToString(), false);

  100                     }

  101                 }

  102             }

  103             base.OnPreRender(e);

  104         }

  105     }

  106 }

The JavaScript

Most of the code is of course JavaScript. Lines 13 to 62 simply create the WilliaBlog Namespace and I 'borrowed' it from the The Yahoo! User Interface Library (YUI). The WilliaBlog.Net.Examples.VirtualForm object begins on line 65. Essentially, it loops through every input control (lines 159-164) within the parent Div (the id of this div is passed as an argument to the contructor function) and assigns an onkeypress event (handleEnterKey) to each of them. All keystrokes other than the enter key pass through the code transparently, but as soon as enter is detected, the default behavior is cancelled and the submitVirtual function is called instead. That function simply checks to see if the button you supplied is an input (image or submit button) or an anchor (Hyperlink or link button), and simulates a click on it, either by calling the click() method of the former or by navigating to the href property of the latter. The removeEvent and stopEvent methods are never actually called, but I included them for good measure.

Listing 4

    1 /****************************************************************************************************************************  

    2 *

    3 * File    : VirtualForm_Debug.js

    4 * Created : April 08

    5 * Author  : Rob Williams

    6 * Purpose : This is the fully annotated, easy to understand and modify version of the file, however I would recommend you use

    7 * something like the YUI Compressor (http://developer.yahoo.com/yui/compressor/) to minimize load time.

    8 * This file has its Build Action Property set to "Embedded Resource" which embeds the file inside the dll, so we never have to

    9 * worry about correctly mapping a path to it.

   10 *

   11 *****************************************************************************************************************************/

   12 

   13 if (typeof WilliaBlog == "undefined" || !WilliaBlog) {

   14     /**

   15     * The WilliaBlog global namespace object.  If WilliaBlog is already defined, the

   16     * existing WilliaBlog object will not be overwritten so that defined

   17     * namespaces are preserved.

   18     * @class WilliaBlog

   19     * @static

   20     */

   21     var WilliaBlog = {};

   22 }

   23 

   24 /**

   25  * Returns the namespace specified and creates it if it doesn't exist

   26  * <pre>

   27  * WilliaBlog.namespace("property.package");

   28  * WilliaBlog.namespace("WilliaBlog.property.package");

   29  * </pre>

   30  * Either of the above would create WilliaBlog.property, then

   31  * WilliaBlog.property.package

   32  *

   33  * Be careful when naming packages. Reserved words may work in some browsers

   34  * and not others. For instance, the following will fail in Safari:

   35  * <pre>

   36  * WilliaBlog.namespace("really.long.nested.namespace");

   37  * </pre>

   38  * This fails because "long" is a future reserved word in ECMAScript

   39  *

   40  * @method namespace

   41  * @static

   42  * @param  {String*} arguments 1-n namespaces to create

   43  * @return {Object}  A reference to the last namespace object created

   44  */

   45 WilliaBlog.RegisterNamespace = function() {

   46     var a=arguments, o=null, i, j, d;

   47     for (i=0; i<a.length; i=i+1) {

   48         d=a[i].split(".");

   49         o=WilliaBlog;

   50 

   51         // WilliaBlog is implied, so it is ignored if it is included

   52         for (j=(d[0] == "WilliaBlog") ? 1 : 0; j<d.length; j=j+1) {

   53             o[d[j]]=o[d[j]] || {};

   54             o=o[d[j]];

   55         }

   56     }

   57 

   58     return o;

   59 };

   60 

   61 //declare the 'WilliaBlog.Net.Examples' Namespace

   62 WilliaBlog.RegisterNamespace("WilliaBlog.Net.Examples");

   63 

   64 //declare Virtual Form Object

   65 WilliaBlog.Net.Examples.VirtualForm = function(formDiv,submitBtnId)

   66 {

   67     this.formDiv = formDiv; //The id of the div that represents our Virtual Form

   68     this.submitBtnId = submitBtnId;   //The id of the button or Linkbutton that should be clicked when pushing Enter

   69 

   70     // When using these functions as event delegates the this keyword no longer points to this object as it is out of context

   71     // so instead, create an alias and call that instead.

   72     var me = this;

   73 

   74     this.submitVirtual = function()

   75     {

   76         var target = document.getElementById(me.submitBtnId);

   77         //check the type of the target: If a button then call the click method.

   78         if(target.tagName.toLowerCase() === 'input')

   79         {

   80             document.getElementById(me.submitBtnId).click();

   81         }

   82         //If a link button then simulate a click.

   83         if(target.tagName === 'A')

   84         {

   85             window.location.href = target.href;

   86         }

   87     };

   88 

   89     this.handleEnterKey = function(event){ 

   90         var moz = window.Event ? true : false;

   91         if (moz) {

   92             return me.MozillaEventHandler_KeyDown(event);

   93         } else {

   94             return me.MicrosoftEventHandler_KeyDown();

   95         }

   96     };

   97 

   98     //Mozilla handler (also Handles Safari)

   99     this.MozillaEventHandler_KeyDown = function(e)

  100     {

  101         if (e.which == 13) {

  102             e.returnValue = false;

  103             e.cancel = true;

  104             e.preventDefault();           

  105             me.submitVirtual(); // call the delegate function that simulates the correct button click

  106             return false;       

  107         }

  108         return true;

  109     };

  110 

  111     //IE Handler

  112     this.MicrosoftEventHandler_KeyDown = function()

  113     {

  114         if (event.keyCode == 13) {

  115             event.returnValue = false;

  116             event.cancel = true;

  117             me.submitVirtual(); // call the delegate function that simulates the correct button click

  118             return false;

  119         }

  120         return true;

  121     };

  122 

  123     this.addEvent = function(ctl, eventType, eventFunction)

  124     {

  125         if (ctl.attachEvent){

  126             ctl.attachEvent("on" + eventType, eventFunction);

  127         }else if (ctl.addEventListener){

  128             ctl.addEventListener(eventType, eventFunction, false);

  129         }else{

  130             ctl["on" + eventType] = eventFunction;

  131         }

  132     };

  133 

  134     this.removeEvent = function(ctl, eventType, eventFunction)

  135     {

  136         if (ctl.detachEvent){

  137             ctl.detachEvent("on" + eventType, eventFunction);

  138         }else if (ctl.removeEventListener){

  139             ctl.removeEventListener(eventType, eventFunction, false);

  140         }else{

  141             ctl["on" + eventType] = function(){};

  142         }

  143     };

  144 

  145     this.stopEvent = function(e)

  146     {

  147         if (e.stopPropagation){

  148         // for DOM-friendly browsers

  149             e.stopPropagation();

  150             e.preventDefault();

  151         }else{

  152         // For IE

  153             e.returnValue = false;

  154             e.cancelBubble = true;

  155         }

  156     };

  157 

  158     //Grab all input elements within virtual form (contents of a div with divID)

  159     this.inputs = this.formDiv.getElementsByTagName("input");

  160 

  161     //loop through them and add the keypress event to each to listen for the enter key

  162     for (var i = 0; i < this.inputs.length; i++){

  163         this.addEvent(this.inputs[i],"keypress",this.handleEnterKey);

  164     }

  165 }


Posted by Williarob on Monday, April 28, 2008 8:31 AM
Permalink | Comments (0) | Post RSSRSS comment feed