Our development process calls for a design component up front. Design is typically one or two weeks ahead of development so that by the time a developer is ready to implement the feature, there’s a pretty good idea of how the interactions will work, and even better, part or all of the markup has already been written. This introduces a problem where at any point in time there will be Work In Progress functionality that hasn’t been wired to the backend. This creates frustration and confusion for the product owner, who gets oh so excited to see a feature corresponding to the story they have just recently created, only to find out that it’s only a placeholder.
Displaying work in progress UI elements is also sometimes not an option,
especially on an iterative development process where each week the app is shown
to real users to gather feedback. Hiding .wip elements using CSS,
commiting and redeploying to prime the app for user testing, followed by
reverting the CSS commit in order to continue with development soon became old
and cumbersome. There’s a better way to support the process.
You should go here on a new tab and click play. Also, here’s a picture of Devo. I’ve included a unicorn with a shining star to keep them company. Don’t worry, it’s Work In Progress.

What I present here is not ground breaking, but it is a well tested piece of code that you can drop into your app and will give your product owners the ability to view how the interface is shaping up. It still gives developers and designers the opportunity to continue implementing Work In Progress features on a live app without having to deal with too many feature branches, where keeping the markup and styles up to date with master at an early stage is an expensive moving target.
The idea is to have the ability to toggle visibility of Work In Progress
elements of the app. For a developer or designer, it’s as simple as setting a
CSS class of wip on any DOM element. For a user, pressing Ctrl-W
reveals the WIP elements.
We practice Outside-In development, so let’s use Cucumber to specify this behavior:
Feature:
As a product owner
So that I can view what's work in progress
I can press Control W
@javascript
Scenario:
Given I visit the home page and the body is marked as wip
When I press Control "w"
Then the wip tags are visible
When I press Control "a"
Then the wip tags are visible
When I press Control "w"
Then the wip tags are not visible
When I press Control "a"
Then the wip tags are not visible
First, we use jQuery to set the wip class on the document’s body from within the step definition. Here’s the
implementation:
Given /^I visit "([^"]*)" and the body is marked as wip$/ do |page_name|
visit(path_to(page_name))
page.execute_script("$('body').addClass('wip');")
page.should have_css('body.wip')
end
Pressing Control W also evaluates javascript to trigger the event:
When /^I press Control "(\w)"/ do |character|
code = case character
when 'w'
23
when 'a'
65
end
page.execute_script(<<-JS)
var e = $.Event('keypress', { which: #{code}, ctrlKey: true });
$('body').trigger(e)
JS
end
Finally, checking whether the WIP elements are visible is a matter of checking the CSS classes:
Then /^the wip tags are visible/ do
page.should have_css('.wip.visible')
end
Then /^the wip tags are not visible/ do
page.should have_css('.wip')
page.should have_no_css('.wip.visible')
end
I placed all of these steps definitions in features/step_definitions/wip_steps.rb
To make this pass, the jQuery implementation is simple:
$(document).ready(function() {
$('html').keypress(function(e) {
var codeForW = 23;
if (e.ctrlKey && e.which === codeForW) {
$('.wip').toggleClass('visible');
}
});
});
Here are some styles that handle WIP element visibility and give them a yellow outline:
.wip {
outline: 1px solid #ff0 !important;
visibility: hidden;
}
.wip.visible {
visibility: visible;
}
When dropping this into your app, just remember that when a problem comes along, you must WIP it. Before the cream sets out too long, you must WIP it. When something’s goin’ wrong, you must WIP it. To WIP it, well, WIP it good.
A recipe for adding searching and filtering to your Rails app.
Provide faster feedback for users. Increase the chance that they’ll find what they want.
Side note: This feature is testing the non-Javascript path through the app. I’m not going to show any Javascript testing in this recipe. The thoughtbot team has been trying to settle on a Javascript integration strategy that we’re happy with. We’ve tried some things but aren’t in love with anything yet.
Scenario: Search by fieldnotes
Given a project exists with a name of "Big Dig"
And the following reports exist:
| name | fieldnotes | project |
| Traffic Control | Barricade | name: Big Dig |
| Gases, Vapors | Dust | name: Big Dig |
When I sign in as a safety manager of "Big Dig"
And I go to the "Traffic Control" report page
And I search for "Dust"
Then I should be on the reports page
And I should see "Gases, Vapors"
And I should not see "Traffic Control"
And the search field should contain "Dust"
The following #{factory_name.pluralize} exist: step definition comes for free with Factory Girl. Note the extra cleverness FG has with the name: #{project_name} field in the table. Real time-saver.
I know I’m going to be writing multiple search scenarios so I created some step definitions for search:
When /^I search for "([^\"]*)"$/ do |query|
When %{I fill in "search-field" with "#{query}"}
And %{I press "Search"}
end
Then /^the search field should contain "([^\"]*)"$/ do |query|
field_with_id("search-field").value.should == query
end
I know I want search-field because the designer, Fred, has already sliced the HTML and CSS.
Another side note: field_with_id comes from Webrat::Locators but Joe is starting to advocate for a nicer abstraction called NamedElements that wouldn’t require writing your own step definitions.
The search box:
<% form_tag reports_path, :method => :get do %>
<%= text_field_tag "query", params[:query], :id => "search-field" %>
<%= submit_tag "Search", :id => "search-reports-button" %>
<% end %>
Only thing that might be of note is using params[:query] to get the ‘And the search field should contain “Dust”’ test to pass.
The filters:
<div id="filter-list" style="display: none;">
<form id="filters">
<!-- a bunch of checkboxes -->
</form>
</div>
The results:
<tbody class="striped" id="reports-tbody">
<%= render :partial => "report", :collection => @reports %>
</tbody>
The index action:
def index
@reports = Report.search(params)
if request.xhr?
render :partial => "/reports/report", :collection => @reports
end
end
There’s some authorization around scoping results to the current user’s project that I’ve removed for brevity.
There’s about a dozen different ways this data can be filtered. There’s specs for each kind that look something like this:
it "should find reports assigned to any of a set of safety inspectors" do
first = Factory(:safety_inspector)
second = Factory(:safety_inspector)
third = Factory(:safety_inspector)
find_me = Factory(:report, :safety_inspector => first)
me_too = Factory(:report, :safety_inspector => second)
not_me = Factory(:report, :safety_inspector => third)
reports = Report.search({ :safety_inspectors => "#{first.id},#{second.id}" })
reports.should include(find_me)
reports.should include(me_too)
reports.should_not include(not_me)
end
There’s one for sending a text query, and a giant one that tests combinations.
The method through which all searching and filtering passes:
def self.search(params)
full_text_search(params[:query].to_s).
location_in(params[:locations].to_s).
safety_inspector_in(params[:safety_inspectors].to_s).
safety_category_in(params[:safety_categories].to_s).
created_after(params[:from].to_s).
created_before(params[:to].to_s).
severity_in(params[:severities].to_s).
state_in(params[:states].to_s).
descend_by_severity_and_created_at.
distinct
end
Most items in this chain are custom class methods that wrap Searchlogic:
def self.location_in(locations)
return no_op if locations.blank?
location_id_equals_any(locations.to_array_of_ints)
end
named_scope :distinct, :select => "distinct reports.*"
named_scope :no_op, {}
The no_op is our way of maintaining chainability in the common cases where most searching and filtering criteria is blank. We don’t even want the SQL to be built for blank options. Maybe we should patch this upstream to Searchlogic?
The array of ints is a custom String extension:
class String
def to_array_of_ints
self.split(',').collect { |integer| integer.to_i }.to_a
end
end
The idea here is to do all filtering on highly indexed integers, which Postgres handles quickly. It’s also easy to pass in comma-separated ids from jQuery as we’ll see later.
“Full text search” is just SQL LIKEing while avoiding SQL injection:
def self.full_text_search(query)
return no_op if query.blank?
text_search(query)
end
named_scope :text_search, lambda {|query|
{
:joins => "INNER JOIN locations ON locations.id = reports.location_id
INNER JOIN users ON users.id IN (reports.safety_inspector_id, reports.supervisor_id, reports.subcontractor_id)",
:conditions => ["(reports.fieldnotes ILIKE :query) OR
(reports.name ILIKE :query) OR
(locations.name ILIKE :query) OR
(users.name ILIKE :query)", { :query => "%#{query}%" }]
}
}
Bind all the necessary events:
$(document).ready(function() {
$('#clear-filters-btn').click(function() {
$('#filters :checked').attr('checked', false);
$('#slider').slider('values', 0, $('#slider').slider('option', 'min'));
$('#slider').slider('values', 1, $('#slider').slider('option', 'max'));
searchReports();
});
$("#filter-list-btn").click(function(){
$(this).toggleClass("active");
$("#filter-list").slideToggle("500");
return false;
});
$('#search-field').keyup(searchReports);
$('#filters :checkbox').click(searchReports);
$('#filters :text').focus(searchReports);
});
Build the Ajax call with jQuery.param and call it:
function searchReportsURL(){
var params = {
'query' : escape($('#search-field').val()),
'locations' : checkedIdsForFilter('location'),
'supervisors' : checkedIdsForFilter('supervisor'),
'safety_categories' : checkedIdsForFilter('category'),
'severities' : checkedIdsForFilter('severity'),
'states' : checkedIdsForFilter('state'),
'risk_profiles' : checkedIdsForFilter('risk-profile'),
'from' : $('.left-handle').text(),
'to' : $('.right-handle').text()
}
return '/reports?' + $.param(params) + '&' + (new Date()).getTime();
}
var searchReportsTimeout = null;
function searchReports() {
if (searchReportsTimeout) {
clearTimeout(searchReportsTimeout);
}
searchReportsTimeout = setTimeout(function() {
$.get(searchReportsURL(),
function(data) {
$('#reports-tbody').html(data);
$(document).trigger('stripeRows');
}
);
}, 500);
}
For brevity, I’ve omitted some helpers like checkedIdsForFilter that build a comma-separated list of ids for each criteria. You can figure that out based on your own markup and Javascript’s replace() method.
I kept the timeout, however, because I think it’s important for the user experience. Without it, the search will happen too fast on every keyup(), resulting in a herky-jerky experience. There’s a half-second pause now, but that’s preferred over “strobe-lighting” the user.
Appending the date to the end of the URL is a cache-buster for Internet Explorer.
Bon appétit!
I’m still in the 101 classes of jQuery culinary school, which is why I’m excited about thoughtbot teaming up with Bocoup to provide our first jQuery class in June.
If you’re not familiar with Bocoup, they’ve been setting Boston afire recently with their torrid pace of about one Javascript event a week at their loft, where this training will also be held.
Hope to see you there!