Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker
JB Steadman

more page dynamics, less client logic

JB Steadman
Saturday, January 23, 2010

Ajaxed pages frequently need to update many parts of the page within ajax callbacks. Here I’ll outline how we use jQuery and Rails do this on Mavenlink. Our approach encodes behavior declaratively in markup and minimizes client-side logic.

Mavenlink provides a rich collaborative workspace for clients and consultants. All workspace activity happens within a single page. When a user adds a new deliverable to the workspace, we need to update three parts of the page. The dynamic page areas are highlighted in this screenshot of a typical workspace:

Mavenlink workspace

See the page in action yourself by creating your own workspace on Mavenlink.

To add a deliverable, the user submits the ajaxed form circled at right. Upon success, we need to update the deliverable module (A), the list of deliverables in the popup (B), and the event feed (C).

However, we’d like to avoid having our javascript know this. We try to avoid callback code along the lines of “when X happens, do A, B, and C”. Additionally, we want to avoid creating multiple event listeners responsible for dealing with something like a “deliverableCreated” event.

Instead, we mark up the parts of our document that can be updated dynamically. When new content arrives via ajax, we match the new content to the marked up dynamic elements, using shared attribute values to perform the match.

Specifically, we define a custom attribute called ‘content-key’ on our dynamic elements. Here’s three sections of the page’s original markup defining the three elements outlined in the screen shot:

<!-- deliverable module (A) -->
<div content-key="deliverable-module">
  ...
</div>

<!-- the deliverable popup (B) -->
<ul content-key="deliverable-popup">
  ...
</ul>

<!-- the event feed (C) -->
<ul content-key="events">
  ...
</ul>

In handling an ajax request to create a deliverable, we generate response markup with matching elements. The response looks something like this:

<div content-key="deliverable-module">
 <!-- new html -->
</div>
<ul content-key="deliverable-popup">
 <!-- new html -->
</ul>
<ul content-key="events">
 <!-- new html -->
</ul>

Our ajax success callback invokes a function that uses jQuery to inject the new html into the page’s original content-key elements:

function updateContent(event, newContent) {
  var $contentKeyElements = $(newContent).filter('[content-key]');

  // iterate through each new content-key element

  $contentKeyElements.each(function() {
    var contentKey = $(this).attr("content-key");

    // replace existing content-key html with new content-key html

    $("[content-key="+contentKey+"]").html($(this).html());
  });

  $(document).trigger('content-updated');
}

One advantage of this approach is that we can add new behavior without adding client-side logic. For example, if we want to show a flash message when a deliverable is created, we add a <div content-key='flash-notice'> element to the page, and match it with markup returned in the ajax response. The new behavior is encoded declaratively in our markup.

This approach keeps the server side pretty simple as well. In our create() action, we render the same partial whether the xhr request succeeds or fails:

class DeliverablesController < ApplicationController
  ...

  def create
    @deliverable = @workspace.deliverables.build(params[:deliverable])
    status = @deliverable.save ? 200 : 400
    render :partial => 'deliverables/deliverable_response', :status => status
  end
end

The deliverable_response partial renders secondary partials that each render a content-key element reflecting the new state of the page:

<%= render :partial => 'deliverables/deliverables_module' %>
<%= render :partial => 'workspaces/deliverable_popup' %>
<%= render :partial => 'workspaces/events' %>

These secondary partials are the same ones used to build the original page, so we’re not writing any new rendering code for ajax. Validation error messages are rendered within deliverables/deliverables_module in the manner of normal Rails forms.

You may have noticed that in our ajax responses, we render more than we strictly need to. For example, when a deliverable is created, we could just add a single new <li> element within our <ul>s instead of rebuilding the <ul>s entirely. For moderately-sized, moderately dynamic pages like our workspace page, micro-optimizing responses can needlessly complicate code without providing any meaningful performance benefits. When reality permits, we prefer blunt stupidity over intricate precision. It’s much easier to deal with.

