Replacing special characters and normalization works differently on different environments

I have the following function:

  normalize = (input) => {
    return input
      .toLowerCase()
      .normalize('NFD')
      .replace(/[u0300-u036f]/g, '')
      .replace(/ı/g, "i");
  }

and I use it as follows:

let places = [{
    "name": "haci osman"
}, {
    "name": "sample 2"
}, {
    "name": "sample 3"
}];

let candidate = {
    "placeName": "Hacı osman"
};

let exists = (places|| []).some(place=> this.normalize(candidate?.placeName).includes(this.normalize(place.name)));

items and candidate are fethed from API. This works perfectly on my local machine, but fails on the customer environment. At this point, I’m too blind to figure out what I’m missing. I expect the exists variable to be equal to true on the server/customer environment as well, but it is not. Why?

  normalize = (input) => {
    return input
      .toLowerCase()
      .normalize('NFD')
      .replace(/[u0300-u036f]/g, '')
      .replace(/ı/g, "i");
  }


let places = [{
    "name": "haci osman"
}, {
    "name": "sample 2"
}, {
    "name": "sample 3"
}];

let candidate = {
    "placeName": "Hacı osman"
};

let exists = (places|| []).some(place=> this.normalize(candidate?.placeName).includes(this.normalize(place.name)));
console.log(exists);

How do I use the new Google Places API’s autocomplete?

I am mantaining a website with a distance calculator, that has 2 text fields that use Google’s Autocomplete to select two locations. I am trying to migrate it to the new Place API and to make it use sessions for the autocomplete feature.

Now, from what I have been told, if you use Google’s JS widget, it should be using the new Places API automatically, but in my case it’s not happening. My HTML is:

                <input type="text" id="punto_inicio" name="origen" value="" onFocus="geolocate()"  class="formc full-width input-text" data-live-search="true" data-toggle="tooltip" data-placement="top" required>

                <input type="text" id="punto_final" name="destino" value="" class="formc full-width input-text" data-live-search="true" data-toggle="tooltip" data-placement="top"  required>

<script src="https://maps.googleapis.com/maps/api/js?key={{ googlemapsAPIkey }}&libraries=places&callback=initAutocomplete&language=es-ES&v=weekly" async defer></script>

(I added the “v=weekly” part to try to force Google to load the newest version of the widget, but initially I didn’t use it. Neither works).

My JS code is:

function initAutocomplete() {       
    var sessionTokenInicio = new google.maps.places.AutocompleteSessionToken();
    var sessionTokenFinal = new google.maps.places.AutocompleteSessionToken();
    
    var autocompleteInput = document.getElementById('punto_inicio');
    autocomplete = new google.maps.places.Autocomplete(autocompleteInput, 
            {
            fields: ["address_components", "geometry", "formatted_address", "name", "url", "website"],
            componentRestrictions: { country: ["es", "fr", "pt"] }
            }
        );
    autocomplete.setOptions({ sessionToken: sessionTokenInicio });

    var autocomplete2Input = document.getElementById('punto_final');
    var options2 = { componentRestrictions: { country: ['es', 'fr', 'pt'] } };
    autocomplete2 = new google.maps.places.Autocomplete(autocomplete2Input, 
            {
            fields: ["address_components", "geometry", "formatted_address", "name", "url", "website"],
            componentRestrictions: { country: ["es", "fr", "pt"] }
            }       
        );
    autocomplete2.setOptions({ sessionToken: sessionTokenFinal });


    autocomplete.addListener('place_changed', function() {
        fillInAddress(autocomplete, "", sessionTokenInicio);
        sessionTokenInicio = new google.maps.places.AutocompleteSessionToken();
        autocomplete.setOptions({ sessionToken: sessionTokenInicio });          
    });

    autocomplete2.addListener('place_changed', function() {
        fillInAddress(autocomplete2, "2"), sessionTokenInicio;
        sessionTokenFinal = new google.maps.places.AutocompleteSessionToken();
        autocomplete2.setOptions({ sessionToken: sessionTokenFinal });
    });         

}

