Highly interactive JavaScript widgets

Manfred Stienstra, 16 Oct 2009, 13:58 in portfolio, javascript, and practices (edit).

For a project we worked on recently the user needed to categorize works of art into disciplines and movements and tag them. We developed a little control panel that is easy to use for first time users and powerful for daily users.

We used standard form elements to make them recognizable and implemented functionality on top of that using JavaScript.

A lot of stuff happens in that video so I’ll explain a bit about what you’re seeing. You can add new items using either the drop down or the text field. Once the item is added, you can toggle it using the check box. Text fields allow you to add any value, but assist after a while by suggesting values previously filled out by others.

We don’t use spinners for background processing because it’s visually distracting, instead we add something to the interface when you add something and disable check boxes when they’re active.

We’ve made sure the panel stays consistent with the information on the server and kept interaction with the server at a minimum. When you select the same item twice nothing happens. If you select a previously unchecked item it doesn’t create a new item but instead it checks the item already on the page. If any communication with the server fails, the controls are reset to their previous state.

Finally, we make sure ‘Browse’ tab is always up to date, otherwise the user might think we lost her carefully filled out information.

No comments yet

Collecting with Moksi

Manfred Stienstra, 18 Apr 2009, 14:54 in testing and javascript (edit).

For some reason language designers and implementers usually do a bad job a designing the API of the standard library. That’s why I’m pretty happy with the bareness of the JavaScript standard library: it leaves room for libraries like Prototype and JSQuery to step in and tailor the API’s to the task the everyday programmer is trying to achieve.

I do mind that JavaScript doesn’t allow you to introspect or invoke event handlers because that’s bad for people who like to write tests.

Getting your test to introspect observers is not that hard, you just collect all the calls to Event.observe and element.observe.

Event.observers = [];
Moksi.stub(Event, 'observe', function(element, name, handler) {
  Event.observers.push([element, name, handler]);
});
$$('input').each(function(element) {
  Moksi.stub(element, 'observe', function(name, handler) {
    Event.observers.push([element, name, handler]);
  });
});

Now you can look in Event.observers and see what handlers got defined on which elements.

this.assertObserves = function(expectedElement, expectedName) {
  this.assert(Event.observers.any(function(args) {
    return (args[0] == expectedElement) && (args[1] == expectedName);
  }));
};

this.assertObserves($('input[name="artist_name"]'), 'blur');

And you can also call them from a test to make sure they work as expected. To keep the example short we assume you don’t have to pass arguments to the handler.

this.callObserver = function(element, eventName) {
  var observer = Event.observers.detect(function(args) {
    return (args[0] == element) && (args[1] == eventName);
  });
  observer[2]();
};

this.callObserver($('input[name="artist_name"]'), 'blur');

You can find Moksi on GitHub and testunit.js is part of script.aculo.us.

No comments yet

Stubbing with Moksi

Manfred Stienstra, 20 Mar 2009, 00:02 in testing and javascript (edit).

In the summer of 2008 I wrote a small mocking and stubbing library for JavaScript called Moksi. I used it a little bit in a project and then forgot about it. When we picked up the project again a few weeks ago I was happy to rediscover Moksi. Because there isn’t much written about testing in JavaScript, I figured it might be a good idea to share some tips and tricks.

We do most of our testing with unittest.js which was developed as part of the testsuite for script.aculo.us. If you’re not familiar with it: Thomas Fuchs did a nice presentation at RailsConf 2006 about it. You might want to read up if you don’t know what I’m taling about.

So now you have assertions galore, but what do you do when you don’t want to recreate an entire scenario in the DOM just to test one or two things? You stub them.

Suppose you want to test the following function code:
var Autosave = Class.create({
  initialize: function(form) {
    this.form = $(form);
  },
  
  installObserver: function() {
    this.form.observe('change', function() {
      this.saveAsDraft();
    });
  },
  
  saveAsDraft: function() {
    form.request({ parameters: { draft: true }});
  }
});

The problem might be that you want to make sure the form is saved as draft, but you don’t actually want to do the AJAX request.

new Test.Unit.Runner({
  setup: function() {
    // Create a form in the DOM and initialize an Autosave.
    $('sample').innerHTML = '<form action="" id="testform"><input type="checkbox" value="money" name="get" /></form>';
    
    this.autosave = new Autosave('testform');
  },
  
  teardown: function() {
    Moksi.revert();
  }
  
  testFormIsSavedAsDraftWhenChanged: function() { with(this) {
    // Stub Form.request on the form and record its requests
    Moksi.stub(this.autosave.form, 'request', function(options) {
      this.autosave.form.requests = this.autosave.form.requests || [];
      this.autosave.form.requests.push(options);
    }.bind(this));
    
    this.autosave.saveAsDraft();
    
    self.assertEqual(1, this.autosave.form.requests.length);
    
    var requestOptions = this.autosave.form.requests.last();
    self.assertNotNull(requestOptions.parameters);
    self.assertEqual(true, requestOptions.parameters.draft);
  }}
});

