Instantly share code, notes, and snippets.
Created
September 3, 2018 14:49
-
Save jasperkennis/977b2638068082800a473473d6d48222 to your computer and use it in GitHub Desktop.
Retain open inbetween layers in Algolia Hierarchical menu
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Custom implementation of hierarchical menu that keeps inbetween layers of the | |
* menu open. | |
* | |
* Implementation depends on jQuery being present. | |
*/ | |
var $, | |
instantsearch, | |
instance_header, | |
retained_open_between_levels = {}; | |
/** | |
* Render individual items and their children by recursively mapping the | |
* children through this method too. | |
* | |
* Also retains the collection, and updates it if needed. | |
* | |
* @todo Abstract a little more | |
* | |
* @param {int} lvl the current level | |
* | |
* @returns {function} The iterator | |
*/ | |
function renderItem (lvl) { | |
if (lvl == null) { | |
lvl = 0; | |
} | |
return function (item, i, collection) { | |
var deeper_data = '', | |
deeper_data_set, | |
classes = ['search-filter__item']; | |
if (item.isRefined) { | |
classes.push('search-filter__item--active'); | |
retainCollection(lvl, collection); | |
if (item.data && item.data.length > 0) { | |
deeper_data_set = item.data; | |
if (retained_open_between_levels[lvl + 1]) { | |
deeper_data_set = mergeRetainedDataWithCurrentData( | |
retained_open_between_levels[lvl + 1], | |
item.data | |
); | |
} | |
deeper_data = '<ul>' + deeper_data_set.map(renderItem(lvl + 1)).join('') + '</ul>'; | |
} | |
} | |
return '<li class="' + classes.join(' ') + '"><span class="search-filter__link" data-value="' + item.value + '" data-lvl="' + lvl + '"><span class="icon icon-radio"></span>' + item.label + '</span>' + deeper_data + '</li>'; | |
}; | |
} | |
/** | |
* Updates the retained data with the current data. | |
* | |
* @param {array} retained_data Previously retained data | |
* @param {array} current_data Newest data | |
* | |
* @returns {array} Smartly merged arrays | |
*/ | |
function mergeRetainedDataWithCurrentData (retained_data, current_data) { | |
var ck, cd, rk, rd; | |
for (ck = 0; ck < current_data.length; ck++) { | |
cd = current_data[ck]; | |
for (rk = 0; rk < retained_data.length; rk++) { | |
rd = retained_data[rk]; | |
if (cd.value != rd.value) { | |
continue; | |
} | |
rd.isRefined = cd.isRefined; | |
if (cd.data == null) { | |
continue; | |
} | |
rd.data = cd.data; | |
} | |
} | |
return retained_data; | |
} | |
/** | |
* Updates the retained levels when: | |
* - There is currently no value for the level saved, or; | |
* - There are as many or more items in the currently selected level as were | |
* previously stored in the retention object. | |
* | |
* @param {integer} lvl The index of the current level iterated | |
* @param {array} collection The collection of data currently iterated | |
* | |
* @returns {void} | |
*/ | |
function retainCollection (lvl, collection) { | |
if (lvl == 0) { | |
return; | |
} | |
if (retained_open_between_levels[lvl] == null || | |
collection.length >= retained_open_between_levels[lvl].length | |
) { | |
return retained_open_between_levels[lvl] = collection; | |
} | |
} | |
/** | |
* On first render, this registers a live click handler for each link within the | |
* node. This handler refines the search and, if the click was on the deepest | |
* level, also resets the retention object. | |
* | |
* For every render, this renders the interface for each item and it's | |
* potentially open children. | |
* | |
* @param {object} renderOpts Data to render with | |
* @param {bool} isFirstRendering Wether or not first render | |
* | |
* @returns {void} | |
*/ | |
function renderFn (renderOpts, isFirstRendering) { | |
var rendered_html; | |
if (isFirstRendering) { | |
instance_header = '<div class="ais-refinement-list--header search-filter__head ais-header">' + renderOpts.widgetParams.templates.header + '</div>'; | |
$(renderOpts.widgetParams.container).on('click', '.search-filter__link', function (e) { | |
e.preventDefault(); | |
renderOpts.refine( | |
$(e.currentTarget).data('value') | |
); | |
if (parseInt($(e.currentTarget).data('lvl')) === 0) { | |
retained_open_between_levels = {}; | |
} | |
}); | |
} | |
rendered_html = renderOpts.items.map(renderItem()).join(''); | |
$(renderOpts.widgetParams.container).html('<div class="ais-root ais-refinement-list search-filter ais-root__collapsible ais-root__collapsed">' + instance_header + '<ul>' + rendered_html + '</ul></div>'); | |
} | |
instantsearch.widgets.makeHierarchicalMenu = instantsearch.connectors.connectHierarchicalMenu( | |
renderFn | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment