Canvas animation shows differently on Safari?

I’ve been unable to find someone discussing the same issue so far, but I have a canvas animation that displays on the background of my webpage that displays perfectly on firefox but is extremely simplified on Safari.

The animation is supposed to be subtle, blurry orbs that morph into one another and change colour while floating around the screen in a jellyfish-like rhythm; this works perfectly on Firefox. However, on Safari the animation just shows as different coloured circles bouncing around the screen.

I was wondering why this happens and if it’s possible to fix it?

Here is a link to the page in question:
https://k1ssch4se.neocities.org/

Here is the code for the animation:

const rand = function(min, max) {
  return Math.random() * ( max - min ) + min;
}

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

window.addEventListener('resize', function() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  ctx = canvas.getContext('2d');
  ctx.globalCompositeOperation = 'lighter';
});
let backgroundColors = [ '#000', '#000' ];
let colors = [
  [ '#69ffa5', "#d0ff26" ],
  [ '#00eeff', '#27e49b' ], 
  [ '#ffc800', '#f2ff00' ],
  [ '#00e1ff', '#26ffed' ],
  [ '#3cff00', '#0346ff' ]
];
let count = 15;
let blur = [ 15, 20 ];
let radius = [ 10, 80 ];

ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.globalCompositeOperation = 'lighter';

let grd = ctx.createLinearGradient(0, canvas.height, canvas.width, 0);
grd.addColorStop(0, backgroundColors[0]);
grd.addColorStop(1, backgroundColors[1]);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, canvas.width, canvas.height);

let items = [];

while(count--) {
    let thisRadius = rand( radius[0], radius[1] );
    let thisBlur = rand( blur[0], blur[1] );
    let x = rand( -100, canvas.width + 100 );
    let y = rand( -100, canvas.height + 100 );
    let colorIndex = Math.floor(rand(0, 299) / 100);
    let colorOne = colors[colorIndex][0];
    let colorTwo = colors[colorIndex][1];
    
    ctx.beginPath();
    ctx.filter = `blur(${thisBlur}px)`;
    let grd = ctx.createLinearGradient(x - thisRadius / 2, y - thisRadius / 2, x + thisRadius, y + thisRadius);
  
    grd.addColorStop(0, colorOne);
    grd.addColorStop(1, colorTwo);
    ctx.fillStyle = grd;
    ctx.fill();
    ctx.arc( x, y, thisRadius, 0, Math.PI * 2 );
    ctx.closePath();
    
    let directionX = Math.round(rand(-99, 99) / 100);
    let directionY = Math.round(rand(-99, 99) / 100);
  
    items.push({
      x: x,
      y: y,
      blur: thisBlur,
      radius: thisRadius,
      initialXDirection: directionX,
      initialYDirection: directionY,
      initialBlurDirection: directionX,
      colorOne: colorOne,
      colorTwo: colorTwo,
      gradient: [ x - thisRadius / 2, y - thisRadius / 2, x + thisRadius, y + thisRadius ],
    });
}


function changeCanvas(timestamp) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  let adjX = 2;
  let adjY = 2;
  let adjBlur = 1;
  items.forEach(function(item) {
    
      if(item.x + (item.initialXDirection * adjX) >= canvas.width && item.initialXDirection !== 0 || item.x + (item.initialXDirection * adjX) <= 0 && item.initialXDirection !== 0) {
        item.initialXDirection = item.initialXDirection * -1;
      }
      if(item.y + (item.initialYDirection * adjY) >= canvas.height && item.initialYDirection !== 0 || item.y + (item.initialYDirection * adjY) <= 0 && item.initialYDirection !== 0) {
        item.initialYDirection = item.initialYDirection * -1;
      }
      
      if(item.blur + (item.initialBlurDirection * adjBlur) >= radius[1] && item.initialBlurDirection !== 0 || item.blur + (item.initialBlurDirection * adjBlur) <= radius[0] && item.initialBlurDirection !== 0) {
        item.initialBlurDirection *= -1;
      }
    
      item.x += (item.initialXDirection * adjX);
      item.y += (item.initialYDirection * adjY);
      item.blur += (item.initialBlurDirection * adjBlur);
      ctx.beginPath();
      ctx.filter = `blur(${item.blur}px)`;
      let grd = ctx.createLinearGradient(item.gradient[0], item.gradient[1], item.gradient[2], item.gradient[3]);
      grd.addColorStop(0, item.colorOne);
      grd.addColorStop(1, item.colorTwo);
      ctx.fillStyle = grd;
      ctx.arc( item.x, item.y, item.radius, 0, Math.PI * 2 );
      ctx.fill();
      ctx.closePath();
    
  });
  window.requestAnimationFrame(changeCanvas);
  
}

window.requestAnimationFrame(changeCanvas);
canvas {
  bottom: 0;
  left: 0;
  position: fixed;
  right: 0;
  top: 0;
}
<canvas id="canvas" style="opacity: 0.5;" ></canvas>

I’m Having Trouble with a Google Apps Script Array to Update Sheet Cells

I need to update the values of cells. Whatever I try, it either coughs up an error, or only updates the value in the script temporarily, but doesn’t update the cell.

