CSS – Divs are shifting vertically due to SVGs inside

I have a project I am working on that takes data from a database of a card game I am creating and displays it to a user. I am struggling with displaying a list of cards to the user, because some of the div items are not aligned, despite them having the same size and only differ by content.

I have been able to deduce so far that the issue is originating with an embedded SVG in a div alongside text, as every card that is misaligned has an SVG in the card’s title. But I cannot figure out why. It seems that if I make the SVG smaller, the problem goes away, but if it is too small, the card instead moves down. I have tried numerous display types to no avail. The SVG is much smaller than the text (I’d honestly want it bigger as well), and yet it seems to control where the card is positioned vertically.

I think that most likely it is due to the mess of CSS rules I have to display an individual card, and something is conflicting. Or maybe there’s something I’m missing? I realize ther

Apologies for the messy code. I’m not sure what’s to be expected or needed as far as code here, so I’ve pretty much just pasted the whole WIP file here. Not included is the card data that is actually passed, but that is simple JSON data containing strings and integers that are interpreted by the javascript.

image of what the page looks like

cardSearch.html: Displayed to the user, takes cardResults (array containing cards) and repeatedly adds card.html to the page with each card’s data.

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8" />
        <title>CE War - Card Search</title>
        <link rel="stylesheet" type="text/css" th:href="@{style.css}">
        <link rel="icon" type="image/png" th:href="@{images/logo.png}">
        <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
    </head>
    <body>
        <div id="card-results">
            <div th:each="aCard : ${cardResults}" class="card-display" style="--card-height: 400px">
                <div th:replace="~{fragments/card :: card(${aCard})}"></div>
            </div>
        </div>
    </body>
</html>

card.html (fragment that is repeated in cardSearch.html)

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8" />
        <title>CE War - Card Display</title>
        <link rel="stylesheet" type="text/css" th:href="@{style.css}">
        <link rel="icon" type="image/png" th:href="@{images/logo.png}">
        <script src="https://code.jquery.com/jquery-3.7.1.js"></script>
    </head>
    <body>
        <!-- Insert the container located at fragments/header with the tag "th:fragment="header"" -->
        <div th:insert="~{fragments/header :: header}"/>

        <main>

            <div class="card-table blank-card" th:fragment="card(cardData)">
                <div class="header dark-color">
                    <div class="name"></div>
                    <img class="rarity">
                </div>
                <img class="art">
                <p class="desc light-color"/>
                <div class="footer dark-color">
                    <div class="attack"></div>
                    <div class="center">
                        CE 2nd
                    </div>
                    <div class="health"></div>
                </div>
                <script th:inline="javascript">
                    // Needed for Thymeleaf to be able to run this code
                    /*<![CDATA[*/

                    createCard(/*[[${cardData}]]*/[0]);

                    function createCard(card) {

                        // SECTION Script Functions

                        /** Constructs a card symbol in the form of an html span element.
                         * This element contains a svg with size 1em of the given symbolType, and the same color as the card's type.
                         * This element has the "card-symbol" class.
                         * 
                         * @param symbolType - which symbol to create. Valid symbols are c1, c2, c3, c4, stage, equip, god, back-slot, [] 
                         */
                        function formatSymbol(symbolType) {
                            // Determine which symbol to use
                            switch (symbolType.toLowerCase()) {
                                case "c1": case "c2": case "c3": case "c4": 
                                case "stage": case "equip": case "god": 
                                    // symbol type is valid
                                    var symbolID = symbolType.toLowerCase();
                                    break;
                                case "back-slot": case "[]":
                                    // symbol type is valid
                                    var symbolID = "back-slot";
                                    break;
                                default:
                                    var symbolID = "";
                                    // Log error to website console. Symbol should end up not appearing.
                                    console.error("Invalid symbol type of type "" + symbolType + """);
                            }

                            // Valid types: CREATURE, ACTION, BUILDING, MATERIAL, GOD, REPLICA, OTHER
                            switch (card.type) {
                                case "CREATURE": case "ACTION": case "BUILDING": case "MATERIAL": case "GOD": case "REPLICA":
                                    var symbolColor = `var(--${card.type.toLowerCase()}-color)`;
                                    break;
                                case "OTHER":
                                    if (card.id == "gap") {
                                        var symbolColor = "var(--gap-color)";
                                        break;
                                    }
                                    // else: Continue to default case
                                default:
                                    var symbolColor = "#000000";
                                    // Log error to website console. Symbol color should be black.
                                    console.error("Invalid card type of type "" + card.type + """);
                            }

                            // Combine strings into final html element
                            return `<svg class="card-symbol">
                                            <use href="images/card_symbol.svg#${symbolID}" style="--icon-color: ${symbolColor};"/>
                                        </svg>`;
                        }

                        // Formats card effects to include line breaks, italics, and lists.
                        function formatEffect(string) {
                            const listRegex = new RegExp("(^[0-9]+: )|(^- )");

                            // Split effect, based on line breaks for further processing
                            let cardEffectArr = string.split("n");

                            for (let i = 0; i < cardEffectArr.length; i++) {
                                if (listRegex.test(cardEffectArr[i])) {
                                    // If this is a list item

                                    if (i == 0 | cardEffectArr[i-1].charAt(0) != "<") {
                                        // If previous item was not a list item
                                        if (cardEffectArr[i].charAt(0) == "-") {
                                            var isOrdered = false;
                                            cardEffectArr[i] = cardEffectArr[i].replace(listRegex, "<li>") + "</li>";
                                            cardEffectArr[i] = "<ul>" + cardEffectArr[i];
                                        } else {
                                            var isOrdered = true;
                                            cardEffectArr[i] = cardEffectArr[i].replace(listRegex, "<li>") + "</li>";
                                            cardEffectArr[i] = "<ol>" + cardEffectArr[i];
                                        }
                                    } else {
                                        // Ensure this is a list item
                                        // This allows the regex to work properly and avoid issues
                                        cardEffectArr[i] = cardEffectArr[i].replace(listRegex, "<li>") + "</li>";
                                    }

                                    if (cardEffectArr[i].charAt(0) == "<" && i == cardEffectArr.length - 1) {
                                        // If this is the last item in the list
                                        if (isOrdered) {
                                            cardEffectArr[i] = cardEffectArr[i] + "</ol>";
                                        } else {
                                            cardEffectArr[i] = cardEffectArr[i] + "</ul>";
                                        }
                                    }
                                } else {
                                    // Not a list item
                                    if (i > 0 && listRegex.test(cardEffectArr[i - 1])) {
                                        // If previous index was a list item
                                        if (isOrdered) {
                                            cardEffectArr[i - 1] = cardEffectArr[i - 1] + "</ol>";
                                        } else {
                                            cardEffectArr[i - 1] = cardEffectArr[i - 1] + "</ul>";
                                        }
                                    }
                                    cardEffectArr[i] = cardEffectArr[i] + "<br>";
                                }
                            }

                            let cardEffect = cardEffectArr.join("");
                            const symbolRegex = new RegExp("\(\S+\)", "g");
                            cardEffect = cardEffect.replaceAll(symbolRegex, function(match) {
                                return formatSymbol(match.slice(1,-1)); // Remove outer parentheses
                            });

                            return cardEffect;
                        }

                        // Constructs card description field from multiple data sources.
                        function constructCardDesc() {
                            let cardArchetypes = "";
                            if (card.archetypes.length > 0) {
                                // Format archetypes to be lowercase, since the enums are declared as uppercase
                                for (let i = 0; i < card.archetypes.length; i++) {
                                    card.archetypes[i] = card.archetypes[i].charAt(0) + card.archetypes[i].slice(1).toLowerCase();
                                }
                                cardArchetypes = "<em>" + card.archetypes.join(", ") + "</em><br>";
                            }
                            let cardMaterials = "";
                            if (card.materials.length > 0) {
                                cardMaterials = "<em>" + card.materials + "</em><br>";
                            }
                            let cardFlavorText = "";
                            if (card.flavorText.length > 0) {
                                if (card.effect.length > 0) {
                                    cardFlavorText = "<br>";
                                }
                                cardFlavorText = cardFlavorText + "<em>" + card.flavorText + "</em>";
                            }
                            
                            let cardEffect = formatEffect(card.effect);

                            return cardArchetypes + cardMaterials + cardEffect + cardFlavorText;
                        }
                        // !SECTION

                        console.log(card);

                        // Pick the first blank card
                        var cardDiv = $(".card-table.blank-card").first();
                        // Prevent this card from being selected again
                        cardDiv.removeClass("blank-card");

                        // Construct card name field, adding any needed attribute symbols
                        let cardAttributes = [];
                        for (let i = 0; i < card.attributes.length; i++) {
                            cardAttributes[i] = formatSymbol(card.attributes[i]);
                        }
                        cardDiv.find(".name").html(cardAttributes.join("") + " " + card.name);

                        // Determine card rarity
                        switch (card.rarity) {
                            case "COMMON":
                                cardDiv.find(".rarity").attr("src", "images/rarity_common.png");
                                break;
                            case "UNCOMMON":
                                cardDiv.find(".rarity").attr("src", "images/rarity_uncommon.png");
                                break;
                            case "RARE":
                                cardDiv.find(".rarity").attr("src", "images/rarity_rare.png");
                                break;
                            case "ULTRA_RARE":
                                cardDiv.find(".rarity").attr("src", "images/rarity_ultra_rare.png");
                                break;
                            case "NONE":
                                if (card.type == "GOD") {
                                    // Stretch card text div to take up entire width of card.
                                    cardDiv.find(".name").css("width", "calc(125% - 10px)"); // Accomodate border space
                                    cardDiv.find(".rarity").style = "display: none;" // Hide rarity symbol
                                }
                                break;
                            default:
                        }

                        // Add Card Art
                        cardDiv.find(".art").attr("src", card.artSource);

                        // Construct card description
                        cardDiv.find(".desc").html(constructCardDesc());

                        // Determine left footer text
                        if (card.attack == -1) {
                            if (card.size.length == 2) {
                                cardDiv.find(".attack").text(card.size[0] + "x" + card.size[1]);
                            } else {
                                cardDiv.find(".attack").text("");
                            }
                        } else if (card.attack == -2) {
                            cardDiv.find(".attack").text("???");
                        } else {
                            cardDiv.find(".attack").text(card.attack);
                        }

                        // Determine right footer text
                        if (card.health == -2) {
                            cardDiv.find(".health").text("???");
                        } else {
                            if (card.health == -1) {
                                cardDiv.find(".health").text("");
                            } else {
                                cardDiv.find(".health").text(card.health);
                            }
                            if (card.god) {
                                cardDiv.find(".health").append(formatSymbol("back-slot")); // Could also do "[]" instead
                            }
                        }

                        // Determine card color
                        switch(card.type) {
                            case "CREATURE":
                                var darkColor = "#7fdc39";
                                var lightColor = "#b6d7a8";
                                break;
                            case "ACTION":
                                var darkColor = "#4c4cff";
                                var lightColor = "#9fc5e8";
                                break;
                            case "BUILDING":
                                var darkColor = "#ff0000";
                                var lightColor = "#ea9999";
                                break;
                            case "MATERIAL":
                                var darkColor = "#ffff00";
                                var lightColor = "#ffe599";
                                break;
                            case "GOD":
                                var darkColor = "#ff9900";
                                var lightColor = "#f9cb9c";
                                break;
                            case "REPLICA":
                                var darkColor = "#b7b7b7";
                                var lightColor = "#d9d9d9";
                                break;
                            default:
                                var darkColor = "#ffffff";
                                var lightColor = "#ffffff";
                        }
                        cardDiv.find(".dark-color").css("background-color", darkColor);
                        cardDiv.find(".light-color").css("background-color", lightColor);
                    }

                    // Adjust text scaling (at least trying to)
                    // TODO

                    /*]]>*/
                </script>
            </div>
        </main>

    </body>
