In a recent project I had to extend the functionality of a web app at the user interface level. I am sort of administering this web app, so I have admin privileges on the server (Windows Server 2008) and full access to the web pages, ASPs in this case. That helps a lot. But you have to resist the seduction to wildly modify files. Because whenever you have to update the app you have to possibly fix or recreate the hack. And even worse you have to remember and/or document everything you changed.
When you have no access on the filesystem level there might be other strategies: In my case, many information get pulled from a database. There even exists a functionality that allows you to define arbitrary SQL select statements and get the resulting database columns and rows displayed in the corresponding HTML table form. The retrieved data can include HTML. Which will be interpreted by the browser. This way you can use database values to pull some seed script in and subsequently redefine anything on the page without ever touching a ASP.
OK, the script gets loaded. Before taking the next step, I feel it is appropriate to talk about the things, I actually had to modify on the page. The bigger goal of the hack is to generate a unique order number per business unit. The host system doesn’t provide a way to achieve this goal. It is possible to define custom fields and forms, though. The strategy is to create a custom field that holds the number and hand the actual generation of the number over to a small service that accesses the field on the database level to find the next free number. That number gets returned and displayed on the page.
The to-be-hacked-page has a custom form designed to display and edit custom fields. Holding of course the order number field and besides that, some element to trigger the creation and deletion of the order number. For that purpose I defined a drop down box called: “Generate order number” with two values: “Yes” and “No”. Usually you simply insert values and save the data or cancel the changes. So the page is generally built to reflect this purpose. That forced me to change some aspects to achieve the desired behavior:
- When the page loads, the script “looks” if there is already an order number defined in the field. If yes, the drop down box is set to “Yes”. If not, the drop down box is set to “No”.
- When the user selects “Yes” , a AJAX request is send to the server. There the next free number gets generated, is returned and gets displayed in the order number field (read-only for the user).
- When the user selects “No”, a AJAX request is send to the server, the server deletes the number from the database, an acknowledge message is send back and the order number field is updated to display the default value (‘undef’) – meaning no number exists for that particular element.
- To make it more obvious to the user, there must exists only one button that finishes the form and returns to the calling page. Instead of the usual “Save” and “Cancel” buttons which don’t have any special purpose in the actual case.
- The drop down box holds by default a third possible value: “[None]”. Which gets removed to further reduce the chance to confuse the user: What should happen if she selects this option?
The first step is to ensure the script gets executed when the hacked page is loaded. The widely known jQuery hook comes in handy:
At first I have to check if the page is responsible for generating the order number. That means, if it is displaying the order number field and the generate-trigger drop down box. The function: “isOrderNumberGenPage” is responsible for that.
The following gist shows the called functions that finds the actual fields. As those fields are accessed quite often I save the jQuery selector results in local variables:
Back to the page load hook from the first gist. The whole purpose of the “jQuery.support.cors = true”-expression is to allow cross site scripting in Internet Explorer 8, sadly it’s still my companies default web browser. More on AJAX requests later, as it is a little complicated. The “handleButtons()” function call shown in the next gist removes the “Cancel” button and renames the “Save” button to “OK”:
That’s quite easy and self-explanatory if you have ever worked with jQuery. The next gist shows the “handleGenerateSwitch”-function as well as the functions this one calls. Together they are responsible to hide the third option from the drop down box and selecting the right one on load time:
The last call in the hook registers an anonymous function to the change event of the generate trigger drop down box. It simply calls the function: “handleOrdernumber” when executed. I introduced this indirection for debugging purposes, e.g. looking at the received event. The next gist shows the called function. It looks at the selected text in the drop down box and decides which action to take. Either send a delete or create request:
The next and last code snippet shows both functions and the function that does the actual sending of the request. The fourth function discovers the id of the actual element for which the order number should be created.
Before explaining the AJAX stuff here is for short the purpose of the fourth function. The hacked-page contains an HTML form and its action attribute a URL that includes the parameter: “code”. This parameters value is the id of the element, the page is displayed for. The function extracts the id and returns it.
Now the hardest part. I struggled for days to get it working in IE8. In modern browsers this part is a lot easier. One central aspect is the “dataType” property. In case of “json” you have to set “jQuery.support.cors = true” and always respond with the right headers defined. In my case, since I’m using custom headers for selfmade basic authentication, different HTTP methods and change the “Content-Type” header, the following should be set:
- Access-Control-Allow-Methods: PUT, GET, POST, DELETE, OPTIONS
- Access-Control-Allow-Headers: x_auth_user, x_auth_password, Content-Type
- Access-Control-Allow-Origin: *
IE8 doesn’t send preliminary OPTIONS requests. In case you have to support modern browsers as well, you have to deal with them. Usually responding with the above Access-Control-Allow-??? headers defined and an empty body should work well. But it depends on the server implementation and is out of scope for this post.
Another IE8 hint: you have to be careful to define the charset correctly (e.g. to “utf-8”). The browser throws an error and spits some strange number out in case it doesn’t understand the charset.