In OpenTelemetry typescript, can you exclude instruments from view?

Let’s say I have something like this:

const fooView = new View({
      instrumentName: "*_foo",
      // options for foo instruments
    });

const barView = new View({
      instrumentName: "*_bar",
      // options for bar instruments
    });

Then I also want to have something like this:

const defaultView = new View({
      // do something here to capture all instruments EXCEPT foo and bar ones
    });

And just for full context, these will then be used like this:

var meterProvider = new MeterProvider({
      views: [fooView, barView, defaultView],
      // other options here
    });

My question is: how do I define defaultView to achieve this? The problem is instrumentName is not a standard regexp. And the view documentation from here doesn’t mention any way of excluding specific names. Is there a workaround for this?

Resizing KonvaJS Dynamically Similar to Canvas

I have a need to adjust the KonvaJS Container/Stage in a similar way to a canvas element. I’ve provided an example below showcasing my exact need. In this code the source of the image to select is the canvas, and so I need the canvas and the Konva elements to dynamically update to the screen size and perfectly align while still maintaining dimensions of 1000×1000.

However, for some reason the Konva container’s (select-container) width is not adjusting the same way as the canvas does with the same css. In addition, it seems that the stage is not adjusting to the fit within the container. Is there any built in solution to fix my issue?

<!DOCTYPE html>
<html>
  <head>
    <script src="https://unpkg.com/[email protected]/konva.min.js"></script>
    <meta charset="utf-8" />
    <title>Canvas to Konva Object</title>
    <style>
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        background-color: #f0f0f0;
      }
      .over-container {
            top: 100%;
            display: flex;
            flex-wrap: nowrap; /* Prevent wrapping */
            flex-direction: column;
            width: 100%;
            height: 100vh;
            align-items: center;
            margin-top: 35px;
        }

        #canvas-container {
            position: relative;
            width: 100%;
            overflow: hidden;
            /* Aspect Ratio */
            padding-top: 100%;
            display: flex;
            justify-content: center;
        }

        #select-container {
            cursor: crosshair;
            position: absolute;
            top: 0;
            left: 50%;
            transform: translate(-50%);
            max-width: calc(100vw - 10px); /* Subtracting 5px margin on both sides */
            /* Set explicitly or let JavaScript control */
            border: 1px solid #571bfa;
            flex: 1;
        }

        #canvas {
            cursor: crosshair;
            position: absolute;
            top: 0;
            left: 50%;
            transform: translate(-50%);
            /* Adjust size based on viewport */
            max-width: calc(100vw - 10px); /* Subtracting 5px margin on both sides */
            touch-action: none; 
            image-rendering: pixelated; 
            flex: 1;
            border: 1px solid #000;
        }
    </style>
  </head>
  <body>
    <div class="over-container" id="Ocontainer">
        <div id="canvas-container">
            <canvas id="canvas"></canvas>
            <div id="select-container"></div>
        </div>
    </div>
    <script>
        var width = 1000;
        var height = 1000;

        // Get the canvas and set dimensions
        var canvas = document.getElementById('canvas');
        canvas.width = width;
        canvas.height = height;

        var ctx = canvas.getContext('2d');
        ctx.fillStyle = 'blue';
        ctx.fillRect(50, 50, 300, 200);
        ctx.fillStyle = 'orange';
        ctx.fillRect(400, 100, 200, 300);

        // Create Konva stage and layer
        var stage = new Konva.Stage({
            container: 'select-container',
            width: width,
            height: height,
        });

        var layer = new Konva.Layer();
        stage.add(layer);

        var selectionRectangle = new Konva.Rect({
            fill: 'rgba(0, 0, 255, 0.5)',
            visible: false,
            listening: false,
        });
        layer.add(selectionRectangle);

        var x1, y1, x2, y2, selecting = false, activeObject = null;
        var copiedObject = null;
        var objects = [];
        var transformers = []; // Keep track of transformers for cleanup

        // Helper function to create a transformer for a given object
        function createTransformer(object) {
            const transformer = new Konva.Transformer({
            nodes: [object],
            });
            layer.add(transformer);
            transformer.update();
            transformers.push(transformer); // Keep track of the transformer
            return transformer;
        }

        stage.on('mousedown touchstart', (e) => {
            if (e.target !== stage) return;

            e.evt.preventDefault();
            x1 = stage.getPointerPosition().x;
            y1 = stage.getPointerPosition().y;
            selecting = true;

            selectionRectangle.setAttrs({
            visible: true,
            x: x1,
            y: y1,
            width: 0,
            height: 0,
            });
        });

        stage.on('mousemove touchmove', (e) => {
            if (!selecting) return;

            e.evt.preventDefault();
            x2 = stage.getPointerPosition().x;
            y2 = stage.getPointerPosition().y;

            let width = Math.abs(x2 - x1);
            let height = Math.abs(y2 - y1);

            // Check if Shift key is held
            if (e.evt.shiftKey) {
            // Force square by setting width and height to the smaller of the two
            const sideLength = Math.min(width, height);
            width = sideLength;
            height = sideLength;

            // Adjust x2 and y2 to maintain the square shape
            if (x2 < x1) x2 = x1 - width; // Adjust for leftward movement
            else x2 = x1 + width; // Adjust for rightward movement

            if (y2 < y1) y2 = y1 - height; // Adjust for upward movement
            else y2 = y1 + height; // Adjust for downward movement
            }

            selectionRectangle.setAttrs({
            x: Math.min(x1, x2),
            y: Math.min(y1, y2),
            width: width,
            height: height,
            });

            layer.batchDraw();
        });

        stage.on('mouseup touchend', () => {
            if (!selecting) return;
            selecting = false;
            selectionRectangle.visible(false);

            const box = selectionRectangle.getClientRect();
            const sx = box.x;
            const sy = box.y;
            const sw = box.width;
            const sh = box.height;

            if (sw === 0 || sh === 0) return;

            // Get the selected area from the visible canvas
            const imageData = ctx.getImageData(sx, sy, sw, sh);

            // Remove the selected portion from the canvas
            ctx.clearRect(sx, sy, sw, sh);

            // Create an off-screen canvas to crop the image data
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = sw;
            tempCanvas.height = sh;
            const tempCtx = tempCanvas.getContext('2d');
            tempCtx.putImageData(imageData, 0, 0);

            // Create a Konva.Image from the cropped area
            const image = new Konva.Image({
                x: sx,
                y: sy,
                image: tempCanvas,
                draggable: true,
            });
            layer.add(image);
            objects.push(image);

            // Create a transformer for this object
            createTransformer(image);

            activeObject = image;
            layer.batchDraw();
        });

        window.addEventListener('keydown', (e) => {
            if (e.ctrlKey && e.key === 'c' && activeObject) {
                // Copy the active object
                const clonedCanvas = document.createElement('canvas');
                clonedCanvas.width = activeObject.image().width;
                clonedCanvas.height = activeObject.image().height;
                const clonedCtx = clonedCanvas.getContext('2d');
                clonedCtx.drawImage(activeObject.image(), 0, 0);

                copiedObject = {
                    x: activeObject.x() + 10,
                    y: activeObject.y() + 10,
                    image: clonedCanvas,
                    rotation: activeObject.rotation(),
                    scaleX: activeObject.scaleX(),
                    scaleY: activeObject.scaleY(),
                };
            }
            if (e.ctrlKey && e.key === 'v' && copiedObject) {
                // Paste the copied object
                const newCopy = new Konva.Image({
                    x: copiedObject.x,
                    y: copiedObject.y,
                    image: copiedObject.image,
                    draggable: true,
                    rotation: copiedObject.rotation,
                    scaleX: copiedObject.scaleX,
                    scaleY: copiedObject.scaleY,
                });
                layer.add(newCopy);
                objects.push(newCopy);

                // Create a transformer for the new copy
                createTransformer(newCopy);

                activeObject = newCopy;
                layer.batchDraw();
            }
        });

        stage.on('click tap', (e) => {
            if (activeObject && e.target === stage) {
            // Apply all objects back to the canvas
            objects.forEach((obj) => {
                const image = obj.image(); // Get the underlying image
                const rotation = obj.rotation(); // Rotation angle in degrees

                // Use getClientRect to get transformed position
                const clientRect = obj.getClientRect();
                const boundingBoxCenterX = clientRect.x + clientRect.width / 2;
                const boundingBoxCenterY = clientRect.y + clientRect.height / 2;

                // Use the original image dimensions
                const originalWidth = image.width;
                const originalHeight = image.height;

                // Use scaleX and scaleY for scaling
                const scaledWidth = originalWidth * obj.scaleX();
                const scaledHeight = originalHeight * obj.scaleY();

                // Save the canvas context state
                ctx.save();

                // Translate to the bounding box center
                ctx.translate(boundingBoxCenterX, boundingBoxCenterY);

                // Rotate the canvas context around the center of the bounding box
                ctx.rotate((rotation * Math.PI) / 180);

                // Draw the object onto the canvas using the original dimensions and scaling
                ctx.drawImage(
                image,
                -scaledWidth / 2, // Center the image horizontally
                -scaledHeight / 2, // Center the image vertically
                scaledWidth,
                scaledHeight
                );

                // Restore the canvas context state
                ctx.restore();
            });

            // Destroy all Konva objects and transformers
            objects.forEach((obj) => obj.destroy());
            transformers.forEach((transformer) => transformer.destroy());
            transformers = [];

            // Reset state variables
            objects = [];
            activeObject = null;

            // Redraw the layer
            layer.batchDraw();
            }
        });

        const selectContainer = document.getElementById('select-container');
        const canvasContainer = document.getElementById('canvas-container');
        let imageSize = 1000;

        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);
        window.addEventListener('load', resizeCanvas);
        document.body.addEventListener('resize', function(event) {
            document.body.style.zoom = 1;
        });

        function resizeCanvas() {
            const screenWidth = window.innerWidth;
            const screenHeight = window.innerHeight;
            const spaceToBottom = window.innerHeight - (canvasContainer.offsetTop + canvasContainer.offsetHeight);
            const topElementsHeight = screenHeight;

            canvas.width = Math.round(imageSize);
            canvas.height = Math.round(imageSize);

            canvas.style.maxHeight = topElementsHeight + 'px';
            canvas.style.height = topElementsHeight - spaceToBottom + 'px';

            selectContainer.style.setProperty('width', Math.round(imageSize) + 'px', 'important');
            selectContainer.style.setProperty('height', Math.round(imageSize) + 'px', 'important');

            selectContainer.style.setProperty('max-height', topElementsHeight + 'px', 'important');
            selectContainer.style.setProperty('height', topElementsHeight - spaceToBottom + 'px', 'important');


            const newWidth = Math.round(imageSize);
            const newHeight = Math.round(imageSize);

            stage.width(newWidth);
            stage.height(newHeight);
            stage.batchDraw();

            redrawCanvas();
        }

        function redrawCanvas() {
            // Clear the main canvas and set the background to white
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = 'blue';
            ctx.fillRect(50, 50, 300, 200);
            ctx.fillStyle = 'orange';
            ctx.fillRect(400, 100, 200, 300);
        }
    </script>
  </body>
