MCMS, XML Web Service and AJAX Data Collection

Recently I found myself having to create a system by which I could capture some user data from a web application using some unobtrusive javascript.

I settled on a system using jQuery, jQueryUI and an XML Web Service. The basic idea is quite simple: the user clicks a hyperlink, this triggers a form to pop up dialog (using jQueryUI) and when the data is submitted, a call is made by AJAX to the XML Web Service.

An added complication for me was that the site I needed this functionality on was running in MCMS 2002, which means that I needed to use C# 1.1 - which doesn’t have the in-built AJAX capability of later versions. The site uses a number of templates that I was loath to go in and edit individually to load each the javascript frameworks I needed. I might have been tempted if this wasn’t just a pilot system with no guarantee that the system would eventually find wide scale adoption.

I therefore opted for a system of ‘lazy loading’. That is, the javascript frameworks are loaded only on those pages where they are needed, and only when required. All the templates on the site already referenced a ‘main.js’ file so I wrote my lazy-load scripts into this file.

Firstly, I worked out how I wanted to implement this from the consumer perspective. I didn’t want the content editors to have to do anything more complicated that modifying a simple HTML hyperlink. Therefore the link in the HTML is to be implemented as one of the two links below depending on whether other javascript needed to be executed in addition to my jQueryUI dialog. Non-javascript users will not get the onclick event fired and the link continues to function as a normal hyperlink.

  1. <a href="mynewpage.htm" onclick="return DoStuff('newpagejs.htm');">click here</a>
  2. <a href="mynewpage.htm" onclick="return DoStuff('newpagejs.htm', function(){ //execute other stuff here });">click here</a>

The lazy loading function for the jQuery framework file is shown below. Note how the last line in the DoStuff method returns false. This ensures that a hyperlink does not ‘activate’ and allows the javascript to execute. I also built in a ‘timeout’ loop so that multiple attempts can be made to load the scripts needed. In the example below the script tries to initialise jQuery (JQueryScriptInit) 4 times a second up to a pre-set maximum attempts. If it fails it just runs ContinueWithOtherStuff which could be an alternative to the popup dialog system.

  1. //DoStuff: function to kick off the initialisation and load of the popup dialog
  2. function DoStuff(file, func)
  3. {
  4.  if(func!=null) func();  //execute the other functions
  5.  clearTimeout(FormTimeout);
  6.  var scriptsInitialised = JQueryScriptInit();  
  7.  if(scriptsInitialised)
  8.  {
  9.   //implement the functionality here
  10.  }
  11.  else
  12.  {
  13.   if(FormLoadAttempts<=MaxDownloadFormLoadAttempts)
  14.   {
  15.    FormTimeout = setTimeout(function() { DoStuff(file); }, 250);
  16.   }
  17.   else
  18.    ContinueWithOtherStuff(file,null);
  19.  }
  20.  return false;  //prevent the href of the download link from running
  21. }
  22.  
  23. //JQueryScriptInit: functions to load the jQuery and jQueryUI framework file
  24. function JQueryScriptInit()
  25. {
  26.  //initialise jQuery
  27.  if(JQueryInit())
  28.  {
  29.   //we have jQuery!
  30.   //alert($('#accessHelp').text());
  31.   //use jQuery to get the ui file
  32.   if(JQueryUIInit()) {
  33.       JQueryCookie();  
  34.    JQueryUICss();
  35.    return true;
  36.   }
  37.   else
  38.    return false;
  39.  
  40.  }
  41.  else
  42.   return false;
  43. }
  44.  
  45. function JQueryInit()
  46. {
  47.  if(!self.jQuery)
  48.  {
  49.   var head = document.getElementsByTagName("head")[0];
  50.  
  51.   //get the script
  52.   var scriptitem = document.createElement("script");
  53.   scriptitem.id = "jquery";
  54.   scriptitem.src = "/htaweb/scripts/jquery-1.3.2.js";
  55.   scriptitem.type = "text/javascript";
  56.   head.appendChild(scriptitem);
  57.  
  58.   var maxcheckattempts = 100;
  59.   var checks = 1;
  60.   var jqueryloaded = true;
  61.   while (!self.jQuery)
  62.   {
  63.    if(checks>maxcheckattempts)
  64.    {
  65.     jqueryloaded = false;
  66.     //alert('Timed out');
  67.     break;
  68.    }  
  69.    setTimeout(function(){}, 100);
  70.    checks++;
  71.   }
  72.   return jqueryloaded;
  73.  }
  74.  else
  75.  {
  76.   //alert('jQuery already loaded');
  77.   return true;
  78.  }
  79. }

Once jQuery is loaded, I could easily use it to load the other scripts and files. I load the jQueryUI and the required CSS as follows:

  1. function JQueryUIInit()
  2. {
  3.  if(!$.ui)
  4.  {
  5.   var uiscript = '<script language="javascript" type="text/javascript" id="jqueryui" src="/htaweb/scripts/jquery-ui-1.7.2.js" />';
  6.   $('head').append(uiscript);
  7.  
  8.   var maxcheckattempts = 100;
  9.   var checks = 1;
  10.   var jqueryloaded = true;
  11.   while (!$.ui)
  12.   {
  13.    if(checks>maxcheckattempts)
  14.    {
  15.     jqueryloaded = false;
  16.     //alert('jQuery UI load timed out');
  17.     break;
  18.    }  
  19.    setTimeout(function(){}, 100);
  20.    checks++;
  21.   }
  22.   return jqueryloaded;
  23.  }
  24.  else
  25.  {
  26.   //alert('jQuery UI already loaded');
  27.   return true;
  28.  }
  29. }
  30.  
  31. function JQueryUICss()
  32. {
  33.  if($('#jqueryuicss').length==0)
  34.  {
  35.   $('head').append('<link id="jqueryuicss" rel="stylesheet" type="text/css" media="screen" href="/htaweb/css/jquery-ui-1.7.2.css" />');
  36.   //alert('css loaded');
  37.  }
  38. }