</html>

style.css (properties relating to other pages have been omitted)

:root {
    /* Colors used for cards. Maybe should be moved elsewhere. */
    --creature-color: #7fdc39;
    --action-color: #4c4cff;
    --building-color: #ff0000;
    --material-color: #ffff00;
    --god-color: #ff9900;
    --replica-color: #b7b7b7;
    --gap-color: #c934e8;
}

/* used for cards */
@property --icon-color {
    syntax: "<color>";
    inherit: false;
    initial-value: var(--action-color);
}

/* variable way to modify card height */
@property --card-height {
    syntax: "<length>";
    inherit: true;
    initial-value: 600px;
}

main {
    padding: 10px;
    font-family: Arial;
}

body {
    padding: 0px;
    margin: 0px;
}

header {
    text-align: center;
    background-color: #3C0;
    padding: 50px;
    box-sizing: border-box;
}

/* Navigation bar on all pages */
nav {
    text-align: center;
    background-color: #aaa;
    padding: 10px;
    box-sizing: border-box;
}

/* Anchor tags inside navigation bar */
nav a {
    display: inline-block;
    border: 1px solid black;
    padding: 5px;
    box-sizing: border-box;
}

.card-table {
    display: grid;
    grid-template-areas:
        "header"
        "art"
        "desc"
        "footer";
    grid-template-rows: 12% 34% 42% 12%;
    grid-template-columns: 100%;
    width: calc(var(--card-height) * 0.725);
    height: var(--card-height);
    border: 5px solid black;
    border-radius: 5px;

    div {
        display: grid;
        margin: 0px 0px 0px 0px;
        padding: 5px;
        border-radius: 0px;
        overflow: hidden;
        border: 0px;
    }

    p {
        margin: 0;
        line-height: 1.2em;
    }

    /* No margin, uses area's padding instead. Between paragraphs, insert a new margin.*/
    p + p {
        margin-block-start: 0.5em;
    }

    > .header {
        grid-area: header;
        grid-template-areas:
            "name rarity";
        grid-template-columns: 80% 20%;
        grid-template-rows: 100%;
        border-bottom: 5px solid black;
        gap: 0px;
        padding: 0px;

        > .name {
            grid-area: name;
            font-size: 1.5em;
            border-right: 5px solid black;
            display: flex;
            align-items: center;
            padding: 5px;
        }

        > .rarity {
            grid-area: rarity;
            justify-self: center;
            height: 100%;
            width: auto;
        }
    }
    
    > .art {
        grid-area: art;
        padding: 0px;
        width: 100%;
        height: 100%; /* will contract just barely */
    }

    > .desc {
        grid-area: desc;
        padding: 5px;
        border-top: 5px solid black;
        border-bottom: 5px solid black;

        ul, ol { /* remove spacing before list */
            margin-block: 0;
        }

        svg.card-symbol {
            transform: translateY(0.2em); /* FIXME temporary fix to re-align symbols inside card descriptions */
        }
    }

    > .footer {
        grid-template-areas:
            "attack center health";
        grid-template-columns: 33% 34% 33%;
        grid-template-rows: 100%;

        align-items: center;
        justify-items: center;

        > .attack {
            grid-area: attack;
            font-size: 2em;
            display: flex;
            align-items: center;
        }

        > .center {
            grid-area: center;
            display: flex;
            align-items: center;
        }

        > .health {
            grid-area: health;
            font-size: 2em;
            display: flex;
            align-items:center;
        }
    }

    svg.card-symbol {
        display: inline-flex;
        align-self: center;
        width: 1.25em;
        height: 1.25em;
    }
}