Here is my array:

// A looping function that goes down the rows, starting with row 2, and adds +1 to +5 depending on the priority of the meal.
// Stops and continues the main script once the array finishes going through all filled rows.
function iterateThroughRows() {
  var activePage = SpreadsheetApp.getActive().getSheetByName("Meal List");
  var data = activePage.getDataRange().getValues();
  data.shift(); // Skips the first row header.

  data.forEach(function (row) { 
    console.log("Logger Row -----------------------------------------------------------------------------------------------------"); // DEBUGGING LOGS
    Logger.log(row); // DEBUGGING LOGS

    var mealSkip = row[2];
    var mealPickedChance = row[3];
    var mealPriority = row[4];

    console.log("mealSkip = " + mealSkip); // DEBUGGING LOGS
    console.log("mealPickedChance = " + mealPickedChance); // DEBUGGING LOGS
    console.log("mealPriority = " + mealPriority); // DEBUGGING LOGS

    if (mealSkip === true) {
      //mealSkip.push(false);
      //mealSkip.setValue(false);
      //mealSkip = [false];
      //data.setValue[2](false);
      console.log("Set mealSkip to equal FALSE = " + mealSkip); // DEBUGGING LOGS
    }
    else {
      //mealPickedChance.setValue(mealPickedChance+mealPriority);
      console.log("Update mealPickedChance = " + mealPickedChance); // DEBUGGING LOGS
    }
  });
}

I want to make two changes:

  • If mealSkip equals TRUE, set it to FALSE and continue to the next row.
  • Else, take the int value from mealPriority and add/sum it to int value of mealPickedChance, then continue to the next row.

How do I get an array to update the value of a cell here?

Why is CodeSandbox not showing the correct preview to how my index.css is styled?

I am just recently learning React, and I wanted to change the background of the body, so I switched out the image pdf file. The background was showing perfectly up until I changed the file.

I pushed the commit for the change to GitHub, but the preview still doesn’t look correct.

This is the CodeSandbox link:
https://codesandbox.io/p/github/alissa-17/stickyNoteUi/main

Hide a div generated dynamically via SCSS

I am working on hiding certain elements of an Angular page. I cannot change the source code, however I am able to upload an SCSS stylesheet which is applied to the page. The page I am working on has 2 dynamically generated buttons. The buttons look like
enter image description here

and the html/angular for the two buttons is as follows, from dev tools inspect.

<div ng-class="['gen-home-content', isSingleTile ? 'single-tile' : null]" class="ng-scope gen-home-content">
    <div ng-repeat="tile in tiles" class="ng-scope">
        <landing-page-app-tile tile-data="tile" class="ng-isolate-scope">
            <button ng-click="goApp(tileData.url)" class="gen-app-tile">
                <div class="tile-icon">
                    <img ng-src="../assets/personal_info.svg" aria-hidden="true" src="../assets/personal_info.svg">
                </div>
                <div class="tile-content">
                    <div class="tile-title ng-binding">
                        Personal Information
                    </div>
                    <div class="tile-desc ng-binding">
                        View and update your biographical and demographic information.
                    </div>
                </div>
            </button>
        </landing-page-app-tile>
    </div>
    <div ng-repeat="tile in tiles" class="ng-scope">
        <landing-page-app-tile tile-data="tile" class="ng-isolate-scope">
            <button ng-click="goApp(tileData.url)" class="gen-app-tile">
                <div class="tile-icon">
                    <img ng-src="../assets/direct_deposit.svg" aria-hidden="true" src="../assets/direct_deposit.svg">
                </div>
                <div class="tile-content">
                    <div class="tile-title ng-binding">
                        Direct Deposit
                    </div>
                    <div class="tile-desc ng-binding">
                        Create, view and update your direct deposit allocation(s).
                    </div>
                </div>
            </button>
        </landing-page-app-tile>
    </div>
</div>

My first thought was that since the containing div has the ng-class isSingleTile ? ternary expression but not the single-tile class, that the expression must be evaluating to null, and if I could apply the isSingleTile class to the div with the gen-home-content class, perhaps that would force a single-tile view (which I assume would be helpful to me). Using scss I tried these options

.gen-home-content div:not(:first-child) {
    display: none !important;
}

and

.gen-home-content {
    @extend .single-tile;
}

but these two styles did not resolve the issue. I do not know exactly what the single-tile class is, I tried searching for it but it was not in the existing stylesheet. I think it is related to mat-grid-list but I could not find specific documentation on it.

Any advice on how to hide the direct deposit button is appreciated.

Issue in Google OAuth flow when using PKCE

I am trying to make a drive client for my college project and I am not very experienced in it. That’s why I am building the application using electron so that I can develop the application as if it was a website, but it will work as a Desktop Client. I want it to be completely client-side which means no interaction with any web server other that google for authentication purposes.

Right now, I am facing an issue with the PKCE flow in Google OAuth2.0
I am getting the following error:

