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");

How to resolve duplicate country code and automatic formatting issues with ngx-intl-tel-input in Angular?

I’m using the ngx-intl-tel-input library in my Angular project to handle phone number input fields. I’ve created a reusable component for the phone number field, and I am passing a FormControl from the parent component to it.

Here is my code:

component.ts

import { Component, Input } from '@angular/core';
import { FormControl } from '@angular/forms';
import { CountryISO, PhoneNumberFormat, SearchCountryField } from 'ngx-intl-tel-input';

@Component({
  selector: 'app-new-phone-no-field-v1',
  templateUrl: './new-phone-no-field-v1.component.html',
  styleUrl: './new-phone-no-field-v1.component.css'
})
export class NewPhoneNoFieldV1Component {
  @Input() control!: FormControl;
  @Input() separateDialCode: boolean = false;
  @Input() maxLength: number = 15;
  @Input() phoneValidation: boolean = true;

  preferredCountries: CountryISO[] = [
    CountryISO.UnitedStates,
    CountryISO.UnitedKingdom,
    CountryISO.India,
    CountryISO.Australia,
    CountryISO.Philippines,
    CountryISO.Thailand,
    CountryISO.SouthAfrica,
    CountryISO.Panama,
    CountryISO.Mexico,
    CountryISO.Indonesia,
    CountryISO.Canada,
    CountryISO.DominicanRepublic,
  ];
  searchCountryField: SearchCountryField[] = [
    SearchCountryField.Iso2,
    SearchCountryField.Name
  ];
  phoneNumberFormat: PhoneNumberFormat = PhoneNumberFormat.National;
  selectedCountryISO: CountryISO = CountryISO.UnitedStates;
}

Component.html

<div class="intl-tel-input-wrapper">
    <ngx-intl-tel-input
      [cssClass]="'custom'"
      [onlyCountries]="preferredCountries"
      [enableAutoCountrySelect]="true"
      [enablePlaceholder]="true"
      [searchCountryFlag]="true"
      [searchCountryField]="searchCountryField"
      [selectFirstCountry]="false"
      [selectedCountryISO]="selectedCountryISO"
      [maxLength]="maxLength"
      [phoneValidation]="phoneValidation"
      [separateDialCode]="true"
      [numberFormat]="phoneNumberFormat"
      name="phone"
      [formControl]="control">
    </ngx-intl-tel-input>
    <div *ngIf="control.touched && control.invalid" class="error-messages">
      <div *ngIf="control.errors?.required">Phone number is required.</div>
      <div *ngIf="!control.errors?.validatePhoneNumber?.valid">Invalid phone number.</div>
    </div>
  </div>
  

Parent.component.html

<app-new-phone-no-field-v1 
  [control]="$any(userForm.get('phone'))"
  [separateDialCode]="true">
</app-new-phone-no-field-v1>

<div class="error error-msg">
  <div *ngIf="f.phone.errors?.required">
    Phone Number is required.
  </div>
  <div *ngIf="f.phone.errors?.pattern">
    Phone Number is invalid.
  </div>
</div>

API Data:
The API returns phone numbers in the format: +919876543210. I’m patching this data to the form using:

this.userForm.patchValue({ phone: data.phone });

Issues:

  • On the UI, the country code appears twice: once in the dial code
    dropdown and again in the phone number field. Example: +91 appears in
    the dropdown and +919876543210 appears in the input field.

  • The phone number is not formatted automatically based on the selected
    country. Example: When I select the US as the country, it doesn’t
    format the number as (201)-555-1234.

Attempts:

  • I’ve tried setting the value property of the FormControl explicitly.
  • Ensured ngx-intl-tel-input configuration matches the documentation.

How can I fix these issues so that:

  • The country code does not appear twice.
  • The phone number is automatically formatted based on the selected country?
    Any guidance would be appreciated!

Image1

Image2

What are the differences between a TreeWalker and a NodeIterator?

Both a TreeWalker object and a NodeIterator object can be used to traverse a DOM tree starting from a given node. The APIs to use them, and even the APIs to create them, createTreeWalker and createNodeIterator look virtually identical. Both seem about equally old and neither seems to be deprecated in favour of the other.

JavaScript is hardly a stranger to duplicative, redundant APIs, but this one seems a pretty bizarre instance. Are there any meaningful differences between them, or is it just a historical accident that both exist?

Jest partial mocking from the same file

I am getting crazy on mocking with Jest. I feel that I have a fairly simple case that I cannot get to work.

I have a service extensions.service with a bunch of functions:

async function getSplitCost() {
  // Check if feature is enabled for the customer
  const isEnabled = await isExtensionFeatureEnabled().catch(err => {
    throw new Error(err);
  });

  return {
    split_cost: [],
    feature_enabled: isEnabled
  }
}