.symbol-color {
    fill: var(--icon-color);
}

.card-display {
    padding: 5px;
    display: inline-block;
}

Google chart YAxis height and format

I am rendering actual vs budget hours graph per department per line item to show project performance. I have created a JSfiddle demo with complete working example.

My issue is that for several line items only few departments are needed but Google chart still allocates lot of space for those line items and makes chart bloated.

Any pointers or help will be greatly appreciated.

image showing issue

Example to show the issue:
https://jsfiddle.net/u0vrodcn

What I have done so far:

google.charts.load('current', { packages: ['corechart', 'bar'] })
google.charts.setOnLoadCallback(GenerateComplexBarChartReports)

// Helper function to find the minimum value for hAxis, if needed.
// This is a placeholder; you'll need to implement your actual logic here.
function FindMinValue(data) {
  let minValue = 0 // Default to 0, or calculate based on your data
  // Example: iterate through data to find min value if needed
  // for (let i = 0; i < data.getNumberOfRows(); i++) {
  //     for (let j = 1; j < data.getNumberOfColumns(); j += 8) { // Adjusted to jump by 8 for each department
  //         let actualHours = data.getValue(i, j);
  //         let budgetHours = data.getValue(i, j + 4); // Budget hours are 4 columns after actual
  //         if (typeof actualHours === "number" && actualHours < minValue) {
  //             minValue = actualHours;
  //         }
  //         if (typeof budgetHours === "number" && budgetHours < minValue) {
  //             minValue = budgetHours;
  //         }
  //     }
  // }
  return minValue
}

/**
 * Calculates the appropriate chart height based on the number of bars
 * per y-axis item to prevent overlap and ensure proper spacing.
 * @param {google.visualization.DataTable} data The DataTable object for the chart.
 * @returns {number} The calculated height for the chartArea.
 */
function CalculateChartHeightBasedOnDataItems(data) {
  // Height needed for a single bar including its annotation.
  // This is the fundamental unit of vertical space.
  const heightOfOneBarAndAnnotation = 30 // Further reduced for tighter packing

  // Minimal vertical space for the category label itself, if no bars are present,
  // or as a base for very sparse categories.
  const minHeightForCategoryLabel = 15 // Further reduced for tighter packing

  // Small, consistent padding *between* different y-axis categories.
  const interCategorySpacing = 5 // Further reduced for tighter packing

  let totalChartAreaHeight = 0

  for (let i = 0; i < data.getNumberOfRows(); i++) {
    let numberOfActiveSeriesInThisCategory = 0
    // Iterate through columns to count actual data series (bars) present for this row.
    // Your data structure is: PrimaryKey, SoftwareHours, SoftwareAnnotation, SoftwareToolTip, SoftwareColor,
    // SoftwareBudgetHours, SoftwareBudgetAnnotation, SoftwareBudgetToolTip, SoftwareBudgetColor,
    // ManufacturingHours, ... etc.
    // Each department block is 8 columns wide (value, annotation, tooltip, style, budget_value, budget_annotation, budget_tooltip, budget_style)
    // The first data column is at index 1 (SoftwareHours).
    for (let j = 1; j < data.getNumberOfColumns(); j += 8) {
      // Check for Actual Hours (column 'j')
      let actualHours = data.getValue(i, j)
      if (typeof actualHours === 'number' && actualHours > 0) {
        numberOfActiveSeriesInThisCategory++
      }

      // Check for Budget Hours (column 'j + 4' relative to the start of the department's block)
      let budgetHoursColIndex = j + 4
      // Ensure the budgetHoursColIndex is within the bounds of the data table's columns
      if (budgetHoursColIndex < data.getNumberOfColumns()) {
        let budgetHours = data.getValue(i, budgetHoursColIndex)
        if (typeof budgetHours === 'number' && budgetHours > 0) {
          numberOfActiveSeriesInThisCategory++
        }
      }
    }

    // Calculate height for the current y-axis category (row)
    if (numberOfActiveSeriesInThisCategory === 0) {
      // For categories with no bars, allocate minimal space for the label
      totalChartAreaHeight += minHeightForCategoryLabel + interCategorySpacing
    } else {
      // For categories with bars, calculate space based on active bars + inter-category spacing
      totalChartAreaHeight +=
        numberOfActiveSeriesInThisCategory * heightOfOneBarAndAnnotation +
        interCategorySpacing
    }
  }

  // Add some additional padding for the overall chart area,
  // useful for top/bottom margins within the plotting region.
  totalChartAreaHeight += 50 // General padding for the chart area itself

  return totalChartAreaHeight
}

/**
 * Generates the complex bar chart reports by iterating through elements
 * with the 'complex-barchart' class.
 */
function GenerateComplexBarChartReports() {
  let $jsonValueContainers = $('.complex-barchart')
  if ($jsonValueContainers.length <= 0) {
    console.log("No elements with class 'complex-barchart' found.")
    return
  }

  for (var i = 0; i < $jsonValueContainers.length; i++) {
    let $element = $($jsonValueContainers[i])
    let data = new google.visualization.DataTable($element.data('json'))
    let chartType = $element.data('chart-type')
    if (typeof chartType === 'undefined') {
      chartType = 'bar'
    }

    let hAxisMinValue = $element.data('h-axis-min-value')
    if (typeof hAxisMinValue === 'undefined') {
      // Call your FindMinValue function, or set a default
      hAxisMinValue = FindMinValue(data)
      $element.data('h-axis-min-value', hAxisMinValue)
    }

    let id = $element.attr('id')
    let chart = new google.visualization.BarChart(document.getElementById(id))

    // Calculate the height specifically for the chartArea
    let chartAreaCalculatedHeight = CalculateChartHeightBasedOnDataItems(data)

    // The overall chart height needs to be chartArea height PLUS space for hAxis, title, etc.
    // Adjust this buffer (e.g., 180) based on your chart's title, hAxis labels, and general padding needs.
    let overallChartHeight = chartAreaCalculatedHeight + 180 // Buffer for hAxis, title, etc.

    console.log(
      'Calculated Chart Area Height: ' + chartAreaCalculatedHeight + 'px',
    )
    console.log('Overall Chart Height: ' + overallChartHeight + 'px')

    let options = {
      annotations: {
        textStyle: {
          fontSize: 14          
        },
        // For bar charts, 'alwaysOutside: true' might not always place annotations outside the bar
        // if space is constrained. Google Charts tries its best.
        alwaysOutside: true,
      },
      vAxis: {
        textStyle: {
          fontSize: 14, // Font size for y-axis labels          
        },
        // You can add more vAxis options here for label positioning if needed
        // textPosition: 'out' // Example: position labels outside the chart area
      },
      tooltip: {
        isHtml: true,
        textStyle: {
          fontSize: 14,
        },
      },
      bar: { groupWidth: '75%' }, // Controls the thickness of the bars
      height: overallChartHeight, // Total height of the chart container
      chartArea: {
        height: chartAreaCalculatedHeight, // Height of the actual plotting area for bars
        left: 180, // Increased to give more room for long y-axis labels
        width: '65%', // Adjusted width of the chart area as a percentage
      },
      legend: { position: 'none' }, // No legend displayed
      bars: 'horizontal', // Required for Material Bar Charts to be horizontal
      // For Material charts, the hAxis is actually the x-axis for horizontal bars
      hAxis: {
        minValue: hAxisMinValue,
        textStyle: {
          fontSize: 12
        },
      },
    }

    // Conditionally add minValue to hAxis if hAxisMinValue is not 0
    if (hAxisMinValue !== 0) {
      options.hAxis.minValue = hAxisMinValue
    }

    chart.draw(data, options)
  }
}
body {
            font-family: 'Inter', sans-serif;
            display: flex;
            justify-content: center;
            align-items: flex-start;
            min-height: 100vh;
            background-color: #f0f2f5;
            padding: 20px;
            box-sizing: border-box;
        }
        .chart-container {
            width: 90%; /* Responsive width */
            max-width: 1200px; /* Max width for larger screens */
            background-color: #ffffff;
            border-radius: 12px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            padding: 20px;
            box-sizing: border-box;
        }
        h1 {
            text-align: center;
            color: #333;
            margin-bottom: 20px;
        }
        /* Ensure the chart div itself can grow */
        #chart_div {
            width: 100%;
            /* Height will be set dynamically by Google Charts */
        }