</html>

Cannot get seconds from Timestamp object from Firestore

I am creating a Todo app in Angular using Firestore as my backend. I am really new to Firebase and I am having trouble storing and fetching dates.

When I get the dates from firebase and console.log them i get something like this: date: _Timestamp { seconds: 1736290800, nanoseconds: 0 }

I understand that to get a human readable date out of this i need to access the seconds part of this and convert it. I have tried to use date.seconds and date.getSeconds() but .seconds gives me this error:

[ERROR] TS2339: Property ‘seconds’ does not exist on type ‘Date’. [plugin angular-compiler]

src/app/pages/todolist/todolist.component.ts:79:47:
  79 │         console.log("date: ",todo.deadlineDate.seconds);

And date.getSeconds() is not recognized as a function.

It seems like the date retrieved from firebase is not recognized as a Timestamp object.

I have tried making a Timestamp object of the variable using Timestamp.fromDate() but it did not work.

I am using Angular 18 with AngularFirestore and the dates are from user input with MatDatePickerModule and MatNativeDateModule and is stored as a Date in the object sent to firebase.

Appreciate if someone would have a solution to this.

Error Building .AAB File: Could Not Find com.facebook.react:react-native-gradle-plugin

I’m encountering an issue when trying to build the .aab file using the command:
./gradlew bundleRelease
The error message is as follows:

