Getting Started

index.js

These lines of code are importing several dependencies used by the Blade NFT Button JavaScript library.

The first line imports a CSS file style.scss using Webpack's CSS loader. This file contains styles that are applied to the Blade NFT Button interface.

The second and third lines import two image files - arrow.svg and blade-logo.svg respectively. These images are used in the interface of the Blade NFT Button.

The next two lines import two utility functions from separate files. The isMobile function is used to detect whether the user is on a mobile device or not. The translate function is used for translating strings to different languages.

Finally, the last line imports a attributes object from a models.js file. This object contains the attribute keys and values that are used to create and transfer the NFT.

import css from "../scss/style.scss";

import arrowImageData from '../img/arrow.svg';
import btnImageData from '../img/blade-logo.svg';

import {isMobile, translate} from "./utils.js";
import {attributes} from "./models.js";

This is a JavaScript code snippet that defines a class called "BladeAirdrop". Here's a breakdown of what the code does:

  • The first line defines a constant variable called "SELECTOR" and sets its value to "blade-nft-purchase".

  • The constructor function initializes two properties of the "BladeAirdrop" instance - "overlay" and "stateAttribute". "overlay" is not defined and will be set later, while "stateAttribute" is set to the string "data-waiting".

  • The "isBladeInstalled" property is set to false initially. The code then listens for the "hederaWalletLoaded" event, and when it fires, sets "isBladeInstalled" to true.

  • The code then selects all DOM elements with the class "blade-nft-purchase" using the querySelectorAll method, and for each element found, calls the "createButton" method and binds the "BladeAirdrop" instance as the "this" value.

  • Finally, the code calls the "observeNewPlaceholders" method which is not defined in the code snippet.

Overall, this code seems to be initializing an instance of the "BladeAirdrop" class, which appears to be responsible for creating buttons for the Blade NFT purchase functionality, and observing new placeholders in the DOM.

const SELECTOR = "blade-nft-purchase";

class BladeAirdrop {
  constructor() {
    this.overlay;
    this.stateAttribute = "data-waiting";
    this.isBladeInstalled = false;
    document.addEventListener("hederaWalletLoaded", () => {
      this.isBladeInstalled = true;
    });

    document.querySelectorAll(`.${SELECTOR}`).forEach(this.createButton.bind(this));
    this.observeNewPlaceholders();
  }

The "createButton" method is called for each DOM element with the class "blade-nft-purchase" found in the constructor function. The method takes a "placeholder" parameter, which is a reference to a DOM element with the "blade-nft-purchase" class.

The method starts by creating a new button element using the "document.createElement" method. It sets the innerHTML of the button to the translated string "btnLabelConnect", sets the class name to ${SELECTOR}__btn, and the type to "button".

Then, a new image element is created using the same "document.createElement" method. The "src" attribute of the image is set to a variable called "btnImageData". The class name is set to ${SELECTOR}__btn-img. The image element is then prepended to the button element using the "prepend" method.

The method then sets an attribute on the "placeholder" element with the name of "data-waiting" and the value of "false". It appends the button element to the placeholder element using the "appendChild" method. Finally, it calls the "addBtnListener" method, passing in the button element and the placeholder element as arguments. The "addBtnListener" method is not defined in this code snippet.

  createButton(placeholder) {
    const btn = document.createElement("button");
    btn.innerHTML = translate("btnLabelConnect");
    btn.className = `${SELECTOR}__btn`
    btn.type = "button";
    const img = document.createElement("img");
    img.src = btnImageData;
    img.className = `${SELECTOR}__btn-img`;
    btn.prepend(img);

    placeholder.setAttribute(this.stateAttribute, false)
    placeholder.appendChild(btn);
    this.addBtnListener(btn, placeholder);
  }

The "addBtnListener" method is used to add an event listener to the button element created in the "createButton" method. The method takes two parameters: "btn", which is the button element, and "ph", which is the placeholder element.

The method calls the "addEventListener" method on the button element, passing in the string "click" as the first argument, and a function as the second argument. The function passed to "addEventListener" is an arrow function that takes an "e" parameter (representing the click event object) and calls the "actionClick" method, passing in the "ph" parameter as an argument.

This method essentially sets up a click event listener on the button element, and when the button is clicked, it calls the "actionClick" method, which is not defined in this code snippet.