<script src="https://www.gstatic.com/charts/loader.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<div class="chart-container">
        <h1>Dynamic Bar Chart Report</h1>
        <!-- The data-json attribute holds your chart data.
             It's structured as [label, value1, tooltip1, color1, value2, tooltip2, color2, ...]
             This mock data simulates the structure from your original image. -->
        <div id="chart_div" class="complex-barchart"
             data-chart-type="bar"
             data-json='{"cols": [{"type": "string" ,"id": "PrimaryKey" ,"label": "PrimaryKey" }, {"type": "number" ,"id": "SoftwareHours" ,"label": "Software" }, {"type": "string" ,"id": "SoftwareAnnotation" ,"label": "SoftwareAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "SoftwareToolTip" ,"label": "SoftwareToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "SoftwareColor" ,"label": "SoftwareColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "SoftwareBudgetHours" ,"label": "SoftwareBudgetHours" }, {"type": "string" ,"id": "SoftwareBudgetAnnotation" ,"label": "SoftwareBudgetAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "SoftwareBudgetToolTip" ,"label": "SoftwareBudgetToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "SoftwareBudgetColor" ,"label": "SoftwareBudgetColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "ManufacturingHours" ,"label": "Manufacturing" }, {"type": "string" ,"id": "ManufacturingAnnotation" ,"label": "ManufacturingAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "ManufacturingToolTip" ,"label": "ManufacturingToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "ManufacturingColor" ,"label": "ManufacturingColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "ManufacturingBudgetHours" ,"label": "ManufacturingBudgetHours" }, {"type": "string" ,"id": "ManufacturingBudgetAnnotation" ,"label": "ManufacturingBudgetAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "ManufacturingBudgetToolTip" ,"label": "ManufacturingBudgetToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "ManufacturingBudgetColor" ,"label": "ManufacturingBudgetColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "Electrical_ENGHours" ,"label": "Electrical_ENG" }, {"type": "string" ,"id": "Electrical_ENGAnnotation" ,"label": "Electrical_ENGAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "Electrical_ENGToolTip" ,"label": "Electrical_ENGToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "Electrical_ENGColor" ,"label": "Electrical_ENGColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "Electrical_ENGBudgetHours" ,"label": "Electrical_ENGBudgetHours" }, {"type": "string" ,"id": "Electrical_ENGBudgetAnnotation" ,"label": "Electrical_ENGBudgetAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "Electrical_ENGBudgetToolTip" ,"label": "Electrical_ENGBudgetToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "Electrical_ENGBudgetColor" ,"label": "Electrical_ENGBudgetColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "Mechanical_ENGHours" ,"label": "Mechanical_ENG" }, {"type": "string" ,"id": "Mechanical_ENGAnnotation" ,"label": "Mechanical_ENGAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "Mechanical_ENGToolTip" ,"label": "Mechanical_ENGToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "Mechanical_ENGColor" ,"label": "Mechanical_ENGColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "Mechanical_ENGBudgetHours" ,"label": "Mechanical_ENGBudgetHours" }, {"type": "string" ,"id": "Mechanical_ENGBudgetAnnotation" ,"label": "Mechanical_ENGBudgetAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "Mechanical_ENGBudgetToolTip" ,"label": "Mechanical_ENGBudgetToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "Mechanical_ENGBudgetColor" ,"label": "Mechanical_ENGBudgetColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "ProjectManagementHours" ,"label": "ProjectManagement" }, {"type": "string" ,"id": "ProjectManagementAnnotation" ,"label": "ProjectManagementAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "ProjectManagementToolTip" ,"label": "ProjectManagementToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "ProjectManagementColor" ,"label": "ProjectManagementColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "ProjectManagementBudgetHours" ,"label": "ProjectManagementBudgetHours" }, {"type": "string" ,"id": "ProjectManagementBudgetAnnotation" ,"label": "ProjectManagementBudgetAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "ProjectManagementBudgetToolTip" ,"label": "ProjectManagementBudgetToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "ProjectManagementBudgetColor" ,"label": "ProjectManagementBudgetColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "AdminHours" ,"label": "Admin" }, {"type": "string" ,"id": "AdminAnnotation" ,"label": "AdminAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "AdminToolTip" ,"label": "AdminToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "AdminColor" ,"label": "AdminColor" , "p": {"role" : "style"}}, {"type": "number" ,"id": "AdminBudgetHours" ,"label": "AdminBudgetHours" }, {"type": "string" ,"id": "AdminBudgetAnnotation" ,"label": "AdminBudgetAnnotation" , "p": {"role" : "annotation"}}, {"type": "string" ,"id": "AdminBudgetToolTip" ,"label": "AdminBudgetToolTip" , "p": {"role" : "tooltip"}}, {"type": "string" ,"id": "AdminBudgetColor" ,"label": "AdminBudgetColor" , "p": {"role" : "style"}}], "rows" : [{"c" : [{"v": "DR (OCS) &amp; PASS (ODS)"}, {"v": 660.00}, {"v": "Manufacturing Actual: 660.00hrs (85.3%); Left: 98.00hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Manufacturing Actual: 660.00hrs (85.3%); Left: 98.00hrs"}, {"v": "#A9D08E"}, {"v": 758.00}, {"v": "Manufacturing Budget: 758.00hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Manufacturing Actual: 660.00hrs (85.3%); Left: 98.00hrs"}, {"v": "{fill-color:#A9D08E;fill-opacity: 0.5;}"}, {"v": 35.25}, {"v": "Software Actual: 35.25hrs (4.6%); Left: 144.75hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Software Actual: 35.25hrs (4.6%); Left: 144.75hrs"}, {"v": "#7030A0"}, {"v": 180.00}, {"v": "Software Budget: 180.00hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Software Actual: 35.25hrs (4.6%); Left: 144.75hrs"}, {"v": "{fill-color:#7030A0;fill-opacity: 0.5;}"}, {"v": 73.75}, {"v": "Electrical_ENG Actual: 73.75hrs (9.5%); Left: 14.25hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Electrical_ENG Actual: 73.75hrs (9.5%); Left: 14.25hrs"}, {"v": "#FFFF00"}, {"v": 88.00}, {"v": "Electrical_ENG Budget: 88.00hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Electrical_ENG Actual: 73.75hrs (9.5%); Left: 14.25hrs"}, {"v": "{fill-color:#FFFF00;fill-opacity: 0.5;}"}, {"v": 5.00}, {"v": "Mechanical_ENG Actual: 5.00hrs (0.6%); Left: 103.00hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Mechanical_ENG Actual: 5.00hrs (0.6%); Left: 103.00hrs"}, {"v": "#305496"}, {"v": 108.00}, {"v": "Mechanical_ENG Budget: 108.00hrs"}, {"v": "DR (OCS) &amp; PASS (ODS) Mechanical_ENG Actual: 5.00hrs (0.6%); Left: 103.00hrs"}, {"v": "{fill-color:#305496;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "EOL"}, {"v": 181.50}, {"v": "Manufacturing Actual: 181.50hrs (31.9%); Left: 326.50hrs"}, {"v": "EOL Manufacturing Actual: 181.50hrs (31.9%); Left: 326.50hrs"}, {"v": "#A9D08E"}, {"v": 508.00}, {"v": "Manufacturing Budget: 508.00hrs"}, {"v": "EOL Manufacturing Actual: 181.50hrs (31.9%); Left: 326.50hrs"}, {"v": "{fill-color:#A9D08E;fill-opacity: 0.5;}"}, {"v": 220.00}, {"v": "Electrical_ENG Actual: 220.00hrs (38.7%); OverBudget: 82.00hrs (By: 59.42%)"}, {"v": "EOL Electrical_ENG Actual: 220.00hrs (38.7%); OverBudget: 82.00hrs (By: 59.42%)"}, {"v": "#FFFF00"}, {"v": 138.00}, {"v": "Electrical_ENG Budget: 138.00hrs"}, {"v": "EOL Electrical_ENG Actual: 220.00hrs (38.7%); OverBudget: 82.00hrs (By: 59.42%)"}, {"v": "{fill-color:#FFFF00;fill-opacity: 0.5;}"}, {"v": 142.00}, {"v": "Software Actual: 142.00hrs (25.0%); Left: 37.25hrs"}, {"v": "EOL Software Actual: 142.00hrs (25.0%); Left: 37.25hrs"}, {"v": "#7030A0"}, {"v": 179.25}, {"v": "Software Budget: 179.25hrs"}, {"v": "EOL Software Actual: 142.00hrs (25.0%); Left: 37.25hrs"}, {"v": "{fill-color:#7030A0;fill-opacity: 0.5;}"}, {"v": 24.75}, {"v": "Mechanical_ENG Actual: 24.75hrs (4.4%); Left: 5.25hrs"}, {"v": "EOL Mechanical_ENG Actual: 24.75hrs (4.4%); Left: 5.25hrs"}, {"v": "#305496"}, {"v": 30.00}, {"v": "Mechanical_ENG Budget: 30.00hrs"}, {"v": "EOL Mechanical_ENG Actual: 24.75hrs (4.4%); Left: 5.25hrs"}, {"v": "{fill-color:#305496;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "Roll cart and Lift &amp; Locate"}, {"v": 83.75}, {"v": "Mechanical_ENG Actual: 83.75hrs (85.9%); OverBudget: 45.75hrs (By: 120.39%)"}, {"v": "Roll cart and Lift &amp; Locate Mechanical_ENG Actual: 83.75hrs (85.9%); OverBudget: 45.75hrs (By: 120.39%)"}, {"v": "#305496"}, {"v": 38.00}, {"v": "Mechanical_ENG Budget: 38.00hrs"}, {"v": "Roll cart and Lift &amp; Locate Mechanical_ENG Actual: 83.75hrs (85.9%); OverBudget: 45.75hrs (By: 120.39%)"}, {"v": "{fill-color:#305496;fill-opacity: 0.5;}"}, {"v": 13.75}, {"v": "Manufacturing Actual: 13.75hrs (14.1%); Left: 3.25hrs"}, {"v": "Roll cart and Lift &amp; Locate Manufacturing Actual: 13.75hrs (14.1%); Left: 3.25hrs"}, {"v": "#A9D08E"}, {"v": 17.00}, {"v": "Manufacturing Budget: 17.00hrs"}, {"v": "Roll cart and Lift &amp; Locate Manufacturing Actual: 13.75hrs (14.1%); Left: 3.25hrs"}, {"v": "{fill-color:#A9D08E;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "AC and PB"}, {"v": 24.50}, {"v": "Mechanical_ENG Actual: 24.50hrs (88.3%); Left: 30.00hrs"}, {"v": "AC and PB Mechanical_ENG Actual: 24.50hrs (88.3%); Left: 30.00hrs"}, {"v": "#305496"}, {"v": 54.50}, {"v": "Mechanical_ENG Budget: 54.50hrs"}, {"v": "AC and PB Mechanical_ENG Actual: 24.50hrs (88.3%); Left: 30.00hrs"}, {"v": "{fill-color:#305496;fill-opacity: 0.5;}"}, {"v": 3.25}, {"v": "Electrical_ENG Actual: 3.25hrs (11.7%); Left: 14.00hrs"}, {"v": "AC and PB Electrical_ENG Actual: 3.25hrs (11.7%); Left: 14.00hrs"}, {"v": "#FFFF00"}, {"v": 17.25}, {"v": "Electrical_ENG Budget: 17.25hrs"}, {"v": "AC and PB Electrical_ENG Actual: 3.25hrs (11.7%); Left: 14.00hrs"}, {"v": "{fill-color:#FFFF00;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "Cal Verify &amp; Cert Plate"}, {"v": 16.00}, {"v": "Mechanical_ENG Actual: 16.00hrs (94.1%); Left: 11.00hrs"}, {"v": "Cal Verify &amp; Cert Plate Mechanical_ENG Actual: 16.00hrs (94.1%); Left: 11.00hrs"}, {"v": "#305496"}, {"v": 27.00}, {"v": "Mechanical_ENG Budget: 27.00hrs"}, {"v": "Cal Verify &amp; Cert Plate Mechanical_ENG Actual: 16.00hrs (94.1%); Left: 11.00hrs"}, {"v": "{fill-color:#305496;fill-opacity: 0.5;}"}, {"v": 1.00}, {"v": "Electrical_ENG Actual: 1.00hrs (5.9%); Left: 4.00hrs"}, {"v": "Cal Verify &amp; Cert Plate Electrical_ENG Actual: 1.00hrs (5.9%); Left: 4.00hrs"}, {"v": "#FFFF00"}, {"v": 5.00}, {"v": "Electrical_ENG Budget: 5.00hrs"}, {"v": "Cal Verify &amp; Cert Plate Electrical_ENG Actual: 1.00hrs (5.9%); Left: 4.00hrs"}, {"v": "{fill-color:#FFFF00;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "63-Way Cables"}, {"v": 28.25}, {"v": "Manufacturing Actual: 28.25hrs (60.4%); Left: 16.25hrs"}, {"v": "63-Way Cables Manufacturing Actual: 28.25hrs (60.4%); Left: 16.25hrs"}, {"v": "#A9D08E"}, {"v": 44.50}, {"v": "Manufacturing Budget: 44.50hrs"}, {"v": "63-Way Cables Manufacturing Actual: 28.25hrs (60.4%); Left: 16.25hrs"}, {"v": "{fill-color:#A9D08E;fill-opacity: 0.5;}"}, {"v": 15.00}, {"v": "Mechanical_ENG Actual: 15.00hrs (32.1%); Left: 13.00hrs"}, {"v": "63-Way Cables Mechanical_ENG Actual: 15.00hrs (32.1%); Left: 13.00hrs"}, {"v": "#305496"}, {"v": 28.00}, {"v": "Mechanical_ENG Budget: 28.00hrs"}, {"v": "63-Way Cables Mechanical_ENG Actual: 15.00hrs (32.1%); Left: 13.00hrs"}, {"v": "{fill-color:#305496;fill-opacity: 0.5;}"}, {"v": 3.50}, {"v": "Electrical_ENG Actual: 3.50hrs (7.5%); Left: 12.50hrs"}, {"v": "63-Way Cables Electrical_ENG Actual: 3.50hrs (7.5%); Left: 12.50hrs"}, {"v": "#FFFF00"}, {"v": 16.00}, {"v": "Electrical_ENG Budget: 16.00hrs"}, {"v": "63-Way Cables Electrical_ENG Actual: 3.50hrs (7.5%); Left: 12.50hrs"}, {"v": "{fill-color:#FFFF00;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "ProjectManagement"}, {"v": 24.25}, {"v": "ProjectManagement Actual: 24.25hrs (100.0%); Left: 65.75hrs"}, {"v": "ProjectManagement ProjectManagement Actual: 24.25hrs (100.0%); Left: 65.75hrs"}, {"v": "#ED7D31"}, {"v": 90.00}, {"v": "ProjectManagement Budget: 90.00hrs"}, {"v": "ProjectManagement ProjectManagement Actual: 24.25hrs (100.0%); Left: 65.75hrs"}, {"v": "{fill-color:#ED7D31;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "Red Rabbit Box"}, {"v": 1.00}, {"v": "Electrical_ENG Actual: 1.00hrs (100.0%); Left: 5.00hrs"}, {"v": "Red Rabbit Box Electrical_ENG Actual: 1.00hrs (100.0%); Left: 5.00hrs"}, {"v": "#FFFF00"}, {"v": 6.00}, {"v": "Electrical_ENG Budget: 6.00hrs"}, {"v": "Red Rabbit Box Electrical_ENG Actual: 1.00hrs (100.0%); Left: 5.00hrs"}, {"v": "{fill-color:#FFFF00;fill-opacity: 0.5;}"}]}, {"c" : [{"v": "Admin"}, {"v": 0.50}, {"v": "Admin Actual: 0.50hrs (100.0%); Left: 56.00hrs"}, {"v": "Admin Admin Actual: 0.50hrs (100.0%); Left: 56.00hrs"}, {"v": "#ED7D31"}, {"v": 56.50}, {"v": "Admin Budget: 56.50hrs"}, {"v": "Admin Admin Actual: 0.50hrs (100.0%); Left: 56.00hrs"}, {"v": "{fill-color:#ED7D31;fill-opacity: 0.5;}"}]}]}'
             data-h-axis-min-value="0">
        </div>
    </div>