A problem occurred configuring root project 'ProjectName'.
> Could not resolve all files for configuration ':classpath'.
   > Could not find com.facebook.react:react-native-gradle-plugin:.
     Required by:
         project :

It says the build failed because it “could not resolve all files for configuration ‘:classpath'” and could not find com.facebook.react:react-native-gradle-plugin.

The component exists in my project and is specified as a dependency in the build.gradle. It is also applied correctly. Here is a sample of my build.gradle:

Path: C:\ProjectName\front\android\build.gradle

dependencies {
    classpath("com.android.tools.build:gradle:8.0.0")
    classpath("com.facebook.react:react-native-gradle-plugin")
    classpath("com.google.gms:google-services:4.3.15")
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0")
}

I have tried updating versions and making changes in both build.gradle and package.json as per recommended solutions, but the issue persists.
Here is the output of the npx react-native info command for reference:

info Fetching system and libraries information...
(node:8980) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
System:
  OS: Windows 11 10.0.26100
  CPU: (12) x64 Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
  Memory: 56.05 GB / 63.75 GB
Binaries:
  Node:
    version: 22.11.0
    path: C:Program Filesnodejsnode.EXE
  Yarn: Not Found
  npm:
    version: 10.9.0
    path: C:Program Filesnodejsnpm.CMD
  Watchman: Not Found
SDKs:
  Android SDK:
    API Levels:
      - "33"
      - "34"
      - "35"
    Build Tools:
      - 30.0.3
      - 34.0.0
      - 35.0.0
    System Images:
      - android-35 | Google Play Intel x86_64 Atom
    Android NDK: Not Found
  Windows SDK: Not Found
IDEs:
  Android Studio: AI-242.23339.11.2421.12550806
  Visual Studio: Not Found
Languages:
  Java: 17.0.13
  Ruby: Not Found
npmPackages:
  "@react-native-community/cli": Not Found
  react: Not Found
  react-native: Not Found
  react-native-windows: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: false
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Search within a string and stops immediately at the first found

I’m working with CSV files and need to check newline character.

This function works fine:

function detectNewlineCharacter(csvContent)
{
  if (csvContent.includes('rn')) {
    return 'rn'; // Windows-style CRLF
  } else if (csvContent.includes('n')) {
    return 'n'; // Unix-style LF
  } else if (csvContent.includes('r')) {
    return 'r'; // Old Mac-style CR
  } else {
    return null; // No recognizable newline characters found
  }
}

function fixCsv()
{
  // ...code...
  const newlineCharacter = detectNewlineCharacter(fileContent);
  const rows = fileContent.split(newlineCharacter);
  // ...code...

}

Problem:

csvContent is very large. I need a method that stops immediately at the first found, just like .some() for array. ChatGPT said .includes() stops at the first found, but I’m no sure, can’t find that in the documentation. So does .match(), .indexOf(), etc.

My last resort would be to limit the string using .substring() before searching.

Bubbling up mouse events outside of an IFrame

I have a transparent IFrame that covers the whole page (imagine an IFrame on top of the Google Maps). The IFrame has some UI elements (like menus) so I would like to be able to handle mouse events inside the IFrame in order to interact with the menus, but I want to bubble them up to the parent window if the events were not handled by the IFrame UI.

I am able to successfully pass events to the parent using messaging. I am also able to trigger the events using dispatchEvent, however some UI elements on the page don’t behave as expected. For instance, the text can’t be selected, the texbox doesn’t get the focus, the button is not visually clicked (event though it does get the click event).

Am I trying to do something impossible? Is there a way to trigger events “for real” as if they were caused by the user actions?

Here is he main page:

window.addEventListener('message', (event) => {

  let mouseEvent = new MouseEvent(event.type, event.data);
  let elements = document.elementsFromPoint(mouseEvent.x, mouseEvent.y);
  if (elements.length < 2) return;

  let element = elements[1];
  mouseEvent.currentTarget = element;
  mouseEvent.target = element;

  element.dispatchEvent(new MouseEvent(event.data.type, event));
});
<input type="text" id="text" />
        
<button onmousedown="console.log('clicked')">Click</button>

Some text

<iframe id="frame" style="position:absolute; top: 0px; left: 0px; width: 100%; height: 100%" src="iframe.html"></iframe>  

Here is the IFrame:

document.onmousedown = bubleUp;
document.onmouseenter = bubleUp;
document.onmouseleave = bubleUp;
document.onmousemove = bubleUp;
document.onmouseout = bubleUp;
document.onmouseover = bubleUp;
document.onmouseup = bubleUp;
document.ondblclick = bubleUp;
document.oncontextmenu = bubleUp;
document.onclick = bubleUp;

function bubleUp (event) 
{
    let pojo = {};

  // Strip off all the props that can't be serializaed into a mesasge
  for (let key in event)
  {
      let value = event[key];
      if (value instanceof Window || value instanceof Node || value instanceof InputDeviceCapabilities || typeof value === 'function')
          continue;

          pojo[key] = value;
  }

  sendMessageToParent(pojo);
}

function sendMessageToParent(data) {
  window.parent.postMessage(data, '*'); // Send message to parent window
}
<body style="width:100%; height: 100%;">

</body>

Change color of other slices on hover with Chart.js 4

I’m trying to change the color of other slices when hovering one of them, but is a little buggy sometimes and I’m trying to understand why.

As shown in the example, there is a flickering when hovering from one slice to another and the colors don’t change right away, and also when leaving the slice the colors should reset, but it doesn’t work sometimes.

const colors = ["red", "green", "blue"];
const colorsRGBA = ["rgba(255,0,0,.25)", "rgba(0,255,0,.25)", "rgba(0,0,255,.25)"];

