Click event on buttons not working in dropdown Shadow DOM

I’m making a component and ran into an issue that I can’t think and find any working solutions. When anything is in the dropdown their events aren’t being listented too, but when they go outside of the dropdown and into the header then they work. I’m using the Calcite Design System component library for Calcite Actions but I tried using divs/buttons to make sure that wasn’t the issue. I tried playing with pointer-events too but it didn’t work too.

When you open the codepen and click the buttons, you’ll notice that text is being added in the console, however when you click the test button that sends the buttons to the dropdown by changing their slot attribute, the buttons will no longer send stuff to the console anymore.

https://codepen.io/Keeron1/pen/GgpJbEO

class Floater extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({
      mode: "open"
    });

    // Wait for Calcite's <calcite-action> to be defined before injecting HTML
    customElements.whenDefined("calcite-action").then(() => {

      // Inject HTML template
      const tpl = document.getElementById("floater-template");
      const clone = tpl.content.cloneNode(true);
      this.shadowRoot.appendChild(clone);

      this._init();
    });
  }

  // Public API
  get position() {
    return this.getAttribute("position") || "top-left";
  }
  set position(v) {
    this.setAttribute("position", v);
  }
  get isDraggable() {
    if (!this.hasAttribute("draggable")) return true
    return this.getAttribute("draggable") === "true" ? true : false
  }
  set isDraggable(v) {
    this.setAttribute("draggable", v);
  }
  get isResizable() {
    return this.getAttribute("resizable") || "none"
  }
  set isResizable(v) {
    this.setAttribute("resizable", v);
  }
  get isHandleEnabled() {
    if (!this.hasAttribute("handle-enabled")) return false
    return this.getAttribute("handle-enabled") === "true" ? true : false
  }
  set isHandleEnabled(v) {
    this.setAttribute("handle-enabled", v);
  }

  _init() {
    // Elements
    this.floaterCont = this.shadowRoot.querySelector(".floater-container")
    this.floater = this.shadowRoot.querySelector(".floater");
    // Heading
    this.heading = this.shadowRoot.querySelector(".floater-heading")
    this.hTitle = this.shadowRoot.querySelector(".floater-heading-title");
    this.originalhTitle = this.hTitle?.outerHTML || null;
    this.headingEnd = this.shadowRoot.querySelector(".floater-heading-end")
    this.headingEndSlot = this.shadowRoot.querySelector('slot[name="heading-end"]');
    this.closeButton = this.shadowRoot.querySelector(".floater-close");
    this.originalCloseButton = this.closeButton?.outerHTML || null
    // Dropdown
    this.headingDropdown = this.shadowRoot.querySelector(".floater-heading-dropdown")
    this.originalHeadingDropdown = this.headingDropdown?.outerHTML || null
    this.headingDropdownAction = this.shadowRoot.querySelector(".floater-heading-dropdown-action")
    this.headingDropdownItems = this.shadowRoot.querySelector(".floater-heading-dropdown-items")
    this.headingEndDropdownSlot = this.shadowRoot.querySelector('slot[name="dropdown-heading-end"]')
    // Content
    this.floaterContent = this.shadowRoot.querySelector(".floater-content");
    this.contentSlot = this.shadowRoot.querySelector('slot:not([name])');

    this.test = this.shadowRoot.querySelector(".floater-test")

    // Attributes
    this.isDragging = false;
    this.dragOffsetX = 0; // Distance between cursor and left of the component
    this.dragOffsetY = 0; // Distance between cursor and top of the component
    this.lastWindowWH = {
      width: null,
      height: null
    }; // Window width and height

    requestAnimationFrame(() => {
      this._updateTitle();
      this._updateClose();
      this._setScale();
      this._updateDraggable();
      this._updateResizable();
      window.addEventListener("resize", this._onResize);
      this.closeButton?.addEventListener("click", this._close);

      this.test?.addEventListener("click", this._testClick)


      this._setStartingPosition();

      this.lastWindowWH.width = window.innerWidth;
      this.lastWindowWH.height = window.innerHeight;
    });
  }

  // Trigger on component created (not used since we need to wait for calcite action)
  connectedCallback() {}

  // Trigger on component delete
  disconnectedCallback() {
    window.removeEventListener("resize", this._onResize)
    if (this.isDraggable) this.heading.removeEventListener("pointerdown", this._onDown)
    if (this.closeButton) this.closeButton.removeEventListener("click", this._close)

    if (this.test) this.test.removeEventListener("click", this._testClick)
  }

  static get observedAttributes() {
    return ["title", "close-disabled", "scale", "draggable", "resizable", "handle-enabled"];
  }

  // Trigger when attribute changes
  attributeChangedCallback(name, oldValue, newValue) {
    if (name === "title") this._updateTitle();
    if (name === "close-disabled") this._updateClose();
    if (name === "scale") this._setScale();
    if (name === "draggable") this._updateDraggable();
    if (name === "resizable") this._updateResizable();
    if (name === "handle-enabled") this._updateHandle();
  }

  _testClick = () => {
    if (!this.isHandleEnabled) this.isHandleEnabled = true
    else this.isHandleEnabled = false
  }

  _updateHandle = () => {
    if (this.isHandleEnabled) this._sendHeadingItemsToDropdown();
    else this._sendDropdownItemsToHeading();
  }

  _sendHeadingItemsToDropdown = () => {
    if (!this.hasAttribute("close-disabled"))
      this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot)

    const endSlot = this.headingEndSlot.assignedElements()
    if (endSlot.length === 0) return;
    endSlot.forEach(element => {
      element.setAttribute("slot", "dropdown-heading-end");
    });
  }

  _sendDropdownItemsToHeading = () => {
    if (!this.hasAttribute("close-disabled")) this.headingEnd.append(this.closeButton)

    const endSlot = this.headingEndDropdownSlot.assignedElements()
    if (endSlot.length === 0) return;
    endSlot.forEach(element => {
      element.setAttribute("slot", "heading-end");
    });
  }

  _close = () => {
    console.log("closed btn clicked")
    this.dispatchEvent(new CustomEvent("floaterClose", {
      bubbles: true,
      composed: true
    }));

    if (!this.hasAttribute("manual-close"))
      this.remove();
  }

  _onResize = () => {
    // Could be improved by saving the floater's left value before it gets pushed, so
    //  that when the window could grow it could stick to that value

    const winWidth = window.innerWidth
    const winHeight = window.innerHeight

    // Calculate window delta
    const deltaX = winWidth - this.lastWindowWH.width;
    const deltaY = winHeight - this.lastWindowWH.height;

    // Get floater's current properties
    const floaterRect = this.floater.getBoundingClientRect();
    const currentTop = floaterRect.top
    const currentLeft = floaterRect.left
    const fw = this.floater.offsetWidth;
    const fh = this.floater.offsetHeight;

    // Remove inital position class
    this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center");

    let newTop = currentTop + deltaY;
    let newLeft = currentLeft + deltaX;

    // Horizontal nudge only on shrink AND if closer to the right edge
    if (deltaX < 0) {
      const distRight = winWidth - fw - currentLeft;
      if (distRight <= currentLeft) // if right is smaller than left or equal
        newLeft = currentLeft + deltaX;
      else newLeft = currentLeft
    }

    // Vertical nudge only on shrink AND if closer to the bottom edge
    if (deltaY < 0) {
      const distBottom = winHeight - fh - currentTop;
      if (distBottom <= currentTop)
        newTop = currentTop + deltaY;
      else newTop = currentTop
    }

    // Clamp absolute position to viewport
    newLeft = Math.max(0, Math.min(winWidth - fw, newLeft));
    newTop = Math.max(0, Math.min(winHeight - fh, newTop));

    // Convert back to container-relative
    const contRect = this.floaterCont.getBoundingClientRect();
    newLeft -= contRect.left;
    newTop -= contRect.top;

    // Apply
    this.floater.style.top = `${newTop}px`;
    this.floater.style.left = `${newLeft}px`;

    // Save updated values
    this.lastWindowWH.width = winWidth
    this.lastWindowWH.height = winHeight
  }

  // This function will either create or delete a component, depending on the attribute
  _handleElementLife = (atr, currentEl, originalEl, parent) => {
    // If empty then remove the element
    if (!atr.trim()) {
      if (currentEl) currentEl.remove()
      return null;
    }

    // Add the element
    if (!currentEl && originalEl) {
      const temp = document.createElement("div");
      temp.innerHTML = originalEl;
      currentEl = temp.firstElementChild;
      parent.insertBefore(currentEl, parent.firstChild);
    }

    if (currentEl) {
      currentEl.textContent = atr;
      return currentEl;
    }
  }

  _updateDraggable = () => {
    if (this.isDraggable) {
      this.heading.classList.add("draggable")
      this.heading.addEventListener("pointerdown", this._onDown)
    } else {
      this.heading.classList.remove("draggable")
      this.heading.removeEventListener("pointerdown", this._onDown)
    }
  }

  _updateResizable = () => {
    this.floaterContent.classList.remove("resize-horizontal", "resize-vertical", "resize-both");
    switch (this.isResizable) {
      case "horizontal":
        this.floaterContent.classList.add("resize-horizontal")
        break
      case "vertical":
        this.floaterContent.classList.add("resize-vertical")
        break
      case "both":
        this.floaterContent.classList.add("resize-both")
        break
    }
  }

  _updateTitle = () => {
    const titleAtr = this.getAttribute("title") || "";
    this.hTitle = this._handleElementLife(titleAtr, this.hTitle, this.originalhTitle, this.heading)
  }

  _updateClose = () => {
    const disabled = this.hasAttribute("close-disabled");
    if (disabled) {
      this.closeButton.removeEventListener("click", this._close);
      this.closeButton.remove();
      this.closeButton = null;
      return;
    }

    // Add the element
    if (!this.closeButton && this.originalCloseButton) {
      const temp = document.createElement("div");
      temp.innerHTML = this.originalCloseButton;
      this.closeButton = temp.firstElementChild;
      this.closeButton.addEventListener("click", this._close);
      this.closeButton.scale = this.getAttribute("scale") || "s"
      if (this.isHandleEnabled) this.headingDropdownItems.insertBefore(this.closeButton, this.headingEndDropdownSlot)
      else this.headingEnd.append(this.closeButton);
    }
  }

  _setStartingPosition = () => {
    switch (this.position) {
      case "center":
        this.floaterCont.classList.add("center")
        break;
      case "top-left":
        this.floaterCont.classList.add("top-left")
        break;
      case "top-right":
        this.floaterCont.classList.add("top-right")
        break;
      case "bottom-left":
        this.floaterCont.classList.add("bottom-left")
        break;
      case "bottom-right":
        this.floaterCont.classList.add("bottom-right")
        break;
    }
  }

  _setScale = () => {
    let scaleAtr = this.getAttribute("scale") || "s";
    if (this.closeButton) this.closeButton.scale = scaleAtr
    if (this.headingDropdownAction) this.headingDropdownAction.scale = scaleAtr
  }

  // Handle floater movement
  _onDown = (e) => {
    if (e.target.closest(".floater-heading-end")) return;
    if (this.headingEndSlot.assignedElements().some(el => el === e.target)) return

    e.preventDefault();
    this.isDragging = true;

    // capture the pointer so we don't lose events
    this.setPointerCapture(e.pointerId);

    // Compute position based on visual location
    const rect = this.floater.getBoundingClientRect();
    this.dragOffsetX = e.clientX - rect.left;
    this.dragOffsetY = e.clientY - rect.top;

    this.addEventListener("pointermove", this._onMove);
    this.addEventListener("pointerup", this._onUp);
  };

  _onMove = (e) => {
    if (!this.isDragging) return;

    // Remove previous
    this.floaterCont.classList.remove("top-left", "top-right", "bottom-left", "bottom-right", "center");

    // New positions
    let newTop = e.clientY - this.dragOffsetY;
    let newLeft = e.clientX - this.dragOffsetX;

    const vw = window.innerWidth;
    const vh = window.innerHeight;
    const fw = this.floater.offsetWidth;
    const fh = this.floater.offsetHeight;

    // Clamp to viewport
    newTop = Math.max(0, Math.min(vh - fh, newTop));
    newLeft = Math.max(0, Math.min(vw - fw, newLeft));

    // Remove container offset
    const contRect = this.floaterCont.getBoundingClientRect();
    newTop -= contRect.top;
    newLeft -= contRect.left;

    this.floater.style.top = `${newTop}px`;
    this.floater.style.left = `${newLeft}px`;
  }

  _onUp = (e) => {
    this.isDragging = false;
    this.releasePointerCapture(e.pointerId);
    this.removeEventListener("mousemove", this._onMove);
    this.removeEventListener("mouseup", this._onUp);
  }
}

