Skip navigation
Help

search interface

warning: Creating default object from empty value in /var/www/vhosts/sayforward.com/subdomains/recorder/httpdocs/modules/taxonomy/taxonomy.pages.inc on line 33.
Original author: 
(author unknown)

We’ve all been there: that bit of JavaScript functionality that started out as just a handful of lines grows to a dozen, then two dozen, then more. Along the way, a function picks up a few more arguments; a conditional picks up a few more conditions. And then one day, the bug report comes in: something’s broken, and it’s up to us to untangle the mess.

As we ask our client-side code to take on more and more responsibilities—indeed, whole applications are living largely in the browser these days—two things are becoming clear. One, we can’t just point and click our way through testing that things are working as we expect; automated tests are key to having confidence in our code. Two, we’re probably going to have to change how we write our code in order to make it possible to write tests.

Really, we need to change how we code? Yes—because even if we know that automated tests are a good thing, most of us are probably only able to write integration tests right now. Integration tests are valuable because they focus on how the pieces of an application work together, but what they don’t do is tell us whether individual units of functionality are behaving as expected.

That’s where unit testing comes in. And we’ll have a very hard time writing unit tests until we start writing testable JavaScript.

Unit vs. integration: what’s the difference?

Writing integration tests is usually fairly straightforward: we simply write code that describes how a user interacts with our app, and what the user should expect to see as she does. Selenium is a popular tool for automating browsers. Capybara for Ruby makes it easy to talk to Selenium, and there are plenty of tools for other languages, too.

Here’s an integration test for a portion of a search app:

def test_search
  fill_in('q', :with => 'cat')
  find('.btn').click
  assert( find('#results li').has_content?('cat'), 'Search results are shown' )
  assert( page.has_no_selector?('#results li.no-results'), 'No results is not shown' )
end

Whereas an integration test is interested in a user’s interaction with an app, a unit test is narrowly focused on a small piece of code:

When I call a function with a certain input, do I receive the expected output?

Apps that are written in a traditional procedural style can be very difficult to unit test—and difficult to maintain, debug, and extend, too. But if we write our code with our future unit testing needs in mind, we will not only find that writing the tests becomes more straightforward than we might have expected, but also that we’ll simply write better code, too.

To see what I’m talking about, let’s take a look at a simple search app:

Srchr

When a user enters a search term, the app sends an XHR to the server for the corresponding data. When the server responds with the data, formatted as JSON, the app takes that data and displays it on the page, using client-side templating. A user can click on a search result to indicate that he “likes” it; when this happens, the name of the person he liked is added to the “Liked” list on the right-hand side.

A “traditional” JavaScript implementation of this app might look like this:

var tmplCache = {};

function loadTemplate (name) {
  if (!tmplCache[name]) {
    tmplCache[name] = $.get('/templates/' + name);
  }
  return tmplCache[name];
}

$(function () {

  var resultsList = $('#results');
  var liked = $('#liked');
  var pending = false;

  $('#searchForm').on('submit', function (e) {
    e.preventDefault();

    if (pending) { return; }

    var form = $(this);
    var query = $.trim( form.find('input[name="q"]').val() );

    if (!query) { return; }

    pending = true;

    $.ajax('/data/search.json', {
      data : { q: query },
      dataType : 'json',
      success : function (data) {
        loadTemplate('people-detailed.tmpl').then(function (t) {
          var tmpl = _.template(t);
          resultsList.html( tmpl({ people : data.results }) );
          pending = false;
        });
      }
    });

    $('<li>', {
      'class' : 'pending',
      html : 'Searching &hellip;'
    }).appendTo( resultsList.empty() );
  });

  resultsList.on('click', '.like', function (e) {
    e.preventDefault();
    var name = $(this).closest('li').find('h2').text();
    liked.find('.no-results').remove();
    $('<li>', { text: name }).appendTo(liked);
  });

});