new Chart(document.getElementById("doughnut"), {
  type: "doughnut",
  data: {
    labels: ["A", "B", "C"],
    datasets: [
      {
        data: [1, 2, 3],
        backgroundColor: ["red", "green", "blue"],
        hoverBackgroundColor: ["red", "green", "blue"]
      }
    ]
  },
  options: {
    plugins: {
      tooltip: {
        enabled: false
      },
      legend: {
        display: false
      }
    },
    onHover(event, elements, chart) {
      if (event.type === "mousemove") {
        if (elements.length) {
          chart.getDatasetMeta(0).data.forEach((data, index) => {
            if (index !== elements[0].index) {
              data.options.backgroundColor = colorsRGBA[index];
            }
          });
        } else {
          chart.getDatasetMeta(0).data.forEach((data, index) => {
            data.options.backgroundColor = colors[index];
          });
        }
      }
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js"></script>
<div style="height: 350px">
  <canvas id="doughnut"></canvas>
</div>

Array of specific Object in Javascript

I am working on a Vue.js application where I am managing a list of clients. I want to ensure I am applying Object-Oriented Programming (OOP) concepts by using a Client class for each client in my array. However, my clients array is currently just a plain array of objects, not instances of a Client class.

Here is what I am trying to achieve:

I want to fetch the client data from an API.
After fetching, I would like to instantiate a Client class for each client in the response data, so that each client has methods and properties defined in the class.
Currently, my clients array is just an array of plain JavaScript objects, not instances of the Client class.
Here’s what I have so far:

export default class Client {
  constructor(id, cin, nom, prenom) {
    this.id = id;
    ****

  }

}

Fetching data and mapping it to the Client class:

import Client from './Client';
import clientService from '../services/clientService';

export default {
  data() {
    return {
      clients: [],
    };
  },
  created() {
    this.fetchClients();
  },
  methods: {
    fetchClients() {
      clientService.findAll()
        .then(response => {
          // Mapping the raw data to instances of the Client class
          this.clients = response.data.map(clientData => new Client(clientData.id, clientData.cin, clientData.nom, clientData.prenom));
        })
        .catch(error => {
          console.error('Error fetching clients:', error);
        });
    },
  },
};

After calling this.clients = response.data.map(...), I expected the clients array to contain instances of the Client class. However, it seems to be a simple array of plain JavaScript objects.

In java for example , I can specify the type of instances that I will be storing inside my array. In JavaScript, we typically don’t enforce class-based structures for simple data models.

I mean why not :

clients: [] of Clients

Sorry if the question sounds dumb because everything is working fine , but I need some clarification.

Vue 2 to 3 upgrade: simplifying checkbox question component

I have a checkbox form question component I made in Vue 2 and i’m migrating to Vue 3 so want to simplify it as much as possible and make sure i’ve taken advantage of all the new feature of Vue 3.

After a lot of trial and error i’ve managed to get it to work, but have I over complicated it?

The component should take a question using v-slot and answers in the ‘options’ array prop.
It should let the user select as many answers as applicable but if they select ‘none’ it should clear all other answers.
If they have ‘none’ selected then they select another answer it should clear ‘none’ so it isn’t checked anymore.

Parent component

<template>
    <div>
        <h2>title</h2>

        <InputCheckboxGroup
            name="question_one"
            :required="checkIsRequired('question_one')"
            :error="formRef.errors.get('question_one')"
            v-model="formRef.question_one"
            :options="getField('question_one')['options']"
            @update:modelValue="handleAffectedByInput"
        >
            <template v-slot:question>
                {{ getField("question_one").question }}
            </template>
        
        </InputCheckboxGroup>

    </div>
</template>

<script setup>
import InputCheckboxGroup from "../Form/InputCheckboxGroup";
import Form from "../../../../Form";
import { isRequired } from "./formHelper.js";
import { ref, markRaw, defineExpose } from "vue";

const props = defineProps({
    step: Object,
    fields: Object,
    formData: Object
})


let formRef = markRaw(ref(new Form({
    question_one: []
}, props.formData)))

const getField = (fieldName) => {
   return props.fields[fieldName];
}

const checkIsRequired = (fieldName) => {
   var fieldData = getField(fieldName);
   return isRequired(fieldData, formRef.value);
}

function handleAffectedByInput(values) {
    if (values.length && (values[values.length - 1] === 'none')) {
        formRef.question_one = [values[values.length - 1]];
        return;
    }
   clearCheckboxValues(['none'], values, 'question_one');
}

function clearCheckboxValues(previousAnswers, values, formField) { 
    for (const answer of previousAnswers) {
        if (values.includes(answer)) {
            formRef.value[formField] = values.filter(value => value !== answer);
        }
    }
}

defineExpose({
 formRef
})
</script>

Child/Checkbox questionn componet

<template>
    <div class="form-group">
        <fieldset :aria-describedby="name">
            <legend>
                <p v-if="$slots.question" class="input__question">
                    <slot name="question"></slot>
                </p>
            </legend>
            <div
                class="input_checkbox"
                v-for="opt in options"
                v-bind:key="opt.value"  
            >           
                <label v-if="opt.value == 'none'">
                    <input
                        type="checkbox"
                        value="none"
                        v-model="model"
                        @click="onCheckboxChange"
                    />
                    <span>None</span>
                </label>    
                <div v-else class="form-group form-check">
                    <input  
                        type="checkbox"
                        class="form-check-input"
                        :value="opt.value"
                        @click="onCheckboxChange"
                        v-model="model"
                    />
                    <label :for="opt.value" class="form-check-label">
                        {{ opt.label }}
                    </label>    
                </div>
            </div>
        </fieldset>
    </div>
</template>
  
<script setup>
import { defineModel, defineEmits } from "vue";
const props = defineProps({
    error: String,
    name: String,
    options: Array,
    required: Boolean
});

const model = defineModel()

const emit = defineEmits(['update:modelValue'])

function optionIsChecked (value) {
    return model.value.includes(value);
}

function onCheckboxChange($event) {
    var previouslySelected = model.value || [];
    var newValue = [];
    if ($event.target.checked) {
        newValue = [...previouslySelected, $event.target.value];
    } else {
        newValue = previouslySelected.filter(
            (x) => x != $event.target.value
        );
    }
    
    if ($event.target.value === 'none' || $event.target.value === 'involved_none_above') {
        newValue = [$event.target.value];
    }
    
    model.value = newValue
    
    emit("update:modelValue", newValue);
}

</script>

PrimeVue DataTable Lazy Loading not working with API calls

I’m implementing lazy loading with virtual scrolling in a PrimeVue DataTable. The data should be dynamically fetched from an API as the user scrolls. While the initial data fetch (first 25 accounts) works and displays correctly in the table, subsequent data loaded on scroll does not appear in the UI, even though the virtualAccounts array updates correctly and matches the expected length.

I also checked the official PrimeVue documentation about lazy loading and virtual scrolling (https://primevue.org/datatable/#lazy_virtualscroll) and tried to follow everything there, but still, it doesn’t work.

Due to the size of my project (over 22,000 lines of code) and the fact that the relevant module is nearly 1,000 lines long, I can only share the essential portions of my implementation:

DataTable configuration:

<DataTable
  dataKey="ID"
  ref="table"
  :value="virtualAccounts"
  scrollable
  scroll-height="calc(100vh - 23rem)"
  :virtualScrollerOptions="{
    lazy: true,
    onLazyLoad: loadAccountsLazy,
    itemSize: 46,
    delay: 200,
    showLoader: true,
    loading: lazyLoading,
    numToleratedItems: 10,
  }"
/>

LazyLoading logic:

const virtualAccounts = ref(
  Array.from({ length: 100 }, () => ({
    ID: null,
    Rating: 0,
    CompanyName: "",
    Remark: "",
    TradeRegisterNumber: "",
    CreditLimit: 0,
    Revenue: 0,
    FoundationYear: null,
    Employees: 0,
    VatID: "",
    CreatedAt: "",
    UpdatedAt: "",
  }))
);

const lazyLoading = ref(false);

const fetchAccountsOnDemand = async (first, last) => {
  try {
    const response = await axios.get("/api/data/list", {
      params: { first, last },
    });
    return response.data || { data: [] };
  } catch (error) {
    console.error("Error fetching data:", error);
    return { data: [] };
  }
};

const loadAccountsLazy = async (event) => {
  lazyLoading.value = true;

  try {
    const { first, last } = event;
    const response = await fetchAccountsOnDemand(first, last);

    if (Array.isArray(response.data)) {
      const _virtualAccounts = [...virtualAccounts.value];

      for (let i = first; i < last; i++) {
        _virtualAccounts[i] = { ID: null, Rating: 0, CompanyName: "", Remark: "", TradeRegisterNumber: "", CreditLimit: 0, Revenue: 0, FoundationYear: null, Employees: 0, VatID: "", CreatedAt: "", UpdatedAt: "" };
      }

      const processedData = response.data.map((account) => ({
        ID: account.ID || null,
        Rating: account.Rating || 0,
        CompanyName: account.CompanyName || "",
        Remark: account.Remark || "",
        TradeRegisterNumber: account.TradeRegisterNumber || "",
        CreditLimit: account.CreditLimit || 0,
        Revenue: account.Revenue || 0,
        FoundationYear: account.FoundationYear || null,
        Employees: account.Employees || 0,
        VatID: account.VatID || "",
        CreatedAt: account.CreatedAt || "",
        UpdatedAt: account.UpdatedAt || "",
      }));

      _virtualAccounts.splice(first, processedData.length, ...processedData);
      virtualAccounts.value = JSON.parse(JSON.stringify(_virtualAccounts));
    }
  } catch (error) {
    console.error("Error during lazy loading:", error);
  } finally {
    lazyLoading.value = false;
  }
};

Initial data fetch:

onMounted(async () => {
  try {
    const initialData = await fetchAccountsOnDemand(0, 25);

    if (Array.isArray(initialData.data)) {
      const processedData = initialData.data.map((account, i) => ({
        ID: account.ID || i++,
        Rating: account.Rating || 0,
        CompanyName: account.CompanyName || "",
        Remark: account.Remark || "",
        TradeRegisterNumber: account.TradeRegisterNumber || "",
        CreditLimit: account.CreditLimit || 0,
        Revenue: account.Revenue || 0,
        FoundationYear: account.FoundationYear || null,
        Employees: account.Employees || 0,
        VatID: account.VatID || "",
        CreatedAt: account.CreatedAt || "",
        UpdatedAt: account.UpdatedAt || "",
      }));

      virtualAccounts.value = Array(100).fill({
        ID: null,
        Rating: 0,
        CompanyName: "",
        Remark: "",
        TradeRegisterNumber: "",
        CreditLimit: 0,
        Revenue: 0,
        FoundationYear: null,
        Employees: 0,
        VatID: "",
        CreatedAt: "",
        UpdatedAt: "",
      });
      virtualAccounts.value.splice(0, processedData.length, ...processedData);

      console.log("MOUNT:", virtualAccounts.value);
    }
  } catch (error) {
    console.error("Error during initial data fetch:", error);
  }
});

To troubleshoot further, I even tested the virtual scrolling functionality with a testing component where the data is generated entirely in the frontend (no backend calls). In that case, the virtual scrolling works perfectly, suggesting the issue lies somewhere in the integration with the backend-fetching logic or how the virtualAccounts array is updated/reactive.

Working example:

<template>
  <div class="card">
    <DataTable
      :value="virtualCars"
      scrollable
      scrollHeight="500px"
      tableStyle="min-width: 50rem"
      :virtualScrollerOptions="{
        lazy: true,
        onLazyLoad: loadCarsLazy,
        itemSize: 46,
        delay: 200,
        showLoader: true,
        loading: lazyLoading,
        numToleratedItems: 10,
      }"
    >
      <Column field="id" header="Id" style="width: 20%">
        <template #loading>
          <div
            class="flex items-center"
            :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }"
          >
            <Skeleton width="60%" height="1rem" />
          </div>
        </template>
      </Column>
      <Column field="vin" header="Vin" style="width: 20%">
        <template #loading>
          <div
            class="flex items-center"
            :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }"
          >
            <Skeleton width="40%" height="1rem" />
          </div>
        </template>
      </Column>
      <Column field="year" header="Year" style="width: 20%">
        <template #loading>
          <div
            class="flex items-center"
            :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }"
          >
            <Skeleton width="30%" height="1rem" />
          </div>
        </template>
      </Column>
      <Column field="brand" header="Brand" style="width: 20%">
        <template #loading>
          <div
            class="flex items-center"
            :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }"
          >
            <Skeleton width="40%" height="1rem" />
          </div>
        </template>
      </Column>
      <Column field="color" header="Color" style="width: 20%">
        <template #loading>
          <div
            class="flex items-center"
            :style="{ height: '17px', 'flex-grow': '1', overflow: 'hidden' }"
          >
            <Skeleton width="60%" height="1rem" />
          </div>
        </template>
      </Column>
    </DataTable>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";