if (!customElements.get("custom-floater"))
  customElements.define("custom-floater", Floater);
<script type="module" src="https://cdn.jsdelivr.net/npm/@esri/calcite-components@latest/dist/calcite/calcite.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@esri/calcite-components@latest/dist/calcite/calcite.css" />

<template id="floater-template">
  <style>
    :host {
      --floater-offset-x: 0px;
      --floater-offset-y: 0px;
      --floater-min-size-x: auto;
      --floater-max-size-x: none;
      --floater-min-size-y: auto;
      --floater-max-size-y: none;
      --floater-content-padding: var(--calcite-spacing-md);
      --floater-opacity: 1;
      --floater-content-opacity: 1;
    }

    .floater-container {
      position: fixed;
      z-index: var(--calcite-z-index-modal);
      top: var(--floater-offset-y, 0);
      left: var(--floater-offset-x, 0);
      pointer-events: none;
    }

    /* Starting positions */
    .floater-container.center {
      inset: 0;

      >.floater {
        top: calc(50% + var(--floater-offset-y, 0px));
        left: calc(50% + var(--floater-offset-x, 0px));
        transform: translate(-50%, -50%);
      }
    }

    .floater-container.top-right {
      top: var(--floater-offset-y, 0);
      right: var(--floater-offset-x, 0);
      left: auto;

      >.floater {
        top: var(--calcite-spacing-md);
        right: var(--calcite-spacing-md);
      }
    }

    .floater-container.top-left>.floater {
      top: var(--calcite-spacing-md);
      left: var(--calcite-spacing-md);
    }

    .floater-container.bottom-left {
      bottom: var(--floater-offset-y, 0);
      left: var(--floater-offset-x, 0);
      top: auto;

      >.floater {
        bottom: var(--calcite-spacing-md);
        left: var(--calcite-spacing-md);
      }
    }

    .floater-container.bottom-right {
      bottom: var(--floater-offset-y, 0);
      right: var(--floater-offset-x, 0);
      top: auto;
      left: auto;

      >.floater {
        bottom: var(--calcite-spacing-md);
        right: var(--calcite-spacing-md);
      }
    }

    /* End of starting positions */
    .floater {
      display: flex;
      flex-direction: column;
      max-height: 100vh;
      position: absolute;
      pointer-events: all;
      box-sizing: border-box;
      border-radius: 0.25rem;
      background-color: var(--floater-background-color);
      font-family: var(--calcite-sans-family);
      box-shadow: var(--calcite-shadow-sm);
      opacity: var(--floater-opacity);
    }

    .floater-heading {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      flex: 0 0 auto;
      border-bottom: 1px solid var(--calcite-color-border-3);
      user-select: none;
    }

    .floater-heading.draggable {
      cursor: move;
    }

    .floater-heading-title {
      padding: var(--calcite-spacing-xs) var(--calcite-spacing-md-plus);
      font-weight: var(--calcite-font-weight-medium);
      font-size: var(--calcite-font-size-0);
      color: var(--calcite-color-text-1);
    }

    .floater-heading-end {
      margin-left: auto;
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
    }

    /* Dropdown menu */
    .floater-heading-dropdown-action {
      height: 100%;
    }

    .floater-heading-dropdown-items {
      pointer-events: all;
      visibility: hidden;
      position: absolute;
      top: auto;
      right: 0;
      background: var(--calcite-color-background);
      padding: 0.5rem;
      box-shadow: 0 1px 6px rgba(0, 0, 0, 0.2);
      z-index: 100;
      border-radius: 4px;
      cursor: default;
      overflow-y: auto;
      height: 100%;
      max-height: 300px;
      /* Firefox */
      scrollbar-width: thin;
      scrollbar-color: var(--scrollbar-color);
    }

    slot[name="dropdown-heading-end"] {
      display: flex;
      flex-direction: column-reverse;
    }

    /* Chrome, Edge, Safari */
    @supports selector(::-webkit-scrollbar) {
      .floater-heading-dropdown-items {
        /* Override rules */
        scrollbar-width: auto;
        scrollbar-color: auto;
      }

      .floater-heading-dropdown-items::-webkit-scrollbar {
        width: var(--scrollbar-width);
      }

      .floater-heading-dropdown-items::-webkit-scrollbar-thumb {
        background: var(--scrollbar-background-color);
        border-radius: var(--scrollbar-border-radius);
      }
    }

    .floater-heading-dropdown:hover .floater-heading-dropdown-items {
      visibility: visible;
    }

    /* End of Dropdown menu */
    /* .floater-close{ */
    /* height: 100%; */
    /* width: auto; */
    /* background-color: transparent; */
    /* border: 0; */
    /* cursor: pointer; */
    /* } */
    .floater-content {
      box-sizing: border-box;
      padding: var(--floater-content-padding);
      overflow: auto;
      flex: 1 1 auto;
      max-width: var(--floater-max-size-x);
      min-width: var(--floater-min-size-x);
      max-height: var(--floater-max-size-y);
      min-height: var(--floater-min-size-y);
      opacity: var(--floater-content-opacity);
    }

    .floater-content.resize-horizontal {
      resize: horizontal;
    }

    .floater-content.resize-vertical {
      resize: vertical;
    }

    .floater-content.resize-both {
      resize: both;
    }

    /* Hide the resize handle  */
    .floater-content.resize-horizontal::-webkit-resizer,
    .floater-content.resize-vertical::-webkit-resizer,
    .floater-content.resize-both::-webkit-resizer {
      display: none;
    }

    .floater-content.resize-horizontal::-moz-resizer,
    .floater-content.resize-vertical::-moz-resizer,
    .floater-content.resize-both::-moz-resizer {
      display: none;
    }
  </style>
  <div class="floater-container">
    <div class="floater">
      <div class="floater-heading">
        <div class="floater-heading-title"></div>
        <div class="floater-heading-end">
          <slot name="heading-end"></slot>
          <calcite-action class="floater-close" aria-label="Close" title="Close" icon="x"></calcite-action>

          <button class="floater-test">test</button>
        </div>

        <div class="floater-heading-dropdown">
          <calcite-action class="floater-heading-dropdown-action" aria-label="Actions Dropdown" title="Actions Dropdown" icon="handle-vertical"></calcite-action>
          <div class="floater-heading-dropdown-items">
            <slot name="dropdown-heading-end"></slot>
          </div>
        </div>

      </div>
      <div class="floater-content">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<custom-floater title="Draw" position="top-right" scale="s" manual-close style={{
                    "--floater-offset-x": offsetX + "rem",
                    "--floater-offset-y": offsetY + "rem",
                    "--floater-min-size-x": "379px"}}>

  <calcite-action scale="s" slot="heading-end" title="Change Layout" text="Change Layout" icon="layout-horizontal" onclick="console.log('clicked change layout')"></calcite-action>
  <calcite-action scale="s" slot="heading-end" title="Dock widget" text="Dock" icon="right-edge" onclick="console.log('clicked dock')"></calcite-action>
  <div className="custom-sketch" id="draw-container">
    <div>This is some placeholder content</div>
  </div>