I also ‘externalised’ the form into an HTML file which is loaded on demand only when the dialog is initialised. This prevents the load of the form if for any reason the frameworks to not initialise. This is shown below:

  1. function InitDialog(file)
  2. {
  3.  if( $('#dialogPanel').dialog().length==0 )
  4.  {
  5.   //a short loop here to ensure the div is now in the DOM
  6.   var interval = setInterval(function()
  7.   {
  8.    //create a form container
  9.    if($('#dialogPanel').length==0)
  10.     $('#wrapper').append('<DIV style="DISPLAY:block;" id="dialogPanel"></DIV>');
  11.    if($('#dialogPanel').length>0)
  12.    {
  13.     if($('#dialogPanel').html().length==0)
  14.     {
  15.      //get the form html
  16.      $('#dialogPanel').load('/forms/downloadform.htm', null, function()
  17.      {
  18.       InitDialogPopup(file);
  19.      });
  20.     }
  21.     else
  22.      InitDialogPopup(file);
  23.     clearInterval(interval);
  24.    }
  25.   }, 1);
  26.  }
  27. }

When we have the frameworks and other resources (css, html), we can move on to actually making things happen. The dialog definition is shown below:

  1. function InitDialogPopup(file)
  2. {
  3.     $('#dialogPanel').dialog({
  4.         autoOpen: false,
  5.         title: 'Popup Questionnaire',
  6.         buttons: {
  7.             "Skip": function() { CloseAndContinue(file, true); },
  8.             "OK": function() { CloseAndContinue(file, false); }
  9.         }
  10.     });
  11. }

When the buttons on the dialog are clicked, data is to be submitted asynchronously to the server.

  1. var wsUrl = '/Services/DoStuffOnServerService.asmx/DoStuff';
  2. $.ajax({
  3.  type: "POST",
  4.  url: wsUrl,
  5.  cache : false,
  6.  data: {
  7.   agerange:ageGroup,
  8.   gender:gender,
  9.   postcode:pcode,
  10.   uname:uname},
  11.   dataType : 'xml',
  12.  success: function(msg){
  13.   alert( "Data Saved: " +  msg.text);
  14.  },
  15.  error: function(xhr, status, err){
  16.   alert('Status: ' + xhr.status + ': ' + xhr.statusText);
  17.  },
  18.  complete: function(){
  19.   //alert('Redirecting now…');
  20.   location.href = file;
  21.  }
  22.  
  23. });

In my application there is no difference in whether the AJAX call returns a success or failure - the page redirects to another page when the AJAX response processing completes.

The AJAX request needs something to submit to, so I then created an XML Web Service. The service was created as a standalone application but was hosted as an application under the MCMS site (in IIS) so I could use relative URLs and avoid cross-domain script issues.

Using Visual Studio 2003 I created an XML Web Service project. The first thing is to ensure that HTTP POST is enabled for the service. So in the web.config, I added the following lines:

  1.  <!–other stuff edited–>
  2.  <webServices>
  3.   <protocols>
  4.    <add name="HttpPost" />
  5.   </protocols>
  6.  </webServices>
  7. </system.web>

The WebMethod itself reads the posted data.

  1. [WebMethod]
  2. public string DoStuff()
  3. {
  4.  string username = this.Context.Request.Form["uname"];
  5.  string ageRange = this.Context.Request.Form["agerange"];
  6.  string postcode = this.Context.Request.Form["postcode"];
  7.  string gender = this.Context.Request.Form["gender"];
  8.  MyDataItem item = new MyDataItem(name, postcode, ageRange, gender);
  9.  try
  10.  {
  11.   return item.Save();
  12.  }
  13.  catch
  14.  {
  15.   return String.Empty;
  16.  }
  17. }

Once the data was captured through the Request object I had additional classes (MyDataItem) to create data objects which are then persisted to a database (you’ll need to create this as it is outside of the scope of this article), hence the ‘Save’ method.

The overriding concern in all this was to be unobtrusive: so at all steps along the way the user should not be aware that anything has gone wrong. The redirect always takes place regardless of whether any step succeeds or fails.

So if all the javascript frameworks fail to load, or the dialog fails to initialise the user simply does not see the popup form and the application continues to work ‘normally’.

The scripts and functionality I’ve listed here are simple bare-bones and in my implementation I added cookie management and other ‘frills’ to improve the user experience. If you are building a system where the popup (or a set of popups) may be required on a number of pages across your site then I would recommend pre-loading the framework files for jQuery and jQueryUI (and thus simplifying much of the above). You can still lazy-load the actual forms if you like.

0 Comments on “MCMS, XML Web Service and AJAX Data Collection”

Leave a Comment

Powered by WordPress

Website design adapted from WordPress theme PrimePress