 addBtnListener(btn, ph) {
    btn.addEventListener('click', (e) => {
      this.actionClick(ph);
    })
  }

The "actionClick" method is called when the button created in the "createButton" method is clicked. The method takes one parameter, "ph", which is the placeholder element for which the button was created.

The first if-statement checks if the current device is a mobile device using the "isMobile" function. If it is, then a deep link is generated by calling the "generateDeeplink" method and passing in the "ph" parameter as an argument. The deep link is opened in a new window by calling the "window.open" method and passing in the generated deep link.

If the device is not a mobile device, the else block is executed. The first condition in the else block checks if Blade is installed or if the "bladeConnect" object exists in the window. If either is true, the "createOverlay" method is called to create a new overlay element. The "ph" parameter is then updated to have a "data-waiting" attribute with a value of "true". The "observeBox" method is then called to observe the "ph" element.

If Blade is not installed and the "bladeConnect" object does not exist in the window, the else block's second condition is executed. It opens a new window by calling the "window.open" method and passing in the "process.env.WEB_LINK" variable as an argument.

In summary, the "actionClick" method checks if the device is mobile, and if it is, it generates and opens a deep link. If the device is not mobile, it checks if Blade is installed or if the "bladeConnect" object exists, and if either is true, it creates an overlay element, sets the "ph" parameter's "data-waiting" attribute to true, and observes the "ph" element. If Blade is not installed and the "bladeConnect" object does not exist, it opens a new window with a link.

  actionClick(ph) {
    if (isMobile()) {
      const deeplink = this.generateDeeplink(ph);
      window.open(deeplink);
    } else {
      if (this.isBladeInstalled || window.bladeConnect) {
        this.createOverlay();
        ph.setAttribute(this.stateAttribute, true);
        this.observeBox(ph);
      } else {
        window.open(process.env.WEB_LINK);
      }
    }
  }

The "createOverlay" method is called by the "actionClick" method to create a new overlay element that will be displayed on the page. The overlay is created as a div element and is given the class name ${SELECTOR}__overlay, which is defined as "blade-nft-purchase__overlay" in the constant "SELECTOR". The overlay element is then appended to the body of the HTML document.

A new popup element is created as a div element with the class name ${SELECTOR}__popup, which is defined as "blade-nft-purchase__popup" in the constant "SELECTOR". The popup element's innerHTML is set to a translated message, which is returned by the "translate" function (not defined in this code snippet). The popup element is appended to the overlay element.

There is a commented-out section of code that creates an image element with the class name ${SELECTOR}__popup-img and sets its source to an image URL defined in the "arrowImageData" variable (not defined in this code snippet). However, this code is currently not being used and is commented out.

  createOverlay() {
    this.overlay = document.createElement("div");
    this.overlay.className = `${SELECTOR}__overlay`;
    document.body.appendChild(this.overlay);

    const popup = document.createElement("div");
    popup.className = `${SELECTOR}__popup`;
    popup.innerHTML = translate("popupMessage");
    this.overlay.appendChild(popup);

    // const img = document.createElement("img");
    // img.src = arrowImageData;
    // img.className = `${SELECTOR}__popup-img`;
    // popup.prepend(img);
  }

The "generateDeeplink" method is called by the "actionClick" method when the user is on a mobile device. It generates a deep link URL that will open the Blade Wallet app when clicked.

The method first checks the value of the "process.env.NODE_ENV" variable to determine whether the app is running in development or production mode. If in development mode, the "firebase" variable is set to the value of "process.env.F_DEEPLINK_DEV", which is a Firebase dynamic link URL for the development environment. If in production mode, "firebase" is set to the value of "process.env.F_DEEPLINK_PROD", which is a Firebase dynamic link URL for the production environment.

Next, a new URL object is created using the "process.env.DEEPLINK" value, which is the base deep link URL for the Blade Wallet app. The method then calls the "getAttributes" method (not defined in this code snippet) to retrieve the relevant attributes for the placeholder element, and sets them as search parameters in the deeplink URL using the URL object's "searchParams.set" method.

Finally, the search string is encoded and the firebase dynamic link URL is concatenated with the deeplink URL and some app identifiers to generate the final deep link URL.