async function isExtensionFeatureEnabled(): Promise<boolean> {
  console.log('Function is executed');
  let serviceStatus = await servicesService.getServiceStatus('service1').catch( err => {
    throw new Error(err.message);
  });
  
  return serviceStatus;
}

export default {
  getSplitCost,
  isExtensionFeatureEnabled,
};

I now want to create a test case that should validate the function getSpitCost and mock the response from isExtensionFeatureEnabled without running the function. Example that I have tried below:

jest.mock('../services/extensions.service', () => ({
  __esModule: true,
  ...jest.requireActual('../services/extensions.service'),
  isExtensionFeatureEnabled: jest.fn().mockResolvedValue(false),
}));
import extensionsService from "../services/extensions.service";


describe("getSpitCost", () => {
  it("should return empty split cost when extension feature is disabled", async () => {
    const result = await extensionsService.getSplitCost();

    expect(result).toEqual({
      split_cost: [],
      feature_enabled: false
    });
  });
});

As I am not calling the function isExtensionFeatureEnabled directly I cannot use spyOn. However, even though I mock the response to false the actual function is being executed. The extension service contains a lot of more functions that are being tested in the same test file, so I cannot mock everything. Also, I cannot split up the two functions to separate files.

How should I handle this?

GSAP SplitText Breaking Words Instead of Lines

I am using GSAP’s SplitText utility to animate text by splitting it into lines, but instead of splitting by lines, it is splitting by words. My goal is to animate the h2 text line-by-line, but it seems like SplitText is not detecting the lines properly.

Here is my JavaScript code:

function animateTextF() {
  document.querySelectorAll(".animate-title-body").forEach(parentElement => {
    const h2Element = parentElement.querySelector(".h2-slide");
    const pElements = [...parentElement.querySelectorAll(".fade-in-p")];
    const btnElement = parentElement.querySelector(".button-1") || parentElement.querySelector(".button-2");

    const splitText = new SplitText(h2Element, {
      type: "lines" // Expecting this to split the text into lines
    });

    const timeline = gsap.timeline({
      paused: true
    });

    if (pElements.length > 0 && btnElement) {
      timeline
        .fromTo(splitText.lines, {
          opacity: 0,
          y: '100%'
        }, {
          opacity: 1,
          y: 0,
          duration: 0.7,
          ease: "expo.out",
          stagger: 0.2
        })
        .fromTo(pElements, {
          opacity: 0
        }, {
          opacity: 1,
          duration: 2,
          ease: "sine.in",
          stagger: 0.2
        })
        .fromTo(btnElement, {
          opacity: 0
        }, {
          opacity: 1,
          duration: 1,
          ease: "sine.in"
        }, 0);
    } else if (pElements.length > 0) {
      timeline
        .fromTo(splitText.lines, {
          opacity: 0,
          y: '100%'
        }, {
          opacity: 1,
          y: 0,
          duration: 0.7,
          ease: "expo.out",
          stagger: 0.2
        })
        .fromTo(pElements, {
          opacity: 0
        }, {
          opacity: 1,
          duration: 2,
          ease: "sine.in",
          stagger: 0.2
        });
    } else {
      timeline
        .fromTo(splitText.lines, {
          opacity: 0,
          y: '100%'
        }, {
          opacity: 1,
          y: 0,
          duration: 1,
          ease: "expo.out",
          stagger: 0.2
        });
    }

    ScrollTrigger.create({
      trigger: parentElement,
      start: "top 75%",
      end: "bottom top",
      scrub: true,
      invalidateOnRefresh: true,
      onEnter: () => timeline.play(),
      onEnterBack: () => timeline.play(),
      onLeave: () => timeline.reverse(),
      onLeaveBack: () => timeline.reverse(),
    });
  });
}
animateTextF();
<div class="animate-title-body">
  <h2 class="oversized">
    aurora
  </h2>
  <h3 class="h2-slide">
    A naturally occurring phenomenon crafted into a premium layer of protection.
  </h3>
  <p class="fade-in-p">
    Charged with cosmic greens and blues, the Aurora's carbon shell is left looking raw and rich as you flash over the horizon in a wavelength of colour usually blind to the naked eye.
  </p>
  <a href="{{store direct_url='eox-aurora'}}">
    <div class="button-2">
      <div class="p2">SHOP NOW</div>
    </div>
  </a>
</div>

HTML audio element currentTime property too high on mobile

I am developing a website with VoIP. I am setting the srcObj property of an audio element with the MediaStream of the user at the other end of the line (using PeerJS). I am also showing the time since the start of the call by displaying the currentTime property of the audio element every second.