Notice the Moksi.revert() call? That makes sure all your stubs get reverted after running each test, which is really helpful if you only want to stub a function in one test and use the normally defined function in all the others.

3 comments

Dynamic forms with the repetition model

Manfred Stienstra, 25 Jan 2007, 12:06 in ruby on rails, javascript, and web (edit).

I’m sure we’ve all been there; trying to save a list of associated objects together with the instance they belong to. Fortunately with the right tools this can be really simple.

Let’s assume we have a question in a questionnaire and need to specify an arbitrary number of options to this question. We could construct the following database schema.

create_table :questions do |t|
  t.column :stem,        :text
end

create_table :options do |t|
  t.column :question_id, :integer
  t.column :label,       :text
  t.column :feedback,    :text
  t.column :correct,     :boolean, :default => false
end

There are basically three ways I know about to assign multiple options to a question from one page. The first option is to create a Question instance in the database before rendering the form so we can use remote_form to send an Ajax request and directly associate the Option instance. An advantage of this solution is that all data is directly saved to the database, which makes losing any data less likely. Another advantage is that the functionality is spread nicely over the responsible controllers. First you perform a create on the QuestionsController, after that you perform multiple creates on the OptionsController. The disadvantage is that you have to create a Question with default values or by circumventing the initial validation. This could generate some ‘blank’ Question instances in the database which you will have to garbage collect.

The second option is a slight variation on the first option. Instead of creating a Question to link the Options to you keep all the generated Option id’s in the session and link them to the Question after validation. This solution also benefits from nice separation of concern in the controllers, but on the downside it also leaves you with unlinked Option objects you will have to garbage collect.

The third option is to keep all the information about the Questions and Options in the DOM tree of your from and post it all at once. This used to be hard, requiring a lot of custom JavaScript and browser tricks. Fortunately WHATWG is here to help; Web Forms 2.0 defines a repetition model for repeating form controls. Browser support for these interaction models is years away (except for Opera 9, which has an experimental implementation), that’s why there’s a JavaScript library to accelerate the adaptation.

It’s really easy to use the library in your Rails application. Download the repetitionmodel library, extract the zipfile and put the javascript files in public/javascripts. When you’re done with that we can get back to coding our application. Include the JavaScript file somewhere in your views.

<%= javascript_include_tag 'repetition-model-p' %>

We just mentioned that we want to send the entire form at once, we can use validates_associated to make sure the Question is never saved when the options aren’t valid. This allows you to easily validate all the information from the form at once. The models to go with the database look something like this.

class Question < ActiveRecord::Base
  has_many :options, :dependent => :delete
  validates_presence_of :stem
  validates_associated :options
end

class Option < ActiveRecord::Base
  belongs_to :question
  validates_presence_of :label
end

Build the new and edit forms for the QuestionController like you would normally do. A the bottom of the form we will add the HTML to edit the options.

<h2>Options</h2>
<ol id="options">
  <%= render :partial => 'options/option', :collection => @question.options %>
  <%= render :partial => 'options/option', :object => Option.new,
    :locals => {:option_counter => '[option]'} %>
</ol>
<p><button type="add" template="option">New option</button></p>

The first render is for all the existing options, the second render is for what the repetition model calls a template, the template should always be the last in the list of controls.

In app/views/options/_option.rhtml we put the following. This partial doubles as a template for new controls and as a partial for existing options in the database.

<% if option_counter == '[option]' -%>
<li id="option" repeat="template" repeat-start="0">
<% else -%>
<li repeat="<%= option_counter %>">
<% end -%>
  <div>
    <div><label>Label</label></div>
    <%= text_area_tag "options[#{option_counter}][label]", option.label,
      :rows => 1, :cols => 40 %>
  </div>
  <div>
    <div><label>Feedback</label></div>
    <%= text_area_tag "options[#{option_counter}][feedback]", option.feedback,
      :rows => 1, :cols => 40 %>
  </div>
  <div>
    <label><%= radio_button_tag "options[correct]", option_counter, option.correct?,
      :id => "options_correct_#{option_counter}" %> Correct</label>
  </div>
  <div>
    <button type="remove">Delete option</button>
  </div>
</li>

In the HTML you see some extra attributes used by the JavaScript to determine what to do with them. When the partial is rendered with an new Option, the template is flagged by setting the repeat attribute to template. The id of this element is used as a handle for our controls, in our case this is ‘option’. The repeat-start attribute tells the javascript how many empty controls to generate initially from the template, we don’t want any so we’ve set it to 0. Note that we explicitly set the option_counter to ‘[option]’, this is the variable notation for the repetition model. When a new control is instanciated from the template this variable is replaced by the the index of the new control. The first control gets index 0, the second gets index 1 and so forth.

