Refactoring a dozen refinementlistwidgets

I’ve got several indices that I’ll be using InstantSearch for. Along with those indices come with refinement lists. My concern is that I don’t want to copy 'n paste several, even a dozen, different widgets on a .js page. Is there a way that I can refactor the code to account for iteration?

Use this example to see it in action.

const _hitsPerPage = 30;

const _itemsTemplate = `
      	<table class="table table-striped table-hover table-responsive">
          <thead>
            <tr>
              <th>Part Number</th>
              <th>Description</th>
              <th>Available Quantity</th>
            </tr>
          </thead>
          <tbody>
          {{#hits}}
            <tr>
              <td style="white-space:nowrap"><a href="https://www.lyntron.com/{{ url }}">{{ code }}</a></td>
              <td>{{ description }}</td>
              <td>{{ quantityAvailable }}</td>
            </tr>
          {{/hits}}
          </tbody>
        </table>
      `;

var search = instantsearch({
  appId: '9G2RUKPPGE',
  apiKey: '8860a74c330efaf0119818fcdd800126',
  indexName: 'SPR',
  routing: true,
  searchFunction: function(helper) {
    var query = search.helper.state.query;
    SWG_SPR.helper.setQuery(query);
    SWG_SPR.helper.search();
    FF_STDF.helper.setQuery(query);
    FF_STDF.helper.search();
    SWG_STDF.helper.setQuery(query);
    SWG_STDF.helper.search();
    helper.search();
  },
  searchParameters: {
    hitsPerPage: _hitsPerPage
  }
});

var SWG_SPR = instantsearch({
  appId: '9G2RUKPPGE',
  apiKey: '8860a74c330efaf0119818fcdd800126',
  indexName: 'SWG_SPR',
  searchParameters: {
    hitsPerPage: _hitsPerPage
  }
});

var FF_STDF = instantsearch({
  appId: '9G2RUKPPGE',
  apiKey: '8860a74c330efaf0119818fcdd800126',
  indexName: 'FF_STDF',
  searchParameters: {
    hitsPerPage: _hitsPerPage
  }
});

var SWG_STDF = instantsearch({
  appId: '9G2RUKPPGE',
  apiKey: '8860a74c330efaf0119818fcdd800126',
  indexName: 'SWG_STDF',
  searchParameters: {
    hitsPerPage: _hitsPerPage
  }
});

SWG_SPR.addWidget(
  instantsearch.widgets.numericRefinementList({
    container: '#in-stock',
    attributeName: 'quantityAvailable',
    options: [{
      start: 1,
      name: 'In Stock'
    }]
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.numericRefinementList({
    container: '#in-stock',
    attributeName: 'quantityAvailable',
    options: [{
      start: 1,
      name: 'In Stock'
    }]
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.numericRefinementList({
    container: '#in-stock',
    attributeName: 'quantityAvailable',
    options: [{
      start: 1,
      name: 'In Stock'
    }]
  })
);

search.addWidget(
  instantsearch.widgets.numericRefinementList({
    container: '#in-stock',
    attributeName: 'quantityAvailable',
    options: [{
      start: 1,
      name: 'In Stock'
    }]
  })
);

// initialize RefinementList
SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#unitOfMeasurement',
    attributeName: 'unitOfMeasurement.description',
    operator: 'or',
    templates: {
      header: getHeader('Unit of Measure'),
    },
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#unitOfMeasurement',
    attributeName: 'unitOfMeasurement.description',
    operator: 'or',
    templates: {
      header: getHeader('Unit of Measure'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#unitOfMeasurement',
    attributeName: 'unitOfMeasurement.description',
    operator: 'or',
    templates: {
      header: getHeader('Unit of Measure'),
    },
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#unitOfMeasurement',
    attributeName: 'unitOfMeasurement.description',
    operator: 'or',
    templates: {
      header: getHeader('Unit of Measure'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#profile',
    attributeName: 'profile.description',
    operator: 'or',
    templates: {
      header: getHeader('Profile'),
    },
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#profile',
    attributeName: 'profile.description',
    operator: 'or',
    templates: {
      header: getHeader('Profile'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#profile',
    attributeName: 'profile.description',
    operator: 'or',
    templates: {
      header: getHeader('Profile'),
    },
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#profile',
    attributeName: 'profile.description',
    operator: 'or',
    templates: {
      header: getHeader('Profile'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#outsideDiameter',
    attributeName: 'outsideDiameter.description',
    operator: 'or',
    sortBy: ["name:asc"],
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Outside Diameter'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#outsideDiameter',
    attributeName: 'outsideDiameter.description',
    operator: 'or',
    sortBy: ["name:asc"],
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Outside Diameter'),
    },
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#outsideDiameter',
    attributeName: 'outsideDiameter.description',
    operator: 'or',
    transformItems: function(item) {
    	console.log(item);
    },
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Outside Diameter'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#measurement',
    attributeName: 'measurement.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Body Length'),
    },
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#measurement',
    attributeName: 'measurement.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Length'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#measurement',
    attributeName: 'measurement.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Body Length'),
    },
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#measurement',
    attributeName: 'measurement.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Length'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#finish',
    attributeName: 'finish.description',
    limit: 5,
    showMore: true,
    operator: 'or',
    templates: {
      header: getHeader('Finish'),
    },
  })
);


FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#finish',
    attributeName: 'finish.description',
    limit: 5,
    showMore: true,
    operator: 'or',
    templates: {
      header: getHeader('Finish'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#finish',
    attributeName: 'finish.description',
    limit: 5,
    showMore: true,
    operator: 'or',
    templates: {
      header: getHeader('Finish'),
    },
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#finish',
    attributeName: 'finish.description',
    limit: 5,
    showMore: true,
    operator: 'or',
    templates: {
      header: getHeader('Finish'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#screw',
    attributeName: 'screwSize.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Screw Size'),
    },
  })
);


SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#screw',
    attributeName: 'screwSize.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Screw Size'),
    },
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#screw',
    attributeName: 'screwSize.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Screw Size'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#shank',
    attributeName: 'shank.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Shank'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#shank',
    attributeName: 'shank.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Shank'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#shankInsideDiameter',
    attributeName: 'shankInsideDiameter.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Shank Inside Diameter'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#shankInsideDiameter',
    attributeName: 'shankInsideDiameter.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Shank Inside Diameter'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#shankOutsideDiameter',
    attributeName: 'shankOutsideDiameter.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Shank Outside Diameter'),
    },
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#shankOutsideDiameter',
    attributeName: 'shankOutsideDiameter.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Shank Outside Diameter'),
    },
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#threadFemale',
    attributeName: 'threadFemale.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Female Thread'),
    },
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#threadFemale',
    attributeName: 'threadFemale.description',
    operator: 'or',
    limit: 5,
    showMore: true,
    templates: {
      header: getHeader('Female Thread'),
    },
  })
);