</custom-floater>

HOW To Fix header and content using html css?

`

Fixed and Scrolling Containers

body {
margin: 0;
font-family: Arial, sans-serif;
}`

`.container {
padding: 20px;
box-sizing: border-box;
}

.fixed {
position: fixed;
top: 0;
width: 100%;
background-color: #f4f4f4;
border-bottom: 1px solid #ccc;
z-index: 1000;
}

.scrolling {
margin-top: 100px; /* Adjust this value based on the height of the fixed container /
background-color: #eaeaea;
padding: 20px;
height: 200vh; /
Makes the container scrollable by extending its height */
}
`

`

Fixed Container

This container stays fixed at the top when scrolling.

`

Scrolling Container

This container scrolls with the rest of the page.

Content…

More content…

Even more content…

Keep scrolling…

Almost there…

You’ve reached the end!

Hi here i am fixing header in every print page but my content is large means my content is hiding behind header after page breaks . how to fix it . pls help

HTML canvas not drawing BMP image

I’m trying to crop a BMP image and draw it inside an HTML canvas then draw some lines on it. The problem is that the canvas does not draw the image but it draws the lines.

The image:

BMP

Let’s take this simplified code where I draw the top left 1000x1000px of the image inside the canvas (so it should be completely black):

display.html:

<!DOCTYPE html>
<head>
    <title>Test</title>
    <script src="display.js" type="text/javascript"></script>
</head>

<body>
    <div>
        <input type="button" id="submit_traj" value="OK" onclick="displayTraj();"/>
        <br>
    </div>
    <div>
        <canvas id="myCanvas" style="border: 1px solid black;background-repeat: no-repeat;">Canvas is not supported.</canvas>
    </div>
</body>

</html>

display.js:

function displayTraj(){
    var map = new Image();
    map.src = "../db/France11.bmp";
    map.onload = draw;
}

let draw = ( e ) => {
    var canvas = document.getElementById('myCanvas');
    canvas.width = 1000;
    canvas.height = 1000;
    canvas.getContext('2d').drawImage(e.target,
        0, 0,
        1000, 1000, 
        0, 0, 
        1000, 1000
    );
}

The resulting page:
enter image description here

We see the canvas is still blank, yet in the “Network” section I see the BMP has been downloaded and in the “Elements” section the canvas has a size of 1000x1000px so the draw function has been executed.

I tried :

  • different sizes => doesn’t work
  • small random PNG images => works
  • display the whole BMP inside an “img” element => works (but the page becomes immense)

Every solution on the web talks about waiting for the image to be loaded by the server, which is done by the “map.onload” line (I also tried with window.addEventListener).

I’m on Firefox 140.0.4 on Ubuntu.

JS: navigator.mediaDevices.getUserMedia throws NotReadableError: Could not start video source

