category-filter.js

  1. import { queryAll } from '@ecl/dom-utils';
  2. /**
  3. * @param {HTMLElement} element DOM element for component instantiation and scope
  4. * @param {Object} options
  5. * @param {String} options.itemSelector Selector for the tree parent items
  6. * @param {Boolean} options.attachClickListener Whether or not to bind click events
  7. */
  8. export class CategoryFilter {
  9. /**
  10. * @static
  11. * Shorthand for instance creation and initialisation.
  12. *
  13. * @param {HTMLElement} root DOM element for component instantiation and scope
  14. *
  15. * @return {CategoryFilter} An instance of CategoryFilter.
  16. */
  17. static autoInit(root, { CATEGORY_FILTER: defaultOptions = {} } = {}) {
  18. const categoryFilter = new CategoryFilter(root, defaultOptions);
  19. categoryFilter.init();
  20. root.ECLCategoryFilter = categoryFilter;
  21. return categoryFilter;
  22. }
  23. constructor(
  24. element,
  25. {
  26. itemSelector = '.ecl-category-filter__item--has-children',
  27. attachClickListener = true,
  28. } = {},
  29. ) {
  30. // Check element
  31. if (!element || element.nodeType !== Node.ELEMENT_NODE) {
  32. throw new TypeError(
  33. 'DOM element should be given to initialize this widget.',
  34. );
  35. }
  36. this.element = element;
  37. // Options
  38. this.itemSelector = itemSelector;
  39. this.attachClickListener = attachClickListener;
  40. // Private variables
  41. this.items = null;
  42. // Bind `this` for use in callbacks
  43. this.handleClickExpand = this.handleClickExpand.bind(this);
  44. }
  45. /**
  46. * Initialise component.
  47. */
  48. init() {
  49. if (!ECL) {
  50. throw new TypeError('Called init but ECL is not present');
  51. }
  52. ECL.components = ECL.components || new Map();
  53. // Query elementslur
  54. this.items = queryAll(this.itemSelector, this.element);
  55. // Bind click event on open
  56. if (this.attachClickListener && this.items) {
  57. this.items.forEach((item) =>
  58. item.addEventListener('click', this.handleClickExpand),
  59. );
  60. }
  61. // Set ecl initialized attribute
  62. this.element.setAttribute('data-ecl-auto-initialized', 'true');
  63. ECL.components.set(this.element, this);
  64. }
  65. /**
  66. * Destroy component.
  67. */
  68. destroy() {
  69. if (this.attachClickListener && this.items) {
  70. this.items.forEach((item) => {
  71. item.removeEventListener('click', this.handleClickExpand, false);
  72. });
  73. }
  74. if (this.element) {
  75. this.element.removeAttribute('data-ecl-auto-initialized');
  76. ECL.components.delete(this.element);
  77. }
  78. }
  79. /**
  80. * Expand tree list item.
  81. * @param {Event} e
  82. */
  83. handleClickExpand(e) {
  84. e.preventDefault();
  85. // Get item even if we clicked on the icon
  86. const treeItem = e.target.closest('.ecl-category-filter__item');
  87. // Toggle current item
  88. this.items.forEach((item) => {
  89. if (item === treeItem) {
  90. item.setAttribute('aria-current', true);
  91. } else {
  92. item.removeAttribute('aria-current');
  93. }
  94. });
  95. // Toggle expanded
  96. const isExpanded = treeItem.getAttribute('aria-expanded') === 'true';
  97. if (isExpanded) {
  98. treeItem.setAttribute('aria-expanded', 'false');
  99. treeItem.parentElement.classList.remove(
  100. 'ecl-category-filter__list-item--open',
  101. );
  102. } else {
  103. treeItem.setAttribute('aria-expanded', 'true');
  104. treeItem.parentElement.classList.add(
  105. 'ecl-category-filter__list-item--open',
  106. );
  107. }
  108. // For first level, keep only one item open
  109. if (treeItem.classList.contains('ecl-category-filter__item--level-1')) {
  110. this.items.forEach((item) => {
  111. if (item !== treeItem) {
  112. item.parentElement.classList.remove(
  113. 'ecl-category-filter__list-item--open',
  114. );
  115. item.setAttribute('aria-expanded', 'false');
  116. }
  117. });
  118. }
  119. }
  120. }
  121. export default CategoryFilter;