media-container.js

  1. import { queryOne } from '@ecl/dom-utils';
  2. import EventManager from '@ecl/event-manager';
  3. /**
  4. * @param {HTMLElement} element DOM element for component instantiation and scope
  5. * @param {Object} options
  6. * @param {String} options.iframeSelector Selector for iframe element
  7. * @param {boolean} options.useAutomaticRatio Toggle automatic ratio calculus
  8. * @param {String} options.videoTitleSelector Selector for video title
  9. * @param {String} options.videoPlay Selector for the video play button
  10. * @param {String} options.videoPause Selector for the video pause button
  11. */
  12. export class MediaContainer {
  13. /**
  14. * @static
  15. * Shorthand for instance creation and initialisation.
  16. *
  17. * @param {HTMLElement} root DOM element for component instantiation and scope
  18. *
  19. * @return {MediaContainer} An instance of MediaContainer.
  20. */
  21. static autoInit(root, { MEDIA_CONTAINER: defaultOptions = {} } = {}) {
  22. const mediaContainer = new MediaContainer(root, defaultOptions);
  23. mediaContainer.init();
  24. root.ECLMediaContainer = mediaContainer;
  25. return mediaContainer;
  26. }
  27. /**
  28. * An array of supported events for this component.
  29. *
  30. * @type {Array<string>}
  31. * @event MediaContainer#onPlayClick
  32. * @event MediaContainer#onPauseClick
  33. * @memberof Media container
  34. */
  35. supportedEvents = ['onPlayClick', 'onPauseClick'];
  36. constructor(
  37. element,
  38. {
  39. iframeSelector = 'iframe',
  40. useAutomaticRatio = true,
  41. videoTitleSelector = 'data-ecl-media-container-video-title',
  42. videoPlay = '[data-ecl-media-container-play]',
  43. videoPause = '[data-ecl-media-container-pause]',
  44. mediaVideo = '[data-ecl-media-container-video]',
  45. } = {},
  46. ) {
  47. // Check element
  48. if (!element || element.nodeType !== Node.ELEMENT_NODE) {
  49. throw new TypeError(
  50. 'DOM element should be given to initialize this widget.',
  51. );
  52. }
  53. this.element = element;
  54. this.eventManager = new EventManager();
  55. // Options
  56. this.iframeSelector = iframeSelector;
  57. this.useAutomaticRatio = useAutomaticRatio;
  58. this.videoTitleSelector = videoTitleSelector;
  59. this.mediaVideo = queryOne(mediaVideo, this.element);
  60. this.videoPlay = queryOne(videoPlay, this.element);
  61. this.videoPause = queryOne(videoPause, this.element);
  62. // Private variables
  63. this.iframe = null;
  64. this.videoTitle = '';
  65. // Bind `this` for use in callbacks
  66. this.calculateRatio = this.calculateRatio.bind(this);
  67. this.handleParameters = this.handleParameters.bind(this);
  68. this.handlePauseClick = this.handlePauseClick.bind(this);
  69. this.handlePlayClick = this.handlePlayClick.bind(this);
  70. }
  71. /**
  72. * Initialise component.
  73. */
  74. init() {
  75. if (!ECL) {
  76. throw new TypeError('Called init but ECL is not present');
  77. }
  78. ECL.components = ECL.components || new Map();
  79. // Get elements
  80. this.videoTitle = this.element.getAttribute(this.videoTitleSelector);
  81. // Check if a ratio has been set manually
  82. const media = queryOne('.ecl-media-container__media', this.element);
  83. if (media && !/ecl-media-container__media--ratio/.test(media.className)) {
  84. // Get the iframe
  85. this.iframe = queryOne(this.iframeSelector, this.element);
  86. // Check if there is an iframe to handle
  87. if (this.iframe) {
  88. this.handleParameters();
  89. if (this.useAutomaticRatio) this.calculateRatio();
  90. }
  91. }
  92. if (this.videoPlay) {
  93. this.videoPlay.addEventListener('click', (e) => this.handlePlayClick(e));
  94. this.videoPlay.style.display = 'none';
  95. }
  96. if (this.videoPause) {
  97. this.videoPause.addEventListener('click', (e) =>
  98. this.handlePauseClick(e),
  99. );
  100. this.videoPause.style.display = 'flex';
  101. }
  102. // Set ecl initialized attribute
  103. this.element.setAttribute('data-ecl-auto-initialized', 'true');
  104. ECL.components.set(this.element, this);
  105. }
  106. /**
  107. * Register a callback function for a specific event.
  108. *
  109. * @param {string} eventName - The name of the event to listen for.
  110. * @param {Function} callback - The callback function to be invoked when the event occurs.
  111. * @returns {void}
  112. * @memberof Banner
  113. * @instance
  114. *
  115. * @example
  116. * // Registering a callback for the 'onPauseClick' event
  117. * mediaContainer.on('onPauseClick', (event) => {
  118. * console.log('The pause button was clicked', event);
  119. * });
  120. */
  121. on(eventName, callback) {
  122. this.eventManager.on(eventName, callback);
  123. }
  124. /**
  125. * Trigger a component event.
  126. *
  127. * @param {string} eventName - The name of the event to trigger.
  128. * @param {any} eventData - Data associated with the event.
  129. *
  130. * @memberof Banner
  131. */
  132. trigger(eventName, eventData) {
  133. this.eventManager.trigger(eventName, eventData);
  134. }
  135. /**
  136. * Destroy component.
  137. */
  138. destroy() {
  139. if (this.element) {
  140. this.element.removeAttribute('data-ecl-auto-initialized');
  141. ECL.components.delete(this.element);
  142. }
  143. if (this.videoPlay) {
  144. this.videoPlay.removeEventListener('click', this.handlePlayClick);
  145. this.videoPlay.style.display = 'none';
  146. }
  147. if (this.videoPause) {
  148. this.videoPause.remveEventListener('click', this.handlePauseClick);
  149. this.videoPause.style.display = 'flex';
  150. }
  151. }
  152. /**
  153. * Handle the parameters of the iframe video.
  154. */
  155. handleParameters() {
  156. const iframeUrl = new URL(this.iframe.src);
  157. // Youtube
  158. if (iframeUrl.host.includes('youtube')) {
  159. this.iframe.src = iframeUrl;
  160. }
  161. // Update iframe title
  162. if (this.videoTitle) {
  163. this.iframe.setAttribute('title', this.videoTitle);
  164. }
  165. }
  166. /**
  167. * Calculate the ratio of the iframe video.
  168. */
  169. calculateRatio() {
  170. // Get dimension if they are provided
  171. let iframeWidth = this.iframe.width;
  172. let iframeHeight = this.iframe.height;
  173. // If at least one dimension in not provided, calculate them (less reliable)
  174. if (!iframeWidth || !iframeHeight) {
  175. iframeWidth = this.iframe.offsetWidth;
  176. iframeHeight = this.iframe.offsetHeight;
  177. }
  178. // Set aspect ratio
  179. this.iframe.style.aspectRatio = `${iframeWidth}/${iframeHeight}`;
  180. }
  181. /**
  182. * Triggers a custom event when clicking on the play button.
  183. *
  184. * @param {e} Event
  185. * @fires MediaContainer#onPlayClick
  186. */
  187. handlePlayClick(e) {
  188. if (this.mediaVideo) {
  189. this.mediaVideo.play();
  190. }
  191. this.videoPlay.style.display = 'none';
  192. if (this.videoPause) {
  193. this.videoPause.style.display = 'flex';
  194. this.videoPause.focus();
  195. }
  196. const eventData = { ...e, status: 'Playing' };
  197. this.trigger('onPlayClick', eventData);
  198. }
  199. /**
  200. * Triggers a custom event when clicking on the pause button.
  201. *
  202. * @param {e} Event
  203. * @fires MediaContainer#onPauseClick
  204. */
  205. handlePauseClick(e) {
  206. if (this.mediaVideo) {
  207. this.mediaVideo.pause();
  208. }
  209. this.videoPause.style.display = 'none';
  210. if (this.videoPlay) {
  211. this.videoPlay.style.display = 'flex';
  212. this.videoPlay.focus();
  213. }
  214. const eventData = { ...e, status: 'Paused' };
  215. this.trigger('onPauseClick', eventData);
  216. }
  217. }
  218. export default MediaContainer;