GaxiosError: invalid_grant
    at Gaxios._request (MY_PROJECT_PATHdrive-encrypt_(Pre-production)node_modulesgaxiosbuildsrcgaxios.js:142:23)       
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async OAuth2Client.getTokenAsync (MY_PROJECT_PATHdrive-encrypt_(Pre-production)node_modulesgoogle-auth-librarybuildsrcauthoauth2client.js:158:21)
    at async file:///MY_PROJECT_PATH/drive-encrypt_(Pre-production)/index.js:154:35 {
  config: {
    retry: true,
    retryConfig: {
      httpMethodsToRetry: [Array],
      currentRetryAttempt: 0,
      retry: 3,
      noResponseRetries: 2,
      retryDelayMultiplier: 2,
      timeOfFirstRequest: 1743095188566,
      totalTimeout: 9007199254740991,
      maxRetryDelay: 9007199254740991,
      statusCodesToRetry: [Array]
    },
    method: 'POST',
    url: 'https://oauth2.googleapis.com/token',
    data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
      'User-Agent': 'google-api-nodejs-client/9.14.0',
      'x-goog-api-client': 'gl-node/20.10.0'
    },
    paramsSerializer: [Function: paramsSerializer],
    body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
    validateStatus: [Function: validateStatus],
    responseType: 'unknown',
    errorRedactor: [Function: defaultErrorRedactor]
  },
  response: {
    config: {
      retry: true,
      retryConfig: [Object],
      method: 'POST',
      url: 'https://oauth2.googleapis.com/token',
      data: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      headers: [Object],
      paramsSerializer: [Function: paramsSerializer],
      body: '<<REDACTED> - See `errorRedactor` option in `gaxios` for configuration>.',
      validateStatus: [Function: validateStatus],
      responseType: 'unknown',
      errorRedactor: [Function: defaultErrorRedactor]
    },
    data: {
      error: 'invalid_grant',
      error_description: 'Missing code verifier.'
    },
    headers: {
      'alt-svc': 'h3=":443"; ma=2592000,h3-29=":443"; ma=2592000',
      'cache-control': 'no-cache, no-store, max-age=0, must-revalidate',
      'content-encoding': 'gzip',
      'content-type': 'application/json; charset=utf-8',
      date: 'Thu, 27 Mar 2025 17:06:28 GMT',
      expires: 'Mon, 01 Jan 1990 00:00:00 GMT',
      pragma: 'no-cache',
      server: 'scaffolding on HTTPServer2',
      'transfer-encoding': 'chunked',
      vary: 'Origin, X-Origin, Referer',
      'x-content-type-options': 'nosniff',
      'x-frame-options': 'SAMEORIGIN',
      'x-xss-protection': '0'
    },
    status: 400,
    statusText: 'Bad Request',
    request: { responseURL: 'https://oauth2.googleapis.com/token' }
  },
  error: undefined,
  status: 400,
  [Symbol(gaxios-gaxios-error)]: '6.7.1'
}

The main part to focus here is:-

data: {
error: ‘invalid_grant’,
error_description: ‘Missing code verifier.’
},

I have tried and checked the following things:

  1. I am using Google’s Desktop Client ID (So, I only have the default redirect URI)
  2. I have also verified that the generated code verifier matches the code challenge using online PKCE Code Generator
  3. I have also checked that the authorization code is only used once, and never repeated again.

This the my relevant server code (index.js):

import dotenv from "dotenv"
dotenv.config()

import fs from "fs"
import path from "path"
import { fileURLToPath } from "url"
import { google } from "googleapis"
import express from "express"
import bodyParser from "body-parser"
import mime from "mime-types"
import multer from "multer"
import cors from "cors"
import crypto from "node:crypto"
import { Readable } from "stream"
const winregModule = await import('winreg');
const Registry = winregModule.default;
import { generateAndStoreKEK, retrieveKEK, encryptData, decryptData } from "./frontend/scripts/keyManagement.js"
import { recoverPassword, generateAndStorePasswordRecovery, recoveryDataExists } from './frontend/scripts/passwordRecovery.js';
import dns from "dns"
import session from 'express-session';

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const app = express()
app.use(bodyParser.json())
app.use(
  cors({
    origin: "http://localhost:8000",
    credentials: true, // Allow cookies to be sent with requests
  }),
)

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: true,
  cookie: {
    secure: process.env.NODE_ENV === "production",
    httpOnly: true,
    sameSite: "lax",
  }
}));

const isDev = process.env.NODE_ENV === "development"

const frontendDirectoryPath = isDev ? path.join(__dirname, "../frontend") : path.join(__dirname, "frontend")

// Serve static files from the frontend directory
app.use(express.static(frontendDirectoryPath))

// Serve login.html at the root URL
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "frontend/login.html"))
})

const oauth2Client = new google.auth.OAuth2(
  process.env.CLIENT_ID,
  process.env.NODE_ENV === "production" ? "" : process.env.CLIENT_SECRET,
  "http://localhost:8000/google/redirect"
);


let tokens

try {
  const tokenData = fs.readFileSync(path.join(__dirname, "tokens.json"), "utf8")
  tokens = JSON.parse(tokenData)
  oauth2Client.setCredentials(tokens)
} catch (err) {
  console.log("No tokens file found. User will need to authenticate.")
}

const PORT = process.env.PORT || 8000

