Skip to content

Instantly share code, notes, and snippets.

@MatMoore
Created October 10, 2017 16:49
Show Gist options
  • Save MatMoore/d201f556bfed7c0763978a61f54677c6 to your computer and use it in GitHub Desktop.
Save MatMoore/d201f556bfed7c0763978a61f54677c6 to your computer and use it in GitHub Desktop.
Review of live search and analytics javascript

Review of live search and analytics javascript

Summary

  • Both have a form, a result count, and a results block.
  • Both update the results and result count after ajax requests
  • Both use mustache to render parts of the page dynamically
  • Both track ajax updates as new page views
  • Both support the history API
  • Both have a fallback if the history API isn't available
  • Neither refresh filters dynamically, except to change what is selected/deselected. This means the list of available options doesn't take into account what you have selected since loading the page.
  • There are differences in what triggers an update. Site search does a hard refresh when the search term changes, which mitigates the above issue.
  • Finder frontend dynamically updates atom feed URLs to match the search (this could be extracted into a separate plugin)
  • Site search has additional GA stuff which may not be needed anymore

Site search

Binds to .js-live-search-form and .js-aria-live-count and .js-live-search-results-block

Falls back to .js-live-search-fallback if history support is missing.

There is a limit of 15 filters for some reason. If you select 16, page shows an alert box!

There is Google Analytics tracking that works with the "live search" code.

The AJAX rendering only works with filters. There is a wide search bar, and enter/button click submits the form, which fully reloads the page.

Init

liveSearch.action = liveSearch.$form.attr('action') + '.json';
liveSearch.saveState();
liveSearch.$form.on('change', 'input[type=checkbox]', liveSearch.checkboxChange);
$(window).on('popstate', liveSearch.popState);

Popstate event

if(event.originalEvent.state){
  liveSearch.saveState(state);    // Synchronise internal state
  liveSearch.updateResults();     // Cached ajax request
  liveSearch.restoreCheckboxes(); // Sets checked state for each checkbox
  liveSearch.pageTrack();         // Trigger google analytics pageview
}

Checkbox change event

var pageUpdated;
if(liveSearch.checkFilterLimit(e) && liveSearch.isNewState()){ // Does nothing if the form is still the same as the internal state
  liveSearch.saveState();                   // Synchronise internal state
  pageUpdated = liveSearch.updateResults(); // Cached ajax request
  pageUpdated.done(function(){
    history.pushState(liveSearch.state, '', window.location.pathname + "?" + $.param(liveSearch.state)); // Record in browser history
    liveSearch.pageTrack(); // Trigger google analytics pageview
  });
}

Ajax response

if(liveSearch.searchTermValue(liveSearch.previousState) === liveSearch.searchTermValue(liveSearch.state)){
  // Only render results
  liveSearch.$resultsBlock.find('.js-live-search-results-list').mustache('search/_results_list', results);
} else {
  // Search term has changed
  liveSearch.$resultsBlock.mustache('search/_results_block', results);
  liveSearch.$resultsBlock.find('.js-openable-filter').each(function(){
    // Resets open/closed state of the filter unless something is selected
    // and also rebinds event handlers
    new GOVUK.CheckboxFilter({el:$(this)});
  })
}
liveSearch.updateAriaLiveCount();

Analytics

Clicks

Tracks link and position for next pageview. Redundant with enhanced ecommerce?

GOVUK.analytics.setOptionsForNextPageview({
  dimension21: 'position=' + position + sublink
});

Results

This is also sent when navigating back through the browser history API.

GOVUK.analytics.trackEvent('searchResults', 'resultsShown', {
  label: JSON.stringify(searchResultData),
  nonInteraction: true,
  page: window.location.pathname + window.location.search
});

Finder frontend

Binds to #js-results, #js-search-results-info (count block).

Detects atom links with $("link[type='application/atom+xml']").eq('0');.

Falls back to .js-live-search-fallback if history support is missing.

There is a narrow search bar. A form change event is triggered on change or enter key is pressed. I don't think the change event fires for the text box until it loses focus.

Init

this.action = this.$form.attr('action') + '.json';
this.saveState();
this.$form.on('change', 'input[type=checkbox], input[type=text], input[type=radio]', this.formChange.bind(this));

this.$form.find('input[type=text]').keypress(
  function(e){
    if(e.keyCode == 13) {
      // 13 is the return key
      this.formChange();
      e.preventDefault();
    }
  }.bind(this)
);

$(window).on('popstate', this.popState.bind(this));

Popstate event

if(event.originalEvent.state){
  this.saveState(event.originalEvent.state);
  this.updateResults();
  this.restoreBooleans();
  this.restoreTextInputs();
}

Formchange event

var pageUpdated;
if(this.isNewState()){
  this.saveState();
  pageUpdated = this.updateResults();
  pageUpdated.done(
    function(){
      var newPath = window.location.pathname + "?" + $.param(this.state);
      history.pushState(this.state, '', newPath);
      if (GOVUK.analytics && GOVUK.analytics.trackPageview) {
        GOVUK.analytics.trackPageview(newPath);
      }
    }.bind(this)
  );
}

Display results

// As search is asynchronous, check that the action associated with these results is
// still the latest to stop results being overwritten by stale data
if(action == $.param(this.state)) {
  this.$resultsBlock.mustache('finders/_results', results);
  this.$countBlock.mustache('finders/_result_count', results);
  this.$atomAutodiscoveryLink.attr('href', results.atom_url);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment