Java Script function to match a input in array of JSON

looking for your help on Java script function where I want to ass 2 element as input and expect a true or false as response back.

Scenario – I will be passing below 2 input in Java script and need to check if value of variable varTaskID i.e. TaskID3 is matching with ExternalTaskId which is in JSON array structure.. if available return as true as simple say false. SO comparing varTaskID with ExternalTaskId element in complete array file.

Input Id – a variable as varTaskID which will have only 1 value like TaskID3
Input 2(A JSON structure where we have a repetitive structure)

{
  "items" : [ 
  {
    "ExternalParentTaskId" : "12345",
    "ExternalTaskId" : "TaskID1"
  }, 
  {
    "ExternalParentTaskId" : "11111",
    "ExternalTaskId" : "TaskID2"
  }, 
  {
    "ExternalParentTaskId" : "3456",
    "ExternalTaskId" : "TaskID3"
  }, 
  {
    "ExternalParentTaskId" : "423423",
    "ExternalTaskId" : "TaskID3"
  }, 
  {
    "ExternalParentTaskId" : "55666",
    "ExternalTaskId" : "TaskID3"
  }
  ]
}

Restore scroll position in React after navigating back from a dynamic list

I have a car listing page built with React and React Router. Users can scroll down a list of cars, click a car to view its detail page,
and then hit the browser back button.

The problem: when returning, the page always scrolls to the top instead of the position where the user left off.

The list is dynamic: it loads asynchronously from Firestore, shuffles the cars randomly, and supports pagination and filters. Because of this, scroll restoration happens before the list is fully rendered, so the scroll jumps to the top instead of staying at the previous position.

Using window.history.scrollRestoration = “manual”.

A custom SmartScroll component that saves/restores scroll positions.

React Router v6 ScrollRestoration component.

Expected behavior:
When navigating back from a car detail page, the scroll should restore to the exact position the user left off, even with async data loading, shuffling, and pagination.

// src/main.jsx
import React from "react";
import "./index.css";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";
import SmartScroll from './utils/SmartScroll';
import { CartProvider } from "./context/CartContext";
import { WishlistProvider } from "./context/WishlistContext";
import { AuthProvider } from "./context/AuthContext";
import { SearchProvider } from "./context/SearchContext";
import { FilterProvider } from "./context/FilterContext";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
  <BrowserRouter>
    <SmartScroll />
    <AuthProvider>
      <CartProvider>
        <WishlistProvider>
          <SearchProvider>
            <FilterProvider>
             <App />
            </FilterProvider>
          </SearchProvider>
        </WishlistProvider>
      </CartProvider>
    </AuthProvider>
  </BrowserRouter>
  </React.StrictMode>
);

// src/App/jsx
import { Routes, Route } from "react-router-dom";
import Home from "./pages/Home/Home";
import Cart from "./pages/Cart";
import CarDetails from './pages/CarDetails';
import Navbar from "./components/layout/Navbar";
import Footer from "./components/layout/Footer"; 
import Checkout from "./pages/Checkout"; 
import Wishlist from "./pages/Wishlist";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Success from "./pages/Success";
import About from "./pages/About";
import MyOrders from "./pages/MyOrders";
import ReceiptPage from "./pages/ReceiptPage";
import Inventory from "./pages/Inventory";
import SellYourCar from "./pages/Sellcar";
import Profile from "./pages/Profile";
import EditProfile from "./pages/EditProfile";
import SuccessSold from "./pages/SuccessSold";
import Sold from "./pages/Sold";
import { SoldProvider } from "./context/SoldContext";

function App() {
  return (
    <>
      <Navbar />
      <SoldProvider>
        <Routes>
          {/* Public Routes */}
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/cars" element={<Inventory />} />
          <Route path="/cars/:userId/:carId" element={<CarDetails />} /> 
          <Route path="/login" element={<Login />} />
          <Route path="/signup" element={<Signup />} />
          <Route path="/SuccessSold" element={<SuccessSold />} />

          {/* Protected Routes */}
          <Route path="/profile" element={<Profile />} />
          <Route path="/profile/edit" element={<EditProfile />} />
          <Route path="/cart" element={<Cart />} />
          <Route path="/orders" element={<MyOrders />}/>
          <Route path="/checkout" element={<Checkout />}/>
          <Route path="/edit-car/:userId/:carId" element={<SellYourCar />}/>
          <Route path="/sellcar" element={<SellYourCar />}/>
          <Route path="/sold" element={<Sold />}/>
          <Route path="/wishlist" element={<Wishlist />}/>
          <Route path="/receipt" element={<ReceiptPage />}/>
          <Route path="/success" element={<Success />}/>
        </Routes>
      </SoldProvider>
      <Footer />
    </>
  );
}