function fillInAddress(autocomplete, unique, sessionToken) {

    var place = autocomplete.getPlace();

    for (var component in componentForm) {
        if (!!document.getElementById(component + unique)) {
            document.getElementById(component + unique).value = '';
        }
    }

   var service = new google.maps.places.PlacesService(document.createElement('div'));
    service.getDetails(
        {
            placeId: place.place_id,
            sessionToken: sessionToken,
            fields: ["address_components", "geometry", "name", "formatted_address"] // Limit fields to reduce cost
        },
        function (placeDetails, status) {
            if (status === google.maps.places.PlacesServiceStatus.OK) {
                // Update fields with place details
                document.getElementById('name' + unique).value = placeDetails.name || "";
                document.getElementById('latlng' + unique).value = placeDetails.geometry?.location || "";
                //document.getElementById('url' + unique).value = placeDetails.url || "";
                //document.getElementById('web' + unique).value = placeDetails.website || "";

                if (placeDetails.address_components) {
                    for (var i = 0; i < placeDetails.address_components.length; i++) {
                        var addressType = placeDetails.address_components[i].types[0];
                        if (componentForm[addressType] && document.getElementById(addressType + unique)) {
                            var val = placeDetails.address_components[i][componentForm[addressType]];
                            document.getElementById(addressType + unique).value = val || "";
                        }
                    }
                }

                if (document.getElementById('latlng' + unique).value !== '') {
                    document.getElementById('ok' + unique).checked = true;
                    document.getElementById('sel' + unique).innerHTML = placeDetails.formatted_address || "";
                }
            } else {
                console.error("Failed to fetch place details:", status);
            }
        }
    );
}


function geolocate() {
    if (navigator.geolocation) {
      navigator.geolocation.getCurrentPosition(function(position) {
        var geolocation = {
          lat: position.coords.latitude,
          lng: position.coords.longitude
        };
        var circle = new google.maps.Circle({
          center: geolocation,
          radius: position.coords.accuracy
        });
      });
    }
}

Despite all of the above, when I load the page I see it still using the old Places API endpoint:

https://maps.googleapis.com/maps/api/place/js/AutocompletionService.GetPredictions

To make sure, I created a new API key and restricted so that it only used the new Places API… and now the page doesn’t load and says: “Google Maps JavaScript API error: ApiTargetBlockedMapError“. So it looks like it’s trying to use the old Places API no matter what I do.

What’s wrong?

Kendo MVC Chart Not Sorting Categories Correctly and `dataBound` Event Not Firing

I’m working on a .NET MVC 5 project and using Kendo MVC Charts to display data on my dashboard. I’m facing two issues:

  1. Sorting Issue: The chart is not displaying data in correct order on the x-axis, even though the data is sorted correctly on the server side. For example, the x-axis shows: “Dec – 2024”, “Jul- 2024”, “Aug – 2024”, “Feb – 2024”, “Jan – 2025”, “Nov – 2024”, “May – 2024”. Notice that “Jan – 2025” appears in the middle, which shouldn’t happen.

  2. databound Event Not Firing: I wrote a custom function (chartDataBound) to sort the categories on the client side and bound it to the dataBound event of the Kendo chart. However, the function is not being called. I added an alert() inside the function to debug, but it never executed.

Client-Side Code (HTML + JavaScript):

<div class="col-md-6 pt-20"> 
        <label class="switch" title="Toggle to show numbers">
            <input id="toggleForChart8" type="checkbox">
            <span class="slider round"></span>
        </label>

        @(Html.Kendo().Chart<MiCATS.Models.ChartDataItem>()
    .Name("chart8")
    .Title("Gaps by Reasons")
    .Theme("bootstrap")
    .DataSource(dataSource => dataSource
    .Read(read => read.Action("GetGapsIdentifiedByReasonsAnalytics", "Analytics"))
    .Group(group => group.Add(model => model.Question))
     )

   .Series(series => {
    series.Column(model => model.GapsIdentified)
    .Aggregate(ChartSeriesAggregate.Sum)
    .Tooltip(tooltip => tooltip.Visible(true).Template("#= value # Gaps Identified for: #= series.name #"))
    .CategoryField("YearMonth")
    .Name("");
    })
    .Legend(legend => legend
    .Position(ChartLegendPosition.Bottom)
    .Spacing(122)
    .Labels(labels => labels.Template("#= truncateLegendLabel(text) #"))
     )
    .ValueAxis(axis => axis.Numeric().Color("black").Title("Gaps Count"))
    .CategoryAxis(axis => axis.Color("black").Labels(label => label.Rotation(-45)).Title("Months"))

    .Events(e => e.DataBound("chartDataBound"))
    .Events(events => events.SeriesClick("onChartBarClick2").Render("onChartRender"))
    .HtmlAttributes(new { style = "cursor: pointer;" })
)
    </div>
