The Macintosh uses an experimental pointing device called a 'mouse'. There is no evidence that people want to use these things.
New Jersey has the largest petroleum containment area outside of the Middle East
Gone are the days of actually submitting your search query and waiting for an entirely new page to be rendered. The 'Live Search' era is upon us, and I'm here to welcome it with open arms. My first encounter with a Live Search type of form was some time in 2005 when Google launched Google Suggest. I was so blown away that I figured only a giant company full of PhD's could figure out how to implement this. Boy, was I wrong.
I'm here to discuss a step-by-step procedure for adding a live search feature to your Ruby On Rails application, and some of the peculiarities you may encounter while walking through the steps. The wiki on the Rails site is the only real resource I could find regarding live search, and it will be the basis of our discussion here.
The steps to adding live search to your Ruby On Rails app are as follows:
<%= javascript_include_tag :defaults %>
<%= define_javascript_functions %>
define_javascript_functions will place ALL of the ActionPack Javascript within a <script> tag on your page, where the javascript_include_tag will simply link the external ActionPack Javascript files to your page. (HINT: use javascript_include_tag)
<input type="text" id="search" name="search" />
<img id="busy" src="/images/spinner.gif" style="display:none" />
<div id="searchResults"></div>
<%= observe_field 'search',
:frequency => 0.5,
:update => 'searchResults',
:url => { :controller => 'blog', :action=> 'search' },
:with => "'criteria=' + escape(value)",
:loading => "document.getElementById('busy').
style.display='inline'",
:loaded => "document.getElementById('busy').
style.display='none'" %>
This is where we get a first glimpse of the power inherent in the Ajax helpers. To implement this directly through the XMLHttpRequest object would be much more difficult, error-prone, and messy. Let's take a look at each of the expressions we're using here
:frequency - The frequency, in seconds, that you would like the field to be observed:update - The id of the element where the search results should be rendered. This is the id you chose in step 4:url - The controller/action that implements your search functionality (see step 6):with - Used to pass a parameter called 'criteria' which will be set to the value of the text field at the time of the observation. The call to escape ensures that any special characters in the input are escaped. Note that we are breaking out of the traditional form-submission process where a request parameter is automatically created for a form element when the form is submitted. We technically don't have a form in this case (more on this later.):loading - used to display the busy indicator image you created in step 3. The Javascript placed here will be executed when the search results element is being loaded with data:loaded - used to hide the busy indicator once the search results element is done loading.
def search
if 0 == @params['criteria'].length
@items = nil
else
@items = Blog.find(:all, :order_by => 'title',
:conditions => [ 'LOWER(content) LIKE ?',
'%' + @params['criteria'].downcase + '%' ])
@mark_term = @params['criteria']
end
render_without_layout
end
This search function will perform a simple sql like query on the blog's criteria column. Depending on the size of your site and the volume of content you have, this may or may not be a sufficient way to search. For our purposes here, this method works just fine. Once the results have been gathered, we render the search results (using no layout) with the page you will create in the next step. Also note that we have set an instance variable, @mark_term, that we can use to highlight our search criteria within the rendered search results.
<% if @items && @items.length > 0 %>
<ul id="searchResults">
<% for blog in @items %>
<li>
<%= link_to
@mark_term ? highlight(blog.title, @mark_term) : h(blog.title),
:controller => "blog",
:action => "show", :id => blog.id %>
</li>
<% end %>
</ul>
<% elsif @mark_term && @mark_term.length > 0 %>
No Results
<% else %>
<% end %>
If @items is not nil or empty, then we display our search results as a list of links to each blog entry's 'show' action, highlighting the given search term within each link. If you would like to change the appearance of the highlighting, simply add a style rule for the class 'highlight' in your style sheet. If the @mark_term is not nil or 0 characters, then the search must not have returned any results, so we simply display 'No Results'. Otherwise, we show nothing since this would indicate that the search field has been cleared.
<input> tag that isn't wrapped with a <form> tag.Form elements are elements that allow the user to enter information (like text fields, textarea fields, drop-down menus, radio buttons, checkboxes, etc.) in a form.The last part is what gets me: in a form. Form elements allow you to capture data in a form. But we have no form here. I suppose you could argue that wrapping the input tag with a form element will do no harm here and will make the page more semantically correct, however; the action attribute would be left empty (or useless, since we have no submit button,) and that would open up a whole new basket of worries.
text_field html helper that seems unusableobserve_field kind of just hangs out on the page<script> or <style> tag is just as semantically incorrect. In any event, it bothers me.I personally feel very strongly against limiting a site's core functionality when the user has Javascript disabled. I can see this issue becoming less and less prominent as more mainstream sites are depending on Javascript for core functionality, however; I still see the need to support browsers sans Javascript (Certainly in the case of search functionality.)
If Javascript is disabled in our example here, the user has no way of knowing how to search. They'll enter some text, hit enter, wait, and then get frustrated and leave. Therefore, a button should be displayed for the user when Javascript is disabled (or when the XMLHttpRequest object is not available on the user's browser.) At first glance this problem does not seem to be trivial. There are many factors to consider here (one being the previously stated absence of a form tag and submit button.) This will be the topic of a later post.
The benefits of an automated build/test/package system are, in my opinion, quite obvious. But sometimes it takes a little bit more than statistical evidence to get you motivated. At my current project, we've been thinking about setting up such a system for some time now, but it wasn't until a colleague of mine stumbled upon the system described in Mike Clark's Pragmatic Project Automation that we realized we could include Lava Lamps and home automation devices to display the status of the current build. Fun stuff!
I'm happy to report that the system has been functional for about a week now, and I don't know how we ever lived without it. The Lava Lamps (or 'Extreme Feedback Devices' as Mike Clark calls them,) are very effective. As soon as someone sees the red light come on, they immediately look to see who made the last change to the code base so they can begin their tormenting. The system truly makes you want to keep your code base clean and your unit tests running without any errors.
If you're thinking of implementing a similar system, here's a list of things you'll need to get things rolling:
Extensive documentation is available in the book and on the book's website to fit these pieces together, but a few snags and surprises were discovered along the way that I feel should be documented.
The X10 devices don't seem to work as advertised through our building's electrical system. We found that the transceiver and the lamp module have to be plugged into the same power strip. This presented a problem for us, as we wanted to have a single transceiver with multiple lamp modules scattered around the cubicle farm. We were able to get around this by using a transceiver and a lamp module at each cubicle. I don't know how far the transmitter will work, but we tested it up to about 12 cubicles away without any problems.If I run into some spare time in the near future, I'm gonna see how hard it would be to get this thing running within a Ruby On Rails development environment. If that day ever comes, I'll be sure to post my findings. In the meantime, be sure to hit me up with any suggestions!
Monday, February 13, 2006 08:40 PM EST
Yes, snow can be beautiful. Skyscrapers glisten through unique flakes of frozen crystals as they lay fresh white blankets across the urban landscape. Then, those blankets are mixed with salt and dirt, causing them to melt into a never ending stream of substance that amounts to the dirt, grime, and waste of the millions of people, cars, dogs, even horses that pollute the streets and sidewalks each day. I hate the city when it snows.
In a previous post, I outlined the steps necessary to implement live search functionality in a Ruby On Rails application. That post brought forth some reservations I had regarding the semantics and accessibility nuances associated with the ajax-style search that I hope to (mostly) remedy in this post. Let's review the major problems I had with the original implementation:
<input> tag that isn't wrapped with a <form> tag.text_field html helper that seems unusable.observe_field kind of just hangs out on the page.After reviewing these things for a couple of days, I was able to eliminate points 1, 4, and most of point 5. I'm happy with this since I feel that these are the biggest showstoppers (especially point 5,) as I've come to realize that point 2 and 3 are debate able, subtle, and probably not that important in the grand scheme of things. That being said, let's jump in and give our search functionality some enhanced accessibility.
As I said previously, I'm not a big fan of web sites that limit the core functionality (especially those that just sit there and act stupid,) when the user has Javascript disabled. There are lots of reasons why people would disable Javascript: corporate office policy, too many pop ups asking to buy porn, or maybe Grandma did it by accident when she was tinkering with the IE preferences trying to make the font size bigger. Whatever the reason, I feel that we need to accommodate non-Javascript users with very basic site functionality such as a search feature. I'm not advocating that we stop innovating and pushing the envelope of Javascript-only features like auto-spellcheck or auto-save, I'm just saying that being able to search never required Javascript in the past, and it shouldn't require it now or in the future.
Let's go ahead and iterate through each point and see what we can do to correct things.
<input> tag that isn't wrapped with a <form> tag.
This is easy enough to fix. Just wrap the thing in a form element. No harm done, and this will also set us up for point number 5. While we're at it, let's go ahead and add an action and a submit button to the form, as we'll need those for step 5 as well.
<%= start_form_tag :action => 'search_no_javascript' %>
<input type="text" id="search" name="criteria" />
<%= submit_tag 'Go' %>
<%= end_form_tag %>
text_field html helper that seems unusable.I'm still at a loss on this one. I suppose the Rails framework assumes that all form elements will correspond to something in the data model. In our example here, a search function should certainly not be a part of the data model. I'm sure there are other exceptions, so either I'm missing something that the framework provides, or I'm being a bit too anal here and need to stop looking so deeply into these things.
observe_field kind of just hangs out on the page.Once again, I'm probably looking a bit too deep into this, but I do feel that the observers for an ajax-heavy site should probably be declared in a central, easily maintainable place, rather than in the actual markup. However, I admit that the jury is still out on this one, and we are dealing with only a single observer here, so let's forget about this for now.
This could be regarded as a matter of personal preference, but I believe that a persistent list of search results will make it easier for the person searching to find what they are looking for. This is especially true in our case here, where we are showing the titles of blog entries that contain the search criteria in the blog's actual contents, but not in the title.
The fix for this is pretty obvious: use session variable instead of instance variables. The only thing we need to be careful about here is handling the difference between no results, and not submitting any search criteria. The subtlety is in the fact that submitting no search criteria will in fact return no results (admittedly, this is based on your search implementation,) but we probably don't want to show the user any information regarding the results if no criteria has been sent.
To better coincide with our next topic, I've implemented the search results as a partial named _search.rhtml:
<%if session[:items] != nil &&
(session[:mark_term] == nil||session[:mark_term].length> 0)%>
<dl id="searchResultsDl" class="linkList">
<dt>
<%= session[:items].length %>
Result(s) for
<% session[:mark_term] %>
</dt>
<% for blog in session[:items] %>
<dd>
<% session[:mark_term] ? highlight(blog.title,
session[:mark_term]) : h(blog.title) %>
<dd>
<% end %>
</dl>
<% else %>
#this represents the subtlety mentioned above
<% end %>
We now arrive at the heart of the matter. How do we gracefully degrade this thing? We started by wrapping our search criteria input field with a form element, assigning it an action of search_no_javascript, and adding a submit button. As you've probably guessed, we're going to implement an action in our controller to be used when the user has Javascript disabled. The search functionality will be identical to the standard Javascript-enabled search, so you can go ahead and extract that implementation into a separate private method called something like 'get_search_results'. What concerns us here is the final rendering decision of the action.
Previously, we rendered our search results directly into the searchResults div element with no layout. We're gonna change that a bit here so the Javascript and non-Javascript versions will play nice together. As I stated before, the search results rhtml has been moved to a partial named '_search.rhtml', instead of being inside a 'search.rhtml' file to coincide with the 'search' function:
<div id="searchResults">
<%= render :partial => "search" %>
</div>
Note that this implementation is also a bit more semantically correct than our previous implementation, which displayed an empty div in the markup.
Now let's take a look at our two search actions in our controller:
def search #Javascript-enabled search
get_search_results
render :partial => "search"
end
def search_no_javascript #Javascript-disabled search
get_search_results
redirect_to :back
end
When Javascript is enabled, the search method is invoked from the observer, and the results are rendered using the partial _search.rhtml within the searchResults div. But wait, the _search.rhtml partial has already being rendered within the searchResults div, since we specifically placed it there. However, this rendering happens only on the page load. Once you give an observer a target element, the contents of that element will be replaced with the result of the action given to the observer. So in our case here, its kind of like we are hitting the refresh button on just searchResults div (which I must admit, is quite bizarre when you step back and think about it.) When we throw persistent search results into the mix, this method works beautifully, as hitting refresh will render the most current search results within the searchResults div.
Now, what happens when grandma turns off Javascript while trying to make her fonts bigger? Well, since we added a form and a submit button, she can go ahead and submit her search criteria the old-fashioned way: by hitting the submit button (I can hear the 'When I was your age' stories now!) This will invoke the search_no_javascript method in our action, which will gather the search results in the same way as before, but instead of rendering the results instantly within the searchResults div (which we realize is impossible without Javascript,) we simply re-direct her back to whatever page she was looking at when the search was submitted. A re-direct will cause the browser to make a new request for the page, thereby re-rendering the search results we just stuck in the session.
We're almost done here, but we have a couple more things to discuss. We don't want our users to see a search button if they have Javascript enabled. Similarly, we might want the label of the search field to say 'Live Search' when Javascript is present, and just plain 'Search' when it is not present. This can easily be achieved by initially setting the label to 'Search' and adding a small script just after the form element:
<script type="text/javascript">
document.getElementById("searchSubmit").style.display = "none";
document.getElementById("searchLabel").innerHTML =
"Live Search";
</script>
This piece of script will run only when Javascript is enabled (obviously,) and will swap the label to 'Live Search' and hide the submit button. Some would argue that this should be placed in the body's onload attribute, as it should run when the page is loaded. However, the onload method requires the entire page to be loaded before the script is invoked, which causes a delay in its execution. This means that the user can actually see the label change and the button disappear.
One last point and then I'll shut up. We still haven't touched on the scenario of having Javascript enabled but no XMLHttpRequest functionality. As we all know, most recent browsers do in fact have this support, so I don't view this as being a big deal. However, since this is a post about accessibility, I should add that those users will still be able to use our search by entering their criteria and hitting enter, which will in turn submit the form. The only thing we lose is the visibility of the submit button, and the generic 'Search' label. I think I can live with that.