const cars = ref([]);
const virtualCars = ref(Array.from({ length: 100000 }));
const lazyLoading = ref(false);
const loadLazyTimeout = ref();

// Generate car data
const generateCar = (id) => {
  const brands = ["Toyota", "Ford", "BMW", "Honda", "Mazda"];
  const colors = ["Red", "Blue", "Green", "Black", "White"];

  return {
    id,
    vin: `VIN${id}`,
    year: 2000 + (id % 23), // Random year from 2000 to 2023
    brand: brands[id % brands.length],
    color: colors[id % colors.length],
  };
};

onMounted(() => {
  cars.value = Array.from({ length: 100000 }).map((_, i) => generateCar(i + 1));
});

const loadCarsLazy = (event) => {
  !lazyLoading.value && (lazyLoading.value = true);

  if (loadLazyTimeout.value) {
    clearTimeout(loadLazyTimeout.value);
  }

  // Simulate remote connection with a timeout
  loadLazyTimeout.value = setTimeout(() => {
    let _virtualCars = [...virtualCars.value];
    let { first, last } = event;

    console.log(event);

    // Load data of required page
    const loadedCars = cars.value.slice(first, last);

    // Populate page of virtual cars
    Array.prototype.splice.apply(_virtualCars, [
      ...[first, last - first],
      ...loadedCars,
    ]);

    virtualCars.value = _virtualCars;
    lazyLoading.value = false;
  }, Math.random() * 1000 + 250);
};
</script>

