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. // Youtube
  433. if (iframeUrl.host.includes('youtube')) {
  434. iframeUrl.searchParams.set('disablekb', 1);
  435. }
  436. const mediaIframe = document.createElement('iframe');
  437. mediaIframe.setAttribute('src', iframeUrl);
  438. mediaIframe.setAttribute('frameBorder', '0');
  439. // Update iframe title
  440. const videoTitle = selectedItem.getAttribute(this.videoTitleSelector);
  441. if (videoTitle) {
  442. mediaIframe.setAttribute('title', videoTitle);
  443. }
  444. if (this.overlayMedia) {
  445. if (embeddedVideoAudio) {
  446. mediaElement.appendChild(mediaAudio);
  447. }
  448. mediaElement.appendChild(mediaIframe);
  449. this.overlayMedia.innerHTML = '';
  450. this.overlayMedia.appendChild(mediaElement);
  451. }
  452. this.iframeResize(mediaIframe);
  453. } else if (video != null) {
  454. // Media is a video
  455. mediaElement = document.createElement('video');
  456. mediaElement.setAttribute('poster', video.poster);
  457. mediaElement.setAttribute('controls', 'controls');
  458. mediaElement.classList.add('ecl-gallery__slider-video');
  459. // Update video title
  460. const videoTitle = selectedItem.getAttribute(this.videoTitleSelector);
  461. if (videoTitle) {
  462. mediaElement.setAttribute('aria-label', videoTitle);
  463. }
  464. if (this.overlayMedia) {
  465. this.overlayMedia.innerHTML = '';
  466. this.overlayMedia.appendChild(mediaElement);
  467. }
  468. // Get sources
  469. const sources = queryAll('source', video);
  470. sources.forEach((source) => {
  471. const sourceTag = document.createElement('source');
  472. sourceTag.setAttribute('src', source.getAttribute('src'));
  473. sourceTag.setAttribute('type', source.getAttribute('type'));
  474. mediaElement.appendChild(sourceTag);
  475. });
  476. // Get tracks
  477. const tracks = queryAll('track', video);
  478. tracks.forEach((track) => {
  479. const trackTag = document.createElement('track');
  480. trackTag.setAttribute('src', track.getAttribute('src'));
  481. trackTag.setAttribute('kind', track.getAttribute('kind'));
  482. trackTag.setAttribute('srclang', track.getAttribute('srcLang'));
  483. trackTag.setAttribute('label', track.getAttribute('label'));
  484. mediaElement.appendChild(trackTag);
  485. });
  486. mediaElement.load();
  487. } else {
  488. // Media is an image
  489. const picture = queryOne('.ecl-gallery__picture', selectedItem);
  490. const image = queryOne('img', picture);
  491. if (picture) {
  492. image.classList.remove('ecl-gallery__image');
  493. mediaElement = picture.cloneNode(true);
  494. } else {
  495. // backward compatibility
  496. mediaElement = document.createElement('img');
  497. mediaElement.setAttribute('src', image.getAttribute('src'));
  498. mediaElement.setAttribute('alt', image.getAttribute('alt'));
  499. }
  500. mediaElement.classList.add('ecl-gallery__slider-image');
  501. if (this.overlayMedia) {
  502. this.overlayMedia.innerHTML = '';
  503. this.overlayMedia.appendChild(mediaElement);
  504. }
  505. }
  506. // Get id
  507. const id = selectedItem.getAttribute('id');
  508. // Update counter
  509. this.overlayCounterCurrent.innerHTML =
  510. +selectedItem.getAttribute('data-ecl-gallery-item-id') + 1;
  511. this.overlayCounterMax.innerHTML = this.galleryItems.length;
  512. // Prepare display of links for mobile
  513. const actionMobile = document.createElement('div');
  514. actionMobile.classList.add('ecl-gallery__detail-actions-mobile');
  515. // Update download link
  516. if (this.overlayDownload !== null && embeddedVideo === null) {
  517. this.overlayDownload.href = this.selectedItem.href;
  518. if (id) {
  519. this.overlayDownload.setAttribute('aria-describedby', `${id}-title`);
  520. }
  521. this.overlayDownload.hidden = false;
  522. actionMobile.appendChild(this.overlayDownload.cloneNode(true));
  523. } else if (this.overlayDownload !== null) {
  524. this.overlayDownload.hidden = true;
  525. }
  526. // Update share link
  527. const shareHref = this.selectedItem.getAttribute(
  528. 'data-ecl-gallery-item-share',
  529. );
  530. if (shareHref != null) {
  531. this.overlayShare.href = shareHref;
  532. if (id) {
  533. this.overlayShare.setAttribute('aria-describedby', `${id}-title`);
  534. }
  535. this.overlayShare.hidden = false;
  536. actionMobile.appendChild(this.overlayShare.cloneNode(true));
  537. } else {
  538. this.overlayShare.hidden = true;
  539. }
  540. // Update description
  541. const description = queryOne(this.descriptionSelector, selectedItem);
  542. if (description) {
  543. this.overlayDescription.innerHTML = description.innerHTML;
  544. }
  545. if (actionMobile.childNodes.length > 0) {
  546. this.overlayDescription.prepend(actionMobile);
  547. }
  548. }
  549. /**
  550. * Handles keyboard events such as Escape and navigation.
  551. *
  552. * @param {Event} e
  553. */
  554. handleKeyboard(e) {
  555. // Detect press on Escape
  556. // Only used if the browser do not support <dialog>
  557. if (e.key === 'Escape' || e.key === 'Esc') {
  558. this.handleClickOnCloseButton();
  559. }
  560. }
  561. /**
  562. * Invoke listeners for close events.
  563. */
  564. handleClickOnCloseButton() {
  565. if (this.isDialogSupported) {
  566. this.overlay.close();
  567. } else {
  568. this.overlay.removeAttribute('open');
  569. }
  570. // Remove iframe
  571. const embeddedVideo = queryOne('iframe', this.overlayMedia);
  572. if (embeddedVideo) embeddedVideo.remove();
  573. // Stop video
  574. const video = queryOne('video', this.overlayMedia);
  575. if (video) video.pause();
  576. // Untrap focus
  577. this.focusTrap.deactivate();
  578. // Restore css class on items
  579. this.galleryItems.forEach((galleryItem) => {
  580. const image = queryOne('img', galleryItem);
  581. if (image) {
  582. image.classList.add('ecl-gallery__image');
  583. }
  584. });
  585. // Focus item
  586. this.selectedItem.focus();
  587. // Enable scroll on body
  588. document.body.classList.remove('ecl-u-disablescroll');
  589. }
  590. /**
  591. * Invoke listeners for on pressing the spacebar button.
  592. *
  593. * @param {Event} e
  594. */
  595. handleKeyPressOnItem(e) {
  596. if (e.keyCode === 32) {
  597. // If spacebar trigger the modal
  598. this.handleClickOnItem(e);
  599. }
  600. }
  601. /**
  602. * Invoke listeners for on click events on view all.
  603. *
  604. * @param {Event} e
  605. */
  606. handleClickOnViewAll(e) {
  607. e.preventDefault();
  608. if (!this.viewAll) return;
  609. if (this.viewAll.expanded) {
  610. delete this.viewAll.expanded;
  611. this.checkScreen();
  612. this.hideItems();
  613. this.viewAll.textContent = this.viewAllLabel;
  614. } else {
  615. this.viewAll.expanded = true;
  616. this.viewAll.textContent = this.viewAllLabelExpanded;
  617. const hidden = this.galleryItems.filter((item) =>
  618. item.parentNode.classList.contains('ecl-gallery__item--hidden'),
  619. );
  620. if (hidden.length > 0) {
  621. hidden.forEach((item) => {
  622. item.parentNode.classList.remove('ecl-gallery__item--hidden');
  623. });
  624. hidden[0].focus();
  625. }
  626. }
  627. }
  628. /**
  629. * Invoke listeners for on click events on the given gallery item.
  630. *
  631. * @param {Event} e
  632. */
  633. handleClickOnItem(e) {
  634. e.preventDefault();
  635. // Disable scroll on body
  636. document.body.classList.add('ecl-u-disablescroll');
  637. // Display overlay
  638. if (this.isDialogSupported) {
  639. this.overlay.showModal();
  640. } else {
  641. this.overlay.setAttribute('open', '');
  642. }
  643. // Update overlay
  644. this.updateOverlay(e.currentTarget);
  645. // Trap focus
  646. this.focusTrap.activate();
  647. }
  648. /**
  649. * handle click event on gallery items when no overlay.
  650. *
  651. * @param {Event} e
  652. */
  653. // eslint-disable-next-line class-methods-use-this
  654. preventClickOnItem(e) {
  655. e.preventDefault();
  656. e.stopPropagation();
  657. }
  658. /**
  659. * Invoke listeners for on click events on previous navigation link.
  660. */
  661. handleClickOnPreviousButton() {
  662. // Get current id
  663. const currentId = this.selectedItem.getAttribute(
  664. 'data-ecl-gallery-item-id',
  665. );
  666. // Get previous id
  667. let previousId = +currentId - 1;
  668. if (previousId < 0) previousId = this.galleryItems.length - 1;
  669. // Stop video
  670. const video = queryOne('video', this.selectedItem);
  671. if (video) video.pause();
  672. // Update overlay
  673. this.updateOverlay(this.galleryItems[previousId]);
  674. this.selectedItem = this.galleryItems[previousId];
  675. return this;
  676. }
  677. /**
  678. * Invoke listeners for on click events on next navigation link.
  679. */
  680. handleClickOnNextButton() {
  681. // Get current id
  682. const currentId = this.selectedItem.getAttribute(
  683. 'data-ecl-gallery-item-id',
  684. );
  685. // Get next id
  686. let nextId = +currentId + 1;
  687. if (nextId >= this.galleryItems.length) nextId = 0;
  688. // Stop video
  689. const video = queryOne('video', this.selectedItem);
  690. if (video) video.pause();
  691. // Update overlay
  692. this.updateOverlay(this.galleryItems[nextId]);
  693. this.selectedItem = this.galleryItems[nextId];
  694. return this;
  695. }
  696. }
  697. export default Gallery;