function chartDataBound(e) { 
    alert("Inside chartDataBound");
    var axis = e.sender.options.categoryAxis;
    var monthMap = {
        "Jan": 1,
        "Feb": 2,
        "Mar": 3,
        "Apr": 4,
        "May": 5,
        "Jun": 6,
        "Jul": 7,
        "Aug": 8,
        "Sep": 9,
        "Oct": 10,
        "Nov": 11,
        "Dec": 12,
    };

    axis.categories = axis.categories.sort(function (a, b) {
        // Ensure the categories are strings before splitting
        if (typeof a !== 'string' || typeof b !== 'string') return 0;

        var aParts = a.split(' ');
        var bParts = b.split(' ');

        // Check if the split worked correctly and both parts exist
        if (aParts.length < 2 || bParts.length < 2) return 0;

        var monthA = monthMap[aParts[0]];  // Get month number from the first part
        var monthB = monthMap[bParts[0]];
        var yearA = parseInt(aParts[2], 10);  // Get year as an integer from the second part
        var yearB = parseInt(bParts[2], 10);

        // Compare years first, then months if years are the same
        if (yearA === yearB) {
            return monthA - monthB;
        } else {
            return yearA - yearB;
        }
    });
}

What I’ve Tried:

  1. Verified that the server-side data is sorted correctly.
  2. Added console.log and alert() inside chartDataBound to debug, but the function is not being called.
  3. Checked for JavaScript errors in the browser console but none found.

Questions:

  1. Why is the dataBound event not firing, and how can I fix it?
  2. How can I ensure the x-axis categories are sorted correctly in the Kendo chart?

Roadmap for a Next.js App with Google Ads API & Looker Studio Embed [closed]

I need to build a small local dashboard app using Next.js. The app should have two main features:

Looker Studio Embed: A page displaying a simple performance report (e.g., daily purchase data for January 2025).
Google Ads API Integration: A button that fetches and displays the last 7 days of ad spend (cost data) when clicked.

Tech Stack

Frontend: Next.js
Backend: (Not decided yet, open to suggestions.)
Authentication: (Not sure if I need it, but open to best practices.)

What I Need Help With

Step-by-step roadmap for integrating Google Ads API: How should I handle authentication and fetch data properly in a Next.js app?
Best way to embed a Looker Studio report into a Next.js page.
Backend choices: Should I use Next.js API Routes, Express.js, or something else?
Authentication: Since this will run locally (for now), should I even bother with auth, or is there a simple way to handle it for future scalability?

How to persist the selected section state when navigating between pages and reset when changing the store ID?

I’m working on a page where users can select sections, and I need to:

  1. Keep the selected section state when the user returns from the next page or next-to-next page.
  2. Reset the previously selected sections (remove them from session storage) when the storeId changes after returning to the page.

How can I:

Preserve the selected sections when navigating back from the next page or next-to-next page?
and Clear the selected sections from the session storage when changing the storeId, ensuring the state resets?

I’ve tried using sessionStorage to persist the selected section state but it is not working properly, Any help or suggestions on how to achieve this would be greatly appreciated!

let storeId = null;

// Add this function to check if all children of a parent are unselected
function checkParentStatus(parentId, parentSections) {
  const parent = parentSections[parentId];
  let allChildrenUnselected = true;

  parent.children.forEach(function(childId) {
    if ($(`#${childId}`).hasClass('changedBackground')) {
      allChildrenUnselected = false; // If any child is selected, parent remains disabled
    }
  });

  if (allChildrenUnselected) {
    $(`#${parentId}`).removeClass('disabled'); // Enable the parent section if all children are unselected
  } else {
    $(`#${parentId}`).addClass('disabled'); // Keep the parent section disabled if any child is selected
  }
}