Issue:

  1. Initial Load: The first 25 records fetch and display correctly.

  2. Subsequent Loads: When scrolling to load more data, the virtualAccounts array updates as expected, but the UI does not reflect the changes.

Troubleshooting steps:

  1. Verified that the virtualAccounts array updates correctly with the fetched data.

  2. Confirmed that the DataTable renders correctly when using a frontend-only example with generated data.

  3. Ensured I followed the PrimeVue documentation for lazy loading and virtual scrolling.

Questions:

  1. Are there any specific steps missing in my implementation based on the PrimeVue guidelines?

  2. Could the issue be related to how the virtualAccounts array is updated or its reactivity?

Loki and Storybook – run the visual regression test/snapshot with only one story

Configuration:

  • Storybook version: 8
  • Stencil.js
  • Loki version: 0.35.1

I have a storybook with a lot of stories, and I want to run visual regression tests with Loki to test with only one story. It run in the following local address:
http://localhost:6006/?path=/story/project-base-mybutton--default

I have this configuration in my loki.config.js:

module.exports = {
  captureAllStories: false,
  storybookUrl: 'http://localhost:6006',
  configurations: {
    'chrome.desktop': {
      target: 'chrome.app',
      width: 1200,
      height: 800,
      storyFilter: (story) => {
        // I already tried this:
        return story.id === 'project-base-mybutton--default';

        // And this:
        return story.kind === 'Project/Base/MyButton';

        // And this:
        return story.kind.toLowerCase() === 'charter/base/mybutton' && story.name.toLowerCase() === 'default';
      },
    },
  },

  // Also tried this:
  customStories: {
    'individual-story': {
      url: 'http://localhost:6006/?path=/story/project-base-mybutton--default'
    }
  },
  // Running: npx loki update --storiesFilter "individual-story"

  // And this:
  stories: {
    'individual-story': {
      storyUrl: 'http://localhost:6006/?path=/story/project-base-mybutton--default'
    }
  },

  // And this:
  storiesFilter: 'project-base-mybutton--default',

  // And this:
  scenarios: [
    {
      label: 'individual-story',
      url: 'http://localhost:6006/?path=/story/project-base-mybutton--default',
    },
  ],

  verboseRenderer: true,
  reportOnFailure: true,
};