const USER_PREFERENCES_FILE = path.join(__dirname, "userPreferences.json")

function generateCodeVerifier() {
  return crypto.randomBytes(32).toString('base64url');
}

function generateCodeChallenge(codeVerifier) {
  const hash = crypto.createHash("sha256").update(codeVerifier).digest("base64");
  return hash
    .replace(/+/g, "-") // Replace '+' with '-'
    .replace(///g, "_") // Replace '/' with '_'
    .replace(/=+$/, ""); // Remove '=' padding
}

app.get("/auth/google", (req, res) => {
  const pkceVerifier = generateCodeVerifier();
  req.session.pkceCodeVerifier = pkceVerifier; // Store it in the session
  console.log("The pkce code verifier is: ", req.session.pkceCodeVerifier);
  const codeChallenge = generateCodeChallenge(pkceVerifier);

  console.log("Code Challenge:", codeChallenge);
  console.log("Code Verifier:", pkceVerifier);
  
  const authUrl = oauth2Client.generateAuthUrl({
    access_type: "offline",
    scope: [
      "https://www.googleapis.com/auth/userinfo.profile",
      "https://www.googleapis.com/auth/userinfo.email",
      "https://www.googleapis.com/auth/contacts.readonly",
      "https://www.googleapis.com/auth/drive"
    ],
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
  });
  console.log("Generated Auth URL: ", authUrl);
  res.redirect(authUrl);
});

app.get("/google/redirect", async (req, res) => {
  try {
    const { code } = req.query;
    const pkceVerifier = req.session.pkceCodeVerifier;
    console.log("PKCE Code Verifier in redirect: ", pkceVerifier);
    if (!pkceVerifier) {
      throw new Error("PKCE code verifier not found in session");
    }

    console.log("Payload to getToken:", {
      code,
      code_verifier: pkceVerifier,
    });
    const { tokens: newTokens } = await oauth2Client.getToken({
      code,
      code_verifier: pkceVerifier,
    });
    oauth2Client.setCredentials(newTokens)
    tokens = newTokens

    // Save tokens to file
    fs.writeFileSync(path.join(__dirname, "tokens.json"), JSON.stringify(newTokens))

    // Set a cookie to indicate the user is authenticated
    res.cookie("authenticated", "true", { httpOnly: true, secure: true, sameSite: "Lax" })
    req.session.pkceCodeVerifier = null;

    // Check if a password has been set in the registry
    const passwordSet = await isPasswordSet()
    console.log("Is password set?", passwordSet)

    if (!passwordSet) {
      console.log("Redirecting to password creation page")
      res.redirect("/password-creation.html")
    } else {
      console.log("Redirecting to password verification page")
      res.redirect("/password-verification.html")
    }
  } catch (err) {
    console.error("Error in /google/redirect:", err)
    res.redirect("/?error=" + encodeURIComponent("Authentication failed. Please try again."))
  }
})

Sorry in advance for the poor code formatting and if I am doing something extremely stupid here.

Let me know if you need anything else for context. And Thanks in advance

JavaScript- requestAnimationFrame() slows down and then stops when using translate with rotate

const box = document.querySelector('.box');
let id = undefined;

function animateTest() {
  const style = window.getComputedStyle(box);
  const matrix = new DOMMatrixReadOnly(style.transform);
  const translateY = matrix.m42 + 2;
  box.style.transform = `rotate(12deg) translateY(${translateY}px)`;
  id = requestAnimationFrame(() => animateTest());
}
id = requestAnimationFrame(() => animateTest());

let button = document.querySelector('.button');
button.addEventListener('click', function() {
  cancelAnimationFrame(id);
  box.style.transform = `rotate(12deg) translateY(0px)`;
  id = requestAnimationFrame(() => animateTest());
}, false);
.box {
  margin-left: 300px;
  width: 10px;
  height: 10px;
  background-color: black;
  transform: rotate(15deg);
}
<button class='button'>rerun</button>
<div class="box"></div>

Here is jsfiddle demonstrating the problem:-

jsfiddle

Reload or save to rerun animation.

I am trying to move a box 12 degrees straight using translateY but when I add rotate to make it go that direction, the animation runs for a while and then it stops moving.

I think the requestAnimationFrame is called but the translateY property stops working.

If i remove the rotate(..) it works indefinitely as normal.

Why it slows and and stops when using rotate and how to make it not do that? thank you.

More simplified example-

My Html:

<div class="box">
 
</div>

My CSS:

.box {
  margin-left: 300px;
  width: 10px;
  height: 10px;
  background-color: black;
  transform: rotate(15deg);
}

My Script:

let box = document.querySelector('.box');
function animate() {
  const style = window.getComputedStyle(box);
  const matrix = new DOMMatrixReadOnly(style.transform);
  const translateY = matrix.m42 + 2;
  box.style.transform = `rotate(12deg) translateY(${translateY}px)`;
  requestAnimationFrame(() => animate());
}
requestAnimationFrame(() => animate());

How to Use Codemod with TS-Morph?

I want to use Codemod with TS-Morph to refactor TypeScript code, but I couldn’t find any example code demonstrating how to do this.

I found this article from Codemod stating that TS-Morph is supported, but it doesn’t provide a basic example.

How can I use Codemod with TS-Morph to perform a simple transformation, like renaming a variable or modifying a function signature?

An example of a basic setup would be very helpful.

How can I dynamically display and update form fields in Symfony EasyAdmin?

I’m working on a Symfony 7 project with EasyAdmin, and I’m trying to allow users to build article pages with different types of content, such as text blocks, image blocks, blocks with links, etc. To achieve this, I want to dynamically modify the form using the Symfony documentation on dynamic form modification: Symfony Documentation.

Here’s what I’ve done so far:

In my ArticleCrudController, I’m using a CollectionField to handle adding and removing blocks:

CollectionField::new('blocks', 'Blocs')
    ->renderExpanded(true)
    ->setEntryIsComplex()
    ->setEntryType(BlockType::class),

In the BlockType.php file, I add EventListeners to adjust the fields based on the selected type (text, link, etc.):

$builder
    ->add('type', ChoiceType::class, [
        'choices' => [
            'Text' => 'text',
            'Link' => 'link',
        ],
    ]);

$formModifier = function (FormInterface $form, $data = null) {
    if (is_array($data) && $data['type'] === 'text') {
        $form->add('text', TextareaType::class);
    } elseif (is_array($data) && $data['type'] === 'link') {
        $form->add('url', UrlType::class);
    }
};

$builder->addEventListener(
    FormEvents::PRE_SET_DATA,
    function (FormEvent $event) use ($formModifier) {
        $data = $event->getData();
        $formModifier($event->getForm(), $data);
    }
);

$builder->get('type')->addEventListener(
    FormEvents::POST_SUBMIT,
    function (FormEvent $event) use ($formModifier) {
        $type = $event->getForm()->getData();
        $formModifier($event->getForm()->getParent(), $type);
    }
);

$builder->setAction($options['action']);

I’m also adding some JavaScript inspired by the documentation to handle the form modification on the client side. Here’s the JS code I’m using to listen for changes and update the form:

document.addEventListener('DOMContentLoaded', function() {
    document.querySelector('.btn.btn-link.field-collection-add-button').addEventListener('click', function() {
        setTimeout(() => {
            const form_select_type = document.getElementById('Article_blocks_0_type');
            if (form_select_type) {
                const form = document.getElementById('new-Article-form');
                const block_0 = document.getElementById('Article_blocks_0');

                const updateForm = async (data, url, method) => {
                    const req = await fetch(url, {
                        method: method,
                        body: data,
                        headers: {
                            'Content-Type': 'application/x-www-form-urlencoded',
                            'charset': 'utf-8'
                        }
                    });

                    const text = await req.text();
                    return text;
                };

                const parseTextToHtml = (text) => {
                    const parser = new DOMParser();
                    const html = parser.parseFromString(text, 'text/html');
                    return html;
                };

                const changeOptions = async (e) => {
                    const requestBody = e.target.getAttribute('name') + '=' + e.target.value;
                    const updateFormResponse = await updateForm(requestBody, form.getAttribute('action'), form.getAttribute('method'));
                    const html = parseTextToHtml(updateFormResponse);

                    const new_block_0 = html.getElementById('Article_blocks_0');
                    block_0.innerHTML = new_block_0.innerHTML;
                };

                form_select_type.addEventListener('change', (e) => changeOptions(e));
            }
        }, 500);
    });
});

The problem I’m facing is that, currently, when I change the value of the select dropdown, the new fields don’t appear as expected. This is due to the errors that show up in the console. Specifically,
the 405 Method Not Allowed error
and the
TypeError: Cannot read properties of null (reading 'innerHTML').

I tried to manually force the choice of the method, but it didn’t work.

I also tried to follow the JavaScript example from this post: (Dynamic form modification for CollectionField in EasyAdmin) since the author was trying to do the same thing as me, but I don’t fully understand the code and it didn’t work either.

As a workaround, I’ve displayed all possible fields for the block (such as text, link, etc.) without worrying about the selected type, and made every fields not required. Then, I hide the fields that don’t match the selected type. While this approach works initially, it’s not an ideal solution, and it leads to issues with maintainability and performance. I really want to find a proper solution where only the relevant field for the selected block type is shown, making the form more optimized and dynamic.

Has anyone encountered a similar issue or implemented a dynamic form modification like this before? If anyone has any suggestions or can help me resolve the errors , I’d greatly appreciate it!

Web Components: set properties before or after ConnectedCallBack

I have been using Web Components for quite some time but now and then I keep running into some issues that I can’t explain.

Some context:

  • Attributes are used to pass text variables into a web component.
  • Properties can be used to pass complex variables into a web component.
  • A web component has a lifecycle method called ConnectedCallBack() that is called when our element gets added to the DOM.

What I am trying to do:

  • I created two web components: parent-element and child-element
  • Both have a property that you can use to pass in an object holding a text property that I want to display on screen; for these properties, I created a getter and setter method.
  • Each component has a render() method responsible for creating some new HTML elements holding the property value and adding them to the DOM; these methods only produce output if the property is set.

I added some example code to demonstrate this. Mind the output that is generated in the browser but also the console log demonstrating the order in which the various methods are called on both components.

Please run the code snippets in “Full page” as you might not see the full browser output when you run them inline.

<html>
<head></head>
<body>
    <app id="app"></app>
    <script>
        customElements.define('parent-element', class extends HTMLElement {
            set parentProperty(value) {
                console.log('PARENT: SET PROPERTY');
                this._parentProperty = value;
                this.render();
            }

            get parentProperty() {
                return this._parentProperty;
            }

            constructor() {
                super();
                console.log('parent: constructor');
            }

            connectedCallback() {
                console.log('parent: connectedCallback');
                this.render();
            }

            render() {
                console.log(`parent: render ${(this.parentProperty ? 'with' : 'no')} property`);
                if (this.parentProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.parentProperty.value}</p><child-element id="child"></child-element>`;
                    this.appendChild(templateEl.content.cloneNode(true));

                    let childEl = this.querySelector('#child');
                    childEl.childProperty = { value: 'child text' };
                }
            }
        });

        customElements.define('child-element', class extends HTMLElement {
            set childProperty(value) {
                console.log('CHILD: SET PROPERTY');
                this._childProperty = value;
                this.render();
            }

            get childProperty() {
                return this._childProperty;
            }

            constructor() {
                super();
                console.log('child: constructor');
            }

            connectedCallback() {
                console.log('child: connectedCallback');
                this.render();
            }

            render() {
                console.log(`child: render ${(this.childProperty ? 'with' : 'no')} property`);
                if (this.childProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.childProperty.value}</p>`;
                    this.appendChild(templateEl.content.cloneNode(true));
                }
            }
        });

        let appEl = document.querySelector('#app');
        let parentEl = document.createElement('parent-element');
        appEl.appendChild(parentEl);
        parentEl.parentProperty = { value: 'parent text' };
    </script>