This works fine on desktop browsers, but on mobile browsers (specifically Chrome on iOS), the currentTime property is way too high. Like it is equal to several hours when the call has just started.

Has anyone also experienced this or knows why this happens?

How to get image metadata into useChat within Svelte?

I am writing a ChatBot using Svelte and am using the useChat function to interface with my backend to talk to an LLM.

<script lang="ts">
    import ChatList from '$lib/components/ChatList.svelte';
    import PromptForm from '$lib/components/PromptForm.svelte';
    import { useChat, type Message } from 'ai/svelte';
    import { API_BASE_URL } from '$lib/config';

    export let id: string | undefined;
    export let initialMessages: Message[] | undefined;

    const { messages, append, isLoading, input } = useChat({
        initialMessages,
        id,
        api: `${API_BASE_URL}/api/chat`,
    });

    let tempUserInput = '';

    async function handleFinished() {
        await append({ id, content: tempUserInput, role: 'user' });
        tempUserInput = '';
    }
</script>

<div class="flex flex-col min-h-screen bg-background">
    <div class="flex-1 overflow-y-auto pt-6">
        <ChatList {messages} />
    </div>

    <div class="fixed inset-x-0 bottom-0 bg-metallic-gradient">
        <div class="mx-auto sm:max-w-2xl sm:px-4 flex gap-2">
            <div
                class="space-y-4 border-t bg-background px-4 py-2 shadow-subtle sm:rounded-t-xl sm:border md:py-4 flex gap-2 items-center grow"
            >
                <PromptForm on:submit={handleFinished} {input} {isLoading} />
            </div>
        </div>
    </div>
</div>

This is working fine for just chats, but sometimes I want to be able to get back images also from the LLM. According to the documentation, I think I can use experimental_attachments for this.

But I am not sure what to return from my backend to get the information about additional attachments into this experimental_attachments field.

Right now, I am returning a string, and that automatically seem to go inside the message.content field.

I tried returning a json instead that looks like this –

{
    'content': self.settings['dev_test_string'],
    'role': 'assistant',
    'experimental_attachments': {
        'content_type': 'img',
        'url': 'some_string'
    }
}

But this parses the whole of the return json into the message.content field. What should I return from the backend so that I have more control over what goes into the message object?

ReactJS strange state behaviour

I have two components: legacy parent class component, let’s call it RowComponent, and child ModalComponent. State of those modal is located in parent component:

this.state = {
  modalIsOpen: false,
};

When I click on button it executes function which changes modalIsOpen state to true and modal is popped up. So far so good.

showRequestModal() {
  this.setState({ modalIsOpen: true });
}

Inside my child component I have isOpen state which is property that relies on modalIsOpen

<Modal
  width={600}
  destroyOnClose
  open={isOpen}
  onCancel={hideModalHandler}
/>

hideModalHandler is function that passed as property like this:

hideModalHandler={this.hideRequestModal}

That’s how my hideRequestModal looks like (it bind properly):

hideRequestModal() {
  console.log('Executing hideRequestModal');
  this.setState({ modalIsOpen: false }, () => {
    console.log('callback - modalIsOpen:', this.state.modalIsOpen);
  });
}

The real magic (or what I would say my lack of knowledge) starts here. When I try to close my modal from child component I can see text Executing hidRequestModal without changing the state (which I see from the callback). But the most bizarre thing is when I click ESC button on modal it closes (and state is updating). So my question is what the hell is going on and how to close modal on click also, not by clicking Escape key. So I suppose there is some conflicts in events or something like that. I consider rewriting parent component to be function component and maybe it will solve the issue but I don’t know. Appreciate any help

Uncaught TypeError: Cannot set properties of undefined (setting ‘_DT_CellIndex’) for binding values

I have below html for which I am getting error as

Uncaught TypeError: Cannot set properties of undefined (setting ‘_DT_CellIndex’)