export default App;

import { useLayoutEffect, useRef } from "react";
import { useLocation, useNavigationType } from "react-router-dom";

function getScroller() {
  return document.scrollingElement || document.documentElement;
}

export default function SmartScroll({ ready = true }) {
  const location = useLocation();
  const navType = useNavigationType();
  const restored = useRef(false);

  useLayoutEffect(() => {
    if ("scrollRestoration" in window.history) {
      window.history.scrollRestoration = "manual";
    }

    const scroller = getScroller();
    const key = `scroll:${location.key}`;

    const restoreScroll = () => {
      const saved = sessionStorage.getItem(key);
      if (saved) {
        const { x = 0, y = 0 } = JSON.parse(saved);
        scroller.scrollTo({ left: x, top: y, behavior: "auto" });
        restored.current = true;
      }
    };

    if (navType === "POP") {
      // Wait until content is ready
      if (ready) {
        restoreScroll();
      } else {
        const interval = setInterval(() => {
          if (ready && !restored.current) {
            restoreScroll();
            clearInterval(interval);
          }
        }, 50);
        return () => clearInterval(interval);
      }
    } else {
      // New page → scroll to top
      scroller.scrollTo({ left: 0, top: 0, behavior: "auto" });
    }

    // Save scroll on unmount
    return () => {
      const s = getScroller();
      sessionStorage.setItem(key, JSON.stringify({ x: s.scrollLeft, y: s.scrollTop }));
    };
  }, [location, navType, ready]);

  return null;
}

import { useEffect, useState, useLayoutEffect, useRef } from "react";
import { collectionGroup, getDocs, query } from "firebase/firestore";
import { db } from "../../firebase/firebase";
import CarCard from "../../components/cars/CarCard";
import Cambodia from "../../assets/images/logo/Cambodia.png";