Watch out for replacing markup that carries dynamic state, such as tab selection and expand/collapse state, both of which we use on Mavenlink’s workspace page. You can either avoid destroying state by narrowing the scope of your content-key elements, or restore the state in javascript after new markup is loaded. In a later post, I’ll detail how we deal with it on the workspace page.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 3/24/2009: Browser History with Javascript and Page Based Json

Pivotal Labs
Tuesday, March 24, 2009

Interesting Things

  • Browser History with Javascript and Page Based Json: One of our projects solved the vexing problem of browser history for a page that has initial page provided json with subsequent ajax updates. A simple page back operation will display the originally downloaded data, not the updated data. The solution is to add a unique id for each page, and store these ids in a cookie. When an ajax request updates the page it removes its page id from the cookie. When you use the back button, each page checks to see if its unique id is in the cookie, and if it is not, it forces a reload.
    Really Simple History was mentioned as another way to manage javascript/ajax history.
  • Rubymine Build 784 has the Weirdest. Bug. Ever.: This may only be a problem if you work on a mac and you need to enter capital letters in rubymine dialogs like find and replace ;-). Many of us are fans of intellij/rubymine, but we wish they had a better test process. To be fair, rubymine is in public preview, so expect the occasional bug or two.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Abhijit Hiremagalur

Standup 01/16/2009: onReady() for AJAX, Web Sprites & Detecting UTF-8

Abhijit Hiremagalur
Monday, January 19, 2009

Interesting Things

  • Web based sprite generator – here

This also makes the generated sprite really small which is great if you care about page load times. A Ruby+ImageMagick sprite generator might also be a good thing to build.

  • Cool way of detecting if a file is UTF-8 enconded using Ruby+IConv – here

Ask for Help

“Is there an onReady() for AJAX events?”

  • onAJAXReady() ?
  • JQuery Live Events might do the trick
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Edward Hieatt

Taming JavaScript in practice: AJAX

Edward Hieatt
Monday, June 18, 2007

Commonly the JavaScript side of AJAX ends up untested, tightly coupled to the server-side code, and difficult to read. In a previous post, we saw how testability led to the ability to refactor our code to make it readable. This time we’ll focus on ways to test AJAX (which is a fairly lengthy topic in itself) and take it as read that once our code is tested we’ll be in a good position to refactor towards readability and to generally tame the complexities of client-side AJAX code.

Example

Suppose we have a very simple calculator application: the user enters a number into a textfield and presses a button labeled “Double”. The application sends the number to the server, and the server sends back a response containing twice the value of the number. The doubled value is then displayed in a second textfield. Of course, this is an artificial example, but the structure of the code is common enough to demonstrate practices for testing AJAX. Let’s use raw XmlHttpRequests here; I’ll post again separately with how to adapt this if you’re using prototype.js. Our code might look like this:

&lt;script language="javascript"&gt;
var request;
function calculate() {
  var enteredValue = document.entryfield.value;
  if (window.XMLHttpRequest)
    request = new XMLHttpRequest();
  else
    request = new ActiveXObject("Microsoft.XMLHTTP");
  request.onreadystatechange = callback;
  request.open("GET", "/calculate?value=" + enteredValue, true);
  request.send(null);
}

function callback() {
  if (request.readyState == 4) {
    if (request.status != 200) {
      document.outputfield.value = "Error: " + request.status;
      return;
    }
    var responseValue = request.responseText;
    document.outputfield.value = responseValue;
  }
}

&lt;/script&gt;

&lt;input type="text" name="entryfield"&gt;
&lt;input type="button" onclick="calculate()" value="Double"&gt;
&lt;input type="text" name="outputfield"&gt;

When the user presses the “Double” button, an AJAX request is sent to a servlet, passing the value the user entered in the “entryField” text field. When the server responds, the text in the response is displayed in the “outputField” text field. If the server didn’t respond successfully, we display an error message in “outputField” containing the error code.

Starting a Test Page

Let’s try to write a Test Page for our code:

&lt;script language="javascript" src="/path/to/calculate.js"&gt;&lt;/script&gt;
&lt;script language="javascript"&gt;
function testClickCalculate() {
  document.entryfield.value = "5";
  calculate(); //but wait - we don't have a server
  //now what?
}
&lt;/script&gt;