We have problems in our product that we are getting NotReadableError.

First i need to describe our situation. We have windows machine with 2 connected cameras on this machine where we are running our wpf application.

One camera is scanner which is embedded into our application over dll. Second camera we are using in webview(For web age check).

The problem is in webview that sometimes webview was taking camera from scanner(Wrong one). For this situation we added code.

const permissionStream = await navigator.mediaDevices.getUserMedia({ video: true });
    permissionStream.getTracks().forEach((t) => t.stop());

console.log('Permissions checked');

    // Step 2: Now enumerate devices
    const devices = await navigator.mediaDevices.enumerateDevices();
    const cameras = devices.filter(
      (d) => d.kind === 'videoinput' && !d.label.toLowerCase().includes('scanner')
    );

Command navigator.mediaDevices.getUserMedia({ video: true }) is working until our sdk for scanner still using scanner camera.

Is there a option to get camera list where we ignore specific camera from the list ? If we reset connection with scanner camera then this call start working.
Thanks

In async-await function lines execute in arbitrary order and amount ot times?

I have the following code:

async function getAllFiles(res, path) {

  const files = Fs.readdirSync(path);
  const users = [];
  
  for (const file of files) {

    const username = await getFileUser(path + file);

    console.log("Start.");
    console.log("username:");
    console.log(username);
    console.log("End.");

    users.push(username);
  }

  const response = [files, users];

  res.status(200).send(response);
}

In this we get the filenames located at a given path using the FileSystem library, and then in a for loop the getFileUser() function looks for the username paired to every filename in a database. Then at the end it returns both the array of files and usernames.

The problem is despite having an await put in front of the async function using a database operation, this function containing it behaves in a completely unpredictable way. A lot of the time it runs in the intended order and the console.logs in it will show the correct data in the correct order when, for example, checking a path containing nothing but a single file from the admin user:

Start.
username:
admin
End.

However, every now and then it will show the following logs:

admin
Start.
username:
undefined
End.

Which I have no idea, how is possible. It seems like at first it runs nothing in the entire loop, but the async function and the lines containing the variable that gets assigned with it’s value?
Then after that it seems to deassign the variable values, then it runs every line of code from the start of the loop except for the async function call??
I am actually just baffled by the functioning of this simple async code. What am I missing?

Google Auth with Google Identity Services in the front end deprecation confusion

I’m having a hard time understanding what is and what is not deprecated.

In the The Google API client, if you click on the “auth” guide, you end up with a message “gapi.auth2 has been deprecated and replaced with Google Identity Services”. So then I click on the Google Identity Services link and click on Migrate to Google Identity Services which shows a warning that The Google Sign-In JavaScript platform library for Web is deprecated. So I’m a bit confused here.

I have a Single Page Application. I tried to implement the server based workflow using AJAX but the Google Auth URL will not load in an iFrame. Here’s my back end code where I obtain a auth URL with PHP.

    $client = new Google_Client();
    $client->setClientId('redacted');
    $client->setClientSecret('redacted');
    $client->setRedirectUri('redacted');
    $client->addScope('https://www.googleapis.com/auth/business.manage');
    $client->setAccessType('offline');        
    $url = $client->createAuthUrl();

The returned URL only works in a new Window, does not work in an iFrame despite my domain authorized in Google console. How can I implement this workflow from within my SPA app without leaving the app? There’s got to be a workflow in JavaScript? Is there a quickstart somewhere to do that workflow in the front end instead ? I can’t find anything.

Dragging files into HTML element overrides my drop-element’s cursor

How can I stop a drag operation from not honouring my CSS cursor when hovering over my element that will take the drop? With my requirement the drag starts off the web page in the file system or email.

This question has been asked many times before but this one from 8 years ago has the “answer” voted up many times but I’m hoping there’s been a solution in recent years.

Here is a SSCCE that shows the target-svg appearing over my DIV1 when hovering but if it is dragging then I just get the pointer with [+Copy]

<!DOCTYPE HTML>
<html>
<head>
<style>
body {
}

#div1:active {
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 300 300'><path fill='%23FFF' stroke='%23E50024' stroke-width='50' d='M150 25 a125 125 0 1 0 2 0z m 2 100 a25 25 0 1 1-2 0z'/></svg>"), auto;
}
#div1 {
  float: left;
  width: 100px;
  height:100px;
  padding: 10px;
  border: 1px solid black;
  align: center;
  vertical-align: middle;
  background-color: lightblue;
  cursor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 300 300'><path fill='%23FFF' stroke='%23E50024' stroke-width='50' d='M150 25 a125 125 0 1 0 2 0z m 2 100 a25 25 0 1 1-2 0z'/></svg>"), auto;
}

div p {
    color: red;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", (e) => {
        const dropZone = document.getElementById("div1")
        dropZone.addEventListener("dragenter", (e) => {
                console.log("In enter " + dropZone)
                e.preventDefault();
                dropZone.style.backgroundColor = "#FFF"
            })
        dropZone.addEventListener("dragover", (e) => {
                console.log("In over " + dropZone)
                e.preventDefault();
            })
        dropZone.addEventListener("dragleave", (e) => {
                console.log("In leave")     
                e.preventDefault();
                dropZone.style.backgroundColor = ""
            })
        dropZone.addEventListener("drop", catchFiles)
    })

function catchFiles(e) {
  e.preventDefault();
  document.getElementById("div1").style.backgroundColor = ""    
  console.log("File(s) dropped");
  
  let fileHolder = new FormData()
  let fileCount = 0

  if (e.dataTransfer.items) {
    // Use DataTransferItemList interface to access the file(s)
    [...e.dataTransfer.items].forEach((item, i) => {
      // If dropped items aren't files, reject them
      if (item.kind === "file") {
        const file = item.getAsFile()
        console.log(`… file[${i}].name = ${file.name}`)
        fileHolder.append("file"+i, file, file.name)
        fileCount++
      }
    });
  } else {
    // Use DataTransfer interface to access the file(s)
    [...e.dataTransfer.files].forEach((file, i) => {
      console.log(`… file[${i}].name = ${file.name}`);
      fileHolder.append("file"+i, file, file.name)
      fileCount++
    });
  }
  if (fileCount == 0) {
    alert("Zero files received")
    return
  }
  alert("got " + fileCount + " files")
  return
  const abortControl = new AbortController();
  const signal = abortControl.signal;
  const fetchOpts = {signal:signal, method:'POST', body: fileHolder, cache:"no-cache"};
  const response = fetch("https://localhost:7196/data/uploadfiles", fetchOpts).catch(
    err => {
        console.log("Upload failed: " + err.message);
        return
  });
  if (!signal.aborted) {
    alert("Cancelled")
    return
  }

}

</script>
</head>
<body>

<h1>File Drop Upload Example</h1>

<p>Drag your file(s) into the Drop Zone</p>

<div id="div1">
    <p>File Drop Zone</p>
</div>  
</body>
</html>

How to fix the error: ERROR Warning: [runtime not ready]: Error: [@RNC/AsyncStorage]: NativeModule: AsyncStorage is null

When I import firebase version 8.10.0 like this:

import firebase from 'firebase';
import 'firebase/firestore'; 

I always get an error saying this:

ERROR [runtime not ready]: Invariant Violation: AsyncStorage has been removed from react-native core. It can now be installed and imported from '@react-native-async-storage/async-storage' instead of 'react-native'. See https://github.com/react-native-async-storage/async-storage, js engine: hermes

When I import AsyncStorage like this:

import AsyncStorage from '@react-native-async-storage/async-storage';

import firebase from 'firebase';
import 'firebase/firestore'; 

I get this error:

ERROR  Warning: [runtime not ready]: Error: [@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.

Does anybody know how to fix this error? Your help would be greatly appreciated.

Angular TiledWebMap with Providers

I am trying to use OpenStreetMap with Highcharts to create a TiledWebMap (TWM) in Angular20 standalone project but the TiledWebMap is loading but not importing.

ENVIROMENT

  1. Angular 20.0.3
  2. Highcharts 12.3.0
  3. Highcharts-Angular 5.1.0

SETUP

There appears to be two ways to setup TiledWebMaps, but neither of them works – method 1 doesn’t compile and method two doesn’t register TiledWebMaps:

1)

The first is described as manual mode in the component, for example:

map-test.ts

import HC_map from 'highcharts/modules/map';
import TiledWebMap from 'highcharts/modules/tiledwebmap';

HC_map(Highcharts);
TiledWebMap(Highcharts);

If I attempt to load TiledWebMap in this way I get error:

