Source: src/js/views/accordion/AccordionItemView.js

define(["jquery", "backbone", "semantic", "models/accordion/AccordionItem"], (
  $,
  Backbone,
  Semantic,
  AccordionItem,
) => {
  // Base class for the view
  const BASE_CLASS = "accordion-item";

  const CLASS_NAMES = {
    titleIcon: `${BASE_CLASS}__title-icon`,
  };

  const SEM_VARIATIONS = Semantic.CLASS_NAMES.variations;

  /**
   * @class AccordionItemView
   * @classdesc A view representing an accordion item with a title and content
   * @classcategory Views/Accordion
   * @augments Backbone.View
   * @class
   * @since 2.31.0
   * @screenshot views/accordion/AccordionItemViewView.png
   */
  const AccordionItemView = Backbone.View.extend(
    /** @lends AccordionItemView.prototype */
    {
      /** @inheritdoc */
      type: "AccordionItemView",

      /** @inheritdoc */
      className: BASE_CLASS,

      /** @inheritdoc */
      tagName: "div",

      /**
       * An HTML string to use for an icon indicating that the accordion item is
       * collapsible.
       * @type {string}
       */
      dropdownIconTemplate: `<i class="${Semantic.CLASS_NAMES.accordion.icon}"></i>`,

      /**
       * Settings passed to the Formantic UI popup module to configure a tooltip
       * shown over the item title. The item must have a description set
       * in order for the tooltip to be shown.
       * @see https://fomantic-ui.com/modules/popup.html#/settings
       * @type {object|boolean}
       * @since 2.34.0
       */
      tooltipSettings: {
        variation: `${SEM_VARIATIONS.mini} ${SEM_VARIATIONS.inverted}`,
        position: "top center",
        on: "hover",
        hoverable: true,
        delay: {
          show: 500,
          hide: 40,
        },
      },

      /** @inheritdoc */
      initialize(options) {
        this.model = options?.model || new AccordionItem();
        if (
          options?.tooltipSettings &&
          typeof options.tooltipSettings === "object"
        ) {
          this.tooltipSettings = options.tooltipSettings;
        }
      },

      /* Hide the dropdown icon */
      hideIcon() {
        this.iconEl.style.display = "inline-block";
        this.iconEl.style.visibility = "hidden";
      },

      /* Show the dropdown icon */
      showIcon() {
        this.iconEl.style.display = "inline-block";
        this.iconEl.style.visibility = "visible";
      },

      /** @inheritdoc */
      render() {
        // Add any custom classes
        const customClasses = this.model.get("classes") || [];
        const titleClass = Semantic.CLASS_NAMES.accordion.title;
        const contentClass = Semantic.CLASS_NAMES.accordion.content;

        // Dropdown Icon
        const iconContainer = document.createElement("div");
        iconContainer.innerHTML = this.dropdownIconTemplate;
        const iconEl = iconContainer.firstChild;
        this.iconEl = iconEl;

        // Title
        const titleSpan = document.createElement("span");
        const titleContainer = document.createElement("div");
        titleContainer.classList.add(titleClass, ...customClasses);
        titleContainer.appendChild(iconEl);
        titleContainer.appendChild(titleSpan);
        this.titleContainer = titleContainer;
        this.titleSpan = titleSpan;
        this.updateTitle();

        // Content
        const contentContainer = document.createElement("div");
        contentContainer.classList.add(contentClass, ...customClasses);
        this.contentContainer = contentContainer;

        // Initialize the tooltip for the title
        this.updateTooltip();

        // Put it all together
        this.el.appendChild(titleContainer);
        this.el.appendChild(contentContainer);
        this.updateContent(this.model.get("content"));

        this.listenToModel();

        return this;
      },

      /**
       * Listen to changes on the model and update the view accordingly. Called
       * during render.
       * @since 2.34.0
       */
      listenToModel() {
        this.stopListening();
        this.listenTo(this.model, "change:title", (_model, title) => {
          this.updateTitle(title);
        });
        this.listenTo(this.model, "change:content", (_model, content) => {
          this.updateContent(content);
        });
        this.listenTo(this.model, "change:itemId", () => {
          this.el.id = this.model.get("itemId");
        });
        this.listenTo(this.model, "change:description", () => {
          this.updateTooltip();
        });
      },

      /**
       * Change the content of the accordion item.
       * @param {string|HTMLElement|Backbone.View} content - The content to
       * display.
       * @param {boolean} [clear] - Whether to clear the existing content.
       */
      updateContent(content, clear = true) {
        const { contentContainer } = this;
        if (!contentContainer) return;
        if (clear) {
          contentContainer.innerHTML = "";
          this.hideIcon();
          contentContainer.style.padding = "0";
        }
        if (!content) return;
        if (typeof content === "string") {
          contentContainer.innerHTML = content;
        } else if (content instanceof HTMLElement) {
          contentContainer.appendChild(content);
        } else if (content instanceof Backbone.View) {
          contentContainer.appendChild(content.render().el);
        }
        this.showIcon();
        contentContainer.style.padding = "";
      },

      /**
       * Update the title of the accordion item.
       * @param {string} [title] - The new title to set. If not provided, the
       * title will be taken from the model.
       * @since 2.34.0
       */
      updateTitle(title) {
        const { titleSpan } = this;
        if (!this.titleSpan) return;

        const newTitle = title || this.model.get("title");
        const iconName = this.model.get("icon");

        let additionalIcon;
        if (iconName) {
          additionalIcon = document.createElement("i");
          additionalIcon.classList.add(
            CLASS_NAMES.titleIcon,
            "icon",
            `icon-${iconName}`,
          );
        }

        titleSpan.innerHTML = `<span>${newTitle}<span>` || "";
        if (additionalIcon) titleSpan.prepend(additionalIcon);
      },

      /**
       * Update the tooltip for the title of the accordion item.
       * If the model has a description set, it will be used as the tooltip
       * content. If not, the tooltip will be destroyed.
       * @since 2.34.0
       */
      updateTooltip() {
        const { titleContainer } = this;
        if (!titleContainer) return;

        const description = this.model.get("description");

        if (description) {
          $(titleContainer).popup({
            ...this.tooltipSettings,
            html: description,
          });
        } else {
          $(titleContainer).popup("destroy");
        }
      },

      /*
       * Remove the view, DOM elements, and its associated popups.
       */
      remove() {
        $(this.titleContainer).popup("destroy");
        this.titleContainer.remove();
        this.contentContainer.remove();
        Backbone.View.prototype.remove.call(this);
      },
    },
  );

  return AccordionItemView;
});