&lt;input type="text" name="entryfield"&gt;
&lt;input type="text" name="outputfield"&gt;
&lt;/body&gt;

Hmm – how do we proceed? As things are, when we run our test, our request will get sent off to a server that isn’t running. We don’t have enough control over our environment to continue with the test.

So, how can we test AJAX?

The most important thing to bear in mind is that we are trying to write unit tests for our JavaScript – tests that exercise just a unit of our JavaScript code at a time. So we certainly don’t want to bring a server into the picture – that’s way out of scope for our JavaScript unit tests. Instead, what we want to do is insulate ourselves from the machinery of the request/response server interaction. The typical point at which we set up our insulation is at the level of the XmlHttpRequest: rather than a real request, we will use a mock version in our test – a pretend version of the request that we control.

Testing the request

Our first task, then, is to set things up so that our test uses a mock request and our code uses a real one:

function calculate() {
  ...
  request = createRequest();
  ...
}

function createRequest() {
  if (window.XMLHttpRequest)
    return new XMLHttpRequest();
  else
    return new ActiveXObject("Microsoft.XMLHTTP");
}

but in our Test Page, we implement createRequest differently. JsUnit comes with a library called jsUnitAjax.js, which contains a mock implementation of XmlHttpRequest.

&lt;script language="javascript" src="/path/to/jsunit/lib/jsUnitAjax.js"&gt;&lt;/script&gt;

function createRequest() {
  return new MockXmlHttpRequest();
}

Our Test Page’s implementation overrides the real implementation of createRequest. Good: now we are able to call calculate without worrying about a real request getting sent to the server. Let’s go back and continue with our test.

function testClickCalculate() {
  document.entryfield.value = "5";
  calculate();
  assertEquals("GET", request.method);
  assertEquals("/servlet?value=5", request.url);
  assertTrue(request.isAsync);
  assertTrue(request.sendCalled);
  assertNull(request.data);
  assertEquals(callback, request.onreadystatechange);
}

Notice that we’re testing that calling calculate() sends the request, and how the request gets set up – its method, its URL, etc – by examining the mock request. We also verify that the correct callback method has been set up for when the server responds. We aren’t using a real server, and we don’t care (in this test) about a response.

Testing the response

So, that’s half the story. How about testing the response? We need to simulate the server responding to the request. We’ve already tested that the correct callback is set up; let’s take advantage of that now.

function testValidServerResponse() {
  request = new MockXmlHttpRequest();
  request.readyState = 1;
  callback();
  assertEquals("", document.outputfield.value);

  request.readyState = 2;
  callback();
  assertEquals("", document.outputfield.value);

  request.readyState = 3;
  callback();
  assertEquals("", document.outputfield.value);

  request.readyState = 4;
  request.status = 200;
  request.responseText = "50";
  callback();
  assertEquals("50", document.outputfield.value);
}

We go through each readyState, ensuring that nothing happens until state 4. For state 4, we give the mock request a status of 200 and a responseText of “50″, and then verify that calling callback() populates the outputfield correctly. Notice how we’ve tested just the response logic, without using a real server and without the need to set up a meaningful request.

Simulating a server-side error

We have one more test – we need to test what happens when the server responds unsuccessfully:

function testInvalidServerResponse() {
  request = new MockXmlHttpRequest();
  request.readyState = 4;
  request.status = 500;
  callback();
  assertEquals("Error: 500", document.outputfield.value);
}

This time we give the mock request a status of 500, and we verify that the output field contains the expected error message.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (781)
  • rails (113)
  • testing (88)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (55)
  • techtalk (44)
  • rspec (38)
  • ironblogger (32)
  • productivity (30)
  • activerecord (29)
  • gogaruco (29)
  • git (28)
  • nyc (27)
  • rubymine (26)
  • bloggerdome (23)
  • mobile (22)
  • process (21)
  • pivotal tracker (21)
  • cucumber (20)
  • design (19)
  • jasmine (19)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • tracker ecosystem (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • bdd (14)
  • gem (13)
  • css (13)
  • tdd (13)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • api (10)
Subscribe to ajax Feed
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >