• Feeds

    Subscribe in a reader

  • Ads

Interception patterns in JavaScript

Harry Pierson and Phil Weber are talking about the fun that you can have using JavaScript’s intrinsic support for function pointers to achieve interception-like behavior.

This pattern is so useful – when it comes to extending the client-side functionality of ASP.NET, it’s really tough to extend the framework cleanly. This is because so much of the JavaScript that ASP.NET relies on is either generated at render-time ( doPostBack) or tightly coupled to an unmodifiable framework (the stuff in WebUIValidation.js). Function pointer interception provides a pretty good way around these problems.

For example, let’s say that you’re using the ASP.NET client-side validation controls. How do you set focus to the first invalid control without triggering a postback? Conceptually, it’s not that difficult – after client-side validation is complete, just find the first validator that’s in the “invalid” state and set focus to its corresponding control. This can be accomplished with the following loop:

for( var i=0; i < Page_Validators.length; i++ )
{
   if( !Page_Validators[i].isvalid )
   {
     document.getElementById(
             
Page_Validators[i].controltovalidate ).focus();
     return;
   }
}

Logically, you’d want this loop to execute any time that client-side validation completes. The ASP.NET framework function that kicks off client-side validation is called Page_ClientValidate() (it’s implemented in the file WebUIValidation.js, which lives in a version-specific subdirectory of the wwwroot\aspnet client\system.web directory). Unfortunately, it’s not extensible without modifying WebUIValidation.js (which creates deployment headaches and versioning problems). One way to get the desired functionality would be to implement the above loop in standalone function, and call that function immediately after any call to Page_ClientValidate(). However, since all calls to Page_ClientValidate() are generated, this would require some fairly ugly hacking to accomplish. A better way would be to use function pointer interception to add the desired behavior to the framework’s definition of Page_ClientValidate().

Function pointer interception takes advantage of the fact that function names in JavaScript are just variables whose values happen to be function implementations. Like variables of any other type, they are assignable. Using this language feature, it’s possible to swap out the old implementation of Page_ClientValidate() with a new, improved version of Page_ClientValidate() without having to modify any of the code that calls that function (it’s also possible to use this pattern to do all sorts of nasty things that could totally violate all reasonable assumptions that callers make about the behavior of the functions they call. Play nice and use discretion when applying this pattern).

Using function pointer interception, the extended version of Page_ClientValidate() looks like this:

//Make sure the Page_ClientValidate function exists
if( typeof( Page_ClientValidate ) == "function" )

     //Stash the old implementation in a temp variable
     Page_ClientValidateOld = Page_ClientValidate; 
     
     //Create a new implementation and store it
     //in Page_ClientValidate. Callers will now get
     //this implementation.

     Page_ClientValidate = function()
     {
          var isValid;

     
    //Call the old implementation first…
          isValid = Page_ClientValidateOld(); 
          
    
     //and then call our extension
          if( !isValid )
          {
            FocusOnInvalidControl();
          }

          return isValid;
      }
}

Once this code is included on a page, the any call the framework makes to the implementation stored in the Page_ClientValidate variable will include the desired extension functionality. All you have to do is include this after the script include for WebUIValidation.js. ASP.NET has a bad habit of putting that include pretty much wherever it wants, so putting the extension at the top of the page is not the best idea. However, putting it after the last control on the page works pretty well.

Addendum: I really like JavaScript, because it's powerful and flexible enough to make this kind of abstraction very easy. However, as I learned from Spider Man, with great power comes great responsibility. Writing code using this pattern is fun and exciting and potentially useful, but also potentially confusing to the point of being unmaintainable. Injecting just a few intercepted functions into a codebase can be very confusing precisely because it's so transparent to downstream callers. People unaware of the fact that interception is happening will be totally mystified by the behavior they see, and looking at the functions implementation will be of no help whatsoever. Use this pattern judiciously, and only as a last resort.

#1 Shawn on 11.23.2005 at 1:07 PM

Fantastic!Just a wonderfully elegant solution to a sticky problem - came in very handy in my experiments with AJAX and asp.net.