description-list.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.moreItemLabelSelector Selector for more button label attribute
  6. * @param {String} options.listsSelector Selector for list element
  7. * @param {String} options.visibleItemsSelector Selector to retrieve the number of visible items
  8. * @param {Boolean} options.attachClickListener Whether or not to bind click events
  9. */
  10. export class DescriptionList {
  11. /**
  12. * @static
  13. * Shorthand for instance creation and initialisation.
  14. *
  15. * @param {HTMLElement} root DOM element for component instantiation and scope
  16. *
  17. * @return {DescriptionList} An instance of DescriptionList.
  18. */
  19. static autoInit(root, { DESCRIPTION_LIST: defaultOptions = {} } = {}) {
  20. const descriptionList = new DescriptionList(root, defaultOptions);
  21. descriptionList.init();
  22. root.ECLDescriptionList = descriptionList;
  23. return descriptionList;
  24. }
  25. constructor(
  26. element,
  27. {
  28. listsSelector = '[data-ecl-description-list-collapsible]',
  29. visibleItemsSelector = 'data-ecl-description-list-visible-items',
  30. moreItemLabelSelector = 'data-ecl-description-list-more-label',
  31. attachClickListener = true,
  32. } = {},
  33. ) {
  34. // Check element
  35. if (!element || element.nodeType !== Node.ELEMENT_NODE) {
  36. throw new TypeError(
  37. 'DOM element should be given to initialize this widget.',
  38. );
  39. }
  40. this.element = element;
  41. // Options
  42. this.moreItemLabelSelector = moreItemLabelSelector;
  43. this.listsSelector = listsSelector;
  44. this.attachClickListener = attachClickListener;
  45. this.visibleItemsSelector = visibleItemsSelector;
  46. // Private variables
  47. this.moreItemLabel = null;
  48. this.lists = null;
  49. this.handleClickOnMore = this.handleClickOnMore.bind(this);
  50. }
  51. /**
  52. * Initialise component.
  53. */
  54. init() {
  55. if (!ECL) {
  56. throw new TypeError('Called init but ECL is not present');
  57. }
  58. ECL.components = ECL.components || new Map();
  59. this.moreItemLabel = this.element.getAttribute(this.moreItemLabelSelector);
  60. this.visibleItems = this.element.getAttribute(this.visibleItemsSelector);
  61. this.lists = queryAll(this.listsSelector, this.element);
  62. // Add see more button in each list and bind click event on it
  63. if (this.lists[0] && this.visibleItems > 0 && this.moreItemLabel) {
  64. this.lists.forEach((list) => {
  65. if (list.children && list.children.length > this.visibleItems) {
  66. const listItem = document.createElement('li');
  67. listItem.classList.add('ecl-description-list__see_more');
  68. const button = document.createElement('a');
  69. button.classList.add('ecl-link', 'ecl-link--standalone');
  70. button.href = '#';
  71. button.innerHTML = this.moreItemLabel;
  72. listItem.appendChild(button);
  73. list.appendChild(listItem);
  74. this.showHide(
  75. queryAll('.ecl-description-list__definition-item', list),
  76. );
  77. if (this.attachClickListener) {
  78. button.addEventListener('click', this.handleClickOnMore);
  79. }
  80. }
  81. });
  82. // Set ecl initialized attribute
  83. this.element.setAttribute('data-ecl-auto-initialized', 'true');
  84. ECL.components.set(this.element, this);
  85. }
  86. }
  87. /**
  88. * showHide elements basing on user preference.
  89. */
  90. showHide(elements) {
  91. if (elements) {
  92. const items = Array.from(elements);
  93. const baseClass = 'ecl-description-list__definition-item';
  94. const hiddenClass = `${baseClass}--hidden`;
  95. const lastVisibleClass = `${baseClass}--last-visible`;
  96. for (let i = 0; i < items.length; i += 1) {
  97. const el = items[i];
  98. if (i < this.visibleItems) {
  99. el.classList.remove(hiddenClass);
  100. el.classList.remove(lastVisibleClass);
  101. } else if (i >= this.visibleItems) {
  102. el.classList.add(hiddenClass);
  103. }
  104. if (i === this.visibleItems - 1) {
  105. el.classList.add(lastVisibleClass);
  106. } else {
  107. el.classList.remove(lastVisibleClass);
  108. }
  109. }
  110. }
  111. }
  112. /**
  113. * Destroy component.
  114. */
  115. destroy() {
  116. if (this.attachClickListener && this.visibleItems > 0) {
  117. const moreItems = queryAll('.ecl-description-list__see_more');
  118. if (moreItems[0]) {
  119. moreItems.forEach((item) => {
  120. item.removeEventListener('click', this.handleClickOnMore);
  121. });
  122. }
  123. }
  124. if (this.element) {
  125. this.element.removeAttribute('data-ecl-auto-initialized');
  126. ECL.components.delete(this.element);
  127. }
  128. }
  129. /**
  130. * Expands the list of items.
  131. * @param {Event} e
  132. */
  133. handleClickOnMore(e) {
  134. e.preventDefault();
  135. const listItem = e.target.parentNode;
  136. const list = listItem.parentNode;
  137. if (this.element.contains(list)) {
  138. const parentChildren = Array.from(list.children);
  139. parentChildren.forEach((item) => {
  140. item.classList.remove('ecl-description-list__definition-item--hidden');
  141. item.classList.remove(
  142. 'ecl-description-list__definition-item--last-visible',
  143. );
  144. });
  145. // Remove the button
  146. listItem.remove();
  147. }
  148. }
  149. }
  150. export default DescriptionList;