function dispalySignOffSheetFTTX(ReportType, Month, DataList) {
var _reportType = (ReportType == 'ALL') ? "PAN INDIA" : ReportType;
var _month = Month;
var table = $('#grdCicleDatatable');
$(table).empty();
var thead = "";
var datalist = JSON.parse(DataList);
if (ReportType == 'ALL') {        
    thead = "<thead>< tr ><th rowspan='2' class='text-left'>Maintenance Zone</th><th colspan='3'>FTTX</th><th colspan='3'>Grand Total</th></tr><tr><th>UG</th><th>Aerial</th><th>MDU</th><th>UG</th><th>Aerial</th><th>MDU</th></tr></thead >";
}

var tbody = "<tbody>";
table.append(thead);
table.append(tbody);
if (datalist != null && datalist.length > 0) {
    var grandTotalUG = 0;
    var grandTotalAR = 0;
    var grandTotalMDU = 0;
    $.each(datalist, function (key, val) {

        val.NE_LENGTH = val.NE_LENGTH == null ? 0 : parseFloat(val.NE_LENGTH);
        val.UG_LENGTH = val.UG_LENGTH == null ? 0 : parseFloat(val.UG_LENGTH);
        val.AR_LENGTH = val.AR_LENGTH == null ? 0 : parseFloat(val.AR_LENGTH);
        val.MDU_LENGTH = val.MDU_LENGTH == null ? 0 : parseFloat(val.MDU_LENGTH);

        grandTotalUG = val.UG_LENGTH;
        grandTotalUG = grandTotalUG.toFixed(3);
        grandTotalAR = val.AR_LENGTH;
        grandTotalAR = grandTotalAR.toFixed(3);
        grandTotalMDU = val.MDU_LENGTH;
        grandTotalMDU = grandTotalMDU.toFixed(3);

        var tr = "<tr id='" + val.ITEM + "'><td> " + val.ITEM + "</td><td class='text-center'> " + val.UG_LENGTH + "</td><td class='text-center'> " + val.AR_LENGTH + "</td><td class='text-center'> " + val.MDU_LENGTH + "</td><td class='text-center'> " + grandTotalUG + "</td><td class='text-center'> " + grandTotalAR + "</td><td class='text-center'> " + grandTotalMDU + "</td></tr>";

        table.append(tr);
    });

    table.append('</tbody>');
}

//var footer = "<tfoot><tr><th colspan='1' style='text-align:center'><b>Total:</b></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th></tr></tfoot>";

var footer = "<tfoot><tr><th colspan='1' style='text-align:center'><b>Total:</b></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th><th class='text-center'></th></tr></tfoot>";

table.append(footer);

oTable = $(table).dataTable({
    dom: 'tp',
    "dom": 'tp<"bottom"B><"clear">',
    "searching": false,
     responsive: true,
    "autoWidth": true,
    "bDestroy": true,
    "pageLength": 6,
    paging: false,
    "columnDefs": [
        { "width": "38.4%", "targets": 0 },
        { "width": "7.7%", "targets": 1 },
        { "width": "7.7%", "targets": 2 },
        { "width": "7.7%", "targets": 3 },
        { "width": "7.7%", "targets": 4 },
        { "width": "7.7%", "targets": 5 },
        { "width": "7.7%", "targets": 6 },
        { "width": "7.7%", "targets": 7 }

    ],
    buttons: [            
        {
            "extend": "excelHtml5", "text": "Export to Excel", "filename": _reportType + "_Fttx_SignOffSheet_" + _month,
            title: 'Sign Of Sheet of ' + _reportType + ' Circle for ' + _month + ' Month',
            messageBottom: '',
            exportOptions: {
                columns: ':visible',
                format: {
                    header: function (data, columnindex, trDOM, node) {                            
                        return GetColumnPrefixFTTX(columnindex) + data;
                    }
                }
            }
        }
    ],
    "footerCallback": function (row, data, start, end, display) {
        var api = this.api(), data;

        // converting to interger to find total
        var intVal = function (i) {
            return typeof i === 'string' ?
                i.replace(/[$,]/g, '') * 1 :
                typeof i === 'number' ?
                i : 0;
        };

        // computing column Total of the complete result 
        var FTTXUGTotal = api
            .column(1)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var FTTXARTotal = api
            .column(2)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var FTTXMDUTotal = api
            .column(3)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var TotFTTXUGTotal = api
            .column(4)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var TotFTTXARTotal = api
            .column(5)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        var TotFTTXMDUTotal = api
            .column(6)
            .data()
            .reduce(function (a, b) {
                return intVal(a) + intVal(b);
            }, 0).toFixed(2);

        // Update footer by showing the total with the reference of the column index 
        $(api.column(0).footer()).html('Total');
        $(api.column(1).footer()).html(FTTXUGTotal);
        $(api.column(2).footer()).html(FTTXARTotal);
        $(api.column(3).footer()).html(FTTXMDUTotal);
        $(api.column(4).footer()).html(TotFTTXUGTotal);
        $(api.column(5).footer()).html(TotFTTXARTotal);
        $(api.column(6).footer()).html(TotFTTXMDUTotal);
    },
    initComplete: function () {
        var btns = $('.dt-button');
        btns.addClass('btn btn-danger button');
        btns.removeClass('dt-button');


    }

});

if (CurrentGroupName == UserGrouop.NHQPMO) {
    $('.buttons-pdf').css("display", "none");
} else {
    $('.buttons-excel').css("display", "none");

}

}