Virtual-scrolling works with native scrollbar but not with SimpleBar—rows appear but list won’t scroll

I’m trying to add virtual scrolling to a long list that uses SimpleBar for custom styling.
With the native scrollbar the technique works: I create a very tall invisible spacer (.sizer) to give the scroll-bar its full range and then absolutely-position only the visible rows inside the scroll container.
When I switch to SimpleBar the rows render, yet I cannot scroll beyond the first few items.

Native version (works):

  <div class="sizer"></div>   <!-- height = 30 px * 1 000 000 -->
  <!-- visible rows are appended here -->
</div>

<script>
const ROW_HEIGHT  = 30;
const TOTAL_ITEMS = 1_000_000;

const viewport = document.getElementById('viewport');
function render () {
  const scrollTop = viewport.scrollTop;
  /* …compute first/last, paint rows… */
}
viewport.addEventListener('scroll', render);
render();
</script>

SimpleBar version (rows appear, no scrolling):

<div id="viewport" data-simplebar>
  <div class="sizer"></div>
</div>
<script>
const sb  = new SimpleBar(document.getElementById('viewport'));
const scrollEl = sb.getScrollElement();   // .simplebar-content-wrapper
function render () {
  const scrollTop = scrollEl.scrollTop;
  /* …compute first/last, paint rows inside sb.getContentElement() … */
}
scrollEl.addEventListener('scroll', render);
render();
</script>