✘ [ERROR] TS2349: This expression is not callable.
Type 'typeof import("/home/frontend/node_modules/highcharts/highcharts")' has no call signatures. [plugin angular-compiler]

src/app/components/map-test/map-test.ts:7:0:
  7 │ TiledWebMap(Highcharts);

2)

The second method which the rest of my project uses is ProvidePatrialHighcharts helper.

app.config.ts

import { ApplicationConfig, provideBrowserGlobalErrorListeners, provideZoneChangeDetection, importProvidersFrom } from '@angular/core';
import { provideRouter } from '@angular/router';
import {provideHighcharts, providePartialHighcharts} from 'highcharts-angular'
import { provideHttpClient } from '@angular/common/http';

import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBrowserGlobalErrorListeners(),
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideHighcharts(),
    provideHttpClient(),
    providePartialHighcharts({ modules: () => [
      import('highcharts/esm/modules/map'),
      import('highcharts/esm/modules/tiledwebmap')
      ],}),
  ]
};

PROBLEM

When I add some commands to console log the available registered types in my component with this :

  ngOnInit(): void {
    const availableSeries = Object.keys((Highcharts as any).seriesTypes || {});
    console.log('Available Highcharts series types:', availableSeries);

I get:

[
“line”,
“area”,
“spline”,
“areaspline”,
“column”,
“bar”,
“scatter”,
“pie”,
“map”,
“mapline”,
“mappoint”,
“bubble”,
“mapbubble”,
“heatmap”
]

Notice TiledWebMap is missing.

QUESTION

Ideally I would like to be able to get both methods to work.

What am I doing wrong in both approaches – bonus points if you can show a stackblitz with method 2 and OpenStreetMaps working.

I am newish to Angular – so if this question is missing information or badly formed please let me know and I will update it.

BSC nursing Cap registration [closed]

Error or server problem in bsc nursing Cap admission

I try to register to bsc nursing for cap round and I accept the link will open but the link was not opening still to fill up the documents for registration so what can I do for this problem??

EventSource is not a constructor 3 [closed]

The below code supose to connect a server and get adrverstising data but not working for some reason returning this error “ERROR![object Event]”, and I’m not getting to Open connection stage ?

import { EventSource } from 'eventsource';
const es = new EventSource(
  'https://ac1.mqtt.sx3ac.com/api/gap/nodes?filter_mac=DC:0D:30:C6:19:9D&filter_rssi=-75&mac=CC:1B:E0:E4:49:28&active=1&event=1&chip=1&access_token=437099c4a30895840ca7439d38e974d7c38338e72a5190abc7e47b14bcb4094d',
);
es.addEventListener('open', (e) => {
  console.log('The connection has been established.');
});
es.onopen = (e) => {
  console.log('The connection has been established.');
};
checkTime();
var startTime = new Date().getTime();
es.onmessage = function (e) {
  var endTime = new Date().getTime();
  console.log(e.data);
};
es.onerror = function (e) {
  console.log('ERROR!' + e);
  es.close();
  return;
};
function checkTime() {
  setTimeout(function () {
    es.close();
    return;
  }, 60000);
}

The url works fine in a browser as below:

enter image description here

so not sure what could be wrong?

Thanks

How to access authenticated user principal in SessionConnectedEvent when using a ChannelInterceptor for JWT authentication?

I’m using a custom WebSocketChannelInterceptor (implements ChannelInterceptor) to handle authentication during the CONNECT STOMP command. The interceptor extracts and validates a JWT token from the Authorization header like this:

@Override
public Message<?> preSend(@NonNull Message<?> message, @NonNull MessageChannel channel) {
    var accessor = StompHeaderAccessor.wrap(message);
    StompCommand command = accessor.getCommand();
    if (command == null) {
        return null;
    }

    return switch (command) {
        case CONNECT -> handleConnect(message, accessor);
        case SEND -> handleSend(message, accessor);
        default -> message;
    };
}

private Message<?> handleConnect(Message<?> message, StompHeaderAccessor accessor) {
    String authorizationHeader = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION);
    if (authorizationHeader == null || !authorizationHeader.startsWith(JwtService.BEARER_PREFIX)) {
        throw new MessageHandlingException(message, "Missing or invalid authorization header");
    }

    String token = authorizationHeader.substring(JwtService.BEARER_PREFIX.length()).trim();
    try {
        var jwtAuthToken = new JwtAuthenticationToken(token);
        authManager.authenticate(jwtAuthToken);
        return message;
    } catch (BadCredentialsException e) {
        throw new MessageHandlingException(message, e.getMessage());
    } catch (RuntimeException e) {
        throw new MessageHandlingException(message, "Internal server error");
    }
}

My question is: How can I access the authenticated user’s Principal inside the SessionConnectedEvent handler?

@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketEventListener {

    @EventListener
    public void sessionConnectedEvent(SessionConnectedEvent event) {
        // How to get Principal here?
    }
}

I’m not interested in SessionConnectEvent — I specifically want to get the user from SessionConnectedEvent after the handshake and connection are completed.

Thanks in advance!

I’ve tried many different approaches, but none of them worked for my case. The token is sent during the initial WebSocket connection using STOMP headers like this:

const client = new Client({
    brokerURL: 'ws://localhost:8080/ws',
    connectHeaders: {
        "Authorization": `Bearer ${accessToken}`,
    },
    onStompError: await onStompError,
    onConnect: () => {
        console.log("Successfully connected");
    },
    debug: console.debug
});

