Show 'N Tell Friday: Dynamic Search Filters
As a reminder, Show ‘N Tell Fridays are for anyone to talk a little about something they’ve worked on recently and it doesn’t have to be web design/development related. If you aren’t on Tumblr, drop me a line with a link to your post - I’d love to see what others are doing.
One of my ongoing projects is the street sign management application from Elumena. The software is used by towns and cities for keeping track of all of their signage and the information about them - their GPS coordinates, condition, installation date, etc. - about 30 fields in all. The application is primarily used by crews in the field using Motion F5 Tablet PCs. There are several interesting pieces of the application (and the client has given me free reign to discuss it all), but I thought I would touch on the searching and filtering UI today.
This is a feature I built about a year ago, but I’ve been doing some updates to it this week so it’s fresh in my mind. The original search implementation was very basic and it quickly became apparent that we needed a more robust tool. My initial inspiration was the search filter tool at MyFonts.com. Having the ability to combine any number of filters would be a big win for Elumena users.
The new filtering mechanism looks like this (see larger).
The actual search on the server is handled by the fantastic Searchlogic Rails plugin. Assuming you can massage the form parameters into the right syntax for Searchlogic, you have hardly any work to do on the back-end. In fact, the entire controller action for all of this looks like this.
def index @search = Sign.new_search(params[:search]) @all_signs, @signs_count = @search.all, @search.count @signs = @all_signs.paginate(:page => params[:page]) @map_signs = build_simple_signs(@all_signs) end
The new_search method is doing all of the real work. The other variables are for getting the total result set of matches for display on the map (for which I’m using MarkerClusterer, a possible future Show ‘N Tell topic) as well as the pagination pieces.
The rest of the work is on the client-side. When the user clicks the “add” icon, the jQuery clone() function copies that filter row and places it just below the current row. jQuery event handlers are then used to watch for changes on the type of filter (ie. Street Name, Sign Condition, etc.) and updates the associated options. A “template” of all of these options are loaded with the page (in a hidden container) and copied as needed.
<select id="asset_id_filter_type">
<option value="is">is</option>
<option value="is_not">is not</option>
<option value="at_most">is at most</option>
<option value="at_least">is at least</option>
</select>
<select id="background_color_filter_type">
<option value="is">is</option>
<option value="is_not">is not</option>
</select>
<select id="detailed_condition_filter_type">
<option value="is">is</option>
<option value="is_not">is not</option>
</select>
etc...<div id="asset_id_filter_values">
<input type="text" class="search_entry text" />
</div>
<div id="background_color_filter_values">
<select class="search_entry">
<%= options_for_select(BackgroundColor.to_dropdown) %>
</select>
</div>
<div id="detailed_condition_filter_values">
<select class="search_entry">
<%= options_for_select(DetailedCondition.to_dropdown) %>
</select>
</div>
etc...These form elements, however, are not used on the back-end since they are not in a Searchlogic-friendly format. The final piece to the puzzle is to have one more jQuery event handler that updates a hidden field for each filter. Something along these lines:
$.fn.updateSearchValue = function() {
return this.each(function() {
var filter_field = $(this).find('select.filter_field').val();
var filter_type = $(this).find('select.filter_type').val();
$(this).find('.search_value')
.attr('name', "search[conditions][" + filter_field + "_" + filter_type + "]");
// get the value if it's a dropdown
if ($(this).find('select.search_entry')) {
$(this).find('.search_value')
.attr('value', $(this).find('select.search_entry option:selected').text());
}
});I glossed over a lot of the details obviously, but hopefully this helps illustrate how the use of a solid server-side tool can be used “out of the box” with some creativity on the front-end.