  generateDeeplink(ph) {
    let firebase = process.env.NODE_ENV === "development"
      ? process.env.F_DEEPLINK_DEV
      : process.env.F_DEEPLINK_PROD;
    let deeplink = new URL(process.env.DEEPLINK);
    const attributes = this.getAttributes(ph);

    for (let key in attributes) {
      deeplink.searchParams.set(key, attributes[key])
    }

    deeplink.search = encodeURIComponent(deeplink.search).replace("%3F", "");
    return firebase + deeplink + "&apn=org.bladelabs.wallet&isi=1623849951&ibi=org.bladelabs.bladewallet";
  }

The "getAttributes" method takes a placeholder element as an argument and returns an object containing the attributes for that element that are required to generate the deep link URL.

The method first initializes an empty object, "attrs", which will be used to store the attribute key-value pairs. Then, for each attribute in the "attributes" array (which is not defined in this code snippet), it checks if the placeholder element has a dataset attribute with a key matching the attribute's "attr" property. If it does, the method adds the corresponding key-value pair to the "attrs" object, using the attribute's "paramsTag" property as the key and the dataset attribute value as the value.

If the attribute is marked as "required" in the "attributes" array, the method logs an error to the console if the dataset attribute value is missing or falsy. If the attribute is not required, the method deletes the key-value pair from the "attrs" object if the dataset attribute value is missing or falsy.

Finally, the method returns the "attrs" object containing the attribute key-value pairs.

  getAttributes(ph) {
    const attrs = {};
    attributes.forEach(elem => {
      const value = ph.dataset[elem.attr];

      if (elem.required) {
        value
          ? attrs[elem.paramsTag] = value
          : console.error("Required param missing", elem.attr);
      } else {
        value
          ? attrs[elem.paramsTag] = value
          : delete attrs[elem.paramsTag];
      }
    });
    return attrs;
  }

The "observeBox" method takes a placeholder element as an argument and observes its "data-waiting" attribute using a MutationObserver.

The method creates a new MutationObserver and passes in a callback function that will be called whenever a mutation is observed. The callback function receives an array of MutationRecord objects representing the changes to the observed element(s).

For each mutation record, the callback checks if the "data-waiting" attribute of the target element (which is the placeholder element in this case) is "false" or falsy. If it is, the overlay is removed from the document and the observer is disconnected to stop further observation.

The MutationObserver is then set up to observe the "data-waiting" attribute of the placeholder element using the "observe" method. The "attributes" option is set to true to observe attribute changes, and the "attributeFilter" option is set to ["data-waiting"] to only observe changes to the "data-waiting" attribute.

  observeBox(ph) {
    const observer = new MutationObserver((mutations) => {
      mutations.forEach(record => {
        if (record.target.dataset.waiting === "false" || !record.target.dataset.waiting) {
          this.overlay.remove();
          observer.disconnect();
        }
      })
    });

    observer.observe(ph, {attributes: true, attributeFilter: ["data-waiting"]});
  }

The "observeNewPlaceholders" method sets up a MutationObserver to observe the entire document for changes in its child nodes and subtrees.

When a mutation is observed, the observer's callback function is called, which receives an array of MutationRecord objects representing the changes to the document's child nodes and subtrees.

The callback function then creates an empty array called "placeholders" to hold any new elements that match the SELECTOR constant.

For each added node in the mutations, the function checks if it is an element node (as opposed to a text node, for example). If it is, the function searches for any elements that match the SELECTOR constant within the added node using the "querySelectorAll" method. The resulting elements are then added to the "placeholders" array.

If the added node itself matches the SELECTOR constant, it is also added to the "placeholders" array.

Once all relevant nodes have been searched, the "createButton" method is called for each placeholder element in the "placeholders" array.

Finally, the MutationObserver is set up to observe the document with the "childList" option set to true to observe changes in the child nodes of the document, and the "subtree" option set to true to observe changes in the entire subtree of the document.

  observeNewPlaceholders() {
    const observer = new MutationObserver((mutations) => {
      const placeholders = [];
      mutations.forEach(mutation => {
        mutation.addedNodes.forEach(node => {
          if (node.nodeType === node.ELEMENT_NODE) {
            placeholders.push(...node.querySelectorAll(`.${SELECTOR}`));
            if (node.classList.contains(SELECTOR)) {
              placeholders.push(node);
            }
          }
        });
      });
      placeholders.forEach(this.createButton.bind(this));
    });

    observer.observe(document, {childList: true, subtree: true});
  }
}

This code sets the onload event listener on the window object, which means that the new BladeAirdrop() constructor will be called when the page finishes loading. The BladeAirdrop class is responsible for creating and managing a button that triggers a popup or deep link, depending on the device type. It also observes the DOM for new elements with a specific class and creates a button for each new element found. Overall, the code is meant to integrate the Blade wallet into a web application for the purpose of purchasing non-fungible tokens (NFTs).

window.onload = () => {
  new BladeAirdrop();
};

Last updated