gallery.js

  1. import { queryOne, queryAll } from '@ecl/dom-utils';
  2. import { createFocusTrap } from 'focus-trap';
  3. /**
  4. * @param {HTMLElement} element DOM element for component instantiation and scope
  5. * @param {Object} options
  6. * @param {String} options.galleryItemSelector Selector for gallery element
  7. * @param {String} options.descriptionSelector Selector for gallery description element
  8. * @param {String} options.titleSelector Selector for gallery title element
  9. * @param {String} options.closeButtonSelector Selector for close button element
  10. * @param {String} options.allButtonSelector Selector for view all button element
  11. * @param {String} options.overlaySelector Selector for gallery overlay element
  12. * @param {String} options.overlayHeaderSelector Selector for gallery overlay header element
  13. * @param {String} options.overlayFooterSelector Selector for gallery overlay footer element
  14. * @param {String} options.overlayMediaSelector Selector for gallery overlay media element
  15. * @param {String} options.overlayCounterCurrentSelector Selector for gallery overlay current number element
  16. * @param {String} options.overlayCounterMaxSelector Selector for display of number of elements in the gallery overlay
  17. * @param {String} options.overlayDownloadSelector Selector for gallery overlay download element
  18. * @param {String} options.overlayShareSelector Selector for gallery overlay share element
  19. * @param {String} options.overlayDescriptionSelector Selector for gallery overlay description element
  20. * @param {String} options.overlayPreviousSelector Selector for gallery overlay previous link element
  21. * @param {String} options.overlayNextSelector Selector for gallery overlay next link element
  22. * @param {String} options.videoTitleSelector Selector for video title
  23. * @param {Boolean} options.attachClickListener Whether or not to bind click events
  24. * @param {Boolean} options.attachKeyListener Whether or not to bind keyup events
  25. */
  26. export class Gallery {
  27. /**
  28. * @static
  29. * Shorthand for instance creation and initialisation.
  30. *
  31. * @param {HTMLElement} root DOM element for component instantiation and scope
  32. *
  33. * @return {Gallery} An instance of Gallery.
  34. */
  35. static autoInit(root, { GALLERY: defaultOptions = {} } = {}) {
  36. const gallery = new Gallery(root, defaultOptions);
  37. gallery.init();
  38. root.ECLGallery = gallery;
  39. return gallery;
  40. }
  41. constructor(
  42. element,
  43. {
  44. expandableSelector = 'data-ecl-gallery-not-expandable',
  45. galleryItemSelector = '[data-ecl-gallery-item]',
  46. descriptionSelector = '[data-ecl-gallery-description]',
  47. titleSelector = '[data-ecl-gallery-title]',
  48. noOverlaySelector = 'data-ecl-gallery-no-overlay',
  49. itemsLimitSelector = 'data-ecl-gallery-visible-items',
  50. closeButtonSelector = '[data-ecl-gallery-close]',
  51. viewAllSelector = '[data-ecl-gallery-all]',
  52. viewAllLabelSelector = 'data-ecl-gallery-collapsed-label',
  53. viewAllExpandedLabelSelector = 'data-ecl-gallery-expanded-label',
  54. countSelector = '[data-ecl-gallery-count]',
  55. overlaySelector = '[data-ecl-gallery-overlay]',
  56. overlayHeaderSelector = '[data-ecl-gallery-overlay-header]',
  57. overlayFooterSelector = '[data-ecl-gallery-overlay-footer]',
  58. overlayMediaSelector = '[data-ecl-gallery-overlay-media]',
  59. overlayCounterCurrentSelector = '[data-ecl-gallery-overlay-counter-current]',
  60. overlayCounterMaxSelector = '[data-ecl-gallery-overlay-counter-max]',
  61. overlayDownloadSelector = '[data-ecl-gallery-overlay-download]',
  62. overlayShareSelector = '[data-ecl-gallery-overlay-share]',
  63. overlayDescriptionSelector = '[data-ecl-gallery-overlay-description]',
  64. overlayPreviousSelector = '[data-ecl-gallery-overlay-previous]',
  65. overlayNextSelector = '[data-ecl-gallery-overlay-next]',
  66. videoTitleSelector = 'data-ecl-gallery-item-video-title',
  67. attachClickListener = true,
  68. attachKeyListener = true,
  69. attachResizeListener = true,
  70. } = {},
  71. ) {
  72. // Check element
  73. if (!element || element.nodeType !== Node.ELEMENT_NODE) {
  74. throw new TypeError(
  75. 'DOM element should be given to initialize this widget.',
  76. );
  77. }
  78. this.element = element;
  79. // Options
  80. this.galleryItemSelector = galleryItemSelector;
  81. this.descriptionSelector = descriptionSelector;
  82. this.titleSelector = titleSelector;
  83. this.closeButtonSelector = closeButtonSelector;
  84. this.viewAllSelector = viewAllSelector;
  85. this.countSelector = countSelector;
  86. this.itemsLimitSelector = itemsLimitSelector;
  87. this.overlaySelector = overlaySelector;
  88. this.noOverlaySelector = noOverlaySelector;
  89. this.overlayHeaderSelector = overlayHeaderSelector;
  90. this.overlayFooterSelector = overlayFooterSelector;
  91. this.overlayMediaSelector = overlayMediaSelector;
  92. this.overlayCounterCurrentSelector = overlayCounterCurrentSelector;
  93. this.overlayCounterMaxSelector = overlayCounterMaxSelector;
  94. this.overlayDownloadSelector = overlayDownloadSelector;
  95. this.overlayShareSelector = overlayShareSelector;
  96. this.overlayDescriptionSelector = overlayDescriptionSelector;
  97. this.overlayPreviousSelector = overlayPreviousSelector;
  98. this.overlayNextSelector = overlayNextSelector;
  99. this.attachClickListener = attachClickListener;
  100. this.attachKeyListener = attachKeyListener;
  101. this.attachResizeListener = attachResizeListener;
  102. this.viewAllLabelSelector = viewAllLabelSelector;
  103. this.viewAllExpandedLabelSelector = viewAllExpandedLabelSelector;
  104. this.expandableSelector = expandableSelector;
  105. this.videoTitleSelector = videoTitleSelector;
  106. // Private variables
  107. this.galleryItems = null;
  108. this.closeButton = null;
  109. this.viewAll = null;
  110. this.count = null;
  111. this.overlay = null;
  112. this.overlayHeader = null;
  113. this.overlayFooter = null;
  114. this.overlayMedia = null;
  115. this.overlayCounterCurrent = null;
  116. this.overlayCounterMax = null;
  117. this.overlayDownload = null;
  118. this.overlayShare = null;
  119. this.overlayDescription = null;
  120. this.overlayPrevious = null;
  121. this.overlayNext = null;
  122. this.selectedItem = null;
  123. this.focusTrap = null;
  124. this.isDesktop = false;
  125. this.resizeTimer = null;
  126. this.visibleItems = 0;
  127. this.breakpointMd = 768;
  128. this.breakpointLg = 996;
  129. this.imageHeight = 185;
  130. this.imageHeightBig = 260;
  131. // Bind `this` for use in callbacks
  132. this.iframeResize = this.iframeResize.bind(this);
  133. this.handleClickOnCloseButton = this.handleClickOnCloseButton.bind(this);
  134. this.handleClickOnViewAll = this.handleClickOnViewAll.bind(this);
  135. this.handleClickOnItem = this.handleClickOnItem.bind(this);
  136. this.preventClickOnItem = this.preventClickOnItem.bind(this);
  137. this.handleKeyPressOnItem = this.handleKeyPressOnItem.bind(this);
  138. this.handleClickOnPreviousButton =
  139. this.handleClickOnPreviousButton.bind(this);
  140. this.handleClickOnNextButton = this.handleClickOnNextButton.bind(this);
  141. this.handleKeyboard = this.handleKeyboard.bind(this);
  142. this.handleResize = this.handleResize.bind(this);
  143. }
  144. /**
  145. * Initialise component.
  146. */
  147. init() {
  148. if (!ECL) {
  149. throw new TypeError('Called init but ECL is not present');
  150. }
  151. ECL.components = ECL.components || new Map();
  152. // Query elements
  153. this.expandable = !this.element.hasAttribute(this.expandableSelector);
  154. this.visibleItems = this.element.getAttribute(this.itemsLimitSelector);
  155. this.galleryItems = queryAll(this.galleryItemSelector, this.element);
  156. this.closeButton = queryOne(this.closeButtonSelector, this.element);
  157. this.noOverlay = this.element.hasAttribute(this.noOverlaySelector);
  158. if (this.expandable) {
  159. this.viewAll = queryOne(this.viewAllSelector, this.element);
  160. this.viewAllLabel =
  161. this.viewAll.getAttribute(this.viewAllLabelSelector) ||
  162. this.viewAll.innerText;
  163. this.viewAllLabelExpanded =
  164. this.viewAll.getAttribute(this.viewAllExpandedLabelSelector) ||
  165. this.viewAllLabel;
  166. }
  167. this.count = queryOne(this.countSelector, this.element);
  168. // Bind click event on view all (open first item)
  169. if (this.attachClickListener && this.viewAll) {
  170. this.viewAll.addEventListener('click', this.handleClickOnViewAll);
  171. }
  172. if (!this.noOverlay) {
  173. this.overlay = queryOne(this.overlaySelector, this.element);
  174. this.overlayHeader = queryOne(this.overlayHeaderSelector, this.overlay);
  175. this.overlayFooter = queryOne(this.overlayFooterSelector, this.overlay);
  176. this.overlayMedia = queryOne(this.overlayMediaSelector, this.overlay);
  177. this.overlayCounterCurrent = queryOne(
  178. this.overlayCounterCurrentSelector,
  179. this.overlay,
  180. );
  181. this.overlayCounterMax = queryOne(
  182. this.overlayCounterMaxSelector,
  183. this.overlay,
  184. );
  185. this.overlayDownload = queryOne(
  186. this.overlayDownloadSelector,
  187. this.overlay,
  188. );
  189. this.overlayShare = queryOne(this.overlayShareSelector, this.overlay);
  190. this.overlayDescription = queryOne(
  191. this.overlayDescriptionSelector,
  192. this.overlay,
  193. );
  194. this.overlayPrevious = queryOne(
  195. this.overlayPreviousSelector,
  196. this.overlay,
  197. );
  198. this.overlayNext = queryOne(this.overlayNextSelector, this.overlay);
  199. // Create focus trap
  200. this.focusTrap = createFocusTrap(this.overlay, {
  201. escapeDeactivates: false,
  202. returnFocusOnDeactivate: false,
  203. });
  204. // Polyfill to support <dialog>
  205. this.isDialogSupported = true;
  206. if (!window.HTMLDialogElement) {
  207. this.isDialogSupported = false;
  208. }
  209. // Bind click event on close button
  210. if (this.attachClickListener && this.closeButton) {
  211. this.closeButton.addEventListener(
  212. 'click',
  213. this.handleClickOnCloseButton,
  214. );
  215. }
  216. // Bind click event on gallery items
  217. if (this.attachClickListener && this.galleryItems) {
  218. this.galleryItems.forEach((galleryItem) => {
  219. if (this.attachClickListener) {
  220. galleryItem.addEventListener('click', this.handleClickOnItem);
  221. }
  222. if (this.attachKeyListener) {
  223. galleryItem.addEventListener('keyup', this.handleKeyPressOnItem);
  224. }
  225. });
  226. }
  227. // Bind click event on previous button
  228. if (this.attachClickListener && this.overlayPrevious) {
  229. this.overlayPrevious.addEventListener(
  230. 'click',
  231. this.handleClickOnPreviousButton,
  232. );
  233. }
  234. // Bind click event on next button
  235. if (this.attachClickListener && this.overlayNext) {
  236. this.overlayNext.addEventListener(
  237. 'click',
  238. this.handleClickOnNextButton,
  239. );
  240. }
  241. // Bind other close event
  242. if (!this.isDialogSupported && this.attachKeyListener && this.overlay) {
  243. this.overlay.addEventListener('keyup', this.handleKeyboard);
  244. }
  245. if (this.isDialogSupported && this.overlay) {
  246. this.overlay.addEventListener('close', this.handleClickOnCloseButton);
  247. }
  248. } else {
  249. this.galleryItems.forEach((galleryItem) => {
  250. galleryItem.classList.add('ecl-gallery__item__link--frozen');
  251. galleryItem.addEventListener('click', this.preventClickOnItem);
  252. });
  253. }
  254. // Bind resize events
  255. if (this.attachResizeListener) {
  256. window.addEventListener('resize', this.handleResize);
  257. }
  258. // Init display of gallery items
  259. if (this.expandable) {
  260. this.checkScreen();
  261. this.hideItems();
  262. }
  263. // Add number to gallery items
  264. this.galleryItems.forEach((galleryItem, key) => {
  265. galleryItem.setAttribute('data-ecl-gallery-item-id', key);
  266. });
  267. // Update counter
  268. if (this.count) {
  269. this.count.innerHTML = this.galleryItems.length;
  270. }
  271. // Set ecl initialized attribute
  272. this.element.setAttribute('data-ecl-auto-initialized', 'true');
  273. ECL.components.set(this.element, this);
  274. }
  275. /**
  276. * Destroy component.
  277. */
  278. destroy() {
  279. if (this.attachClickListener && this.closeButton) {
  280. this.closeButton.removeEventListener(
  281. 'click',
  282. this.handleClickOnCloseButton,
  283. );
  284. }
  285. if (this.attachClickListener && this.viewAll) {
  286. this.viewAll.removeEventListener('click', this.handleClickOnViewAll);
  287. }
  288. if (this.attachClickListener && this.galleryItems) {
  289. this.galleryItems.forEach((galleryItem) => {
  290. galleryItem.removeEventListener('click', this.handleClickOnItem);
  291. });
  292. }
  293. if (this.attachClickListener && this.overlayPrevious) {
  294. this.overlayPrevious.removeEventListener(
  295. 'click',
  296. this.handleClickOnPreviousButton,
  297. );
  298. }
  299. if (this.attachClickListener && this.overlayNext) {
  300. this.overlayNext.removeEventListener(
  301. 'click',
  302. this.handleClickOnNextButton,
  303. );
  304. }
  305. if (!this.isDialogSupported && this.attachKeyListener && this.overlay) {
  306. this.overlay.removeEventListener('keyup', this.handleKeyboard);
  307. }
  308. if (this.isDialogSupported && this.overlay) {
  309. this.overlay.removeEventListener('close', this.handleClickOnCloseButton);
  310. }
  311. if (this.attachResizeListener) {
  312. window.removeEventListener('resize', this.handleResize);
  313. }
  314. if (this.element) {
  315. this.element.removeAttribute('data-ecl-auto-initialized');
  316. ECL.components.delete(this.element);
  317. }
  318. }
  319. /**
  320. * Check if current display is desktop or mobile
  321. */
  322. checkScreen() {
  323. if (window.innerWidth > this.breakpointMd) {
  324. this.isDesktop = true;
  325. } else {
  326. this.isDesktop = false;
  327. }
  328. if (window.innerWidth > this.breakpointLg) {
  329. this.isLarge = true;
  330. }
  331. }
  332. iframeResize(iframe) {
  333. if (!iframe && this.overlay) {
  334. iframe = queryOne('iframe', this.overlay);
  335. }
  336. if (iframe) {
  337. const width = window.innerWidth;
  338. setTimeout(() => {
  339. const height =
  340. this.overlay.clientHeight -
  341. this.overlayHeader.clientHeight -
  342. this.overlayFooter.clientHeight;
  343. if (width > height) {
  344. iframe.setAttribute('height', `${height}px`);
  345. if ((height * 16) / 9 > width) {
  346. iframe.setAttribute('width', `${width - 0.05 * width}px`);
  347. } else {
  348. iframe.setAttribute('width', `${(height * 16) / 9}px`);
  349. }
  350. } else {
  351. iframe.setAttribute('width', `${width}px`);
  352. if ((width * 4) / 3 > height) {
  353. iframe.setAttribute('height', `${height - 0.05 * height}px`);
  354. } else {
  355. iframe.setAttribute('height', `${(width * 4) / 3}px`);
  356. }
  357. }
  358. }, 0);
  359. }
  360. }
  361. /**
  362. * @param {Int} rows/item number
  363. *
  364. * Hide several gallery items by default
  365. * - 2 "lines" of items on desktop
  366. * - only 3 items on mobile or the desired rows or items
  367. * when using the view more button.
  368. */
  369. hideItems(plus = 0) {
  370. if (!this.viewAll || this.viewAll.expanded) return;
  371. if (this.isDesktop) {
  372. let hiddenItemIds = [];
  373. // We should browse the list first to mark the items to be hidden, and hide them later
  374. // otherwise, it will interfer with the calculus
  375. this.galleryItems.forEach((galleryItem, key) => {
  376. galleryItem.parentNode.classList.remove('ecl-gallery__item--hidden');
  377. if (key >= Number(this.visibleItems) + Number(plus)) {
  378. hiddenItemIds = [...hiddenItemIds, key];
  379. }
  380. });
  381. hiddenItemIds.forEach((id) => {
  382. this.galleryItems[id].parentNode.classList.add(
  383. 'ecl-gallery__item--hidden',
  384. );
  385. });
  386. return;
  387. }
  388. this.galleryItems.forEach((galleryItem, key) => {
  389. if (key > 2 + Number(plus)) {
  390. galleryItem.parentNode.classList.add('ecl-gallery__item--hidden');
  391. } else {
  392. galleryItem.parentNode.classList.remove('ecl-gallery__item--hidden');
  393. }
  394. });
  395. }
  396. /**
  397. * Trigger events on resize
  398. * Uses a debounce, for performance
  399. */
  400. handleResize() {
  401. clearTimeout(this.resizeTimer);
  402. this.resizeTimer = setTimeout(() => {
  403. this.checkScreen();
  404. this.hideItems();
  405. this.iframeResize();
  406. }, 200);
  407. }
  408. /**
  409. * @param {HTMLElement} selectedItem Media element
  410. */
  411. updateOverlay(selectedItem) {
  412. this.selectedItem = selectedItem;
  413. const embeddedVideo = selectedItem.getAttribute(
  414. 'data-ecl-gallery-item-embed-src',
  415. );
  416. const embeddedVideoAudio = selectedItem.getAttribute(
  417. 'data-ecl-gallery-item-embed-audio',
  418. );
  419. const video = queryOne('video', selectedItem);
  420. let mediaElement = null;
  421. // Update media
  422. if (embeddedVideo != null) {
  423. // Media is a embedded video
  424. mediaElement = document.createElement('div');
  425. mediaElement.classList.add('ecl-gallery__slider-embed');
  426. const mediaAudio = document.createElement('div');
  427. mediaAudio.classList.add('ecl-gallery__slider-embed-audio');
  428. if (embeddedVideoAudio) {
  429. mediaAudio.innerHTML = embeddedVideoAudio;
  430. }
  431. const iframeUrl = new URL(embeddedVideo);
  432. const mediaIframe = document.createElement('iframe');
  433. mediaIframe.setAttribute('src', iframeUrl);
  434. mediaIframe.setAttribute('frameBorder', '0');
  435. // Update iframe title
  436. const videoTitle = selectedItem.getAttribute(this.videoTitleSelector);
  437. if (videoTitle) {
  438. mediaIframe.setAttribute('title', videoTitle);
  439. }
  440. if (this.overlayMedia) {
  441. if (embeddedVideoAudio) {
  442. mediaElement.appendChild(mediaAudio);
  443. }
  444. mediaElement.appendChild(mediaIframe);
  445. this.overlayMedia.innerHTML = '';
  446. this.overlayMedia.appendChild(mediaElement);
  447. }
  448. this.iframeResize(mediaIframe);
  449. } else if (video != null) {
  450. // Media is a video
  451. mediaElement = document.createElement('video');
  452. mediaElement.setAttribute('poster', video.poster);
  453. mediaElement.setAttribute('controls', 'controls');
  454. mediaElement.classList.add('ecl-gallery__slider-video');
  455. // Update video title
  456. const videoTitle = selectedItem.getAttribute(this.videoTitleSelector);
  457. if (videoTitle) {
  458. mediaElement.setAttribute('aria-label', videoTitle);
  459. }
  460. if (this.overlayMedia) {
  461. this.overlayMedia.innerHTML = '';
  462. this.overlayMedia.appendChild(mediaElement);
  463. }
  464. // Get sources
  465. const sources = queryAll('source', video);
  466. sources.forEach((source) => {
  467. const sourceTag = document.createElement('source');
  468. sourceTag.setAttribute('src', source.getAttribute('src'));
  469. sourceTag.setAttribute('type', source.getAttribute('type'));
  470. mediaElement.appendChild(sourceTag);
  471. });
  472. // Get tracks
  473. const tracks = queryAll('track', video);
  474. tracks.forEach((track) => {
  475. const trackTag = document.createElement('track');
  476. trackTag.setAttribute('src', track.getAttribute('src'));
  477. trackTag.setAttribute('kind', track.getAttribute('kind'));
  478. trackTag.setAttribute('srclang', track.getAttribute('srcLang'));
  479. trackTag.setAttribute('label', track.getAttribute('label'));
  480. mediaElement.appendChild(trackTag);
  481. });
  482. mediaElement.load();
  483. } else {
  484. // Media is an image
  485. const picture = queryOne('.ecl-gallery__picture', selectedItem);
  486. const image = queryOne('img', picture);
  487. if (picture) {
  488. image.classList.remove('ecl-gallery__image');
  489. mediaElement = picture.cloneNode(true);
  490. } else {
  491. // backward compatibility
  492. mediaElement = document.createElement('img');
  493. mediaElement.setAttribute('src', image.getAttribute('src'));
  494. mediaElement.setAttribute('alt', image.getAttribute('alt'));
  495. }
  496. mediaElement.classList.add('ecl-gallery__slider-image');
  497. if (this.overlayMedia) {
  498. this.overlayMedia.innerHTML = '';
  499. this.overlayMedia.appendChild(mediaElement);
  500. }
  501. }
  502. // Get id
  503. const id = selectedItem.getAttribute('id');
  504. // Update counter
  505. this.overlayCounterCurrent.innerHTML =
  506. +selectedItem.getAttribute('data-ecl-gallery-item-id') + 1;
  507. this.overlayCounterMax.innerHTML = this.galleryItems.length;
  508. // Prepare display of links for mobile
  509. const actionMobile = document.createElement('div');
  510. actionMobile.classList.add('ecl-gallery__detail-actions-mobile');
  511. // Update download link
  512. if (this.overlayDownload !== null && embeddedVideo === null) {
  513. this.overlayDownload.href = this.selectedItem.href;
  514. if (id) {
  515. this.overlayDownload.setAttribute('aria-describedby', `${id}-title`);
  516. }
  517. this.overlayDownload.hidden = false;
  518. actionMobile.appendChild(this.overlayDownload.cloneNode(true));
  519. } else if (this.overlayDownload !== null) {
  520. this.overlayDownload.hidden = true;
  521. }
  522. // Update share link
  523. const shareHref = this.selectedItem.getAttribute(
  524. 'data-ecl-gallery-item-share',
  525. );
  526. if (shareHref != null) {
  527. this.overlayShare.href = shareHref;
  528. if (id) {
  529. this.overlayShare.setAttribute('aria-describedby', `${id}-title`);
  530. }
  531. this.overlayShare.hidden = false;
  532. actionMobile.appendChild(this.overlayShare.cloneNode(true));
  533. } else {
  534. this.overlayShare.hidden = true;
  535. }
  536. // Update description
  537. const description = queryOne(this.descriptionSelector, selectedItem);
  538. if (description) {
  539. this.overlayDescription.innerHTML = description.innerHTML;
  540. }
  541. if (actionMobile.childNodes.length > 0) {
  542. this.overlayDescription.prepend(actionMobile);
  543. }
  544. }
  545. /**
  546. * Handles keyboard events such as Escape and navigation.
  547. *
  548. * @param {Event} e
  549. */
  550. handleKeyboard(e) {
  551. // Detect press on Escape
  552. // Only used if the browser do not support <dialog>
  553. if (e.key === 'Escape' || e.key === 'Esc') {
  554. this.handleClickOnCloseButton();
  555. }
  556. }
  557. /**
  558. * Invoke listeners for close events.
  559. */
  560. handleClickOnCloseButton() {
  561. if (this.isDialogSupported) {
  562. this.overlay.close();
  563. } else {
  564. this.overlay.removeAttribute('open');
  565. }
  566. // Remove iframe
  567. const embeddedVideo = queryOne('iframe', this.overlayMedia);
  568. if (embeddedVideo) embeddedVideo.remove();
  569. // Stop video
  570. const video = queryOne('video', this.overlayMedia);
  571. if (video) video.pause();
  572. // Untrap focus
  573. this.focusTrap.deactivate();
  574. // Restore css class on items
  575. this.galleryItems.forEach((galleryItem) => {
  576. const image = queryOne('img', galleryItem);
  577. if (image) {
  578. image.classList.add('ecl-gallery__image');
  579. }
  580. });
  581. // Focus item
  582. this.selectedItem.focus();
  583. // Enable scroll on body
  584. document.body.classList.remove('ecl-u-disablescroll');
  585. }
  586. /**
  587. * Invoke listeners for on pressing the spacebar button.
  588. *
  589. * @param {Event} e
  590. */
  591. handleKeyPressOnItem(e) {
  592. if (e.keyCode === 32) {
  593. // If spacebar trigger the modal
  594. this.handleClickOnItem(e);
  595. }
  596. }
  597. /**
  598. * Invoke listeners for on click events on view all.
  599. *
  600. * @param {Event} e
  601. */
  602. handleClickOnViewAll(e) {
  603. e.preventDefault();
  604. if (!this.viewAll) return;
  605. if (this.viewAll.expanded) {
  606. delete this.viewAll.expanded;
  607. this.checkScreen();
  608. this.hideItems();
  609. this.viewAll.textContent = this.viewAllLabel;
  610. } else {
  611. this.viewAll.expanded = true;
  612. this.viewAll.textContent = this.viewAllLabelExpanded;
  613. const hidden = this.galleryItems.filter((item) =>
  614. item.parentNode.classList.contains('ecl-gallery__item--hidden'),
  615. );
  616. if (hidden.length > 0) {
  617. hidden.forEach((item) => {
  618. item.parentNode.classList.remove('ecl-gallery__item--hidden');
  619. });
  620. hidden[0].focus();
  621. }
  622. }
  623. }
  624. /**
  625. * Invoke listeners for on click events on the given gallery item.
  626. *
  627. * @param {Event} e
  628. */
  629. handleClickOnItem(e) {
  630. e.preventDefault();
  631. // Disable scroll on body
  632. document.body.classList.add('ecl-u-disablescroll');
  633. // Display overlay
  634. if (this.isDialogSupported) {
  635. this.overlay.showModal();
  636. } else {
  637. this.overlay.setAttribute('open', '');
  638. }
  639. // Update overlay
  640. this.updateOverlay(e.currentTarget);
  641. // Trap focus
  642. this.focusTrap.activate();
  643. }
  644. /**
  645. * handle click event on gallery items when no overlay.
  646. *
  647. * @param {Event} e
  648. */
  649. // eslint-disable-next-line class-methods-use-this
  650. preventClickOnItem(e) {
  651. e.preventDefault();
  652. e.stopPropagation();
  653. }
  654. /**
  655. * Invoke listeners for on click events on previous navigation link.
  656. */
  657. handleClickOnPreviousButton() {
  658. // Get current id
  659. const currentId = this.selectedItem.getAttribute(
  660. 'data-ecl-gallery-item-id',
  661. );
  662. // Get previous id
  663. let previousId = +currentId - 1;
  664. if (previousId < 0) previousId = this.galleryItems.length - 1;
  665. // Stop video
  666. const video = queryOne('video', this.selectedItem);
  667. if (video) video.pause();
  668. // Update overlay
  669. this.updateOverlay(this.galleryItems[previousId]);
  670. this.selectedItem = this.galleryItems[previousId];
  671. return this;
  672. }
  673. /**
  674. * Invoke listeners for on click events on next navigation link.
  675. */
  676. handleClickOnNextButton() {
  677. // Get current id
  678. const currentId = this.selectedItem.getAttribute(
  679. 'data-ecl-gallery-item-id',
  680. );
  681. // Get next id
  682. let nextId = +currentId + 1;
  683. if (nextId >= this.galleryItems.length) nextId = 0;
  684. // Stop video
  685. const video = queryOne('video', this.selectedItem);
  686. if (video) video.pause();
  687. // Update overlay
  688. this.updateOverlay(this.galleryItems[nextId]);
  689. this.selectedItem = this.galleryItems[nextId];
  690. return this;
  691. }
  692. }
  693. export default Gallery;