(function (DragNBar, EventDispatcher) {

  /**
   * Allows different forms to be places on top of each other instead of
   * in a dialog.
   *
   * @class H5P.DragNBar.FormManager
   * @extends H5P.EventDispatcher
   * @param {*} parent
   * @param {Object} l10n
   */
  DragNBar.FormManager = function (parent, l10n, customIconClass) {
    /** @alias H5P.DragNBar.FormManager# */
    var self = this;

    // Initialize event inheritance
    EventDispatcher.call(self);

    const formTargets = [self];
    let head, footer, subForm, titles, handleTransitionend, proceedButton, breadcrumbButton, alwaysShowButtons;

    /**
     * Initialize the FormManager.
     * Create frame breadcrumbs, and fullscreen button.
     *
     * @private
     */
    const initialize = function () {
      self.isMainLibrary = !(parent instanceof H5PEditor.Library)

      // Locate target container
      self.formContainer = (self.isMainLibrary ? parent.$form : parent.$libraryWrapper)[0];
      self.formContainer.classList.add('form-manager');
      self.formContainer.classList.add('root-form');

      head = document.createElement('div');
      head.classList.add('form-manager-head');
      footer = document.createElement('div');
      footer.classList.add('form-manager-head');
      footer.classList.add('form-manager-footer');
      self.footer = footer;

      // Create button to toggle preivous menu on narrow layouts
      breadcrumbButton = createButton('breadcrumb-menu', l10n.expandBreadcrumbButtonLabel, self.toggleBreadcrumbMenu);
      breadcrumbButton.classList.add('form-manager-disabled');
      head.appendChild(breadcrumbButton);

      // Create breadcrumb menu to use when the layout is too narrow for the regular breadcrumb
      self.formBreadcrumbMenu = document.createElement('div');
      self.formBreadcrumbMenu.classList.add('form-manager-breadcrumb-menulist');
      head.appendChild(self.formBreadcrumbMenu);

      // Create breadcrumb wrapper
      self.formBreadcrumb = document.createElement('div');
      self.formBreadcrumb.classList.add('form-manager-breadcrumb');
      head.appendChild(self.formBreadcrumb);

      // Create the first part of the breadcrumb
      const titles = createTitles(parent);
      titles.breadcrumb.classList.add('form-manager-comein');
      self.formBreadcrumb.appendChild(titles.breadcrumb);
      self.formBreadcrumbMenu.appendChild(titles.menu);

      // Create 'Proceed to save' button
      proceedButton = createButton('proceed', H5PEditor.t('core', 'proceedButtonLabel'), function () {
        if (manager.exitSemiFullscreen) {
          // Trigger semi-fullscreen exit
          manager.exitSemiFullscreen();
          manager.exitSemiFullscreen = null;
        }
      });
      hideElement(proceedButton);
      head.appendChild(proceedButton);

      // Create a container for the action buttons
      self.formButtons = document.createElement('div');
      self.formButtons.classList.add('form-manager-buttons');
      self.footerFormButtons = document.createElement('div');
      self.footerFormButtons.classList.add('form-manager-buttons');
      hideElement(self.footerFormButtons);
      hideElement(self.formButtons); // Buttons are hidden by default
      footer.appendChild(self.footerFormButtons);
      head.appendChild(self.formButtons);

      // Create 'Delete' button
      self.formButtons.appendChild(createButton('delete', l10n.deleteButtonLabel, function () {
        const e = new H5P.Event('formremove');
        e.data = formTargets.length;
        formTargets[formTargets.length - 1].trigger(e);
        if (!e.preventRemove && formTargets.length > 1) {
          closeForm();
        }
      }));

      // Create 'Done' button
      self.formButtons.appendChild(createButton('done', l10n.doneButtonLabel, function () {
        formTargets[formTargets.length - 1].trigger('formdone', formTargets.length);
        if (formTargets.length > 1) {
          closeForm();
        }
      }));

      // Footer form buttons
      self.footerFormButtons.appendChild(createButton('done', l10n.doneButtonLabel, function () {
        formTargets[formTargets.length - 1].trigger('formdone', formTargets.length);
        if (formTargets.length > 1) {
          closeForm();
        }
      }));

      self.footerFormButtons.appendChild(createButton('delete', l10n.deleteButtonLabel, function () {
        const e = new H5P.Event('formremove');
        e.data = formTargets.length;
        formTargets[formTargets.length - 1].trigger(e);
        if (!e.preventRemove && formTargets.length > 1) {
          closeForm();
        }
      }));

      // Check if we should add the fullscreen button
      if (self.isMainLibrary && H5PEditor.semiFullscreen !== undefined) {
        // Create and insert fullscreen button into header
        const fullscreenButton = createButton('fullscreen', '', function () {
          if (manager.exitSemiFullscreen) {
            // Trigger semi-fullscreen exit
            manager.exitSemiFullscreen();
          }
          else {
            // Trigger semi-fullscreen enter
            manager.exitSemiFullscreen = H5PEditor.semiFullscreen([manager.formContainer], function () {
              if (!subForm) {
                showElement(proceedButton);
              }
              toggleFullscreenButtonState(fullscreenButton, true);
              self.trigger('formentersemifullscreen');
            }, function () {
              manager.exitSemiFullscreen = null;
              if (!subForm) {
                hideElement(proceedButton);
              }
              toggleFullscreenButtonState(fullscreenButton);
              self.trigger('formexitsemifullscreen');
            });
          }
        });
        toggleFullscreenButtonState(fullscreenButton);
        head.appendChild(fullscreenButton);
      }

      window.addEventListener('resize', self.updateFormResponsiveness);
      // Always clean up on remove
      self.on('remove', function () {
        window.removeEventListener('resize', self.updateFormResponsiveness);
      });

      const overlay = document.createElement('div');
      overlay.classList.add('form-mananger-overlay');
      self.formContainer.insertBefore(overlay, self.formContainer.firstChild);

      // Insert everything in the top of the form DOM
      self.formContainer.insertBefore(head, self.formContainer.firstChild);
      hideElement(footer);
      self.formContainer.appendChild(manager.footer);


      // Always clean up on remove
      self.on('validate', function () {
        if (parent.metadata && (!parent.metadata.title || !H5P.trim(parent.metadata.title))) {
          // We are trying to save the form without a title
          self.closeFormUntil(0);
        }
      });
    };

    /**
     * Helper for creating buttons.
     *
     * @private
     * @param {string} id
     * @param {string} text
     * @param {function} clickHandler
     * @return {Element}
     */
    const createButton = function (id, text, clickHandler) {
      const button = document.createElement('button');
      button.setAttribute('type', 'button');
      button.classList.add('form-manager-button');
      button.classList.add('form-manager-' + id);
      button.setAttribute('aria-label', text);
      button.addEventListener('click', clickHandler);

      // Create special inner filler to avoid focus from pointer devices.
      const content = document.createElement('span');
      content.classList.add('form-manager-button-inner');
      content.innerText = text
      content.tabIndex = -1;
      button.appendChild(content);

      return button;
    };

    /**
     * Create two titles, one for the breadcrumb and for the expanded
     * breadcrumb menu used for narrow layouts.
     *
     * @private
     * @param {H5PEditor.Library} libraryField
     * @return {Element[]}
     */
    const createTitles = function (libraryField, customTitle, customIconId) {

      const library = (libraryField.params && libraryField.params.library) ? libraryField.params.library : (libraryField.currentLibrary ? libraryField.currentLibrary : undefined);

      // Create breadcrumb section.
      const title = document.createElement('div');
      title.classList.add('form-manager-title');

      // Create breadcrumb section.
      const menuTitle = document.createElement('div');
      menuTitle.classList.add('form-manager-menutitle');
      menuTitle.tabIndex = '0';
      menuTitle.addEventListener('click', function () {
        handleBreadcrumbClick.call(title);
      });
      menuTitle.addEventListener('keypress', function (e) {
        handleBreadcrumbKeypress.call(title, e);
      });

      // For limiting the length of the menu title
      const menuTitleText = document.createElement('span');
      menuTitleText.classList.add('form-manager-menutitle-text');
      menuTitle.appendChild(menuTitleText);

      // Create a tooltip that can be display the whole text on hover
      const menuTitleTooltip = document.createElement('span');
      menuTitleTooltip.classList.add('form-manager-tooltip');
      menuTitle.appendChild(menuTitleTooltip);

      // Create a text wrapper so we can limit max-width on the text
      const textWrapper = document.createElement('span');
      textWrapper.classList.add('truncatable-text');
      textWrapper.tabIndex = -1;
      title.appendChild(textWrapper);

      // Create a tooltip that can display the whole text on hover
      const tooltip = document.createElement('span');
      tooltip.classList.add('form-manager-tooltip');
      title.appendChild(tooltip);

      /**
       * @private
       * @param {string} title WARNING: This is Text do not use as HTML.
       */
      const setTitle = function (title) {
        textWrapper.innerText = menuTitleText.innerText = tooltip.innerText = menuTitleTooltip.innerText = title;
      };

      /**
       * @private
       * @return {string} WARNING: This is Text do not use as HTML.
       */
      const getTitle = function () {
        if (customTitle) {
          return customTitle;
        }
        else if (libraryField.params && libraryField.params.metadata && libraryField.params.metadata.title &&
            libraryField.params.metadata.title.substr(0, 8) !== 'Untitled' ||
            libraryField.metadata && libraryField.metadata.title &&
            libraryField.metadata.title.substr(0, 8) !== 'Untitled') {
          return getText(libraryField.metadata ? libraryField.metadata.title : libraryField.params.metadata.title);
        }
        else {
          if (libraryField.$select !== undefined && libraryField.$select.children(':selected').text() !== '-') {
            return libraryField.$select.children(':selected').text();
          }
          else {
            return H5PEditor.libraryCache[library].title;
          }
        }
      };

      // Set correct starting title
      setTitle(getTitle());

      /**
       * Help listen for title changes after library has been fully loaded
       * @private
       */
      const listenForTitleChanges = function () {
        if (libraryField.metadataForm) {
          libraryField.metadataForm.on('titlechange', function (e) {
            // Handle changes to the metadata title
            setTitle(getTitle());
            manager.updateFormResponsiveness();
          });
        }

        if (textWrapper.innerText === 'Loading...') {
          // Correct title was not set initally, try again after library load
          setTitle(getTitle());
          manager.updateFormResponsiveness();
        }
      };

      // Listen for title updates
      if (libraryField.metadataForm === undefined && libraryField.change) {
        libraryField.change(listenForTitleChanges);
      }
      else {
        listenForTitleChanges();
      }

      const iconId = customIconId ? customIconId : library.split(' ')[0].split('.')[1].toLowerCase();
      title.classList.add('form-manager-icon-' + iconId);
      menuTitle.classList.add('form-manager-icon-' + iconId);
      if (customIconClass) {
        title.classList.add('form-manager-' + customIconClass);
        menuTitle.classList.add('form-manager-' + customIconClass);
      }

      return {
        breadcrumb: title,
        menu: menuTitle
      };
    };

    /**
     * Look through all parent ancestors to see if a manager already exists.
     *
     * @private
     * @param {*} parent
     * @return {DragNBar.FormManager}
     */
    const findExistingManager = function (parent) {
      if (parent instanceof DragNBar.FormManager) {
        return parent.getFormManager(); // Found our parent manager
      }
      if (parent.parent) {
        // Looks deeper
        return findExistingManager(parent.parent);
      }
      else {
        return self; // Use our self
      }
    };

    /**
     * Help hide an element.
     *
     * @param {Element} element
     * @private
     */
    const hideElement = function (element) {
      // Make sure element is hidden while still retaining its width without
      // expanding the container's height. This is due to some editors resizing
      // if their container changes size which leads to some funny transitions.
      // Also, having invisible height causes resize loops.
      element.classList.add('form-manager-hidden');
      element.setAttribute('aria-hidden', true);
    };

    /**
     * Help show a hidden element again
     *
     * @param {Element} element
     * @private
     */
    const showElement = function (element) {
      element.classList.remove('form-manager-hidden');
      element.removeAttribute('aria-hidden');
    };

    /**
     * Update fuillscreen button's attributes dependent on fullscreen or not
     *
     * @private
     * @param {Element} element The fullscreen button element
     * @param {boolean} isInFullscreen
     */
    const toggleFullscreenButtonState = function (element, isInFullscreen) {
      if (isInFullscreen) {
        // We are entering fullscreen mode
        element.setAttribute('aria-label', H5PEditor.t('core', 'exitFullscreenButtonLabel'));
        element.classList.add('form-manager-exit');
      }
      else {
        // We are exiting fullscreen mode
        element.setAttribute('aria-label', H5PEditor.t('core', 'enterFullscreenButtonLabel'));
        element.classList.remove('form-manager-exit');
      }
    };

    /**
     * Closes the current form.
     *
     * @private
     */
    const closeForm = function () {
      const activeManager = formTargets.pop();
      
      // Close any open CKEditors
      if (H5PEditor.Html) {
        H5PEditor.Html.removeWysiwyg();
      }

      // Let everyone know we're closing
      activeManager.trigger('formclose');

      // Locate open form and remove it from the manager
      const activeSubForm = activeManager.popForm();

      if (handleTransitionend) {
        // Cancel callback for form if not fully opened.
        activeSubForm.removeEventListener('transitionend', handleTransitionend);
        handleTransitionend = null;
      }

      // Find last part of breadcrumb and remove it from the manager
      const titles = activeManager.popTitles();

      // Remove menu title
      manager.formBreadcrumbMenu.removeChild(titles.menu);

      // The previous breadcrumb must no longer be clickable
      const previousBreadcrumb = titles.breadcrumb.previousSibling;
      previousBreadcrumb.removeEventListener('click', handleBreadcrumbClick);
      previousBreadcrumb.removeEventListener('keypress', handleBreadcrumbKeypress);
      previousBreadcrumb.classList.remove('clickable');
      previousBreadcrumb.removeAttribute('tabindex');

      const headHeight = manager.getFormHeadHeight();

      // Freeze container height to avoid jumping while showing elements
      manager.formContainer.style.height = (activeSubForm.getBoundingClientRect().height + headHeight) + 'px';

      // Make underlay visible again
      if (activeSubForm.previousSibling.classList.contains('form-manager-form')) {
        // This is not our last sub-form
        showElement(activeSubForm.previousSibling);
      }
      else {
        // Show bottom form
        for (let i = 1; i < manager.formContainer.children.length - 1; i++) {
          showElement(manager.formContainer.children[i]);
        }

        // No need for the buttons any more
        if (!alwaysShowButtons) {
          hideElement(manager.formButtons);
          manager.formButtons.classList.remove('form-manager-comein');

          // Hide footer
          manager.footerFormButtons.classList.remove('form-manager-comein');
          hideElement(manager.footerFormButtons);
          hideElement(manager.footer);
        }

        manager.formContainer.classList.add('root-form');
      }

      // Animation fix for fullscreen max-width limit.
      activeSubForm.style.marginLeft = window.getComputedStyle(activeSubForm).marginLeft

      // Make the sub-form animatable
      activeSubForm.classList.add('form-manager-movable');

      // Resume natural container height
      manager.formContainer.style.height = '';

      // Set sub-form height to cover container
      activeSubForm.style.height = (manager.formContainer.getBoundingClientRect().height - headHeight) + 'px';

      // Clean up when the final transition animation is finished
      onlyOnce(activeSubForm, 'transitionend', function () {
        // Remove from DOM
        manager.formContainer.removeChild(activeSubForm);
      });
      // Start the animation
      activeSubForm.classList.remove('form-manager-slidein');

      if (titles.breadcrumb.offsetWidth === 0) {
        // Remove last breadcrumb section in case it's hidden
        manager.formBreadcrumb.removeChild(titles.breadcrumb);
      }
      else {
        onlyOnce(titles.breadcrumb, 'transitionend', function () {
          // Remove last breadcrumb section
          manager.formBreadcrumb.removeChild(titles.breadcrumb);
        });
        // Start the animation
        titles.breadcrumb.classList.remove('form-manager-comein');
      }

      if (!subForm) {
        if (proceedButton && manager.exitSemiFullscreen) {
          // We are in fullscreen and closing sub-form, show proceed button
          showElement(proceedButton);
        }
        if (breadcrumbButton) {
          breadcrumbButton.classList.add('form-manager-disabled');
        }
      }
      if (self.formContainer.classList.contains('mobile-menu-open')) {
        self.toggleBreadcrumbMenu();
      }

      // Scroll parent manager header into view
      manager.formButtons.scrollIntoView();
    };

    /**
     * The breadcrumb click handler figures out how many forms to close.
     *
     * @private
     */
    const handleBreadcrumbClick = function () {
      for (let i = 0; i < manager.formBreadcrumb.children.length; i++) {
        if (manager.formBreadcrumb.children[i] === this) {
          manager.closeFormUntil(i);
          break;
        }
      }
    };

    /**
     * The breadcrumb click handler figures out how many forms to close.
     *
     * @private
     */
    const handleBreadcrumbKeypress = function (e) {
      if (e.which === 13 || e.which === 32) {
        handleBreadcrumbClick.call(this);
      }
    };

    /**
     * Close all forms until the given index.
     *
     * @param {number} index
     */
    self.closeFormUntil = function (index) {
      while (formTargets.length - 1 !== index) {
        formTargets[formTargets.length - 1].trigger('formdone');
        closeForm();
      }
    };

    /**
     * Retrieve the current form element and remove it from the manager.
     *
     * @return {Element}
     */
    self.popForm = function () {
      const sF = subForm;
      subForm = null;
      return sF;
    };

    /**
     * Retrieve the current title element and remove it from the manager.
     *
     * @return {Element}
     */
    self.popTitles = function () {
      const t = titles;
      titles = null;
      return t;
    };

    /**
     * Retrieve the active manager.
     *
     * @return {DragNBar.FormManager}
     */
    self.getFormManager = function () {
      return manager;
    };

    /**
     * Set the form manager to be used for the next button clicks.
     *
     * @param {DragNBar.FormManager} target
     */
    self.addFormTarget = function (target) {
      formTargets.push(target);
    };

    /**
     * Create a new sub-form and shows it.
     *
     * @param {H5PEditor.Library} libraryField
     * @param {Element} formElement
     */
    self.openForm = function (libraryField, formElement, customClass, customTitle, customIconId) {
      if (subForm) {
        return; // Prevent opening more than one sub-form at a time per editor.
      }

      // Tell manager that we should be receiving the next buttons events
      manager.formContainer.classList.remove('root-form');
      manager.addFormTarget(self);

      // Create the new sub-form
      subForm = document.createElement('div');
      subForm.classList.add('form-manager-form');
      subForm.classList.add('form-manager-movable');
      if (customClass) {
        subForm.classList.add(customClass);
      }
      subForm.appendChild(formElement);

      // Ensure same height as container
      subForm.style.height = (manager.formContainer.getBoundingClientRect().height - manager.getFormHeadHeight()) + 'px';

      // Insert into DOM
      manager.formContainer.appendChild(subForm);

      // Make last part of breadcrumb clickable
      const lastBreadcrumb = manager.formBreadcrumb.lastChild;
      lastBreadcrumb.addEventListener('click', handleBreadcrumbClick);
      lastBreadcrumb.addEventListener('keypress', handleBreadcrumbKeypress);
      lastBreadcrumb.classList.add('clickable');
      lastBreadcrumb.tabIndex = '0';

      // Add breadcrumb section
      titles = createTitles(libraryField, customTitle, customIconId);
      manager.formBreadcrumb.appendChild(titles.breadcrumb);
      manager.formBreadcrumbMenu.insertBefore(titles.menu, manager.formBreadcrumbMenu.firstChild);

      // Show our buttons
      showElement(manager.formButtons);
      showElement(manager.footerFormButtons);
      showElement(manager.footer);

      // Ensure footer is at the bottom of the form
      manager.formContainer.appendChild(manager.footer);

      // When transition animation is done and the form is fully open...
      handleTransitionend = onlyOnce(subForm, 'transitionend', function () {
        handleTransitionend = null;

        // Hide everything except first, second, last child and footer
        for (let i = 2; i < manager.formContainer.children.length - 1; i++) {
          const child = manager.formContainer.children[i];
          const skipHiding = child === subForm
            || child.classList.contains('sp-container')
            || child.classList.contains('form-manager-footer');
          if (!skipHiding) {
            hideElement(manager.formContainer.children[i]);
          }
        }

        // Resume natural height
        subForm.style.height = '';
        subForm.style.marginLeft = '';
        subForm.classList.remove('form-manager-movable');

        self.trigger('formopened');
      });

      // Start animation on the next tick
      setTimeout(function () {
        // Animation fix for fullscreen max-width limit.
        subForm.style.marginLeft = (parseFloat(window.getComputedStyle(manager.formContainer.children[manager.formContainer.children.length - 2]).marginLeft) - 20) + 'px';

        subForm.classList.add('form-manager-slidein');
        titles.breadcrumb.classList.add('form-manager-comein');
        manager.formButtons.classList.add('form-manager-comein');
        manager.footerFormButtons.classList.add('form-manager-comein');
        manager.updateFormResponsiveness();
      }, 0);

      if (proceedButton && manager.exitSemiFullscreen) {
        // We are in fullscreen and opening sub-form, hide Proceed button
        hideElement(proceedButton);
      }
      if (breadcrumbButton) {
        breadcrumbButton.classList.remove('form-manager-disabled');
      }
    };

    /**
     * Check if the sub-form is fully opened. (animation finished)
     *
     * @return {boolean}
     */
    self.isFormOpen = function () {
      return subForm && !handleTransitionend;
    };

    /**
     * Determine the overall height of the form head section.
     *
     * @return {number}
     */
    self.getFormHeadHeight = function () {
      return (alwaysShowButtons ? 0 : head.getBoundingClientRect().height);
    };

    /**
     * Toggle the breadcrumb menu.
     */
    self.toggleBreadcrumbMenu = function () {
      if (self.formContainer.classList.contains('mobile-menu-open')) {
        // Close breadcrumb menu
        self.formContainer.classList.remove('mobile-menu-open');
        breadcrumbButton.children[0].innerText = l10n.expandBreadcrumbButtonLabel;
        breadcrumbButton.setAttribute('aria-label', l10n.expandBreadcrumbButtonLabel);
        self.formBreadcrumbMenu.classList.remove('form-manager-comein');
      }
      else {
        // Open breadcrumb menu
        self.formContainer.classList.add('mobile-menu-open');
        breadcrumbButton.children[0].innerText = l10n.collapseBreadcrumbButtonLabel;
        breadcrumbButton.setAttribute('aria-label', l10n.collapseBreadcrumbButtonLabel);
        self.formBreadcrumbMenu.classList.add('form-manager-comein');
      }
    };

    /**
     * Resize form header elements to fit better inside narrow forms.
     */
    self.updateFormResponsiveness = function () {
      if (head.classList.contains('mobile-view-large')) {
        head.classList.remove('mobile-view-large');
      }
      if (self.formContainer.classList.contains('mobile-view-small')) {
        self.formContainer.classList.remove('mobile-view-small');
      }
      if (head.offsetWidth < 481) {
        self.formContainer.classList.add('mobile-view-small');
      }

      /**
       * Enable tooltips where we have text-ellipsis.
       *
       * @private
       * @param {Element} element
       */
      const updateActiveTooltips = function (element) {
        let tooltipActive;
        for (let i = 0; i < element.children.length; i++) {
          const breadcrumbTitle = element.children[i];
          if (breadcrumbTitle.firstChild.offsetWidth && breadcrumbTitle.firstChild.scrollWidth > breadcrumbTitle.firstChild.offsetWidth + 1) {
            breadcrumbTitle.classList.add('form-mananger-tooltip-active');
            tooltipActive = true;
          }
          else {
            breadcrumbTitle.classList.remove('form-mananger-tooltip-active');
          }
        }
        return tooltipActive;
      };
      if (updateActiveTooltips(self.formBreadcrumb)) {
        head.classList.add('mobile-view-large');
        // Check again since we made buttons smaller
        updateActiveTooltips(self.formBreadcrumb)
      }
      updateActiveTooltips(self.formBreadcrumbMenu);
    };

    /**
     * Keep the buttons visible even though the last sub-form is closed.
     *
     * @param {Boolean} state
     */
    self.setAlwaysShowButtons = function (state) {
      alwaysShowButtons = state;

      if (alwaysShowButtons) {
        // Show our buttons
        showElement(manager.formButtons);
        manager.formButtons.classList.add('form-manager-comein');
      }
    };

    // Figure out which manager to use.
    const manager = findExistingManager(parent);
    if (manager === self) {
      initialize(); // We are the first of our kind
    }
  };

  DragNBar.FormManager.prototype = Object.create(EventDispatcher.prototype);
  DragNBar.FormManager.prototype.constructor = DragNBar.FormManager;

  /**
   * Help convert any HTML into text.
   *
   * @param {string} value
   * @return {string}
   */
  const getText = function (value) {
    const textNode = H5PEditor.$.parseHTML(value);
    if (textNode !== null) {
      return textNode[0].nodeValue;
    }
    return value;
  };

  /**
   * Help make sure that an event handler is only triggered once.
   *
   * @private
   * @param {Element} element
   * @param {string} eventName
   * @param {function} handler
   * @return {function} Callback in case of manual cancellation
   */
  const onlyOnce = function (element, eventName, handler) {
    const callback = function () {
      // Make sure we're only called once.
      element.removeEventListener(eventName, callback);

      // Trigger the real handler
      handler.apply(this, arguments);
    };
    element.addEventListener(eventName, callback);
    return callback;
  };

})(H5P.DragNBar, H5P.EventDispatcher);