The spacer (.sizer) is inside the SimpleBar content element, yet dragging the thumb only moves a few pixels and the list never reaches the bottom.
What am I missing so that SimpleBar respects the spacer’s height and allows full scrolling?

Full Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8"/>
  <title>Virtual scroll + SimpleBar</title>

  <!-- SimpleBar CSS -->
  <link rel="stylesheet" href="simplebar.min.css"/>
  <style>
    #viewport {
      height: 400px;
      width: 300px;
      border: 1px solid #444;
      /* SimpleBar will turn this into a scrollable widget */
    }

    /* invisible spacer that gives the scrollbar its full range */
    .sizer {
      height: calc(30px * 1000000);
    }

    /* rows are absolutely positioned inside the simplebar-content area */
    .row {
      position: absolute;
      left: 0;
      width: 100%;
      height: 30px;
      line-height: 30px;
      box-sizing: border-box;
      border-bottom: 1px solid #ddd;
    }
  </style>
</head>

<body>
  <h2>Virtual list (1 000 000 rows) with SimpleBar</h2>
  <div id="viewport" data-simplebar>
    <div class="sizer"></div>
  </div>

  <!-- SimpleBar JS -->
  <script src="simplebar.min.js"></script>
  <script>
    const ROW_HEIGHT  = 30;
    const TOTAL_ITEMS = 1_000_000;
    const BUFFER      = 5;

    // SimpleBar gives us the *real* scrollable wrapper
    const sb       = new SimpleBar(document.getElementById('viewport'));
    const scrollEl = sb.getScrollElement();      // the <div class="simplebar-content-wrapper">

    function render() {
      const scrollTop = scrollEl.scrollTop;
      const first     = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER);
      const last      = Math.min(TOTAL_ITEMS - 1,
                                 Math.ceil((scrollTop + scrollEl.clientHeight) / ROW_HEIGHT) + BUFFER);

      /* clear old rows */
      sb.getContentElement().querySelectorAll('.row').forEach(r => r.remove());

      /* add visible rows */
      for (let i = first; i <= last; i++) {
        const div = document.createElement('div');
        div.className = 'row';
        div.style.top  = i * ROW_HEIGHT + 'px';
        div.textContent = `Row #${i + 1}`;
        sb.getContentElement().appendChild(div);
      }
    }

    scrollEl.addEventListener('scroll', render, { passive: true });
    render();        // initial draw
  </script>
</body>
</html>

How to set custom validation message for HTML input on mouseenter?

I have ASP.NET Core project and trying to set custom validation message for HTML form input field on mouseenter. I tried to add event listeners for input, change and mouseenter events like this.

<!DOCTYPE html>
<html>
<body>
    <form>
        <label for="field1">Field1:</label>
        <input id="field1" type="text" class="validate" data-error-message="You must fill in field1!" required
               oninput="this.setCustomValidity('');this.reportValidity()" onchange="validateInputElement(this)" onmouseenter="validateInputElement(this)" />
        <label for="field2">Field1:</label>
        <input id="field2" type="text" class="validate" data-error-message="You forgot to fill in field2!" required
               oninput="this.setCustomValidity('');this.reportValidity()" onchange="validateInputElement(this)" onmouseenter="validateInputElement(this)" />
    </form>
    <script>
        function validateInputElement(element)
        {
            let validationMessage = "";
            if(element.validity !== ValidityState.valid)
            {
                validationMessage = element.dataset["errorMessage"];
            }
            else if(document.querySelector("input#field1")?.value === document.querySelector("input#field2")?.value)
            {
                validationMessage = "You must enter different values!"
            }
            element.setCustomValidity(validationMessage);
            element.reportValidity();
        }
    </script>