My friend Adam Sontag calls this Choose Your Own Adventure code—on any given line, we might be dealing with presentation, or data, or user interaction, or application state. Who knows! It’s easy enough to write integration tests for this kind of code, but it’s hard to test individual units of functionality.

What makes it hard? Four things:

  • A general lack of structure; almost everything happens in a $(document).ready() callback, and then in anonymous functions that can’t be tested because they aren’t exposed.
  • Complex functions; if a function is more than 10 lines, like the submit handler, it’s highly likely that it’s doing too much.
  • Hidden or shared state; for example, since pending is in a closure, there’s no way to test whether the pending state is set correctly.
  • Tight coupling; for example, a $.ajax success handler shouldn’t need direct access to the DOM.

Organizing our code

The first step toward solving this is to take a less tangled approach to our code, breaking it up into a few different areas of responsibility:

  • Presentation and interaction
  • Data management and persistence
  • Overall application state
  • Setup and glue code to make the pieces work together

In the “traditional” implementation shown above, these four categories are intermingled—on one line we’re dealing with presentation, and two lines later we might be communicating with the server.

Code Lines

While we can absolutely write integration tests for this code—and we should!—writing unit tests for it is pretty difficult. In our functional tests, we can make assertions such as “when a user searches for something, she should see the appropriate results,” but we can’t get much more specific. If something goes wrong, we’ll have to track down exactly where it went wrong, and our functional tests won’t help much with that.

If we rethink how we write our code, though, we can write unit tests that will give us better insight into where things went wrong, and also help us end up with code that’s easier to reuse, maintain, and extend.

Our new code will follow a few guiding principles:

  • Represent each distinct piece of behavior as a separate object that falls into one of the four areas of responsibility and doesn’t need to know about other objects. This will help us avoid creating tangled code.
  • Support configurability, rather than hard-coding things. This will prevent us from replicating our entire HTML environment in order to write our tests.
  • Keep our objects’ methods simple and brief. This will help us keep our tests simple and our code easy to read.
  • Use constructor functions to create instances of objects. This will make it possible to create “clean” copies of each piece of code for the sake of testing.

To start with, we need to figure out how we’ll break our application into different pieces. We’ll have three pieces dedicated to presentation and interaction: the Search Form, the Search Results, and the Likes Box.

Application Views

We’ll also have a piece dedicated to fetching data from the server and a piece dedicated to gluing everything together.

Let’s start by looking at one of the simplest pieces of our application: the Likes Box. In the original version of the app, this code was responsible for updating the Likes Box:

var liked = $('#liked');

var resultsList = $('#results');


// ...


resultsList.on('click', '.like', function (e) {
  e.preventDefault();

  var name = $(this).closest('li').find('h2').text();

  liked.find( '.no-results' ).remove();

  $('<li>', { text: name }).appendTo(liked);

});

The Search Results piece is completely intertwined with the Likes Box piece and needs to know a lot about its markup. A much better and more testable approach would be to create a Likes Box object that’s responsible for manipulating the DOM related to the Likes Box:

var Likes = function (el) {
  this.el = $(el);
  return this;
};

Likes.prototype.add = function (name) {
  this.el.find('.no-results').remove();
  $('<li>', { text: name }).appendTo(this.el);
};

This code provides a constructor function that creates a new instance of a Likes Box. The instance that’s created has an .add() method, which we can use to add new results. We can write a couple of tests to prove that it works:

var ul;

setup(function(){
  ul = $('<ul><li class="no-results"></li></ul>');
});

test('constructor', function () {
  var l = new Likes(ul);
  assert(l);
});

test('adding a name', function () {
  var l = new Likes(ul);
  l.add('Brendan Eich');

  assert.equal(ul.find('li').length, 1);
  assert.equal(ul.find('li').first().html(), 'Brendan Eich');
  assert.equal(ul.find('li.no-results').length, 0);
});

