Source: src/js/views/maps/VisualizationPanelView.js

"use strict";

define(["backbone", "common/TrustedContentUtilities"], (
  Backbone,
  trustedContent,
) => {
  const BASE_CLASS = "visualization-panel";
  const CLASS_NAMES = {
    body: `${BASE_CLASS}__body`,
    closeButton: `${BASE_CLASS}__close-button`,
    header: `${BASE_CLASS}__header`,
    iframe: `${BASE_CLASS}__iframe`,
    open: `${BASE_CLASS}--open`,
    untrustedMessage: `${BASE_CLASS}__untrusted-message`,
    untrusted: `${BASE_CLASS}__untrusted`,
    untrustedLink: `${BASE_CLASS}__untrusted-link`,
  };

  const VIEW_TEMPLATE = `
    <div class="${CLASS_NAMES.header}">
      <button class="${CLASS_NAMES.closeButton} map-view__button" type="button" aria-label="Close visualization"><i class="icon-remove"></i></button>
    </div>
    <div class="${CLASS_NAMES.body}">
      <iframe
        class="${CLASS_NAMES.iframe}"
        title="Visualization"
      ></iframe>
      <div class="${CLASS_NAMES.untrusted}" style="display:none">
        <p class="${CLASS_NAMES.untrustedMessage}">This content cannot be displayed here because its source is not in the list of trusted sources.</p>
        <a class="${CLASS_NAMES.untrustedLink} map-view__button map-view__button--emphasis" target="_blank" rel="noopener noreferrer">
          <i class="icon icon-external-link"></i>
          Open in Browser
        </a>
      </div>
    </div>
  `;

  /**
   * @class VisualizationPanelView
   * @classdesc A full-screen overlay panel that displays an external
   * visualization application in an iframe above the map. Opened by clicking
   * an 'iframe' type button on a ViewfinderCardView card. Closed by clicking
   * the close button in the upper right, pressing the Escape key, or by clicking
   * another card action.
   *
   * Security: the iframe `src` is only set when the URL matches the app's
   * `trustedContentSources` configuration. Untrusted URLs are displayed as a
   * plain link instead.
   * @classcategory Views/Maps
   * @name VisualizationPanelView
   * @augments Backbone.View
   * @constructs VisualizationPanelView
   * @screenshot views/maps/VisualizationPanelView.png
   * @since 2.37.0
   */
  const VisualizationPanelView = Backbone.View.extend(
    /** @lends VisualizationPanelView.prototype */ {
      /**
       * The type of View this is
       * @type {string}
       */
      type: "VisualizationPanelView",

      /** @inheritdoc */
      className: BASE_CLASS,

      /**
       * The events this view will listen to and the associated function to
       * call.
       * @type {object}
       */
      events() {
        return {
          [`click .${CLASS_NAMES.closeButton}`]: "close",
        };
      },

      /**
       * Bind the Escape-key handler once so the same reference can be
       * added and removed from the document.
       */
      initialize() {
        this.handleEscapeKey = (e) => {
          if (e.key === "Escape") this.close();
        };
      },

      /**
       * Open the panel and load the given URL in the iframe. If the URL is
       * not trusted, show a plain link fallback instead.
       * @param {string} url The URL to load.
       */
      open(url) {
        const iframe = this.el.querySelector(`.${CLASS_NAMES.iframe}`);
        const untrustedMessage = this.el.querySelector(
          `.${CLASS_NAMES.untrustedMessage}`,
        );
        const untrusted = this.el.querySelector(`.${CLASS_NAMES.untrusted}`);
        const untrustedLink = this.el.querySelector(
          `.${CLASS_NAMES.untrustedLink}`,
        );

        if (trustedContent.isTrustedUrl(url)) {
          iframe.setAttribute(
            "sandbox",
            trustedContent.getTrustedIframeSandbox(url),
          );
          iframe.src = url;
          iframe.style.display = "";
          untrusted.style.display = "none";
        } else {
          iframe.removeAttribute("sandbox");
          iframe.removeAttribute("src");
          iframe.style.display = "none";

          if (trustedContent.isHttpUrl(url)) {
            untrustedMessage.textContent =
              "This content cannot be displayed here because its source is not in the list of trusted sources.";
            untrustedLink.href = url;
            untrustedLink.textContent = url;
            untrustedLink.style.display = "";
          } else {
            untrustedMessage.textContent =
              "This content cannot be displayed here because the URL protocol is unsafe.";
            untrustedLink.removeAttribute("href");
            untrustedLink.textContent = "";
            untrustedLink.style.display = "none";
          }

          untrusted.style.display = "";
        }

        this.el.classList.add(CLASS_NAMES.open);
        document.addEventListener("keydown", this.handleEscapeKey);
      },

      /**
       * Close the panel, stop the iframe, and fire a "close" event.
       */
      close() {
        const iframe = this.el.querySelector(`.${CLASS_NAMES.iframe}`);
        if (iframe) iframe.removeAttribute("src");
        this.el.classList.remove(CLASS_NAMES.open);
        document.removeEventListener("keydown", this.handleEscapeKey);
        this.trigger("close");
      },

      /**
       * Cleanup global handlers if the view is destroyed while open.
       */
      onClose() {
        document.removeEventListener("keydown", this.handleEscapeKey);
        const iframe = this.el.querySelector(`.${CLASS_NAMES.iframe}`);
        if (iframe) iframe.removeAttribute("src");
      },

      /**
       * Render the panel. The panel is hidden by default; call `open()` to
       * show it.
       * @returns {VisualizationPanelView} Returns the rendered view element.
       */
      render() {
        this.el.innerHTML = VIEW_TEMPLATE;
        return this;
      },
    },
  );

  return VisualizationPanelView;
});