Is there a way to disable mobile device AGC when designing an in-browser experiment in javascript (jsPsych)?

I am currently designing an experiment to couple with REPP (https://gitlab.com/computational-audition/repp) for use in a web-based experiment, requiring mobile users’ microphones to be enabled during audio playback. The main issue I’m faced with currently is that when audio recording is enabled in mobile browsers, some sort of OS-level AGC decreases the volume of audio playback. I currently include the following in my code:

        navigator.mediaDevices.getUserMedia({
            audio: {
              echoCancellation: false,
              noiseSuppression: false,
              autoGainControl: false,
              channelCount: 1,
              sampleRate: 44100
            }

…but the volume is still far too quiet for mobile users. Is there an effective way of keeping audio playback volume consistent for microphone-enabled mobile users? Sorry if my question is difficult to follow, I am quite new at programming in general. Thanks in advance.

I tried disabling autoGainControl in getUserMedia, and manually increasing playback volume using gainNode, neither of which worked

n8n: Reddit Node Throws

I’m building a workflow in n8n (v1.100.1, Cloud) that reads a list of Reddit posts from a Google Sheet and posts a unique comment to each one in a loop.

The Goal:

The workflow should iterate through each row in my Google Sheet, post the specified comment to the corresponding Post ID, wait for a set period, and then move on to the next row until the list is complete.
The Problem:
The workflow runs successfully for the first item only. I can go to Reddit and see that the comment has been successfully posted. However, the Create a comment in a post node in n8n then throws the following error, which breaks the loop and prevents it from processing the rest of the items:

TypeError: Cannot read properties of undefined (reading 'things') 

Because the node errors out (even after a successful action), the workflow doesn’t proceed to the Wait node or the next loop iteration.

What I’ve Tried:

Error Handling: Setting the node’s “On Error” setting to “Continue” allows the loop to finish, but it feels like a workaround, not a solution to the underlying error.

Post ID Format: I have confirmed I am using the correct “Fullname” for the Post ID (e.g., 2919299, not the URL or short ID.

Permissions: My Reddit API credentials have the submit, read, and identity scopes. I can post manually from the same account without issue.

My Question:

  • Why would the n8n Reddit node successfully execute its primary function (posting the comment) but then fail to process the successful response from Reddit’s API?
  • What is the correct way to structure this final part of the workflow to ensure the loop completes reliably for all items?
  • What node should I add? What should I set it to?

Workflow & Node Configuration
Here is a simplified overview of the final section of my workflow and the JSON for the relevant nodes.
Visual Overview:

1.). Loop Over Items (SplitInBatches Node)
This node is configured to process each row from the Google Sheet one at a time.
JSON:


{
 "parameters": {
   "batchSize": 1,
   "options": {
     "reset": false
   }
 },
 "name": "Loop Over Items",
 "type": "n8n-nodes-base.splitInBatches",
 "typeVersion": 3,
 "position": [1540, 1960]
}

  1. Get row(s) in sheet (Google Sheets Node)
    This node fetches the list of posts and comments to be made.
    Output Data Example (one item):
    JSON:
[
  {
    "row_number": 6,
    "Comment": "EXAMPLE COMMENT",
    "Post Text": "EXAMPLE POST TEXT",
    "Subreddit": "n8n",
    "Title": "EXAMPLE TITLE",
    "Upvotes": 45,
    "URL": "https://www.reddit.com",
    "ID": "EXAMPLE ID"
  }

  1. Create a comment in a post (Reddit Node) – THIS IS THE FAILING NODE
    This node takes the data for a single row from the loop and attempts to post the comment.
    Node JSON:

{
  "parameters": {
    "resource": "postComment",
    "postId": "={{ $('Loop Over Items').item.json.ID }}",
    "commentText": "={{ $('Loop Over Items').item.json.Comment }}"
  },
  "name": "Create a comment in a post",
  "type": "n8n-nodes-base.reddit",
  "typeVersion": 1,
  "position": [2100, 1980],
  "credentials": {
    "redditlink": {
      "id": "EXAMPLE ID",
      "name": "Reddit account"
    }
  }

  1. Wait Node
    This node is intended to pause the workflow for a set amount of time between each comment to avoid spam filters.
{
  "parameters": {
    "amount": 30,
    "unit": "seconds"
  },
  "name": "Wait",
  "type": "n8n-nodes-base.wait",
  "typeVersion": 1.1,
  "position": [2300, 1980]
}

Error Details
Here is the full error returned by the “Create a comment in a post” node after it has already posted the comment successfully.

{
  "errorMessage": "Cannot read properties of undefined (reading 'things')",
 }

Any insight into why this might be happening or a more robust way to structure this loop would be greatly appreciated. Thank you!

How to correctly work with string like “1.” in parseFloat, because it is user typing input

I have wrong with it how can I fix the parsing string such as “0., 1.”.
It is user typing input but how to handle it correctly, Here is my simple code and return number in onChange

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value

    if (!value) return onChange?.(null)

    if (isValidNumberInput(value)) {
      const parsed = parseFloat(value)

      if (!isNaN(parsed)) onChange?.(value)
    }
  }