Not so hard, is it? Here we’re using Mocha as the test framework, and Chai as the assertion library. Mocha provides the test and setup functions; Chai provides assert. There are plenty of other test frameworks and assertion libraries to choose from, but for the sake of an introduction, I find these two work well. You should find the one that works best for you and your project—aside from Mocha, QUnit is popular, and Intern is a new framework that shows a lot of promise.

Our test code starts out by creating an element that we’ll use as the container for our Likes Box. Then, it runs two tests: one is a sanity check to make sure we can make a Likes Box; the other is a test to ensure that our .add() method has the desired effect. With these tests in place, we can safely refactor the code for our Likes Box, and be confident that we’ll know if we break anything.

Our new application code can now look like this:

var liked = new Likes('#liked');
var resultsList = $('#results');



// ...



resultsList.on('click', '.like', function (e) {
  e.preventDefault();

  var name = $(this).closest('li').find('h2').text();

  liked.add(name);
});

The Search Results piece is more complex than the Likes Box, but let’s take a stab at refactoring that, too. Just as we created an .add() method on the Likes Box, we also want to create methods for interacting with the Search Results. We’ll want a way to add new results, as well as a way to “broadcast” to the rest of the app when things happen within the Search Results—for example, when someone likes a result.

var SearchResults = function (el) {
  this.el = $(el);
  this.el.on( 'click', '.btn.like', _.bind(this._handleClick, this) );
};

SearchResults.prototype.setResults = function (results) {
  var templateRequest = $.get('people-detailed.tmpl');
  templateRequest.then( _.bind(this._populate, this, results) );
};

SearchResults.prototype._handleClick = function (evt) {
  var name = $(evt.target).closest('li.result').attr('data-name');
  $(document).trigger('like', [ name ]);
};

SearchResults.prototype._populate = function (results, tmpl) {
  var html = _.template(tmpl, { people: results });
  this.el.html(html);
};

Now, our old app code for managing the interaction between Search Results and the Likes Box could look like this:

var liked = new Likes('#liked');
var resultsList = new SearchResults('#results');


// ...


$(document).on('like', function (evt, name) {
  liked.add(name);
})

It’s much simpler and less entangled, because we’re using the document as a global message bus, and passing messages through it so individual components don’t need to know about each other. (Note that in a real app, we’d use something like Backbone or the RSVP library to manage events. We’re just triggering on document to keep things simple here.) We’re also hiding all the dirty work—such as finding the name of the person who was liked—inside the Search Results object, rather than having it muddy up our application code. The best part: we can now write tests to prove that our Search Results object works as we expect:

var ul;
var data = [ /* fake data here */ ];

setup(function () {
  ul = $('<ul><li class="no-results"></li></ul>');
});

test('constructor', function () {
  var sr = new SearchResults(ul);
  assert(sr);
});

test('display received results', function () {
  var sr = new SearchResults(ul);
  sr.setResults(data);

  assert.equal(ul.find('.no-results').length, 0);
  assert.equal(ul.find('li.result').length, data.length);
  assert.equal(
    ul.find('li.result').first().attr('data-name'),
    data[0].name
  );
});

test('announce likes', function() {
  var sr = new SearchResults(ul);
  var flag;
  var spy = function () {
    flag = [].slice.call(arguments);
  };

  sr.setResults(data);
  $(document).on('like', spy);

  ul.find('li').first().find('.like.btn').click();

  assert(flag, 'event handler called');
  assert.equal(flag[1], data[0].name, 'event handler receives data' );
});

The interaction with the server is another interesting piece to consider. The original code included a direct $.ajax() request, and the callback interacted directly with the DOM:

$.ajax('/data/search.json', {
  data : { q: query },
  dataType : 'json',
  success : function( data ) {
    loadTemplate('people-detailed.tmpl').then(function(t) {
      var tmpl = _.template( t );
      resultsList.html( tmpl({ people : data.results }) );
      pending = false;
    });
  }
});

Again, this is difficult to write a unit test for, because so many different things are happening in just a few lines of code. We can restructure the data portion of our application as an object of its own:

var SearchData = function () { };