export default function CarList({ filters = {}, sortOption }) {
  const [cars, setCars] = useState([]);
  const [carsToDisplay, setCarsToDisplay] = useState(12);
  const containerRef = useRef(null); // New: ref for car grid container

  // Fetch cars
  useEffect(() => {
    const fetchCars = async () => {
      const savedCars = sessionStorage.getItem("carsList");
      if (savedCars) {
        setCars(JSON.parse(savedCars));
        return;
      }

      const carsCollection = collectionGroup(db, "cars");
      const q = query(carsCollection);
      const querySnapshot = await getDocs(q);
      const fetchedCars = querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
      const shuffledCars = fetchedCars.sort(() => Math.random() - 0.5);
      setCars(shuffledCars);
      sessionStorage.setItem("carsList", JSON.stringify(shuffledCars));
    };

    fetchCars();
  }, []);

  const filterAndSortCars = () => {
    let result = [...cars];
    const { brand, condition, location, price, search, type, year } = filters;
    result = result.filter((car) => {
      const matchesSearch =
        !search || `${car.name} ${car.model}`.toLowerCase().includes(search.toLowerCase());
      const matchesLocation = location === "All locations" || car.location === location;
      const matchesBrand = brand === "All brands" || car.name === brand;
      const matchesType = type === "All types" || car.type === type;
      const matchesCondition = condition === "All conditions" || car.condition === condition;
      const matchesYear = year === "All years" || car.year === year;
      const matchesPrice =
        price === "No max" || (price && car.price <= parseInt(price.replace(/D/g, ""), 10));
      return (
        matchesSearch &&
        matchesLocation &&
        matchesBrand &&
        matchesType &&
        matchesCondition &&
        matchesYear &&
        matchesPrice
      );
    });

    switch (sortOption) {
      case "price-asc":
        return result.sort((a, b) => a.price - b.price);
      case "price-desc":
        return result.sort((a, b) => b.price - a.price);
      case "year-desc":
        return result.sort((a, b) => b.year - a.year);
      case "year-asc":
        return result.sort((a, b) => a.year - b.year);
      default:
        return result;
    }
  };

  const filteredCars = filterAndSortCars();
  const carsToShow = filteredCars.slice(0, carsToDisplay);
  const handleViewMore = () => setCarsToDisplay((prev) => prev + 8);

  // ✅ Scroll restoration
  useLayoutEffect(() => {
    const key = "cars-scroll"; // fixed key for CarList
    const scroller = document.scrollingElement || document.documentElement;

    const restoreScroll = () => {
      const saved = sessionStorage.getItem(key);
      if (saved) {
        const { x = 0, y = 0 } = JSON.parse(saved);
        scroller.scrollTo({ left: x, top: y, behavior: "auto" });
      }
    };

    // Only restore when grid has been painted
    if (cars.length > 0) {
      restoreScroll();
    }

    return () => {
      sessionStorage.setItem(
        key,
        JSON.stringify({ x: scroller.scrollLeft, y: scroller.scrollTop })
      );
    };
  }, [cars]);

  return (
    <section className="m-6 mx-2 rounded-[2px] bg-card p-3 px-2 md:m-4 md:p-3 sm:px-10 lg:m-6 lg:mx-10 lg:p-4" ref={containerRef}>
      <div className="mx-auto max-w-7xl">
        <div className="mb-5 flex items-center gap-2">
          <h2 className="text-xl font-bold text-gray-800">Cars for Sale</h2>
          <img src={Cambodia} alt="Cambodia Flag" className="h-7 w-10" />
        </div>

        <div className="grid grid-cols-2 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
          {carsToShow.length > 0
            ? carsToShow.map((car) => <CarCard key={car.id} car={car} />)
            : (
              <div className="col-span-full py-10 text-center text-gray-500">
                <p className="text-lg">No cars found matching your criteria.</p>
                <p className="mt-2 text-sm">Try adjusting your filters or search query.</p>
              </div>
            )}
        </div>

        {filteredCars.length > carsToDisplay && (
          <div className="mt-8 text-center">
            <button onClick={handleViewMore} className="font-base text-[#2384C1] underline">
              View More
            </button>
          </div>
        )}
      </div>
    </section>
  );
}

How can we achieve similar drag-n-drop behaviour of speed dials of opera browser for both sort and group?

I was trying to implement similar drag drop behaviour like opera browser’s speed dial.

  1. Group: It creates new group or add speed dial to existing group when we drag a speed dial over other speed dial or group.
  2. sorting with Smart positioning: Unlike typical sorting behaviour by various sorting libraries, opera browser triggers movement when a speed dial dragged to specific gap positions between cards it seems. It does not trigger movement when we drag a speed dial card over another partially or even completly

I tried Sortable.js and Muuri libs but they triggers movement as soon as the pointer entered into other items boundary and this blocks the possibility of grouping or other actions we can add.

if we want to move card 1 to location of card 2 then we need to drag to opposite side gap not adjusent gap

[ card 1 ] [ card 2 ]
[ card 1 ]|gap1|[ card 2 ]|gap2|

card 1 should be dragged to gap2 then only card 2 will pushed to empty area left by card after dragged.

this allows us to make group by not going to that gap and keep the card 1 over card 2 which will trigger creation of new group with card 1 and card 2.

If you can suggest something about it that will be helpful.

vernabulities in the session cookie

Let’s imagine the user forgets to log out and directly closes the application, so the session ID is still in the browser, and the server has an expiry.If it has the expiry, then if the user is logged in and the cookie expired then does they have to relogin or something else?

I just want to know the answer

“Cannot read property ‘computeModPow’ of null”

I am using aws-identity-js in a React Native Expo project, and when I try to sign in with my Cognito user pool credentials, the sign-in process does not work as expected. Instead, it throws the following error: “Cannot read property ‘computeModPow’ of null.” How can I resolve this issue and successfully authenticate users?

give solution to that problem

Click doesn’t respond on iOS

The click event doesn’t fire on iOS with the following JavaScript. It works fine on Windows. Why is this happening? Is there a workaround?