</body>
</html>

In this case, my custom validation messages are displayed, but the default validation messages are also displayed. How to disable default validation messages?

Jquery DatetimePicker Selected Datetime automatically shifting Issue

I have configured the jquery datetimepicker by cdn. The datetimepicker have shifting the time 1 hour back from the input field value for each time opened the datetimepicker.

Given below the code

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.min.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.full.min.js"></script>

<input required="required" name="shift-datetime" id="shift-datetime" value="Jul 14, 2025 07:30 PM" class="form-control" autocomplete="off">

  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.min.css" />
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.full.min.js"></script>

<input required="required" name="shift-datetime" id="shift-datetime" value="Jul 14, 2025 07:30 PM" class="form-control" autocomplete="off">

<script>
    $("#shift-datetime").datetimepicker({
    format: "M d, Y h:i A",
    formatTime: "h:i A",
    step: 30,
    timepicker: true,
    hours12: false,
    defaultTime: false,
    seconds: true
    });
    </script>

onclick event not firing under any circumstance, but worked earlier in document

I have an onclick event that should fire a function to pull data from a form and calculate a z score. The previous onclick usage worked to write a random number to a label, but this one won’t even show the alert that pulls the value from one of the form fields. Any ideas?

<form>
   <label>Random Number</label>
   <label id="rannum"></label>
   <script>
      function writeRand(){
         let surprise = Math.floor(Math.random() * 100) + 1;
          document.getElementById("rannum").innerHTML='Random number: ' + surprise;
         }
   </script>
   <br />
   <button type="button" onclick="writeRand()">Get Random Number</button>
</form>
</section>

<!--Section to calcculate Z-Score based on user input-->
<section name="ZScoreCalc">
   <p>Calculate Z Score</p>
   <form>
      <label>X</label>
      <input type="number" id="setX"></input><br>
      <label>Sample Mean</label><br>
      <input type="number" id="splMean"></input><br>
      <label>Standard Deviation</label><br>
      <input type="number" id="stdDev"></input><br>
      <button type="button" id="CalcBtn" onmousedown="findZscore()">Calculate Z</button>
      <label>Z Score is:</label>
      <label id="zScore"></label>
      <script>
         function findZscore(){
             let ex=document.getElementById("setX").value;
             let mn=documment.getElementById("splMean").value;
             let sd=document.getElementById("stdDev").value;
         
             let z=(ex-mn)/sd;
             window.alert(document.getElementById("setX").value);
             window.alert(mn);
             window.alert(sd);
             window.alert(z);
             
             document.getElementById("zScore").value=z;
         }
      </script>
   </form>
</section>

I tried putting a listener event in the script to catch the button click. As you can see in the code, I also tried capturing it as a mousedown event. Nothing seems to fire the function.

Why does my JWT-based session not auto-expire after 2 hours in a MERN HRMS project?

I’m building an HRMS dashboard using the MERN stack as part of a hiring test. I’m using JWT for authentication. I want the user to be automatically logged out after 2 hours.

Here’s what I did in the backend:

js
Copy
Edit
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
expiresIn: ‘2h’,
});
On the frontend (React), I store the token in localStorage and attach it to headers for API calls.

js
Copy
Edit
localStorage.setItem(“token”, res.data.token);
But the token still works even after 2 hours unless I manually delete it.
I expected the session to auto-expire and log out the user.

What I want:
Auto-logout after 2 hours.

Either on token expiry or by checking on each route.

What I tried:
Checked token expiry with jwt.decode().

Manually clearing token after setTimeout (not ideal).

Not sure how to sync frontend with token expiry.

How can I correctly implement JWT auto-expiry and frontend logout when the token is expired?

Why should we use enum in TS instant of object?

to achieve enum in js we use Objects. it will the same impact as enum. Even though we have objects in TS we are using enum instant of object so what will be the difference of enum and object?

let HttpStatusCode = {
OK : 200,
INTERNAL_SERVER_ERROR : 500,
NOT_FOUND : 404
}

this is js

enum HttpStatusCode {
  OK = 200,
  NOT_FOUND = 404,
  INTERNAL_SERVER_ERROR = 500
}

this is TS so whats the difference

JointJS is not rendering HTML elements with scroll on Safari

In my Vue app, I’m using JointJS to create a process diagram.

Everything works fine on Chrome – both locally and in the testing environment.
On Safari, it also works fine locally, but after being deployed to the testing environment – styles break.

There are 2 elements in the process (we can call them “Input” and “Output”), which contain a list of items inside. In case there are too many items, I want to be able to scroll – therefore, I’ve added overflow-y: auto; to the div styles. Instead, those items are rendered in the top-left corner of the canvas for some reason.

enter image description here

I’ve read that there are some issues with overflow-y: auto; on Safari, so I’ve changed it to overflow-y: scroll; – but it doesn’t help. Seems like for some reason scroll doesn’t work – just on Safari & testing environment… any idea how to solve it?

Everything works well when I’m removing overflow-y: scroll; line:
enter image description here

… or, keeping it, but removing some element from the list, so it prevents overflowing:
enter image description here

Unfortunately, scrolling is essential here.

Here’s a function responsible for generating those 2 elements:

const generateMassDataHtml = (title, massData = null, totalMass = null) => `
    <div style="height: ${unitOpHeight}px; padding: 10px 16px; width: 100%; overflow-y: ${isSafari ? 'scroll' : 'auto'};">
      <p style="text-align: center; ${textLabelStyle} font-weight: 600; margin-bottom: 4px;">${title}</p>
      <hr style="border: none; border-top: 1px solid #e0e0e0; margin: 0 0 10px 0;">
      ${
        this.hasResults && massData && totalMass
          ? Object.entries(massData).map(([name, mass]) => `
              <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; margin-bottom: 8px;">
                <p style="text-align: center; ${textLabelStyle} font-weight: 500; margin-bottom: 0;">${name}</p>
                ${
                  this.isFree && mass.component
                    ? `<span style="${getHiddenValueBlockStyle()}"></span>`
                    : `<p style="text-align: center; ${textValueStyle} margin-bottom: 0;">${mass.value.toLocaleString('en-US', { maximumFractionDigits: 0 })} kg</p>`
                }
              </div>
            `).join('') + `
              <p style="text-align: center; ${textLabelStyle} font-weight: 500; margin-bottom: 0;">Total Mass:</p>
              <p style="text-align: center; ${textValueStyle}; margin-bottom: 0;">${totalMass.toLocaleString('en-US', { maximumFractionDigits: 0 })} kg</p>
            `
          : this.hasConfiguration
            ? `<p style="text-align: center; ${textLabelStyle} margin-top: 20px;">No results can be calculated, please reconfigure the process parameters</p>`
            : `<p style="text-align: center; ${textLabelStyle} margin-top: 20px;">No process parameters configured</p>`
      }
    </div>
  `

