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):
...
<meta name="lastnameInitial" content="C" />
<meta name="lastname" content="Citizen" />
Map the initial field (we'll use '0' in this example):
# Example metadata field mapped to initial of last name
0,0,lastnameInitial
Create a metadata-driven facet from this field:
<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:
ui.modern.full_facets_list=true
Create a pre-process hook script to listen for, process, and rewrite '?letter=X' CGI parameters:
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:
...
<@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:
!padrenullqueryAZ
Optional extras
Create a dedicated profile for A-Z listings to suppress these queries from ever being logged:
query_processor_options=-nolog=true
Create an additional check inside the results loop to output letter tiers (useful when browsing by 'All'):
...
<#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):
<#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> 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}&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}&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}&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}&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}&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}&sort=dmetaS">Department (Z-A)</a></li>
</ul>
</div>
</#macro>
A-Z macro template
<#---
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>&<@s.IfDefCGI name="profile">profile=<@s.cgi>profile</@s.cgi>&</@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>&<@s.IfDefCGI name="profile">profile=<@s.cgi>profile</@s.cgi>&</@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>&<@s.IfDefCGI name="profile">profile=<@s.cgi>profile</@s.cgi>&</@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
- Modifying geo-spatial parameters in the data model (origin, maxdist)
- Set non CGI query processor options in hook script
- Supporting dynamic date range searches
- Random sorting of search results
- Sorting and renaming faceted navigation categories
- Accessing data model variables that start with a single lower case letter