SearchData.prototype.fetch = function (query) {
  var dfd;

  if (!query) {
    dfd = $.Deferred();
    dfd.resolve([]);
    return dfd.promise();
  }

  return $.ajax( '/data/search.json', {
    data : { q: query },
    dataType : 'json'
  }).pipe(function( resp ) {
    return resp.results;
  });
};

Now, we can change our code for getting the results onto the page:

var resultsList = new SearchResults('#results');

var searchData = new SearchData();

// ...

searchData.fetch(query).then(resultsList.setResults);

Again, we’ve dramatically simplified our application code, and isolated the complexity within the Search Data object, rather than having it live in our main application code. We’ve also made our search interface testable, though there are a couple caveats to bear in mind when testing code that interacts with the server.

The first is that we don’t want to actually interact with the server—to do so would be to reenter the world of integration tests, and because we’re responsible developers, we already have tests that ensure the server does the right thing, right? Instead, we want to “mock” the interaction with the server, which we can do using the Sinon library. The second caveat is that we should also test non-ideal paths, such as an empty query.

test('constructor', function () {
  var sd = new SearchData();
  assert(sd);
});

suite('fetch', function () {
  var xhr, requests;

  setup(function () {
    requests = [];
    xhr = sinon.useFakeXMLHttpRequest();
    xhr.onCreate = function (req) {
      requests.push(req);
    };
  });

  teardown(function () {
    xhr.restore();
  });

  test('fetches from correct URL', function () {
    var sd = new SearchData();
    sd.fetch('cat');

    assert.equal(requests[0].url, '/data/search.json?q=cat');
  });

  test('returns a promise', function () {
    var sd = new SearchData();
    var req = sd.fetch('cat');

    assert.isFunction(req.then);
  });

  test('no request if no query', function () {
    var sd = new SearchData();
    var req = sd.fetch();
    assert.equal(requests.length, 0);
  });

  test('return a promise even if no query', function () {
    var sd = new SearchData();
    var req = sd.fetch();

    assert.isFunction( req.then );
  });

  test('no query promise resolves with empty array', function () {
    var sd = new SearchData();
    var req = sd.fetch();
    var spy = sinon.spy();

    req.then(spy);

    assert.deepEqual(spy.args[0][0], []);
  });

  test('returns contents of results property of the response', function () {
    var sd = new SearchData();
    var req = sd.fetch('cat');
    var spy = sinon.spy();

    requests[0].respond(
      200, { 'Content-type': 'text/json' },
      JSON.stringify({ results: [ 1, 2, 3 ] })
    );

    req.then(spy);

    assert.deepEqual(spy.args[0][0], [ 1, 2, 3 ]);
  });
});

For the sake of brevity, I’ve left out the refactoring of the Search Form, and also simplified some of the other refactorings and tests, but you can see a finished version of the app here if you’re interested.

When we’re done rewriting our application using testable JavaScript patterns, we end up with something much cleaner than what we started with:

$(function() {
  var pending = false;

  var searchForm = new SearchForm('#searchForm');
  var searchResults = new SearchResults('#results');
  var likes = new Likes('#liked');
  var searchData = new SearchData();

  $(document).on('search', function (event, query) {
    if (pending) { return; }

    pending = true;

    searchData.fetch(query).then(function (results) {
      searchResults.setResults(results);
      pending = false;
    });

    searchResults.pending();
  });

  $(document).on('like', function (evt, name) {
    likes.add(name);
  });
});

Even more important than our much cleaner application code, though, is the fact that we end up with a codebase that is thoroughly tested. That means we can safely refactor it and add to it without the fear of breaking things. We can even write new tests as we find new issues, and then write the code that makes those tests pass.

Testing makes life easier in the long run

It’s easy to look at all of this and say, “Wait, you want me to write more code to do the same job?”

The thing is, there are a few inescapable facts of life about Making Things On The Internet. You will spend time designing an approach to a problem. You will test your solution, whether by clicking around in a browser, writing automated tests, or—shudder—letting your users do your testing for you in production. You will make changes to your code, and other people will use your code. Finally: there will be bugs, no matter how many tests you write.