const configComboBoxEvent = function(calendar, controller, storeId, sectionId, errorLog) {
  $(storeId).on('change', function() {
    const selectedStoreId = $(this).val(); // Get the storeId value from the select input

    if (selectedStoreId) {
      $.ajax({
          url: `/${controller}/selectFacility`,
          type: 'POST',
          data: {
            storeId: selectedStoreId
          },
          dataType: "json",
          cache: false
        })
        .done(function(data) {
          $(sectionId).empty();
          let ids = [];
          let parentSections = {}; // Store sections grouped by their parent section
          let childSections = {}; // Store child sections by their parent

          data.forEach(function(item) {
            if (item.section_groups && item.section_groups.section_children_id) {
              let childrenIds = item.section_groups.section_children_id.split(',');

              // Group parent section with its children
              parentSections[item.id] = {
                section_name: item.section_name,
                children: childrenIds
              };

              childrenIds.forEach(function(childId) {
                childSections[childId] = true;
              });
            } else {
              // If section has no children, just display as a separate section (if not already a child)
              if (!childSections[item.id]) {
                const button = $('<div/>', {
                  text: item.section_name,
                  'id': item.id,
                  'class': 'button-location-style',
                  click: function() {
                    var index = ids.indexOf(item.id);
                    if (index > -1) {
                      ids.splice(index, 1);
                      $(this).removeClass("changedBackground");
                    } else {
                      ids.push(item.id);
                      $(this).addClass("changedBackground");
                    }
                    $('#SectionId').attr('value', ids.join(','));
                  }
                });
                $(sectionId).append(button);
              }
            }
          });

          // Add parent sections with children below them
          Object.keys(parentSections).forEach(function(parentId) {
            const parent = parentSections[parentId];

            // Create the parent section button
            const parentButton = $('<div/>', {
              text: parent.section_name,
              'id': parentId,
              'class': 'button-location-style parent-section',
              click: function() {
                var index = ids.indexOf(parentId);
                if (index > -1) {
                  ids.splice(index, 1);
                  $(this).removeClass("changedBackground");
                  // Enable the child sections when the parent is unselected
                  parent.children.forEach(function(childId) {
                    $(`#${childId}`).removeClass('disabled');
                  });
                } else {
                  ids.push(parentId);
                  $(this).addClass("changedBackground");
                  // Disable the child sections when the parent is selected
                  parent.children.forEach(function(childId) {
                    $(`#${childId}`).addClass('disabled');
                  });
                }
                // Update the section ID input field with the selected IDs
                $('#SectionId').attr('value', ids.join(','));
              }
            });

            $(sectionId).append(parentButton);

            // Create a container for child sections under the parent button
            const childContainer = $('<div/>', {
              'class': 'child-container'
            });

            // Append child sections under the parent section
            parent.children.forEach(function(childId) {
              // Find the child section from the data
              const childItem = data.find(function(item) {
                return item.id == childId;
              });

              if (childItem) {
                const childButton = $('<div/>', {
                  text: childItem.section_name,
                  'id': childItem.id,
                  'class': 'button-location-style child-section',
                  click: function() {
                    var index = ids.indexOf(childItem.id);
                    if (index > -1) {
                      ids.splice(index, 1);
                      $(this).removeClass("changedBackground");
                      // Enable the parent section when the child is unselected
                      $(`#${parentId}`).removeClass('disabled');
                      checkParentStatus(parentId, parentSections);
                    } else {
                      ids.push(childItem.id);
                      $(this).addClass("changedBackground");
                      // Disable the parent section when the child is selected
                      $(`#${parentId}`).addClass('disabled');
                    }
                    // Update the section ID input field with the selected IDs
                    $('#SectionId').attr('value', ids.join(','));
                  }
                });
                childContainer.append(childButton); // Append child button
              }
            });

            $(sectionId).append(childContainer); // Append child container under parent
          });

          if (data.length === 0) {
            const button = $('<div/>', {
              text: '',
              'class': 'button-location-style disabled',
            });
            $(sectionId).append(button);
          }
        })
        .fail(function() {
          alert("Error loading data.");
        });
    } else {
      alert("Please select a valid store ID.");
    }
  });
}

How could I make a class object to be used as a state for Preact component

I have an set of values that share a common state for my app.

My app should display a document being loaded in a container called Workspace, this container store user preferences to display the document, the data and path of the document.

Here what I’ve made as a first attempt to do this:

import { useMemo, useState } from "preact/hooks";
import { useStorage, UseStorageSetter } from "./use-storage";
import { readTextFile } from "@tauri-apps/plugin-fs";

export class WorkspaceSession
{
    #userZoom: number;
    #setUserZoom!: UseStorageSetter<number>;

    #doc: Object | null;
    #setDoc!: (form: Object | null) => void;

    #docPath: string | null;
    #setDocPath!: (path: string | null) => void;


    public constructor ()
    {
        this.#userZoom = 100;
        this.#doc = null;
        this.#docPath = null;
    }
    