The code runs, but it screenshoots all of the stories. I want to take a screenshot only of the story project-base-mybutton--default. Does anyone know how to configure this?

Also, is there anyway I can place the loki configuration file in any other directory that is not in the root of the project? How would the command look like?

Best way to refresh a child React component when props don’t change

I have a Dashboard React component (parent) which contains N widget React components (children).

Each child has a widgetId as the input which uses to call the back-end to get the data it needs to render the widget.

The dashboard has a date parameter (synced and stored in the back-end database) which the user can change. When this changes, I need to re-render all the child widgets as their data will change.

But from a child perspective, their widgetId remains the same (even though on the DB side, date has changed).

How can I tell those child components to re-render themselves without passing the date directly to them?

The reason I prefer not to pass the date parameter to each child is that in the code there are many more parameters that can cause a refresh and passing all of those items to each child component makes my code messy.

WHY I can’t use await inside normal function, but I can use it at top level?

What is the point of this design? It makes no sense to me.

If we are allowed to use await on the top level, we should also be able to use await inside a normal function. Yes I know it will make the function blocking, which is exactly the desired behaviour.

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

// works
print("bef");
await sleep(1000); // blocking
print("aft");
async function asyncsleep(ms) {
    return new Promise((resolve) => {
        setTimeout(resolve, ms);
    });
}

// works
print("bef");
await asyncsleep(1000); // blocking
print("aft");