const pin = new google.maps.marker.PinElement({
    scale: 1.0,
    glyphColor: "green"
});
var testMarker = new google.maps.marker.AdvancedMarkerElement({
    content:        pin.element,
    gmpClickable:   true,
    gmpDraggable:   true
});
testMarker.position = new google.maps.LatLng(
    34.718420, 135.556109
);
testMarker.map = mapObj;
testMarker.addListener(
    'dragend',
    function (event) {
        alert('dragged');
    }
);
testMarker.addListener(
    'click',
    function (event) {
        alert('clicked');
    }
);

On Windows + Chrome, the “clicked” alert is displayed correctly, but on iOS + Chrome (or Safari), nothing happens. Regarding “dragend”, it works as expected in both cases. Using “gmp-click” instead of the “click” event produces the same results. Also, if you don’t add the “dragend” event, the click event works correctly.

How to add an item to the cart only once?

I am working on my store project using JS. I have the addToCard code to add a product to the cart. This code works, but I would like to add the product only once. After which, when I click the button(add-to-cart) again, there was a message – This product is already in the cart. For network requests, I use the Axios library along with Firebase services(Realtime Database, Storage)

export class HomePage extends Component {
  constructor() {
    super();
    this.state = {
      
      orderCart: [],
    };
  }
 
  addToCard = (e) => {
    if (e.target.closest(".add-to-cart")) {
      
      let id = e.target.parentElement.parentElement.dataset.id;
      let price = e.target.parentElement.parentElement.dataset.price;
      let name = e.target.parentElement.parentElement.parentElement.dataset.name;
      let img = e.target.parentElement.parentElement.parentElement.dataset.img;
      
      const cartItems = { id, price, name, img };

      apiService.post("/order", cartItems).then(() => {
       this.setState({
          ...this.state,
          orderCart: this.state.orderCart?.concat(cartItems),
        })
      })
      console.log(id, cartItems);
      
      useToastNotification({
        message: "Product in the cart!",
        type: TOAST_TYPE.success,
      });
    }
  };
  
  componentDidMount() {
    this.addEventListener("click", this.addToCard);
  }

  componentWillUnmount() {
    this.removeEventListener("click", this.addToCard);
  }
}

customElements.define('home-page', HomePage);

For page navigation and for the product card, I use Handlebars. Here is my product card:

