Skip to content

Instantly share code, notes, and snippets.

@jasperkennis
Created September 3, 2018 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasperkennis/977b2638068082800a473473d6d48222 to your computer and use it in GitHub Desktop.
Save jasperkennis/977b2638068082800a473473d6d48222 to your computer and use it in GitHub Desktop.
Retain open inbetween layers in Algolia Hierarchical menu
/**
* 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