The thing about testing is that while it might require a bit more time at the outset, it really does save time in the long run. You’ll be patting yourself on the back the first time a test you wrote catches a bug before it finds its way into production. You’ll be grateful, too, when you have a system in place that can prove that your bug fix really does fix a bug that slips through.

Additional resources

This article just scratches the surface of JavaScript testing, but if you’d like to learn more, check out:

  • My presentation from the 2012 Full Frontal conference in Brighton, UK.
  • Grunt, a tool that helps automate the testing process and lots of other things.
  • Test-Driven JavaScript Development by Christian Johansen, the creator of the Sinon library. It is a dense but informative examination of the practice of testing JavaScript.
0
Your rating: None

  

Editor’s Note: Smashing Magazine is happy to present this sample chapter from Theresa Neil’s new book Mobile Design Pattern Gallery: UI Patterns for iOS, Android and More, which provides solutions to common design challenges. We’re certain you’ll find the information useful for your next mobile project.

As I was waiting for a table at a local restaurant the other day, I flipped through a couple of the free classified papers. I was shocked to realize how dependent I’ve grown on three simple features that just aren’t available in the analog world: search, sort and filter.

AutoDirect and some of the other freebies are organized by category (like trucks, vans, SUVs) but others, like Greensheet, just list page after page of items for sale. I would actually have to read every single ad in the paper to find what I wanted. No thank you, I’ll use Craigslist on my phone instead.

But after taking a look at Craigslist mobile, it became obvious we could all benefit from some best practices around mobile search, sort and filter UI design. This chapter explores a dozen different ways to surface and refine the data your customers want.

Search Patterns

Before you ever try to design a search interface for any platform, buy and read these two books: Search Patterns: Design for Discovery by Peter Morevill and Jeffery Callendar, and Designing Search: UX Strategies for eCommerce Success by Greg Nudelman.

Then take a look at these search patterns specific to mobile applications:

  • Explicit Search
  • Auto-Complete
  • Dynamic Search
  • Scoped Search
  • Saved & Recent
  • Search Form
  • Search Results

Explicit Search

Explicit search relies on an explicit action to perform the search and view results. That action might be to tap a search button on the screen, like Walmart, or on the keyboard, like Target. The results are typically displayed in the area below the search bar. Consider pairing an explicit search pattern with the auto-complete pattern.


Walmart uses a search button (Go) on the screen, Target uses the Search on the keyboard.


Target loading and then displaying search results.

Offer a clear button in the field and an option to cancel the search. Use feedback to show the search is being performed.

Auto-Complete

Probably the most useful search pattern that emerged in Web 2.0 is auto-complete. Typing will immediately surface a set of possible results, just tap on one to selected it and the search will be performed. Or continue typing your own criteria and then tap the explicit search button.


Android Marketplace (Google Play) and Netflix both use auto-complete

Ideally the results will be displayed immediately, but a progress indicator (searching…) should be used for system feedback. Netflix (above) uses an indicator in the search field, whereas Fidelity (below) displays one where the results will eventually be displayed.


Fidelity shows feedback while loading the auto-complete options.

TripAdvisor provides an enhanced auto-complete, grouping the results by popular destinations, hotels, restaurants. LinkedIn does something similar by showing direct connections first, then other people in LinkedIn.


TripAdvisor and LinkedIn group the suggested options.

Provide feedback if there could be a delay in displaying the results. Consider emphasizing the matching search text in the search results.

Dynamic Search

This pattern may also be called dynamic filtering. Entering text in the search field will dynamically (onkeypress) filter the data on the screen. Note, the examples may look similar to auto-complete but there is a different interation model. The dynamic search pattern is used to refine or whittle down a existing and visible list of objects. In these examples from BlackBerry App World and WorldMate on Android, apps and hotels, respectively, were already displayed on the page.


BlackBerry App World and WorldMate offer dynamic search for refining a big list of results.

Works well for refining constrained data sets, like an address book or personal media library, but may be impractical for searching large data sets from multiple sources.

Scoped Search

Sometimes it is easier (and faster) to get to the desired result by scoping the search criteria before performing the search. Google and Photobucket use different designs to the same end.


Google uses an overlay to present scope options, whereas Photobucket uses a dialog.

AllRecipes also lets you select criteria (or filters) before submitting the search. Dropbox defaults the initial scope to All, but you can switch it to Files or Folders before or after tapping the search button.


AllRecipes pushes the limit of scoping options, Dropbox keeps it simple with just 3.

Offer reasonable scoping options based on the data set. Three to six scoping options are plenty, consider a search form for advanced searching capabilities.

Saved & Recent

Successful mobile interfaces follow a basic usability maxim: respect the users effort. Saved and recent searches do this by making it easy to select from previous searches, instead of retyping the same keywords or search criteria.


eBay lets customers explicity save searches. Both eBay and Walmart implicity save customers’ most recent searches.

Other options to respect the users’ effort involve location based searching options like Trulia, and bar code searching, like PriceCheck by Amazon.


Trulia offers location based searching, Amazon offers 4 ways to search.


Google Shopper offers scan and speak search options and a full search history grouped by search date.

Saved searches typically require additional steps to name a search for later use, whereas recent searches are implicitly saved and surfaced. Consider which one will best serve your customers’ needs.

Search Form

This pattern is characterized by a separate form for entering search criteria, and an explicit search button.


Search forms on WorldMate and airbnb.


WholeFood’s recipe search allows customers to add multiple criteria, course, category, special diets and keywords.

Minimize the number of input fields. Implement OS specific input controls properly. Follow form design best practices (alignment, labels, size).

Search Results

Once a search is performed the results can be displayed in the same screen or on a dedicate results screen. Results may be displayed in a table or list, on a map or satellite, or as thumbnail images. Multiple view options can be used depending on the type of results and user preferences.


Kayak and Foursquare (webOS) show the results in a table.


airbnb shows the results in a list and offers a map view toggle.


Zappos offers a list view and alternate carousel view for browsing search results.

Lazy loading is a common technique to use so that some results will be displayed while the rest are being loaded. Many applications offer either a button to explicitly “view more results” or will automatically load more results when the screen is flicked.


eBay Motors and Best Buy.

Label the results with the number returned. Use lazy loading instead of paging. Apply a reasonable default sort order. Avoid paging tables, they break the natural interaction model for viewing information on a mobile device.

Sort

It is important to choose a reasonable default sort for displaying search results. A little common sense plus user validation is the best way to choose the default sort order. These patterns offer options for changing the default sort:

  • Onscreen Sort
  • Sort Selector
  • Sort Form

Onscreen Sort

When there are only a few sort options, an onscreen sort can provide a simple one tap solution. Placing the sort toggle at the top or bottom of the screen will depend on the other screen elements.

Target provides four sort options with a three toggle button. For the price sort option, they offer two choices: sort by price ascending and sort by price descending.


Expedia (older version) and Target iOS use onscreen sort.

Clearly show which option is selected or “on”. Consider the Sort Order Selector pattern if the option labels don’t fit nicely in a toggle button bar.

Sort Order Selector

The selector pattern is a good alternative to the onscreen sort. There are a number of different UI controls that can be used for selection, but consider the design guidelines for the OS you are designing for (ex. the menu is common for Android app, and the picker and actionsheet are common in iOS apps).


Walmart on Android uses the common menu control.

The option titles can be longer (more explicit) and more options can be displayed. Walmart places the sort button in proximity with the search field, wheras Kayak offers sort and filter options at the bottom of the screen.


Kayak on iOS uses the standard selector control.

OS neutral solutions include a simple combobox, like Target on Android, or an overlay menu, like Awesome Note.


Target on Android just uses a combobox. AwesomeNote uses an overlay.

