renderers/dom/dom.js

// Dependencies
const React = require("react");
const ReactDOM = require("react-dom");
const Card = require("../shared/Card");
const CardKitRenderer = require("../shared/base");
const UI = require("./ui/ui");
const SVGToImage = require("./SVGToImage");
const { slugify } = require("../../helpers");

/**
 * @name CardKitDOM
 * @class Additional CardKit class used for rendering in the DOM
 */
class CardKitDOM extends CardKitRenderer {
  /**
   * Constructor takes in an instance of CardKit and stores it for later use
   *
   * @param {CardKit} cardkit - An instance of CardKit
   */
  constructor(cardkit) {
    // Ensure we're operating in a browser environment
    if (typeof document === "undefined") {
      throw new Error("CardKitDOM can only be used in a browser environment");
    }

    super(cardkit);

    // Store render IDs
    this.renderedCardID = null;
    this.renderedUIID = null;
  }

  /**
   * Renders the built-in UI to the supplied element
   *
   * @param {string} id - The ID of the element to render the UI into
   */
  renderUI(id) {
    if (!this._isValidElement(id)) {
      throw new Error("Invalid element ID provided");
    }

    const element = document.getElementById(id);

    const template = this.cardkit.templates
      ? Object.keys(this.cardkit.templates)[0]
      : false;
    const theme = this.cardkit.themes
      ? Object.keys(this.cardkit.themes)[0]
      : false;
    const layout = this.cardkit.layouts
      ? Object.keys(this.cardkit.layouts)[0]
      : false;

    this.renderedUIID = id;
    ReactDOM.render(
      React.createElement(UI, {
        configuration: this.computeConfiguration({
          template: template,
          theme: theme,
          layout: layout,
        }),
        templates: this.cardkit.templates,
        themes: this.cardkit.themes,
        layouts: this.cardkit.layouts,
        cardKit: this,
      }),
      element
    );
  }

  /**
   * Renders just the Card as a React component to the supplied element
   *
   * @param {string} id - The ID of the element to render the card into
   * @param {object} options - Any override data (e.g. theme, layout) to use when rendering the card
   */
  renderCard(id, options) {
    if (!this._isValidElement(id)) {
      throw new Error("Invalid element ID provided");
    }

    const element = document.getElementById(id);

    this.renderedCardID = id;

    ReactDOM.render(
      React.createElement(Card, {
        configuration: this.computeConfiguration(options),
      }),
      element
    );
  }

  /**
   * Checks if the ID provided is valid
   *
   * @param {string} id - The ID to validate
   * @return {boolean} If the ID was valid
   */
  _isValidElement(id) {
    if (!id) {
      return false;
    }

    const element = document.getElementById(id);
    if (!element) {
      return false;
    }

    return true;
  }

  /**
   * Re-renders the Card or UI
   */
  rerender() {
    if (this.renderedUIID) {
      this.renderUI(this.renderedUIID);
    }

    if (this.renderedCardID) {
      this.renderCard(this.renderedCardID);
    }
  }

  /**
   * Downloads the card as an image in the browser
   *
   * @param {number} scale - The scale to output at
   * @param {object} element - The element to use to generate the image
   */
  download(scale = 2, element) {
    element =
      element.childNodes[0] ||
      document.getElementById(this.renderedCardID).childNodes[0];

    const svgToImage = new SVGToImage(element);

    // Setup default filename
    let filename = "cardkit-default.jpg";

    // Get the configuration
    const configuration = this.computeConfiguration();

    // If there's a layer that has the useAsFilename property, find it
    const filenameLayerKey = Object.keys(configuration.layers).find((key) => {
      const layer = configuration.layers[key];

      return (
        layer.useAsFilename === true && // Has the useAsFilename property
        layer.hidden !== true && // Is not hidden
        layer.type === "text"
      ); // Is of type text
    });

    // Get the layer that has the filename on it
    const filenameLayer = configuration.layers[filenameLayerKey];

    // Update the filename
    if (filenameLayer) {
      filename = slugify(filenameLayer.text) + ".jpg";
    }

    // Trigger the download
    svgToImage.download(filename, {
      format: "image/jpeg",
      scale: scale,
    });
  }
}

module.exports = CardKitDOM;