{{#each products}}
      <div data-id="{{this.id}}" class="w-full md:w-1/3 xl:w-1/4 p-6 flex flex-col">
        
        <div
          data-img="{{this.img}}"
          data-name="{{this.name}}"
        >
          <img class="hover:grow hover:shadow-lg" src="{{this.img}}" alt="product" />
          <p data-name="{{this.name}}">{{this.name}}</p>
          <div 
            data-id="{{this.id}}"
            data-img="{{this.img}}" 
            data-name="{{this.name}}"
            data-price="{{this.price}}" class="pt-3 flex items-center justify-between">
            <p data-price="{{this.price}}" class="pt-1 text-gray-900">{{this.price}}</p>

            <button
             class="add-to-cart"
             data-id="{{this.id}}"
             data-img="{{this.img}}" 
             data-name="{{this.name}}"
             data-price="{{this.price}}">
             <svg class="h-6 w-6 fill-current text-gray-500 hover:text-black" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
               <path d="M12,4.595c-1.104-1.006-2.512-1.558-3.996-1.558c-1.578,0-3.072,0.623-4.213,1.758c-2.353,2.363-2.352,6.059,0.002,8.412 l7.332,7.332c0.17,0.299,0.498,0.492,0.875,0.492c0.322,0,0.609-0.163,0.792-0.409l7.415-7.415 c2.354-2.354,2.354-6.049-0.002-8.416c-1.137-1.131-2.631-1.754-4.209-1.754C14.513,3.037,13.104,3.589,12,4.595z M18.791,6.205 c1.563,1.571,1.564,4.025,0.002,5.588L12,18.586l-6.793-6.793C3.645,10.23,3.646,7.776,5.205,6.209 c0.76-0.756,1.754-1.172,2.799-1.172s2.035,0.416,2.789,1.17l0.5,0.5c0.391,0.391,1.023,0.391,1.414,0l0.5-0.5 C14.719,4.698,17.281,4.702,18.791,6.205z" />
             </svg>
            </button>
          </div>
        </div>
      </div>
    {{/each}}

I don’t understand how to implement adding a product via ID only once, with this addToCard code

How to log certain Form Responses

I am using the code below from Apps Script ItemResponse documentation. It is triggered by a form response. The trigger and code are working fine, but I only want to log the most recent form response, and I only want to log the responses to certain items only (items 1 through 3). How can I alter the code to do this?

// Open a form by ID and log the responses to each question.
const form = FormApp.openById('1234567890abcdefghijklmnopqrstuvwxyz');
const formResponses = form.getResponses();
for (let i = 0; i < formResponses.length; i++) {
  const formResponse = formResponses[i];
  const itemResponses = formResponse.getItemResponses();
  for (let j = 0; j < itemResponses.length; j++) {
    const itemResponse = itemResponses[j];
    Logger.log(
        'Response #%s to the question "%s" was "%s"',
        (i + 1).toString(),
        itemResponse.getItem().getTitle(),
        itemResponse.getResponse(),
    );
  }
}

I tried the following code with ran successfully, but nothing was logged. Additionally I’m not sure how to specify I only want responses for items 1 through 3.

  var formResponses = form.getResponses();
  var lastResponse = formResponses[formResponses.length - 1];

  const itemResponses = lastResponse.getItemResponses();
  for {
    const itemResponse = itemResponses[1:3];
    Logger.log(
        'Response #%s to the question "%s" was "%s"',
        (i + 1).toString(),
        itemResponse.getItem().getTitle(),
        itemResponse.getResponse(),
    );
  }

How to fix the Facebook’s “Advertiser Tracking Enabled parameter volume out of range” error

My team wants to run an ad campaign, but the event which we wanna use is not eligible for it, as it says “Advertiser Tracking Enabled parameter volume out of range (Your app needs a certain volume of events with the Advertiser Tracking Enabled (ATE) parameter set as false or no.)”.

We are using appsflyer to send the event from appsflyer to facebook. From server we are sending event to appsflyer then mapping same event to facebook.

Appsflyer says that they pass the ATE internally based on the value of ATT.

In order to fix the issue we are now passing
att , idfa & idva in the event.

Still the event is not yet eligible for the ad.

We followed this doc for the appsflyer event.
https://dev.appsflyer.com/hc/reference/s2s-events-api3-post

Code:


  export const ATT_STATUS = {
   NOT_DETERMINED: 0,
   RESTRICTED: 1,
   DENIED: 2,
   AUTHORIZED: 3,
  };

  const IDFA_VALUES = {
  RESTRICTED: '00000000-0000-0000-0000-000000000000',
  NOT_AVAILABLE: 'Not Available / No Consent',
};

// Determine ATT status based on IDFA value
export const getAttStatus = (idfa) => {
  if (!idfa) return ATT_STATUS.NOT_DETERMINED;

  if (idfa === IDFA_VALUES.RESTRICTED) return ATT_STATUS.RESTRICTED;
  if (idfa === IDFA_VALUES.NOT_AVAILABLE) return ATT_STATUS.DENIED;

  return ATT_STATUS.AUTHORIZED;
};

  async function sendEvent(eventName, customer) {
  try {
    const headers = {
      'Content-Type': 'application/json',
      authentication: configs.appsflyer.apiAccessToken,
    };

    const { metaData } = customer;

    const payload = {
      att: ATT_STATUS.NOT_DETERMINED, // Default value
      eventName,
      customer_user_id: customer?.id,
      appsflyer_id: customer?.appsflyerId,
      eventTime: moment().utc().format('YYYY-MM-DD HH:mm:ss.SSS'),
      bundleIdentifier: configs.appsflyer.bundleIdentifier,
    };

    // Set IP address (prioritize metaData.ipv4 over customer.ip_address)
    if (metaData?.ipv4) {
      payload.ip = metaData.ipv4;
    } else if (customer?.ip_address) {
      payload.ip = customer.ip_address;
    }

    // Process metaData if available
    if (metaData && typeof metaData === 'object') {
      const idfa = metaData.IDFA;

      if (idfa) {
        payload.idfa = idfa;
        payload.att = getAttStatus(idfa);
      }

      // Add IDFV if available
      if (metaData.IDFV) {
        payload.idfv = metaData.IDFV;
      }
    }

    const response = await axios.post(
      `${configs.appsflyer.apiHost}/inappevent/${configs.appsflyer.appId}`,
      payload,
      { headers }
    );
    return response.data;
  } catch (error) {
    logError(error);
  }
}

After updating the code we have sent around 50+ events, still the event is not eligible, also waited 24hrs still no update.

Has anyone faced this issue? Or does anyone knows how to fix it? Facebook don’t even provide the number of event it needs to track to make it eligible.
Thanks.

TypeError ‘vega-util’ does not resolve to valid URL

I am new to Javascript, Vega-lite, and Rollup. I have been able to solve some errors and unresolved dependencies. I am getting an error in the browser. I do not know what is going on in the bundle js file in the browser.

I am working to create a template or an intial Vega-lite data visualization. This is from the vizhub’s “Fork for Vega-lite api template”.

https://vizhub.com/Dalborg/f5ab04b76aa3483da70c4b58bb635fe7

The error message states: TypeError: Module name, ‘vega-util’ does not resolve to a valid URL.

I have looked up javascript and my the error, but I have not found something that I can deceipher to be relevant to my particular type of error.

I have fixed the circular dependencies by defining some variables for the different vega-lite plugins in the external and plugins (fields?). See the first code block below.

//rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import nodeResolve from '@rollup/plugin-node-resolve';
import replace from '@rollup/plugin-replace';
import commonjs from '@rollup/plugin-commonjs';
import { babel } from '@rollup/plugin-babel';
import nodePolyfills from 'rollup-plugin-polyfill-node';

export default {
    // onwarn: function ( message ) {
    // if (message.code === 'CIRCULAR_DEPENDENCY') {
    //   return;
    // }
    // console.error(message);
    // },
    input:'./main.js',
    output: {
      file:'build/bundle.js',
      format:'iife',
      name: 'MyReactApp',
      globals: {
        'd3': 'd3',
        'vega-embed': 'vega-embed',
        'vega': 'vega',
        'vega-lite': 'vega-lite',
        'vl': 'vl',
        'vega-lite-api': 'vega-lite-api'
      }
    },
    external: [
      'd3',
      'vega-embed',
      'vega-lite',
      'vega-lite-api',
      'vega'
    ],
    plugins: [
      resolve(),
      nodeResolve({
          jsnext:true,
          main:true,
      }),
      replace({
        'process.env.NODE_ENV': JSON.stringify('development'),
        preventAssignment: true,
      }),
      babel({
        babelHelpers: 'bundled',
        extensions: ['.js', '.jsx']
      }),
      commonjs({
        include: 'node_modules/**',
        requireReturnsDefault: 'auto'
      }),
      nodePolyfills(),
    ]
};

This is the second code block retrieved from source plugin in my safari browser.

import { isArray, isString, isObject } from 'vega-util';

var version$1 = "1.0.0";
var pkg = {
    version: version$1};

/**
 * Format the value to be shown in the tooltip.
 *
 * @param value The value to show in the tooltip.
 * @param valueToHtml Function to convert a single cell value to an HTML string
 */
function formatValue(value, valueToHtml, maxDepth, baseURL) {
    if (isArray(value)) {
        return `[${value.map((v) => valueToHtml(isString(v) ? v : stringify(v, maxDepth))).join(', ')}]`;
    }
    if (isObject(value)) {
        let content = '';
        const { title, image, ...rest } = value;
        if (title) {
            content += `<h2>${valueToHtml(title)}</h2>`;
        }
        if (image) {
            content += `<img src="${new URL(valueToHtml(image), baseURL || location.href).href}">`;
        }
        const keys = Object.keys(rest);
        if (keys.length > 0) {
            content += '<table>';
            for (const key of keys) {
                let val = rest[key];
                // ignore undefined properties
                if (val === undefined) {
                    continue;
                }
                if (isObject(val)) {
                    val = stringify(val, maxDepth);
                }
                content += `<tr><td class="key">${valueToHtml(key)}</td><td class="value">${valueToHtml(val)}</td></tr>`;
            }
            content += `</table>`;
        }
        return content || '{}'; // show empty object if there are no properties
    }
    return valueToHtml(value);
}
function replacer(maxDepth) {
    const stack = [];
    return function (key, value) {
        if (typeof value !== 'object' || value === null) {
            return value;
        }
        const pos = stack.indexOf(this) + 1;
        stack.length = pos;
        if (stack.length > maxDepth) {
            return '[Object]';
        }
        if (stack.indexOf(value) >= 0) {
            return '[Circular]';
        }
        stack.push(value);
        return value;
    };
}

I can’t find why nth iterations of an image are not loading

Hi I am trying to create a timeline for my fantasy world, and it works for the most part. You can see it in action here. https://www.skazkaworld.com/timeline.html

As you can see, the background rune image from the 8th entry and beyond does not display correctly. I cannot for the life of me figure out what the issue is. I’m still learning so my code is not very elegant.

Relevant HTML:

<body>
      <div id="navbar"></div><!-- Navbar will be loaded here -->
      <div class="timeline" id="timeline"></div>
    <script src="main.js"></script>
    <script src="timeline.js"></script>
    <script>
        // Load navbar.html dynamically        
        fetch("navbar.html")
            .then(response => response.text())
            .then(data => {
                document.getElementById("navbar").innerHTML = data;

                // Now that navbar is loaded, attach hamburger event
                const hamburger = document.querySelector(".hamburger");
                const navLinks = document.querySelector(".nav-links");

                if (hamburger && navLinks) {
                    hamburger.addEventListener("click", () => {
                        navLinks.classList.toggle("active");
                        hamburger.classList.toggle("open");
                    });
                }
            });
    </script> 
  </body>

CSS:

/* Timeline */ .timeline { 
  display: block; 
  margin: 100px auto 20px; 
  width: 800px; 
  padding: 14px; 
  background: rgba(20, 20, 20, 0.85); 
  height: 80vh; 
  overflow: auto;
  background-image: url("assets/timeline.jpg"); /* replace with your image */ 
  background-repeat: repeat-y; /* repeats vertically */ 
  background-position: center top; /* centers it horizontally */ 
  background-size:cover; /* adjust if needed, e.g., "contain" or specific width */ 
}

.event { 
  margin: 18px 0; 
  cursor: pointer; 

} .event-header { 
  display: flex; 
  align-items: center; 
}

.rune { 
  flex-shrink: 0; 
  width: 28px; 
  height: 28px; 
  margin-right: 8px; 
  background: url("assets/rune-marker.png") center/contain no-repeat; 
  filter: grayscale(100%) brightness(0.9); 
  transition: filter 0.3s ease, transform 0.35s ease; 
}

.rune.active { filter: grayscale(0%) brightness(1.1); 
  transform: scale(1.12); }

.event-details { 
  max-height: 0; 
  overflow: hidden; 
  opacity: 0; 
  transition: max-height 0.4s ease, opacity 0.4s ease; 
  padding-left: 36px; 
  font-size: 0.9em; 
  color: #ccc; 
}

.event.open .event-details { 
  max-height: 200px; 
  opacity: 1; 
  margin-top: 4px; 
}

Finally the JS:

// timeline.js
document.addEventListener("DOMContentLoaded", () => {
  const timeline = document.getElementById("timeline");

  // Timeline data array
  const timelineEvents = [
    {
      year: "1200 AE",
      title: "Founding of the First Ascent",
      details: "The First Ascent arises atop the frozen peaks of Skazka, where mortals defied the giants."
    },
    {
      year: "1345 AE",
      title: "The Rift of Molach",
      details: "A tear in the weave of reality as Molach's fall tore open the land and spirits slipped through."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    {
      year: "1502 AE",
      title: "The Night of Whispering",
      details: "When every shadow spoke and the forests sang curses in voices not their own."
    },
    // Add more events here...
  ];

  // Generate events dynamically
  timelineEvents.forEach((ev, i) => {
    const eventDiv = document.createElement("div");
    eventDiv.className = "event";
    eventDiv.dataset.id = i;

    eventDiv.innerHTML = `
      <div class="event-header">
        <span class="rune"></span>
        <div>
          <strong>${ev.year}</strong><br>
          ${ev.title}
        </div>
      </div>
      <div class="event-details">
        ${ev.details}
      </div>
    `;

    timeline.appendChild(eventDiv);
  });

  // Scroll rune activation - FIXED to use timeline container scroll
  const events = document.querySelectorAll(".event");
  
  function onTimelineScroll() {
    const timelineRect = timeline.getBoundingClientRect();
    const scrollTop = timeline.scrollTop;
    const viewportMiddle = timelineRect.height / 2;
    
    events.forEach(evt => {
      const eventRect = evt.getBoundingClientRect();
      const timelineTop = timelineRect.top;
      const eventTop = eventRect.top - timelineTop + scrollTop;
      const rune = evt.querySelector(".rune");
      
      // Activate rune if event is in upper half of timeline viewport
      if (eventTop < scrollTop + viewportMiddle) {
        rune.classList.add("active");
      } else {
        rune.classList.remove("active");
      }
    });
  }

  // Toggle event details on click
  events.forEach(evt => {
    evt.addEventListener("click", () => {
      evt.classList.toggle("open");
    });
  });

  // Listen to timeline scroll instead of window scroll
  timeline.addEventListener("scroll", onTimelineScroll);
  onTimelineScroll(); // Initial call
});

testing an useState change inside a callback

I have a component like this to test:

import {doSomethingMocked} form 'mockedLibrary';

const TheComponent = (props) => {
  const [isToTest, setIsToTest] = useState(false);
  const handleClick = useCallback(()=>{
    if(isToTest) return;
    setIsToTest(true);
    doSomethingMocked();
  }, [isToTest]);
  return (
    <button onClick={handleClick}>Click me</button>
  );
}

The Unit Test is something like this

import {render, fireEvent, act} form '@testing-library/react';
import {doSomethingMocked} form 'mockedLibrary';
jest.mock('mockedLibrary');

describe('The Component', ()=>{
  beforeEach(async ()=>{
    const sut = render(<MyProvider value={{}}><TheComponent/></MyProvider>);
    await act(async()=> await fireEvent.click(sut.getByText('Click me'))); // in theory the act now is unnecesary, any way still the state dows not toggle
    await act(async()=> await fireEvent.click(sut.getByText('Click me')));
  });
  it('Should only doSomething once', sync ()=>{
    expect(doSomethingMocked).toHaveBeenCalledTimes(1); // but it's called twice
  })
})

How can I test the boolean useState toggles?

I already run the component and works fine, but test wise, the useState change never happens.
Previously with enzyme after the wraper.update() was enought to test it.

Karma + Jasmin mock query param

I am new to ts and to frontend. I want to write test that mocks query param in uri.

it('should read the query parameter', () => {
  const stub = sinon.stub(window.location, 'search').get(() => '?query=test');

  const getQueryParam = () => {
    return new URLSearchParams(window.location.search).get('query');
  };

  const query = getQueryParam();

  assert.strictEqual(query, 'test');

  stub.restore();
});

I have googled that sinon can help me with this but i get: TypeError: Cannot redefine property: search. My intuition says that it shouldn’t be so complicated. Any best practices to accomplish it?

How can I prevent the second digit from shifting when deleting the first digit when editing a day in a date imask?

I am using IMaskJS. When you delete the first digit of the day of the date, the second one shifts to the left and takes its place, please tell me how to prevent this behavior.

{
          mask: Date,
          pattern: '`DD.`MM.`YYYY',
          format: date => moment(date).format('DD.MM.YYYY'),
          parse: str => moment(str, 'DD.MM.YYYY'),
          lazy: false,
          blocks: {
            DD: {
              mask: IMask.MaskedRange,
              from: 1,
              to: 31
            },
            MM: {
              mask: IMask.MaskedRange,
              from: 1,
              to: 12
            },
            YYYY: {
              mask: IMask.MaskedRange,
              from: 1000,
              to: 2999
            }
          }
        }

I tried to change the pattern field in the imask settings. Correct behavior is expected without shifting the numbers.

How to send a print command from a web application to a Bluetooth Brother label printer?

I am developing a mobile web application (React/JavaScript) that needs to send print commands to a Brother label printer (model TD-2120N / QL series) connected via Bluetooth.

  1. Using the browser’s window.print() – not suitable since it opens the system dialog and doesn’t support direct label formatting.

  2. Checking Brother’s SDK – seems focused on native apps (Android/iOS/Windows), not web.

  3. Looking into Web Bluetooth API – limited documentation on integrating with Brother printers.

Goal

Allow users to print labels (text + barcodes) directly from a mobile web app to a Bluetooth-connected Brother label printer without installing a native app..

Questions

  1. Can the Web Bluetooth API be used on mobile browsers (Chrome/Android or Safari/iOS) to connect and send print commands to Brother printers?

  2. Is a bridge app or service (like a small native companion app) required?

  3. Any JavaScript examples of successfully sending label data to a Brother printer over Bluetooth?