Using Funnelback to produce an A-Z listing

Managed by | Updated .

Background

Funnelback is frequently used to provide A-Z listings for staff directories, service listings, etc.

'Dumb' A-Z browse listings will not disable fields that have no results starting with a given letter, allowing users to browse to zero-result listing pages (similar to a zero-result search page).

Facet-based searches can be used to intelligently render A-Z listings, disabling (or even suppressing) zero-match letters, in addition to indicating the number of matching results.  A-Z listings can be further refined by sibling facets, if required.

Prerequisites

Assuming you've been able to create an index containing a metadata field with a single alphanumeric character from the start of a field (e.g. 'LastNameInitial', 'ServiceNameInitial', etc.).  Generally, the 'Initial' fields will be derivable (most likely through use of the MetadataScraper):

CACHE/path/to/sample/listing.html
...
<meta name="lastnameInitial" content="C" />
<meta name="lastname" content="Citizen" />

Map the initial field (we'll use '0' in this example):

metamap.cfg
# Example metadata field mapped to initial of last name
0,0,lastnameInitial

Create a metadata-driven facet from this field:

faceted_navigation.cfg
<Facets qpoptions=" -rmcf=0">
  <Data></Data>
  <Facet>
    <Data>lastnameInitial</Data>
    <MetadataFieldFill>
      <Data>0</Data>
    </MetadataFieldFill>
  </Facet>
  ...
</Facets>

Configure the ModernUI to run an extra search that returns all facet values:

collection.cfg
ui.modern.full_facets_list=true

Create a pre-process hook script to listen for, process, and rewrite '?letter=X' CGI parameters:

pre_process_hook.groovy
def q = transaction.question
    // Hookscripts for A-Z listings.  Assume:
    // - 'lastnameInitial' facet
    // - sourced from metadata field '0')
    if(q.extraSearch == false){ 
        if (q.inputParameterMap["letter"] != null) {
                     
              q.query = "!padrenullqueryAZ"
              q.inputParameterMap["sort"] = "meta0"
               
              // Avoid pagination - show 5000 results when browsing A-Z
              q.additionalParameters["num_ranks"] = ["5000"] as List
               
              // Apply facet constraint on selected letter     
              if (q.inputParameterMap["letter"] != "all") {
                q.inputParameterMap["f.lastnameInitial|0"] = q.inputParameterMap["letter"]
              }
            }
        }

Add the A-Z listing macro to your template, specifying which result metadata count field (rmcfield) is used for the initial.  Suggested placement is above the result set summary message, and below pagination:

simple.ftl
...
<@azList rmcField="0"/>
...
<div id="search-result-count" class="text-muted">
...
<!-- PAGINATION -->
...
<@azList rmcField="0" />
...

Prevent the query that generates A-Z listings from being reported in analytics:

reporting-blacklist.cfg
!padrenullqueryAZ

Optional extras

Create a dedicated profile for A-Z listings to suppress these queries from ever being logged:

collection.cfg
query_processor_options=-nolog=true

Create an additional check inside the results loop to output letter tiers (useful when browsing by 'All'):

simple.ftl
...
<#assign currentLetter = "0-9" >
<@s.Results>
    ...
    <#if query.inputParameterMap["letter"] = "all">  
      <#if currentLetter != s.result.metaData["0"]>
          <h2 class="letter-tier text-muted">${s.result.metaData["0"]}</h2>
      </#if>
      <#assign currentLetter = s.result.metaData["0"]>
    </#if>
    ...
</@s.Results>
...

Create a sort drop-down that suppresses 'Relevance' when browsing in A-Z mode (but still allows other sort modes):

simple.ftl
<#macro sortDropdown>
<#-- SORT MODES -->
        <div class="dropdown pull-right">
            <a class="dropdown-toggle text-muted" data-toggle="dropdown" href="#" id="dropdown-sortmode" title="Sort">
            <small><span class="glyphicon glyphicon-sort"></span>&nbsp;Sort</small>
            </a>
            <ul class="dropdown-menu" role="menu" aria-labelledby="dropdown-sortmode">
            <#if !question.inputParameterMap["letter"]??><li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}"><span class="glyphicon glyphicon-sort-by-attributes-alt"></span> Relevance</a></li></#if>
            <li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}&amp;sort=metaj">Last Name (A-Z)</a></li>
            <li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}&amp;sort=dmetaj">Last Name (Z-A)</a></li>
             <li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}&amp;sort=metag">First Name (A-Z)</a></li>
            <li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}&amp;sort=dmetag">First Name (Z-A)</a></li>
            
            <li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}&amp;sort=metaS">Department (A-Z)</a></li>
            <li role="menuitem"><a href="${question.collection.configuration.value("ui.modern.search_link")}?${removeParam(QueryString,["start_rank","sort"])?html}&amp;sort=dmetaS">Department (Z-A)</a></li>
            </ul>
        </div>
</#macro>

A-Z macro template

simple.ftl
<#---
Create an A-Z listing (including 'All', and '0-9' options) based on facets
returned from a metadata field count. 
@param rmcField Mandatory result metadata count field
-->
<#macro azList rmcField>
 
  <#if question.inputParameterMap["letter"]?? >       
    <#assign currentLetter = (question.inputParameterMap["letter"]!"a")?lower_case />
    <#assign alphabet = ["0-9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"] />
     
    <ul class="pagination pagination-sm">
        <li<#if !currentLetter?? || currentLetter = "all"> class="active"</#if>>
            <a href="/s/search.html?collection=<@s.cgi>collection</@s.cgi>&amp;<@s.IfDefCGI name="profile">profile=<@s.cgi>profile</@s.cgi>&amp;</@s.IfDefCGI>letter=all">All</a>
        </li>
        <#list alphabet as letter>
            <#if letter == currentLetter>
                <li class="active">
                    <a href="/s/search.html?collection=<@s.cgi>collection</@s.cgi>&amp;<@s.IfDefCGI name="profile">profile=<@s.cgi>profile</@s.cgi>&amp;</@s.IfDefCGI>letter=${letter}">${letter?upper_case}</a>
                </li>
            <#else>
                <#assign rmc = rmcField + ":" + letter?upper_case  />                
                  <#if extraSearches.FACETED_NAVIGATION??
                    && extraSearches.FACETED_NAVIGATION.response.resultPacket.rmcs??
                    && extraSearches.FACETED_NAVIGATION.response.resultPacket.rmcs[rmc]??>

                    <#assign count = extraSearches.FACETED_NAVIGATION.response.resultPacket.rmcs[rmc] />
                      <li>
                          <a
                            id="az_browse_${letter?upper_case}"
                            title="Browse by letter: '${letter?upper_case}' (${count} results)"
                            href="/s/search.html?collection=<@s.cgi>collection</@s.cgi>&amp;<@s.IfDefCGI name="profile">profile=<@s.cgi>profile</@s.cgi>&amp;</@s.IfDefCGI>letter=${letter}">
                                ${letter?upper_case}
                        </a>
                        </li>
                  <#else>
                        <li class="disabled"><a href="#">${letter?upper_case}</a></li>
                  </#if>
            </#if>
        </#list>
    </ul>
   </#if>
</#macro>

Screenshots

A-Z listing example.

Was this artcle helpful?

Tags
Type:
Features: Frontend > Templating Frontend > Freemarker