Archive for the ‘jQuery’ Category
jQuery: $.post, 'jsonp' and cross-domain requests
We spent a bit of time yesterday looking through the jQuery code trying to work out why a cross domain request we were making using jQuery's '$.post' function wasn't working.
In hindsight perhaps it should have been obvious that you wouldn't be able to do that but I didn't completely understand how we were able to do cross domain requests were possible at all but we had some '$.getJson' 'jsonp' function calls around our code base which were doing just that.
The jQuery documentation seemed to suggest it was possible to do a 'jsonp' 'POST' request but when we tried to do so we got the following error:
Error: uncaught exception: [Exception... "Access to restricted URI denied" code: "1012" nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI)"
The failure was occurring in this part of the code on line 3517 inside the 'ajax' function:
3512 3513 3514 3515 3516 3517 | // Open the socket // Passing null username, generates a login popup on Opera (#2865) if( s.username ) xhr.open(type, s.url, s.async, s.username, s.password); else xhr.open(type, s.url, s.async); |
I had initially thought that passing in 'jsonp' to the function did something clever to fool the browser into sending the Xml Http Request but actually in a thread from the jQuery mailing list from a year ago where Michael Geary explains what's actually happening:
Cross-domain JSONP isn't AJAX at all. It doesn't use XMLHttpRequest. It's nothing more than a dynamic script element that loads JavaScript code.
You can't do a POST with a dynamic script element. Where would you put the POST data?
I don't know what the $.ajax code is trying to do – maybe it should fail in a more informative way. It will fail one way or another regardless.
We could see where this was being done in the jQuery code:
3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 | // If we're requesting a remote document // and trying to load JSON or Script with a GET if ( s.dataType == "script" && type == "GET" && parts && ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){ var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); script.src = s.url; if (s.scriptCharset) script.charset = s.scriptCharset; ... head.appendChild(script); ... } |
On line 3477 a script tag is dynamically added into the page and on line 3478 we set 'src' to be the url for our cross domain request.
We can see on line 3473 that this only happens if we have a 'GET' request.
It turned out for us that we were actually only doing this cross domain request on one of our early test environments and that in latter test environments we have everything running on the same domain.
On this occasion we have decided to stop using this environment since it's not reflective of what our application will run like in production but if we wanted to do a cross domain request then we would need to make use of the '$.get' method with 'jsonp' passed as an option.
I was talking about this with Dave Yeung at our coding dojo last night and he pointed me to an article describing how Firefox 3.5 is now supporting the 'access control for cross site requests' recommendation which will allow cross domain XHR requests to happen by providing some extra header tags and then reading some additional tags sent back in the response where the server on the other domain can decide which domains are allowed to make calls to it.
I'm still learning this stuff so if anything I've said isn't accurate please point that out.
Selenium: Waiting for jQuery AJAX calls
Lu Ning and I have been working on trying to improving the consistency of our selenium driven acceptance test build, the main problem being the number of false negatives we've been getting.
The reason for these false negatives has been due to the difficulty in working out the necessary condition to wait for when making ajax calls using the jQuery library.
The conditions can be a combination of elements on the page appearing, disappearing, being enabled or disabled and sometimes we don't actually have any visible change on the page which we can hook into to know whether or not an ajax call has returned.
We tried several different solutions to this problem, each one resulting in less and less code until we came across some jQuery ajax hooks which we weren't previously aware of.
The events we looked at were ajaxComplete, ajaxError, ajaxSend, ajaxStart, ajaxStop and ajaxSuccess which don't seem to fire exactly when you might expect.
We had thought that we might just be able to listen to ajaxStop and then we would know when all the ajax calls on the page had completed but this didn't seem to fire all the time. A couple of the other events were equally troublesome but we've ended up with the following code to allow us to tell when ajax requests have returned so we can continue with the next selenium command in our test.
// extend jQuery ajax with the capability of remembering the count of active ajax requests $.activeAjaxRequestCount = 0; $().ajaxSend(function() { $.activeAjaxRequestCount++; // register lazily to make sure it's the last one and get executed after all other handlers if (!$._ajaxErrorHandlerAdded) { $().ajaxError(function() { $.activeAjaxRequestCount--; }); $._ajaxErrorHandlerAdded = true; } }) .ajaxSuccess(function() { $.activeAjaxRequestCount--; });
We have some customised error handling in our code which already hooks into the ajaxError event so we wanted to make sure that we could add a handler which was fired after all the production code handlers had completed.
We originally tried just directly hooking up a handler to the ajaxError event inside the document ready but we found that this handler was getting fired before the customised error handling one.
Lu therefore came up with the idea of creating this handler when we send our first ajax request which means that it will be the last handler fired when an error is raised.
We created a decorator around the selenium client in order to allow us to use this code from our C# code:
public class SeleniumDecorator : ISelenium { public void Click(string locator, int timeout) { WaitComplete(() => selenium.Click(locator), timeout); } private void WaitComplete(Action fn, int timeout) { selenium.RunScript("selenium.browserbot.getCurrentWindow();window.jQuery.activeAjaxRequestCount=0;"); fn(); WaitForCondition("selenium.browserbot.getCurrentWindow();window.jQuery.activeAjaxRequestCount===0;", timeout.ToString()); } }
We are also wrapping other selenium calls which might trigger ajax calls which include 'FireEvent', 'Type' and 'Select'.
The other clever thing which Josh discovered here is that to access jQuery libraries from selenium we need to refer to 'window.jQuery' – we then seem to have access to any jQuery related code that we have loaded on the page.
I've discussed with a couple of colleagues the merits of doing this and it was pointed out to me that it's not generally a good idea to put code in just to make something more testable at the acceptance level.
In this case we are using the activeAjaxRequestCount in production code for being able to monitor the state of our ajax requests so it does have another use as well.
jQuery: Approaches to testing
We've been doing a bit of work with jQuery and true to our TDD roots we've been trying to work out the best way to test drive our coding in this area.
There seem to be 3 main ways that you can go about doing this, regardless of the testing framework you choose to you. We are using screw-unit for our javascript testing.
Mock everything out
The idea here is that we mock out all calls made to jQuery functions and then we assert that the expected calls were made in our test.
Taking this approach means we are able to reduce the dependencies in our tests and they run very quickly.
The problem is that a lot of assertions become assertions checking that certain operations were called on the DOM since jQuery makes a lot of these type of calls in its code. The tests therefore end up being quite long and difficult to understand unless you were the one who initially wrote them.
We also effectively end up testing how the framework interacts with the DOM rather than testing our own code with this approach.
Don't mock anything
The opposite approach is to just test directly against jQuery and then do assertions against the part of the DOM affected by the javascript code we are testing.
The problem here is that if you want to test against plugins which make ajax requests or carry out animation effects then the tests become dependent on how long these calls take and we need to find a way to block the test until those calls return which is quite difficult!
Only stub certain calls
The happy medium is that we test directly against the jQuery library but stub out ajax requests and animation effects. Our test assertions are against the state of the DOM to check that it was changed in the way that we expected.
This approach allows us to test our javascript code in terms of its behaviour without testing the internals of how jQuery works.
The wiring up of events to different controls on the page is done in our test but the actual logic that happens when these events are fired is in our js file under test.
We currently favour this last approach as it seems to give us the best of both worlds so to speak. It be would be interesting to hear how are other people going about Javascript testing.
jQuery datepicker IE6 positioning bug
We've been using the jQuery datepicker on my current project and came across some strange behaviour with regards to the positioning of the calendar in IE6.
The calendar was always positioning itself right at the top of the screen instead of just below the textbox it was hooked up to but in Firefox it was working fine.
After a bit of exploration in the jQuery code (ui.datepicker.js) we worked out that the 'document.documentElement.clientHeight' call in the '_checkOffset' function was always returning a value of 0 meaning that the position of the calendar was always right at the top of the screen.
_checkOffset: function(inst, offset, isFixed) {
var pos = inst.input ? this._findPos(inst.input[0]) : null;
var browserWidth = window.innerWidth || document.documentElement.clientWidth;
var browserHeight = window.innerHeight || document.documentElement.clientHeight;
...It turned that we were missing the doctype at the top of the HTML page which make the page standards compliant and as a result document.documentElement.clientHeight returns the proper height.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
Not having this set puts IE into Quirks Mode meaning that document.documentElement.clientHeight always returns 0.
We found a post on the Webmaster World Forum which helps explain why this change makes a difference.
Javascript: Creating quick feedback loops
I've been working quite a lot with Javascript and in particular jQuery recently and since I haven't done much in this area before all the tips and tricks are new to me.
One thing which is always useful no matter the programming language is to use it in a way that you can get rapid feedback on what you are doing.
Fortunately there are quite a few tools that allow us to do this with Javascript:
Firebug
The Firefox plugin is perhaps the quickest way of getting feedback on anything Javascript and indeed CSS related.
The ability to see which HTTP calls have been made on a page is invaluable for checking whether AJAX functionality is working correctly and DOM manipulation can be executed and tested on the fly.
Including jQuery in a page effectively makes Firebug the jQuery Interactive Console, allowing us to try out the different functions and see their effects immediately.
Unit Testing Frameworks
There are several javascript unit testing frameworks around at the moment which run in the browser and provide the ability to write assertions on our code.
I have been using QUnit and screw-unit and while they work reasonably well for simple tests, neither seems to be at the level of JUnit or NUnit for example. I'm sure this will come as they mature.
Other frameworks I've heard about but not tried: RhinoUnit, JSNUnit, JSUnit, no doubt there are others I haven't heard about yet.
Selenium IDE
The sometimes forgotten Firefox plugin is very useful for quickly creating repeatable scenarios to see the impact that code changes have had.
The beauty of this approach is that it takes out the manual steps in the process, so we can just make our changes and then re-run the test. The runner lights up green or red, taking out the need to manually verify the correctness of our assertions.
Alert
The 'alert' function is perhaps most useful when we want to quickly verify the path being taken through a piece of code without having to step through it using the Firebug debugger.
It's probably more useful for proving our assumptions than actual debugging and it's certainly quick and easy to set up.
jQuery Validation & Firefox Refresh Behaviour
We've been working quite a bit with jQuery and cross browser compatibility and one of the interesting differences we came across today was the behaviour of Firefox and Internet Explorer when it comes to refreshing a page.
When you press refresh in Internet Explorer the page gets refreshed to the state that it was in when you first loaded the URL, meaning that the state of the data in forms is returned to its original state.
Doing the same in Firefox doesn't have the same behaviour – the page refreshes but it keeps the most recent data that was entered into the form. In theory this is quite nice behaviour because it means if you accidentally hit refresh it keeps all your data.
For us it was quite annoying as we had hooked up a validator and some other custom code which declared certain parts of the page invalid if any data had changed from what was on the page when it first loaded.
The way we got around the problem was to fire the validation events on jQuery's document ready as well as firing them on the change events for each of the form elements.
$(document).ready(function(){ // validation code here as well as on change events for form elements });
Although it does mean that we are calling the validation in more places it has helped us to get around the problem.