// initialize pagination
SWG_STDF.addWidget(
  instantsearch.widgets.pagination({
    container: '#pagination',
    scrollTo: false
  })
);

FF_STDF.addWidget(
  instantsearch.widgets.pagination({
    container: '#pagination',
    scrollTo: false
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.pagination({
    container: '#pagination',
    scrollTo: false
  })
);

search.addWidget(
  instantsearch.widgets.pagination({
    container: '#pagination',
    scrollTo: false
  })
);

// initialize SearchBox
search.addWidget(
  instantsearch.widgets.searchBox({
    container: '#search-box',
    placeholder: 'Search for part numbers'
  })
);


search.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    templates: {
    	allItems: _itemsTemplate
    },
    cssClasses: {
      root: 'row',
      item: 'col-lg-12 col-md-12 col-sm-12'
    }
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    templates: {
    	allItems: _itemsTemplate
    },
    cssClasses: {
      root: 'row',
      item: 'col-lg-12 col-md-12 col-sm-12'
    }
  })
);


FF_STDF.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    templates: {
    	allItems: _itemsTemplate
    },
    cssClasses: {
      root: 'row',
      item: 'col-lg-12 col-md-12 col-sm-12'
    }
  })
);

SWG_STDF.addWidget(
  instantsearch.widgets.hits({
    container: '#hits',
    templates: {
    	allItems: _itemsTemplate
    },
    cssClasses: {
      root: 'row',
      item: 'col-lg-12 col-md-12 col-sm-12'
    }
  })
);

SWG_STDF.start();
FF_STDF.start();
SWG_SPR.start();
search.start();

function getHeader(title) {
  return `<h5>${title}</h5>`;
}

Is your intention to have these all visible on all pages, or only some in some cases?

@haroen,

My intention is to have the applicable widgets on the page when the search results return back the visible faceted attributes.

I’m unaware of how to not have all the widgets on the page if I’m searching multiple indices on a single page. Is there a way that I can “share” widgets when the property is shared across multiple indices?

For example the material widgets tied to objects FF_STDF, SWG_SPR and search. The indices share a common facet on them that calls out material.description and a common container.

FF_STDF.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

SWG_SPR.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

search.addWidget(
  instantsearch.widgets.refinementList({
    container: '#material',
    attributeName: 'material.description',
    operator: 'or',
    sortBy: ["name:asc"],
    templates: {
      header: getHeader('Material'),
    }
  })
);

What do ya think @haroen? Is there a better way? :grinning:

I didn’t really find any solution which is better than this. What you can do is mount only those which are relevant, which you can do with query rules and userData, or by changing the way your data is formatted, so you will know which filters will be relevant where.