    public use (): WorkspaceSession
    {
        const [userZoom, setUserZoom] = useStorage(sessionStorage, "userZoom", this.#userZoom);
        const [doc, setDoc] = useState(this.#doc);
        const [docPath, setDocPath] = useState(this.#docPath);

        this.#userZoom = userZoom;
        this.#setUserZoom = setUserZoom;
        this.#doc = doc;
        this.#setDoc = setDoc;
        this.#docPath = docPath;
        this.#setDocPath = setDocPath;

        return this;
    }


    /** the user zoom on the current document. */
    get userZoom (): number
    {
        return this.#userZoom;
    }

    set userZoom (value: number)
    {
        this.#setUserZoom(value);
    }


    public getActiveDocument (): Object | null
    {
        return this.#doc;
    }

    public newDocument (): void
    {
        this.#setDoc(new Object());
    }

    public async openDocumentFromPath (path: string): Promise<boolean>
    {
        const data = await readTextFile(path);

        if (!this.parseDocumentFromString(data))
        {
            return false;
        }

        this.#setDocPath(path);
        return true;
    }

    public parseDocumentFromString (data: string): boolean
    {
        return false;
    }

    public async saveOpenDocument (path: string): Promise<boolean>
    {
        return false;
    }

    public closeActiveDocument (): void
    {
        this.#setDoc(null);
        this.#setDocPath(null);
    }

}

export const useWorkspace = (): WorkspaceSession => useMemo(() => new WorkspaceSession(), []).use();

With this script everything begins with the root component of this app calling useWorkspace and creating a new instance of the object, then every render the object method use is called wich update each individual property that is important. With this approch I don’t re-create the whole object every time and I think is much better.

The object has some method wich are actions used by my app and they handle the state of the Workspace using the setters which properly triggers an update. Which is really good.

However I don’t really like how the use method works, because with the typings I’m forcing the existence of the state setters to exist. And I’m not sure if this design could break or put this object on an undefined/invalid state.

Ignore the empty functions, those methods are not implemented yet. This is what my main component calls this custom hook:

export const AppMain = () =>
{
    const workspace = useWorkspace();
    /// ...
}

Now, I’m not here to ask an opinion about my code. I want to know how other app devs whould solve this problem that are commonly used so I could use it for my app. I couldn’t find much informations about using classes as states.

  • Examples I can find only are all about creating a new object every time with {} and not classes, and what’s unchanged is copyed every time. I don’t really like this approch because of the useless copy of unchanged data and being unable to define outside of the component using this object his methods that could be shared with other components.
  • The reason I want to use the class is because I have some buttons that does the same thing when called and having a object that share those methods into a single workspace and then trigger the update properly to all widgets using the class is really convenient.

Need help creating specific legend view [closed]

I’m currently trying to change the default legend to the following but can’t find a way to

  • Have the background color to the same as the data
  • Displaying the dynamic icon (it’s a closed eye when data is unselected)

enter image description here

I’m playing with the richtext property but can’t find a way to achieve my goal.

Do you have any idea if that’s feasible and if it is, how to please ?

Zoom in on BPMN Canvas

We have used BPMN in our Javascript (MERN) based project, and applied certain customizations in components and design. Now, when I opened the diagram page, I want that it should zoom in a little bit, and the position of the canvas should be at the start of the flow by default i.e. the portion of the start of the flow should be zoomed in for better visibility.

I tried using CSS, and canvas.viewBox() for this, but then my diagram vanished from the canvas. Can someone please assist?

static async renderDiagramCanvas(diagramXML) {
    const modeler = ModelerService.getModeler();
    await modeler.importXML(diagramXML);

    const canvas = modeler.get("canvas");
    canvas.zoom("fit-viewport", "auto");
    canvas.zoom(0.6);
  }

Current Scenario:

For example:
We have a diagram similar to below image:

enter image description here

Sometimes due to complexity of the flow, much zoomed out image gives issue, so we want that following part of the flow should be zoomed in on canvas by default:
enter image description here

(Intended)

So the area with start event should be zoomed in by default.

But currently a central part of flow is zoomed in:

![wrong position of zoom in

(Not intended)

Block access to js file directly without authentication – reactjs

https://toolki.core.co.uk/js/main.6b….438cfb9794.js (modified the link for security)

When I click on the above link from my application it directly shows me all the code of the application on the browser.
I will not even ask for authentication that I have set on azure sso for my application.

How can I restrict access to this file. Or is there a way to hide the details from this url?

Please help I am stuck here for such a long time.

Tabulator 6.3 change params names for sort[0][field] and sort[0][dir]

i am using Tabulator version 6.3
I tried to change the dataSendParams from “sort[0][field]” to “myField” without success.
As we can read in the documentation:
https://tabulator.info/docs/6.3/page#remote-url

If you need to change the names of any of these parameters 
to fit your existing system, you can use the dataSendParams option 
to set alternative parameter names.

I tried the solutions found in other posts, but without success.
Any updates for this issue in version 6.3 ?
thank you

Swiper js navigation arrows are not showing

I’ve been trying to use Swiper js, it went well until I needed to use it 2 more times at different parts of the code, the problem is: the navigation arrows simply don’t appear in the page and I can’t find out what I’ve been doing wrong. There is a total of 3 times I’ve used swiper in the page, only the first went right.

This is the code that is not working (I haven’t written any CSS to them and the JS code is inside the DOMContentLoaded event listener)

<div class="swiper-estrutura">
    <div class="swiper-wrapper" id="swiper-wrapper">
        <div class="swiper-slide"><img src="assets/sala.jpg"></div>
        <div class="swiper-slide"><img src="assets/escritorio.jpg"></div>                            
    </div>

    <div class="estrutura-prev" id="estrutura-prev">    </div>
    <div class="estrutura-next" id="estrutura-next"></div>
    <div class="estrutura-pagination" id="estrutura-pagination"></div
</div>

Javascript

const estruturaswiper = new Swiper('.swiper-estrutura', {
        direction: 'horizontal',
        // loop: true,
        grabCursor: true,
        centeredSlides: true,
        slidesPerView: 1,
        navigation: {
            nextEl: '.estrutura-next',
            prevEl: '.estrutura-prev',
        },
        pagination: {
            el: '.estrutura-pagination',
            clickable: true,
        },
    });

<div class="swiper-depoimento">
                <div class="swiper-wrapper" id="swiper-wrapper">
                    <div class="swiper-slide">
                        <div class="card-depoimento">
                            <div class="card-depoimento-tag tag-aluno">
                                <center>aluno</center>
                            </div>
                            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Vero minima accusantium 
                            <div class="nome-aluno">
                                Nome do aluno
                            </div>
                        </div>
                    </div>
                    <div class="swiper-slide">
                        <div class="card-depoimento">
                            <div class="card-depoimento-tag tag-aluno">
                                <center>aluno</center>
                            </div>
                            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Vero minima accusantium 
                            <div class="nome-aluno">
                                Nome do aluno
                            </div>
                        </div>
                    </div>
                    <div class="swiper-slide">
                        <div class="card-depoimento">
                            <div class="card-depoimento-tag tag-pacientemodelo">
                                <center>Paciente Modelo</center>
                            </div>
                            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Vero minima accusantium 
                            <div class="nome-aluno">
                                Nome do aluno
                            </div>
                        </div>
                    </div>
                    <div class="swiper-slide">
                        <div class="card-depoimento">
                            <div class="card-depoimento-tag tag-aluno">
                                <center>aluno</center>
                            </div>
                            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Vero minima accusantium 
                            <div class="nome-aluno">
                                Nome do aluno
                            </div>
                        </div>
                    </div>
                    <div class="swiper-slide">
                        <div class="card-depoimento">
                            <div class="card-depoimento-tag tag-aluno">
                                <center>aluno</center>
                            </div>
                            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Vero minima accusantium 
                            <div class="nome-aluno">
                                Nome do aluno
                            </div>
                        </div>
                    </div>
                    <div class="swiper-slide">
                        <div class="card-depoimento">
                            <div class="card-depoimento-tag tag-aluno">
                                <center>aluno</center>
                            </div>
                            Lorem, ipsum dolor sit amet consectetur adipisicing elit. Vero minima accusantium 
                            <div class="nome-aluno">
                                Nome do aluno
                            </div>
                        </div>
                    </div>
                    
                </div>
                <div class="depoimento-prev" id="depoimento-prev"></div>
                <div class="depoimento-next" id="depoimento-next"></div>
            </div>
        </div>

JavaScript

const depoimentoswiper = new Swiper('.swiper-depoimento', {
        direction: 'horizontal',
        loop: true,
        grabCursor: true,
        centeredSlides: true,
        
        navigation: {
            nextEl: '.depoimento-next',
            prevEl: '.depoimento-prev',
        },
        spaceBetween: 30,
        breakpoints: {
            0: {
                slidesPerView: 1,
            },
            640: {
                slidesPerView: 2,
            },
            1049: {
                slidesPerView: 3,
            },
            1388: {
                slidesPerView: 4,
            },
        },
    });

How to I deploy an AWS S3 bucket to Amplify via @aws-sdk/client-amplify?

Here’s how I’ve got deployments working right now with Amplify:

In case it matters, this deployment is entirely frontend.

  1. Deploy application files to an S3 bucket.
  2. In the Amplify project, click Deploy Updates, select Amazon S3, verify the bucket is correct, and click Save and deploy.

Maybe deploying straight to Amplify is the right thing. I’m hoping I can replicate what I’m doing in the Amplify project UI.

I’m trying to replicate this process with @aws-sdk/client-amplify, but I’m running into issues.

Here’s the documentation I’m following: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-client-amplify/ as well as the API reference from https://docs.aws.amazon.com/amplify/latest/APIReference/API_StartDeployment.html.

I’m attempting it this way (let me know if you want actual code examples):

  1. Create and send a CreateDeploymentCommand (https://docs.aws.amazon.com/amplify/latest/APIReference/API_CreateDeployment.html) with the appId and branchName.
  2. Create and send a StartDeploymentCommand (https://docs.aws.amazon.com/amplify/latest/APIReference/API_StartDeployment.html) with the appId and branchName. Additionally, I’m setting jobId to the job ID returned in step 1. I’m also setting sourceUrl to the S3 bucket as it’s set in the Amplify project deployment UI, and sourceUrlType to BUCKET_PREFIX.
  3. Poll by creating and sending a GetJobCommand (https://docs.aws.amazon.com/amplify/latest/APIReference/API_GetJob.html) periodically with the job ID until it returns a status saying the deployment has completed.

Step 1 seems to work. I can see the deployment pending in the Amplify project UI. Step 2 fails with the error BadRequestException: Deployment file has not been uploaded, please make sure you uploaded the deployment file prior to invoke this api.. This is confusing for me because I’m trying to deploy from S3, not upload anything. I feel like I must be missing something, but their documentation doesn’t say anything about this. The only thing in their documentation about this error is that BadRequestException means “A request contains unexpected data.” (https://docs.aws.amazon.com/amplify/latest/APIReference/API_StartDeployment.html). That might be the case, but there is no further information on what I might be doing wrong.

Or maybe I’m approaching this the wrong way. Let me know if you’ve got any insight 😀

fetch response.body locked after awaiting json() [duplicate]

see below example, why is response.body locked ? and how can I unlock it ? (so that I can call cancel())

async function getData() {
  const url = "http://jsonplaceholder.typicode.com/users"
  try {
    const response = await fetch(url)
    if (!response.ok) throw new Error(`Response status: ${response.status}`)

    const json = await response.json()
    console.log(response.body.locked)
  } catch (error) {
    console.error(error.message);
  }
}

getData()

Vanilla JS SPA: inserted scripts execute only once

I’m trying to build a vanilla js single page application (using node.js and express) but I’ve ran into an issue with executing .js files that I just can’t find any solution to, after hours of scouring the web and stackoverflow…

Below is the fairly simple structure with this problem. I have a static header for navigation in my index.html with <a> elements triggering the SPA routing and a container div for the SPA views.

index.html:

<!DOCTYPE html> 
<html lang="en"> 
<head> 
<script src='../scripts/index.js' type='module' defer></script> 
</head> 
<body>  
<nav>    
    <a href='/testA' class='route'>Test A</a>
    <a href='/testB' class='route'>Test B</a>
</nav>  

<div id='views'></div>   

</body>  
</html>    

Exemplary TestA.html

<!DOCTYPE html> 
<html lang="en"> 
<head> 
<script src='../scripts/TestA.js' type='module' defer></script> 
</head> 
<body>  

<p> View: Test A </p>   

</body>  
</html> 

TestA.js

const obj = {name: 'A', val: '000'}
console.log(obj)

index.js

const nav = document.querySelectorAll('.route')
for (const element of nav){
    element.addEventListener('click', (event) => {
        event.preventDefault()
        history.pushState(null, '', element.href)
        spaRouting()
    })
}

const container = document.getElementById('views')

function spaRouting(){
    const path = window.location.pathname
    const html = '...' //fetch request grabbing HTML file from node server 
    container.innerHTML = html

    const oldScripts = container.getElementsByTagName('script')
    for (const oldScript of oldScripts){
        const newScript = document.createElement('script')
        for(const attribute of oldScript.attributes){newScript.setAttribute(attribute.name, attribute.value)}
    newScript.addEventListener('load', () => console.log('script was loaded'))
    oldScript.replaceWith(newScript)
    }
}

window.addEventListener('popstate', spaRouting())

The routing itself works perfectly fine, as does the HTML injection. Jumping back and forth between Test A and Test B I can see the HTML content change. Replacing the scripts with newly created ones also works great and Test??.js is executing and logging to console.

However, it does so only exactly one time for each navigation element / view. If I click a view a second time the HTML content still updates successfully but no scripts are executed. I do get the “script was loaded” logged to console from the eventListener of the newly created script element but the content of the .js files does not get executed (no object logged to console).

For the life of me I can’t figure out why this works but does so only once for every view and not any subsequent time.

I tried:

  • appending the new script tag and then removing the old one instead of using replaceWith/replaceChild
  • creating the new script without copying attributes from the old script but instead defining them manually
  • giving each script tag a unique ID so that every time a script tag is added it has a new ID
  • appending the new script to head or body of index.html instead of the container div and then removing the (inactive) old script inside the container

What I can not do is preload all script files inside index.html because in the actual project many of them reference DOM elements which don’t exist until the corresponding view is loaded (because the project is coming from a multi page application structure).

I really hope someone can explain this behavior to me and knows a way how to fix it because I’m at a loss.

Getting TypeScript to Infer Props Correctly for a Nested Generic Function Component

The title will make more sense with an example:

I have the following component GlobalDialog:

export type GlobalDialogProps<T extends React.FunctionComponent<any>> = {
    Trigger: React.FunctionComponent<{ onToggle: () => void; }>;
    Panel: T;
    panelProps: ComponentProps<T>;
    zIndex: number;
};

export default function GlobalDialog<T extends React.FunctionComponent<any>>(props: GlobalDialogProps<T>) {
    // Implementation details
}

and component FilterProductResultsControllerV2

export default function FilterProductResultsControllerV2<T extends React.FC<any>>(props: { ResultElement: T; propsFactory: (product: IProduct) => ComponentProps<T>; }) {
    // Implementation details
}

I then try to pass FilterProductResultsControllerV2 as a Panel to my Global Dialog, and propsFactory ends up being inferred as (product: IProduct) => any instead of (product: IProduct) => CountTrackerProductProps

<GlobalDialog
    zIndex={10}
    Trigger={({ onToggle }) => (
        // Implementation detail
    )}
    Panel={FilterProductResultsControllerV2}
    panelProps={{
        renderAs: "panel",
        ResultElement: CountTrackerProduct,
        propsFactory: (product) => {
            const orderItemData = value[product.id];

            return {
                product: product,
                onAdd: () => addOrderItem(product),
                quantity: orderItemData?.quantity ?? null,
            };
        },
    }}
/>

where CountTrackerProduct is:

type CountTrackerProductProps = { product: IProduct; onAdd: () => void; quantity: number | null; };

export default function CountTrackerProduct(props: CountTrackerProductProps) { 
    // Implementation details
}

A working solution to this problem is using FilterProductResultsControllerV2<typeof CountTrackerProduct> as Panel, but it’s not the most elegant one either.

So my question is, why exactly does this happen in Typescript? and are there any ways around it, if any?

Full Sample Code that features the issue

import { useState } from "react";
    
interface IProduct {
    id: string;
    name: string;
}

type GlobalDialogProps<T extends React.FunctionComponent<any>> = {
    Trigger: React.FunctionComponent<{ onToggle: () => void }>;
    Panel: T;
    panelProps: React.ComponentProps<T>;
    zIndex: number;
};

function GlobalDialog<T extends React.FunctionComponent<any>>(props: GlobalDialogProps<T>) {
    const [open, setOpen] = useState(false);
    const { Panel, panelProps, Trigger } = props;
    return (
        <>
            <Trigger onToggle={() => setOpen(!open)} />
            {/*<Panel {...panelProps} /> */}
        </>
    );
}

function FilterProductResultsControllerV2<T extends React.FC<any>>(props: { ResultElement: T; propsFactory: (product: IProduct) => React.ComponentProps<T> }) {
    return <div></div>;
}

type CountTrackerProductProps = {
    product: IProduct;
    onAdd: () => void;
    quantity: number | null;
};

function CountTrackerProduct(props: CountTrackerProductProps) {
    return <div></div>;
}

function OrderItemDataWidget() {
    const [value, setValue] = useState<Record<IProduct["id"], { quantity: number }>>({});
    const addOrderItem = (product: IProduct) => console.log(`added product ${product.id}`);

    return (
        <div>
            <div>close off</div>
            <GlobalDialog
                zIndex={10}
                Trigger={({ onToggle }) => <button onClick={onToggle} type="button">dummy</button>}
                Panel={FilterProductResultsControllerV2}
                panelProps={{
                    ResultElement: CountTrackerProduct,
                    propsFactory: (product) => {
                        const orderItemData = value[product.id];

                        return {
                            product: product,
                            onAdd: () => addOrderItem(product),
                            quantity: orderItemData?.quantity ?? null,
                        };
                    },
                }}
            />
        </div>
    );
}