Follow OS design conventions for choosing the selector control, or choose an OS neutral implementation. Clearly show which sort option is applied.

Sort Form

Some applications have consolidated the sort and filter options into one screen, typically titled “Refine”. This is the most effort intensive sort pattern requiring the user to open the form, select an option, and then apply the selection (by tapping “done” or “apply”).


Cars.com and eBay Classifieds.

Consider the more efficient sort option toggle or sort order selector patterns before choosing this design.

Filter

Large sets of data can require additional filtering, also called refining. Filtering relies on the user selecting criteria by which to refine the set of search results or a large set of objects. Common filtering patterns include:

  • Onscreen Filter
  • Filter Drawer
  • Filter Dialog
  • Filter Form

Also see the earlier search pattern, Scoped Search, for an optional pre-filtering technique.

Onscreen filter

Similar to the onscreen sort, the onscreen filter is displayed with the results or list of objects. With one tap, the filter is applied. HeyZap uses the standard toggle button bar, whereas Google uses vertical tabs.


HeyZap and Google.

CBS News and the ACL Festival app uses a scrolling filter bar as a way to let users quickly hone in on certain types of articles and bands, respectively.


CBS News (single filter bar) and Austin City Limits Music Festival (double filter bar).

Don’t use this filter pattern for primary navigation within your app, but instead use it to group and filter related content.

SXSW offers a filter button bar combined with a second row of filtering options. Feed a Fever news reader uses a super simple stylized set of comboboxes for filtering news feeds.


SXSW Conference app and Feed a Fever.

Filter options should be clearly worded and easy to understand. Show the filters that are applied or “on”.

Filter Drawer

Almost as efficient as the onscreen filter, a drawer can be used to reveal filter options. Flicking or tapping a handle will open the drawer. Audible’s drawer reveals a simple filter toggle bar, whereas Sam’s offers a host of filter options that can be applied to the map of club locations. A better design for Sam’s would be to leave the map visible and allow for dynamic filtering instead of the explicit “filter” button.


Audible and Sam’s Club.


Expedia’s new filter drawer.

Filter Dialog

Like a pop-up on in a web app, the filter dialog is modal in nature. It requires the user to select a filter option, or cancel the action. TripAdvisor on iOS has a custom filter dialog, whereas USPS Mobile on Android relies on the default selector control.


TripAdvisor and Due Today Lite.

While the Filter Dialog may get the job done, the first two patterns provide more freedom for users to experiment with and apply filters directly in context.

Keep the options list short, avoid scrolling. Consider a Filter Form for lengthier or multi-select filter options.

Filter Form

Large data sets can benefit from more advanced filter/refinement options. For example, WorldMate uses a form to filter hotels based on price, brand and stars. Zappos uses a similar approach, using the iOS standard drill down for selection, and the clear/done buttons in the title bar.


WorldMate’s filter form (looks very similar to Kayak’s design) and Zappos filter form for iOS.

Freetime uses custom controls in their filter form. First you pick the filter category, then choose the filter criteria, then apply the filter to the calendar.


Free-Time filter form.

Conditional filters, also called predicate editors or expression builders, are an advanced filtering feature typically found in reporting tools. Here’s the standard layout used on the web and desktop.


Predicate editor in the Wufoo web application.

Creating a conditional filtering a mobile application can be challenging in a mobile form factor but Roambi has accomplished it.


Roambi’s predicate editor.

Don’t over-design the filters, a simple onscreen filter or drawer will usually suffice. If a filter form is necessary, follow form design best practices.

More Resources

Learn more about designing usable and effective mobile apps in Mobile Design Pattern Gallery: UI Patterns for iOS, Android, and More:

  • Navigation
  • Forms
  • Tables
  • Tools
  • Charts
  • Invitations
  • Feedback & Affordance
  • Help

Also check out the Mobile Design Pattern Gallery website, blog and Flickr photostream with +600 screenshots.

(fi)

© Theresa Neil for Smashing Magazine, 2012.

0
Your rating: None