When the partial is rendered with a collection, the magic variable option_counter is set to the index of the collection every time the partial is rendered. We use this index to set the repeat attribute of the list item, this signals to the JavaScript that this is an already instantiated control. The JavaScript will start counting from the largest index when it instantiates a new control.

Finally we want the user to add and remove option controls in our page, this is done with the ‘New option’ and ‘Delete option’ buttons. Their type attribute signals what we want the JavaScript to do with the repetition blocks. In case of the ‘New option’ button the template attribute tells the JavaScript which template to instantiate.

In addition to managing the controls and template in the DOM tree, the JavaScript also takes care of disabling buttons on appropriate times and setting the CSS display property of the template to none. They really thought of everything.

The advantage of this solution is that you can use all the standard Rails tricks to keep your database clean. The biggest disadvantage is that the create method in your QuestionsController becomes a lot more complex.

Let’s hope native browser implementations follow quickly.

8 comments

Embedding applications with Javascript

Manfred Stienstra, 06 Jun 2006, 10:53 in javascript and web (edit).

RightCart is a cool service that allows you to embed a shopping cart into your webpage. It looks great, but I think it should be a little bit easier to set up.

When you create an account, you get a small piece of HTML to add to your web page (I’ve added formatting for clarity):

<div class='rightcart_div'>
  <script type='text/javascript'>rightcart_pid='1'</script>
  <script type='text/javascript' src='http://rightcart.com/static/rightcart_display.js'></script>
</div>

Above the code is the following text:

“Simply add the three lines of code below to your web page and your RightCart will appear on your site. It’s that easy.”

But why three lines? Let’s look at the information this application needs. I see a div with a the class ‘rightcart_div’, followed by a variable ‘rightcart_pid’ and after that an URL for a piece of JavaScript. In Rhubarb we do the same with a single line of code:

<script type="text/javascript" src="http://rhubarb/inline/58"></script>

So why is this shorter? We don’t need a named div because our javascript adds it to the page. We don’t need to set a user id of some sort because it’s in the URL. That leaves a single script tag.

Even better, the full code contains a link to a full-page version of the same content for when JavaScript is not available:

<script type="text/javascript" src="http://rhubarb.fngtps.com/inline/58"></script> 
<noscript>
  <p><a href="http://rhubarb.fngtps.com/58">Aksie!</a></p>
</noscript>

It can be dangerous to add HTML to other pages like this. The injected HTML could end up in biggest case of tagsoup you’ve ever seen, completely breaking your layout.

That’s why we offer four different ways to publish an appeal with Rhubarb: a regular page, embedded on another page inside an iframe, embedded inline and embedded with a nice lightbox effect.

This way you can choose the delivery type that best fits your site while keeping everything accessible.

No comments yet

Rails plugins released

Manfred Stienstra, 09 Feb 2006, 11:55 in ruby on rails and javascript (edit).

A few months back we decided to release the inject plugin, however we didn’t post it to the Rails wiki because we were still reorganizing our domain structure and didn’t have anonymous Subversion access to our repositories.

For now we have two plugins in the trunk, but more might follow in the future.

Inject

The inject plugin allows you to do cross-domain AJAX-like http calls. You can find (a little bit) more information on the Rails wiki entry for Inject.

Url for domain

Url for domain adds :subdomain support to the url_for method. Actually it adds this to a the url rewriter code, so you can use this options on every helper/method with url_for-like syntax. Again, more information can be found on the Rails wiki entry for Url for domain

Have fun with the plugins and lets us know if you have any problems. We have a little project coming up which uses both these plugins, so keep an eye out for that.

2 comments

Inject me

Manfred Stienstra, 30 Nov 2005, 12:54 in ruby on rails and javascript (edit).

A month or so ago I found myself staring at my screen, I had done something stupid. Almost the entire application was finished, but when I tested it on another domain nothing worked. When I started exploring the Firefox javascript window and pasted some errors in Google, I found out I couldn’t do cross-domain XMLHttpRequests. I looked at Thijs, my boss / colleague / friend, and said: ‘We have a little problem.’ After some looking around we found out that Julien Lamarre had solved our problem.

In order to fit this solution into our Rails app, I wrote inject.js and some helpers. When plugin support came out I converted my solution to a plugin, and after some testing it is ready for the world. You can download it here.

Some comments are probably in place. The Inject.Request object doesn’t work exactly like the Ajax.Request object as it always evaluates the response from the server and doesn’t support the same arguments. Please take a look at the code, because it’s the best documentation right now.

The general idea behind the javascript is that it creates an invisible div element in the body element of the page and injects script tags into this div. The URL’s in the src attribute of the script tags are actually calls to Rails controllers and actions. You will have to return javascript from the actions if you want to change the state of the page, play nice and return a ‘text/javascript’ content-type when you do this.

4 comments