const input = new joint.shapes.standard.Rectangle()
  input.resize(unitOpWidth, unitOpHeight)
  const inputX = xStart + (stepWidth * (elementsCount % unitsPerRow))
  const inputY = yStart + (Math.floor(elementsCount / unitsPerRow) * (unitOpHeight + yGap))
  input.position(inputX, inputY)
  elementsCount++
  input.markup = [
    { tagName: 'rect', selector: 'body' },
    {
      tagName: 'foreignObject',
      selector: 'fo',
      attributes: { width: unitOpWidth, height: unitOpHeight },
      children: [
        {
          namespaceURI: 'http://www.w3.org/1999/xhtml',
          tagName: 'div',
          selector: 'htmlContent',
          attributes: { style: unitOpCardStyle }
        }
      ]
    }
  ]
  const inputHtml = generateMassDataHtml('DSP Input', this.hasResults ? this.massData.input : null, this.hasResults ? this.results.input.mass : null)
  input.attr({
    body: { fill: '#fff', rx: 16, ry: 16, stroke: '#e0e0e0' },
    fo: { width: unitOpWidth, height: unitOpHeight },
    htmlContent: { html: inputHtml }
  })
  input.addTo(this.graph)

chartjs dynamically increase line sizing

I’m using chartjs for line charts, and I have this code I’m using to dynamically increase the thickness of the lines:

    $(document).on('change', ".line_width", function(e)
    {
        e.preventDefault();

        picked_width = $(this).val();

    $.each( charttest.config.data.datasets, function( index, value )
    {
        charttest.config.data.datasets[index].borderWidth = parseInt(picked_width);
    });

charttest.update();

});

It works but it also annoyingly increases the size of each legend at the top.

Is there a way to stop this? I assume I’m increasing the wrong variable with borderWidth.

Web Component causing Layout Shift

I’m creating a plain vanilla website, and many pages of my website has a component in common (a sidebar), so I created a Web Component to easily include in all pages.

The class of the component:

//sidebar/index.js

class Sidebar extends HTMLElement {
    connectedCallback() {
        if(!this.querySelector("div")) {
            this.innerHTML = 
               '<div id="sidebar" class="d-flex flex-column h-100 p-3 bg-body-tertiary">' +
               '    <a href="/admin/home" class="h4 text-decoration-none text-nowrap">Central Api</a>' +
               '    <hr>' +
               '    <ul class="nav nav-pills flex-column">' +
               '        <li class="nav-item">' +
               '            <a id="home" class="nav-link" href="/admin/home">Home</a>' +
               '        </li>' +
               '        <li class="nav-item">' +
               '            <a id="users" class="nav-link" href="/admin/users">Usuários</a>' +
               '        </li>' +
               '    </ul>' +
               '</div>';
            if(window.location.href.includes("/home")) {
                this.querySelector("#home").classList.add("active");
            } else if(window.location.href.includes("/user")) {
                this.querySelector("#users").classList.add("active");
            }
        }
    }
}

export const defineSideBar = () => {
    customElements.define("x-sidebar", Sidebar);
}

How the main script are imported:

<!-- index.html -->
<script type="module" src="main.js"></script>

And how the main script load the new component

//main.js

import { defineSideBar } from "../../../components/sidebar/index.js";

const app = () => {
    defineSideBar();

    //... more code here but not related
};

document.addEventListener('DOMContentLoaded', app);

The problem is that the component is causing Layout Shift.

Cumulative Layout Shift metric provided by browser Developer Tools

What I already tried:

  1. call the defineSideBar() directly inside main.js script instead inside app() expecting the web component to be defined early.

  2. prefetch/preload the script main.js script and sidebar/index.js expecting reducing the time to render the component.

  3. encapsulating the component with a fixed size div, this really reduced the Layout Shift to zero. But doesn’t look right to me, because the without being a web component, the sidebar don’t cause layout shift, so being a web component should do not cause either.

As I understand, the browser first load the component as an empty tag, draw de rest of page, and then when DOMContentLoaded event occurs the x-sidebar is rendered, causing layout shift. So, maybe there a way to draw the entire page in one time?

Finally, there something that I can do to avoid layout shift using web-components without encapsulating it with a fixed size div?

dont summarize JS function [closed]

// acsesProperty buttons display

let typeBtns = document.querySelectorAll("#typeBtn");
let acsesPropertys = document.querySelectorAll("#acsesProperty");

typeBtns[0].addEventListener("click", () => {
  acsesPropertys[0].classList.remove("d-none");
  acsesPropertys[1].classList.add("d-none");
  acsesPropertys[2].classList.add("d-none");
});

typeBtns[1].addEventListener("click", () => {
  acsesPropertys[1].classList.remove("d-none");
  acsesPropertys[0].classList.add("d-none");
  acsesPropertys[2].classList.add("d-none");
});

typeBtns[2].addEventListener("click", () => {
  acsesPropertys[2].classList.remove("d-none");
  acsesPropertys[1].classList.add("d-none");
  acsesPropertys[0].classList.add("d-none");
});

I’m write this code but it’s soo long this function control the typeBtns clicked show or hide acsesPropertys

Javascript document.write causes ASP 500 – Internal server error [closed]

This works just fine on an HTML page, but breaks ASP, any idea why?

<script>
this.load_Version=function(){
   if (globREG == "EUROPE"){
   document.write("<script src='EUROPE.js'></script>");
   }else{
   document.write("<script src='NORTHAM.js'></script>");
   }
}

load_Version();
</script> 

Even simply commenting out or even deleting the call load_Version(); results in a 500…

When I remove the code, console.log(globREG); confirms that the variable exists.

This produces the same 500 error:

<script>
function load_Version(){
   if (globREG == "EUROPE"){
   document.write("<script src='EUROPE.js'></script>");
   }else{
   document.write("<script src='NORTHAM.js'></script>");
   }
}

load_Version();
<script>

What could be the issue? Thanks.

Unable to get Stripe setup intents to work

Using Stripe’s “Elements” from @stripe/react-stripe-js I am doing the following flow for subscribing a user to a subscription via payment intents:

  1. Customer submits payment form and a request is made to the backend to create the setup intent:

    const setupIntent = await stripe.setupIntents.create({
      usage: 'off_session',
      customer: req.user.stripeCustomerId,
      payment_method_types: ['card']
    });
    
    return {
      statusCode: 200,
      body: JSON.stringify({
        clientSecret: setupIntent.client_secret,
        setupIntentId: setupIntent.id
      })
    };
    
  2. A payment method is created via the frontend Stripe Elements API:

      const cardElement = elements.getElement(CardNumberElement)!;
    
      const { paymentMethod, error: pmError } =
        await stripe.createPaymentMethod({
          type: "card",
          card: cardElement,
          billing_details: {
            address: {
              postal_code: postalCode,
            },
            name: fullName,
          },
        });
    
  3. I then update the setup intent with this newly created payment method id on the backend:

    const { paymentMethodId, setupIntentId } = req.body;
    
    const setupIntent = await stripe.setupIntents.update(setupIntentId, {
      payment_method: paymentMethodId
    });
    
    return {
      statusCode: 200,
      body: JSON.stringify({
        clientSecret: setupIntent.client_secret,
        setupIntentId: setupIntent.id
      })
    };
    
  4. I then confirm the card setup on the frontend, this takes me through the 3DS flow where I successfully confirm through the 3DS portal/ window:

      const { setupIntent, error: confirmError } =
        await stripe.confirmCardSetup(updatedSetupIntentData.clientSecret);
    
  5. I then attempt create a subscription:

    const subscription = await stripe.subscriptions.create({
      customer: req.user.stripeCustomerId,
      items: [
        { price: PRICE_ID } 
      ],
      expand: ['latest_invoice.payment_intent'] // helps with frontend handling
    });
    

This is where the issue comes, I get the following error in Stripe dashboard:

The cardholder began 3D Secure authentication but has not completed
it.

I don’t understand why it’s trying to run the 3DS authentication again. I thought that was already done in step 4?