</body>
</html>

Please focus on the part at the end:

let parentEl = document.createElement(‘parent-element’); — I create an instance of my parent-element
appEl.appendChild(parentEl); — I add it to the DOM by calling the appendChild() method
parentEl.parentProperty = { value: ‘parent text’ }; — I set the property using an object

The browser output looks like this:

parent text
child text

And my console log looks like this:

parent: constructor
parent: connnectedCallback
parent: render no property
PARENT: SET PROPERTY
parent: render with property
child: constructor
child: connnectedCallback
child: render no property
CHILD: SET PROPERTY
child: render with property

Notice that when calling the appendChild() method, the respective connectedCallback() method is triggered which in turn calls the render() method; as the property hasn’t been set yet, no output is produced.

After I set the property, the property setter is triggered which in turn calls the render() method again; as the property has now been set, actual output is generated.

So far, so good.

Now lets see what happens when I set the property on the parent element before adding it to the DOM.

<html>
<head></head>
<body>
    <app id="app"></app>
    <script>
        customElements.define('parent-element', class extends HTMLElement {
            set parentProperty(value) {
                console.log('PARENT: SET PROPERTY');
                this._parentProperty = value;
                this.render();
            }

            get parentProperty() {
                return this._parentProperty;
            }

            constructor() {
                super();
                console.log('parent: constructor');
            }

            connectedCallback() {
                console.log('parent: connectedCallback');
                this.render();
            }

            render() {
                console.log(`parent: render ${(this.parentProperty ? 'with' : 'no')} property`);
                if (this.parentProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.parentProperty.value}</p><child-element id="child"></child-element>`;
                    this.appendChild(templateEl.content.cloneNode(true));

                    let childEl = this.querySelector('#child');
                    childEl.childProperty = { value: 'child text' };
                }
            }
        });

        customElements.define('child-element', class extends HTMLElement {
            set childProperty(value) {
                console.log('CHILD: SET PROPERTY');
                this._childProperty = value;
                this.render();
            }

            get childProperty() {
                return this._childProperty;
            }

            constructor() {
                super();
                console.log('child: constructor');
            }

            connectedCallback() {
                console.log('child: connectedCallback');
                this.render();
            }

            render() {
                console.log(`child: render ${(this.childProperty ? 'with' : 'no')} property`);
                if (this.childProperty) {
                    let templateEl = document.createElement("template");
                    templateEl.innerHTML = `<p>${this.childProperty.value}</p>`;
                    this.appendChild(templateEl.content.cloneNode(true));
                }
            }
        });

        let appEl = document.querySelector('#app');
        let parentEl = document.createElement('parent-element');
        parentEl.parentProperty = { value: 'parent text' };
        appEl.appendChild(parentEl);
    </script>
</body>
</html>

So all I did was swap these lines:

parentEl.parentProperty = { value: ‘parent text’ }; — I set the property using an object
appEl.appendChild(parentEl); — I add it to the DOM by calling the appendChild() method

Now, my browser output looks like this:

parent text
child text
parent text

And my console output looks like this:

parent: constructor
PARENT: SET PROPERTY
parent: render with property
parent: connectedCallback
parent: render with property
child: constructor
child: connectedCallback
child: render no property
child: constructor
child: connectedCallback
child: render with property

It looks like the parent element has been rendered twice as the property on the parent element was available on both calls to the render method.

So finally, after all of this, my questions:

  1. Why does the second rendering of the child element contain no text ?
  2. How can the first rendering of the child element contain text when the setter of the property on the child element has NOT been called (“CHILD: SET PROPERTY” is missing from the console output) ?
  3. As a rule of thumb, should I always call appendChild() before setting my properties on a web component ?
  4. What is the correct time to call the render() method ? On connectedCallback, in the property setter or on both like I do now ?

So grateful for your feedback.

Express variable not passing to POST route

I cannot get the delete functionality to work in a to do list app because my JavaScript file is not recognizing the variable (from postgres DB) I’m passing from my Express file.

Full description

I am building my own app that has to do list functionality using Express, JavaScript, and Postgres for the database). I am trying to pass the unique task id from the index.ejs file to the index.js file using req.body to ultimately be deleted from the database. I expected the id to come through with req.body.deleteTaskId, I would store in taskToDelete, and then use that variable in my SQL code to delete the item from the database (Note: I’ve followed these steps for a different project from a tutorial and did not have this issue). When I log the variable that I expect to include the id, it comes out blank. There are 3 additional columns in the table (task_description, color, section). I can successfully pass task_description and color to the index.js file (for example if I change it to task.color in the same spot in the ejs file it goes through), section comes through blank similar to id. I confirmed that the data type of each column is a string but I cannot figure out why id and section will not pass through correctly (parseInt returns NaN).

SQL Data

  • Column id – integer automatically generated by postgre using SERIAL
  • Column task_description – TEXT added by a separate form in the app
  • Column color – CHARVAR added by a separate form in the app
  • Column section – CHARVAR currently added as the same string each time (not in the form)
CREATE TABLE tasks (
    id SERIAL NOT NULL UNIQUE,
    task_description TEXT NOT NULL,
    color VARCHAR(7) NOT NULL,
    section VARCHAR(50),
    PRIMARY KEY (id)
)

Any thoughts on why id and section are not passing through? (I am most concerned about id at the moment)

import express from "express";
import bodyParser from "body-parser";
import pg from "pg";

const app = express();
const port = 3000;

app.use(bodyParser.urlencoded({ extended: true}));
app.use(express.static("public"));

app.post("/delete", (req, res) => {
    const taskToDelete = req.body.deleteTaskId;
    console.log(taskToDelete);
// Did not include code to delete from DB
    res.redirect("/");
});
<% for (let task of tasks)  {%>                                         
  <form action="/delete" method="post">
    <input type="submit" id="trashImg" name="deleteTaskId" value="<%= task.id %>">
  </form>
<% } %>

P.S. This is my first time posting a question and I’m open to feedback on ways to improve how I presented the information.

Display Chart.js chart(s) depending on Livewire form submission

I am trying to display one or more charts using Chart.js and Laravel Livewire. My issue is that the chart isn’t displayed, even if the chart object receive data (when I console.log(chart) I can see the correct data)

I currently don’t bother to get actual form data, as I can’t even display a chart with dummy data (which was working fine before I tried to introduce Livewire).

Here is some code:

filter-graphs.blade.php (filter view)

<div>
    <form wire:submit="onGraphsChange" class="text-xl w-3/4 mx-auto">
        <div class="flex items-center justify-center">
            <div class="mx-4 flex-1 justify-center">
                <x-input-label for="graphs" value="Graphs à afficher" />
                <select id="graphs" name="graphs" multiple="multiple" class="mt-1 block w-full py-2 bg-gray-200 text-gray-800 rounded focus:border-[#F73F3A] focus:ring-[#F73F3A]">
                    <option class="py-1 bg-gray-200 text-gray-800" value="4">dummy 1</option>
                    <option class="py-1 bg-gray-200 text-gray-800" value="2">dummy 2</option>
                </select>
            </div>
            <div class="mx-4 flex-1 justify-center text-center">
                <button type="submit" class="px-4 py-2 border-2 border-gray-800 text-gray-800 rounded hover:text-gray-200 hover:bg-gray-800 hover:cursor-pointer">Afficher les graphs</button>
            </div>
        </div>
    </form>
</div>

FilterGraphs.php (filter component)

namespace AppLivewire;

use LivewireComponent;

class FilterGraphs extends Component
{
    public $selectedGraphs = null;

    public function onGraphsChange()
    {
        $this->dispatch('graphs-selected', $this->selectedGraphs);
    }

    public function render()
    {
        return view('livewire.filter-graphs');
    }
}

display-graphs.blade.php (charts display view)

<div class="flex">
    <div class="flex-1 shadow-sm rounded-lg bg-white m-4 p-4">
        <canvas id="test-graph"></canvas>
    </div>
</div>

<script>
    document.addEventListener('livewire:init', () => {
        Livewire.on('updateGraphs', (data) => {
            if(Chart.getChart("test-graph"))
                Chart.getChart("test-graph").destroy();

            var ctx = document.getElementById('test-graph').getContext("2d");
            var config = {
                type: 'line',
                data: {
                    labels: data[0]['labels'],
                    datasets: data[0]['datasets']
                },
                options: {
                    scales: {
                        x: {
                           ticks: {
                                display: false
                            },
                            grid: {
                                display: false
                            }
                        }
                    },
                    plugins: {
                        title: {
                            display: true,
                            text: '',
                        }
                    },
                    animation: false
                }
            };

            // myChart.config.options.plugins.title.text = data['title'];
            var myChart = new Chart(ctx, config);
        });
    });
</script>

DisplayGraphs.php (charts display component)

namespace AppLivewire;

use LivewireComponent;

use AppModelsHistory;

class DisplayGraphs extends Component
{
    public array $datasets = [];
    public array $labels = [];
    public $graphs;

    protected $listeners = [
        'graphs-selected' => 'graphsSelected',
    ];

    public function graphsSelected($server_service_ids)
    {
        //Get the dummy data

        $labels = $dates; // Contains an array of 85 dates as strings

        $datasets = [
            [
                'label' => 'Temps de réponse (ms)',
                'backgroundColor' => 'rgba(247, 63, 58, 0.3)',
                'borderColor' => 'rgb(247, 63, 58)',
                'lineTension' => 0.5,
                'pointRadius' => 2,
                'fill' => true,
                'data' => $response_times, // Contains an array of 85 int
            ],
            [
                'label' => 'Niveau d'alerte (ms)',
                'backgroundColor' => 'rgba(255, 87, 34, 0.3)',
                'borderColor' => 'rgb(255, 87, 35)',
                'pointRadius' => 0,
                'data' => $alert, // Contains an array of 85 int
            ],
        ];

        $this->dispatch('updateGraphs', [
            'datasets' => $datasets,
            'labels' => $labels,
        ]);
    }

    public function mount()
    {
        $this->labels = [];
        $this->datasets = [];
    }

    public function render()
    {
        return view('livewire.display-graphs');
    }
}

And the view within which I show all that

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">
            Title
        </h2>
    </x-slot>

    <!--Tabs navigation-->
    @include('partials.subnavigation-tabs')

    <livewire:filter-graphs />
    <livewire:display-graphs />
</x-app-layout>

What am I missing here to get the charts to display?
Thanks in advance

How to Auto-Redirect My Website from Android WebView Apps to an External Browser?

Attempt 1: Using googlechrome://

if (/android/i.test(navigator.userAgent)) {
  window.location.href = `googlechrome://${window.location.href.replace(/^https?:///, '')}`;
}

Result: The link stays inside LinkedIn’s WebView (no redirection happens).

Attempt 2: Using Android Intent

if (/android/i.test(navigator.userAgent)) {
  const intentUrl = `intent://${window.location.href.replace(/^https?:///, '')}#Intent;scheme=https;package=com.android.chrome;end`;
  window.location.href = intentUrl;
}

Result: The page loads infinitely inside LinkedIn’s WebView.

I Need a way to force the link to open in Chrome (or the default browser) only when possible.

Server error in different browsers when i use my local server [closed]

I am writing and testing a full-stack application. My frontend is uploaded to hosting and has the address – https://planner.cifra-plus.com.ua. And my server is on a local computer with a database. For the test, I run the server through the ngrok tunnel, I registered, and received a free static domain – https://grateful-heroic-mite.ngrok-free.app.
In requests, I use axios, I have public requests – $public, and authorized with a token – $auth. Public requests work without problems, but for requests with authorization I get a CORS error. This error is in both desktop Google Chrome and mobile Google Chrome, in Microsoft Edge, and also in Safari on the iPhone.


I am sending screenshots

  1. axios request settings
  2. CORS settings on the server
  3. as well as responses in the Google Chrome browser to an authorized request, as well as the response from the Microsoft Edge browser.
    Links:
    https://ibb.co/4n6S2LsN
    https://ibb.co/NdkrwBj0
    https://ibb.co/7dV8sb8v
    https://ibb.co/Tqgvb44k

Using wildcards in JavaScript [duplicate]

I’m writing a program which generates random letters and determines if there exists a word containing those letters in the order they’re generated.

No matter which letters are generated, the program always returns “Invalid permutation”. So for example, it doesn’t recognise that the letters i, t, and a are a valid permutation, as these can make up the word “dictionary”.

My code is below. What am I doing wrong?

//define dicitionary and alphabet
const dictionaryArray = ["alligator", "breadcrumb", "creation", "dictionary", "elephant", "farm", "general"];
const alphabet = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"];

/*implement function to generate a random number. 
In conjunction with alphabet array, this will generate 
random letters */
function Random(maxNumber) {
    return Math.floor(Math.random() * maxNumber);
  }

//generate random letters
var genLet1 = alphabet[Random(26)];
var genLet2 = alphabet[Random(26)];
var genLet3 = alphabet[Random(26)];

//concatenate with wildcards
var wildGen = "*" + genLet1 + "*" + genLet2 + "*" + genLet3 + "*";

function checkPermutationValidity(string) {

  if (britishDictionary.includes(string)) {
    console.log("Valid permutation");
  } else {
  console.log("Invalid permutation");
    }
}
checkPermutationValidity(wildGen);