I made a to-do list using JavaScript. How do I remember the list items after refresh or exit and revisiting the page without deleting the items?

I have created a basic and unstyled to-do list using Javascript. I want the browser to remember the list items when I refresh the page or when I exit the page and revisit it.

I also want the user to be able to click an “X” or something to delete the list item from the browser’s memory and to forget it.

Here’s my code:

HTML:

<!DOCTYPE html>
<html lang="en">

<head>
    <title>My TO-DOs</title>
</head>

<body>
    <div id="header">
        <h1>My Todos</h1>

        <form>
            <label>Add an item:</label>
            <input type="text">

            <button type="submit">ADD</button>
        </form>
    </div>

    <ul id="itemList"></ul>
    <script src="main.js"></script>
</body>

</html>

JavaScript:

const form = document.querySelector('form');
const input = document.querySelector('input');

form.addEventListener('submit', function(e){
    e.preventDefault();

    if (input.value === '' || input.value === null) {
        alert('You cannot add a blank item.');
    } else{
    const listItem = document.createElement('li');
    const listItemText = document.createTextNode(input.value);
    listItem.appendChild(listItemText);
    

    const listElement = document.getElementById('itemList');
    listElement.appendChild(listItem)

    // Clear the input after adding an item
    input.value = '';
    input.focus();
}});

Here is a code snippet:

const form = document.querySelector('form');
const input = document.querySelector('input');

form.addEventListener('submit', function(e){
    e.preventDefault();

    if (input.value === '' || input.value === null) {
        alert('You cannot add a blank item.');
    } else{
    const listItem = document.createElement('li');
    const listItemText = document.createTextNode(input.value);
    listItem.appendChild(listItemText);
    

    const listElement = document.getElementById('itemList');
    listElement.appendChild(listItem)

    // Clear the input after adding an item
    input.value = '';
    input.focus();
}});
<!DOCTYPE html>
<html lang="en">

<head>
    <title>My TO-DOs</title>
</head>

<body>
    <div id="header">
        <h1>My Todos</h1>

        <form>
            <label>Add an item:</label>
            <input type="text">

            <button type="submit">ADD</button>
        </form>
    </div>

    <ul id="itemList"></ul>
    <script src="main.js"></script>
</body>

</html>

Cast to ObjectId failed for value … at path “author”

I’m fairly new in programming but I am following a udemy course that’s been great to me but I encountered an issue I’m having trouble solving. Whenever I node seed/index.js to add new latitudes and longitudes to the clustermap I’ve just added, I get this campground validation failed error. I’ve checked my models and they look okay to me. I don’t know what to do next at this point. Here’s my whole code, https://github.com/wcadz27/YelpCamp . Any help is greatly appreciated. Thank you very much

check if object element in array 1 exist in array 2?

consider this two array of objects :

const arr1 = [
    {id: 'parent1', title: 'product1', childProducts: [{id: 'child1', title: 'childProduct1'}]},
    {id: 'parent2', title: 'product2', childProducts: [{id: 'child2', title: 'childProduct2'}]},
];

const arr2 = [{id: 'child1', title: 'childProduct1'}];

I need to compare childProducts of arr 1 with arr 2 and somehow filter arr 1 like below:

const result = [{id: 'parent2', title: 'product2', childProducts: [{id: 'child2', title: 'childProduct2'}]}]

in other words, I need to return elements from arr1 that their childProducts are not in arr2 (whith help of loops).
how can I achieve this?

PDF field: Change State/Province name to abbreviation

I have a field in a PDF that is calculating the value of two fields: Billing City and Billing State/Province with a comma between:

event.value=this.getField("Billing City").value + ", " + this.getField("Billing State/Province").value;

The information is imported into a PDF with a txt file where the State/Province is written as the full name, but I would like the above field to use the abbreviation.

I have the state/province field as a dropdown with abbreviations as the export value, however it’s still calculating using the state/province full name. I tried this code but then the State/Province field gets stuck on the first option:

event.value = this.getField("Billing State/Province").valueAsString;

this.getField("Billing State/Province").setItems([["Alberta", "AB"], ["British Columbia", "BC"], ["Manitoba", "MB"], ["New Brunswick", "NB"], ["Newfoundland and Labrador", "NL"], ["Nova Scotia", "NS"], ["Northwest Territories", "NT"], ["Nunavut", "NU"], ["Ontario", "ON"], ["Prince Edward Island", "PE"], ["Quebec", "QC"], ["Saskatchewan", "SK"], ["Yukon Territories", "YT"]]);

Is there a different way to change the state/province name to abbreviations?

Is there any way to change null onClick from a hold to just a click

I’m just trying to see if I change this onClick from a hold to a single click as currently to activate it you need to hold the F1 key instead of clicking it. I can attach any other files anyone needs, I’m also quite new to JS so I understand this may come across as quite newbie.

Anyways, here is the code –


var DEFAULT_SIZE = 100;
var MIN_SECTORS  = 3;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function RadialMenu(params) {
    var self = this;
    self.parent  = params.parent  || [];
    self.size      = params.size    || DEFAULT_SIZE;
    self.onClick   = params.onClick || null;
    self.menuItems = params.menuItems ? params.menuItems : [{id: 'one', title: 'One'}, {id: 'two', title: 'Two'}];

    self.radius      = 45;
    self.innerRadius = self.radius * 0.32;
    self.sectorSpace = self.radius * 0.03;
    self.sectorCount = Math.max(self.menuItems.length, MIN_SECTORS);
    self.closeOnClick = params.closeOnClick !== undefined ? !!params.closeOnClick : false;
    self.scale       = 1;
    self.holder      = null;
    self.parentMenu  = [];
    self.parentItems = [];
    self.levelItems  = null;

    self.createHolder();
    self.addIconSymbols();

    self.currentMenu = null;
    self.wheelBind = self.onMouseWheel.bind(self);
    self.keyDownBind =  self.onKeyDown.bind(self);
    document.addEventListener('wheel', self.wheelBind);
    document.addEventListener('keydown', self.keyDownBind);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.open = function () {
    var self = this;
    if (!self.currentMenu) {
        self.currentMenu = self.createMenu('menu inner', self.menuItems);
        self.holder.appendChild(self.currentMenu);

        // wait DOM commands to apply and then set class to allow transition to take effect
        RadialMenu.nextTick(function () {
            self.currentMenu.setAttribute('class', 'menu');
        });
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.close = function () {
    var self = this;
    if (self.currentMenu) {
        var parentMenu;
        while (parentMenu = self.parentMenu.pop()) {
            parentMenu.remove();
        }
        self.parentItems = [];
        $.post(`http://pepe-radialmenu/closemenu`, JSON.stringify({}));
        RadialMenu.setClassAndWaitForTransition(self.currentMenu, 'menu inner').then(function () {
            self.currentMenu.remove();
            self.currentMenu = null;
        });
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.getParentMenu = function () {
    var self = this;
    if (self.parentMenu.length > 0) {
        return self.parentMenu[self.parentMenu.length - 1];
    } else {
        return null;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createHolder = function () {
    var self = this;

    self.holder = document.createElement('div');
    self.holder.className = 'menuHolder';
    self.holder.style.width  = self.size + 'px';
    self.holder.style.height = self.size + 'px';

    self.parent.appendChild(self.holder);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.showNestedMenu = function (item) {
    var self = this;
    self.parentMenu.push(self.currentMenu);
    self.parentItems.push(self.levelItems);
    self.currentMenu = self.createMenu('menu inner', item.items, true);
    self.holder.appendChild(self.currentMenu);

    // wait DOM commands to apply and then set class to allow transition to take effect
    RadialMenu.nextTick(function () {
        self.getParentMenu().setAttribute('class', 'menu outer');
        self.currentMenu.setAttribute('class', 'menu');
    });
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.returnToParentMenu = function () {
    var self = this;
    self.getParentMenu().setAttribute('class', 'menu');
    RadialMenu.setClassAndWaitForTransition(self.currentMenu, 'menu inner').then(function () {
        self.currentMenu.remove();
        self.currentMenu = self.parentMenu.pop();
        self.levelItems = self.parentItems.pop();
    });
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.handleClick = function () {
    var self = this;

    var selectedIndex = self.getSelectedIndex();
    if (selectedIndex >= 0) {
        var item = self.levelItems[selectedIndex];
        if (item.items) {
            self.showNestedMenu(item);
        } else {
            if (self.onClick) {
                self.onClick(item);
                if (self.closeOnClick) {
                    self.close();
                }
            }
        }
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.handleCenterClick = function () {
    var self = this;
    if (self.parentItems.length > 0) {
        self.returnToParentMenu();
    } else {
        self.close();
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createCenter = function (svg, title, icon, size) {
    var self = this;
    size = size || 8;
    var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
    g.setAttribute('class', 'center');

    var centerCircle = self.createCircle(0, 0, self.innerRadius - self.sectorSpace / 3);
    g.appendChild(centerCircle);
    if (text) {
        var text = self.createText(0,0, title);
        text.innerHTML = title;
        g.appendChild(text);
    }

    if (icon) {
        var use = self.createUseTag(0,0, icon);
        use.setAttribute('width', size);
        use.setAttribute('height', size);
        use.setAttribute('class', 'shadow');
        use.setAttribute('transform', 'translate(-' + RadialMenu.numberToString(size / 2) + ',-' + RadialMenu.numberToString(size / 2) + ')');
        g.appendChild(use);
    }

    svg.appendChild(g);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.getIndexOffset = function () {
    var self = this;
    if (self.levelItems.length < self.sectorCount) {
        switch (self.levelItems.length) {
            case 1:
                return -2;
            case 2:
                return -2;
            case 3:
                return -2;
            default:
                return -1;
        }
    } else {
        return -1;
    }

};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createMenu = function (classValue, levelItems, nested) {
    var self = this;

    self.levelItems = levelItems;

    self.sectorCount = Math.max(self.levelItems.length, MIN_SECTORS);
    self.scale       = self.calcScale();

    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('class', classValue);
    svg.setAttribute('viewBox', '-50 -50 100 100');
    svg.setAttribute('width', self.size);
    svg.setAttribute('height', self.size);

    var angleStep   = 360 / self.sectorCount;
    var angleShift  = angleStep / 2 + 270;

    var indexOffset = self.getIndexOffset();

    for (var i = 0; i < self.sectorCount; ++i) {
        var startAngle = angleShift + angleStep * i;
        var endAngle   = angleShift + angleStep * (i + 1);

        var itemIndex = RadialMenu.resolveLoopIndex(self.sectorCount - i + indexOffset, self.sectorCount);
        var item;
        if (itemIndex >= 0 && itemIndex < self.levelItems.length) {
            item = self.levelItems[itemIndex];
        } else {
            item = null;
        }

        self.appendSectorPath(startAngle, endAngle, svg, item, itemIndex);
    }

    if (nested) {
        self.createCenter(svg, 'Close', '#return', 8);
    } else {
        self.createCenter(svg, 'Close', '#close', 7);
    }

    $(svg).on('click', '.sector', function(event) {
        var index = parseInt($(this).data('index'));
        if (!isNaN(index)) {
            self.setSelectedIndex(index);
        }
        self.handleClick();
    });

    $(svg).on('mouseenter', '.sector', function(event) {
        var index = parseInt($(this).data('index'));
        if (!isNaN(index)) {
            self.setSelectedIndex(index);
        }
    });

    $(svg).on('click', '.center', function(event) {
        self.handleCenterClick();
    });
    
    return svg;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.selectDelta = function (indexDelta) {
    var self = this;
    var selectedIndex = self.getSelectedIndex();
    if (selectedIndex < 0) {
        selectedIndex = 0;
    }
    selectedIndex += indexDelta;

    if (selectedIndex < 0) {
        selectedIndex = self.levelItems.length + selectedIndex;
    } else if (selectedIndex >= self.levelItems.length) {
        selectedIndex -= self.levelItems.length;
    }
    self.setSelectedIndex(selectedIndex);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.onKeyDown = function (event) {
    var self = this;
    if (self.currentMenu) {
        switch (event.key) {
            case 'Escape':
            case 'Backspace':
                self.handleCenterClick();
                event.preventDefault();
                break;
            case 'Enter':
                self.handleClick();
                event.preventDefault();
                break;
            case 'ArrowRight':
            case 'ArrowUp':
                self.selectDelta(1);
                event.preventDefault();
                break;
            case 'ArrowLeft':
            case 'ArrowDown':
                self.selectDelta(-1);
                event.preventDefault();
                break;
        }
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.onMouseWheel = function (event) {
    var self = this;
    if (self.currentMenu) {
        var delta = -event.deltaY;

        if (delta > 0) {
            self.selectDelta(1)
        } else {
            self.selectDelta(-1)
        }
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.getSelectedNode = function () {
    var self = this;
    var items = self.currentMenu.getElementsByClassName('selected');
    if (items.length > 0) {
        return items[0];
    } else {
        return null;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.getSelectedIndex = function () {
    var self = this;
    var selectedNode = self.getSelectedNode();
    if (selectedNode) {
        return parseInt(selectedNode.getAttribute('data-index'));
    } else {
        return -1;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.setSelectedIndex = function (index) {
    var self = this;
    if (index >=0 && index < self.levelItems.length) {
        var items = self.currentMenu.querySelectorAll('g[data-index="' + index + '"]');
        if (items.length > 0) {
            var itemToSelect = items[0];
            var selectedNode = self.getSelectedNode();
            if (selectedNode) {
                selectedNode.setAttribute('class', 'sector');
            }
            itemToSelect.setAttribute('class', 'sector selected');
        }
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createUseTag = function (x, y, link) {
    var use = document.createElementNS('http://www.w3.org/2000/svg', 'use');
    use.setAttribute('x', RadialMenu.numberToString(x));
    use.setAttribute('y', RadialMenu.numberToString(y));
    use.setAttribute('width', '10');
    use.setAttribute('height', '10');
    use.setAttribute('fill', 'white');
    use.setAttribute("style", "color:white");
    use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', link);
    return use;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.appendSectorPath = function (startAngleDeg, endAngleDeg, svg, item, index) {
    var self = this;

    var centerPoint = self.getSectorCenter(startAngleDeg, endAngleDeg);
    var translate = {
        x: RadialMenu.numberToString((1 - self.scale) * centerPoint.x),
        y: RadialMenu.numberToString((1 - self.scale) * centerPoint.y)
    };

    var g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
    g.setAttribute('transform','translate(' +translate.x + ' ,' + translate.y + ') scale(' + self.scale + ')');

    var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    path.setAttribute('d', self.createSectorCmds(startAngleDeg, endAngleDeg));
    g.appendChild(path);

    if (item) {
        g.setAttribute('class', 'sector');
        if (index == 0) {
            g.setAttribute('class', 'sector selected');
        }
        g.setAttribute('data-id', item.id);
        g.setAttribute('data-index', index);

        if (item.title) {
            var text = self.createText(centerPoint.x, centerPoint.y, item.title);
            text.setAttribute('transform', 'translate(0, 1.5)');
       
            let multiTitle = item.title.split(" ")
            for(let title in multiTitle){
                var tspanElement = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
                tspanElement.innerHTML = multiTitle[title];
                tspanElement.setAttribute("x", RadialMenu.numberToString(centerPoint.x));
                tspanElement.setAttribute("dy", "1em");
                text.appendChild(tspanElement);
            }

            g.appendChild(text);
        }

        if (item.icon) {
            var use = self.createUseTag(centerPoint.x, centerPoint.y, item.icon);
            if (item.title) {
                use.setAttribute('transform', 'translate(-5,-8)');
            } else {
                use.setAttribute('transform', 'translate(-5,-5)');
            }

            g.appendChild(use);
        }

    } else {
        g.setAttribute('class', 'dummy');
    }

    svg.appendChild(g);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createSectorCmds = function (startAngleDeg, endAngleDeg) {
    var self = this;

    var initPoint = RadialMenu.getDegreePos(startAngleDeg, self.radius);
    var path = 'M' + RadialMenu.pointToString(initPoint);

    var radiusAfterScale = self.radius * (1 / self.scale);
    path += 'A' + radiusAfterScale + ' ' + radiusAfterScale + ' 0 0 0' + RadialMenu.pointToString(RadialMenu.getDegreePos(endAngleDeg, self.radius));
    path += 'L' + RadialMenu.pointToString(RadialMenu.getDegreePos(endAngleDeg, self.innerRadius));

    var radiusDiff = self.radius - self.innerRadius;
    var radiusDelta = (radiusDiff - (radiusDiff * self.scale)) / 2;
    var innerRadius = (self.innerRadius + radiusDelta) * (1 / self.scale);
    path += 'A' + innerRadius + ' ' + innerRadius + ' 0 0 1 ' + RadialMenu.pointToString(RadialMenu.getDegreePos(startAngleDeg, self.innerRadius));
    path += 'Z';

    return path;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createText = function (x, y, title) {
    var self = this;
    var text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
    text.setAttribute('text-anchor', 'middle');
    text.setAttribute('x', RadialMenu.numberToString(x));
    text.setAttribute('y', RadialMenu.numberToString(y));
    text.setAttribute('font-size', '38%');
    //text.innerHTML = title;
    return text;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.createCircle = function (x, y, r) {
    var circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
    circle.setAttribute('cx',RadialMenu.numberToString(x));
    circle.setAttribute('cy',RadialMenu.numberToString(y));
    circle.setAttribute('r',r);
    return circle;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.calcScale = function () {
    var self = this;
    var totalSpace = self.sectorSpace * self.sectorCount;
    var circleLength = Math.PI * 2 * self.radius;
    var radiusDelta = self.radius - (circleLength - totalSpace) / (Math.PI * 2);
    return (self.radius - radiusDelta) / self.radius;
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.getSectorCenter = function (startAngleDeg, endAngleDeg) {
    var self = this;
    return RadialMenu.getDegreePos((startAngleDeg + endAngleDeg) / 2, self.innerRadius + (self.radius - self.innerRadius) / 2);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.prototype.addIconSymbols = function () {
    var self = this;
    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('class', 'icons');

    // return
    var returnSymbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
    returnSymbol.setAttribute('id', 'return');
    returnSymbol.setAttribute('viewBox', '0 0 489.394 489.394');
    var returnPath =   document.createElementNS('http://www.w3.org/2000/svg', 'path');
    returnPath.setAttribute('d', "M375.789,92.867H166.864l17.507-42.795c3.724-9.132,1-19.574-6.691-25.744c-7.701-6.166-18.538-6.508-26.639-0.879" +
        "L9.574,121.71c-6.197,4.304-9.795,11.457-9.563,18.995c0.231,7.533,4.261,14.446,10.71,18.359l147.925,89.823" +
        "c8.417,5.108,19.18,4.093,26.481-2.499c7.312-6.591,9.427-17.312,5.219-26.202l-19.443-41.132h204.886" +
        "c15.119,0,27.418,12.536,27.418,27.654v149.852c0,15.118-12.299,27.19-27.418,27.19h-226.74c-20.226,0-36.623,16.396-36.623,36.622" +
        "v12.942c0,20.228,16.397,36.624,36.623,36.624h226.74c62.642,0,113.604-50.732,113.604-113.379V206.709" +
        "C489.395,144.062,438.431,92.867,375.789,92.867z");
    returnSymbol.appendChild(returnPath);
    svg.appendChild(returnSymbol);

    var closeSymbol = document.createElementNS('http://www.w3.org/2000/svg', 'symbol');
    closeSymbol.setAttribute('id', 'close');
    closeSymbol.setAttribute('viewBox', '0 0 41.756 41.756');

    var closePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
    closePath.setAttribute('d', "M27.948,20.878L40.291,8.536c1.953-1.953,1.953-5.119,0-7.071c-1.951-1.952-5.119-1.952-7.07,0L20.878,13.809L8.535,1.465" +
        "c-1.951-1.952-5.119-1.952-7.07,0c-1.953,1.953-1.953,5.119,0,7.071l12.342,12.342L1.465,33.22c-1.953,1.953-1.953,5.119,0,7.071" +
        "C2.44,41.268,3.721,41.755,5,41.755c1.278,0,2.56-0.487,3.535-1.464l12.343-12.342l12.343,12.343" +
        "c0.976,0.977,2.256,1.464,3.535,1.464s2.56-0.487,3.535-1.464c1.953-1.953,1.953-5.119,0-7.071L27.948,20.878z");
    closeSymbol.appendChild(closePath);
    svg.appendChild(closeSymbol);

    self.holder.appendChild(svg);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.getDegreePos = function (angleDeg, length) {
    return {
        x: Math.sin(RadialMenu.degToRad(angleDeg)) * length,
        y: Math.cos(RadialMenu.degToRad(angleDeg)) * length
    };
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.pointToString = function (point) {
    return RadialMenu.numberToString(point.x) + ' ' + RadialMenu.numberToString(point.y);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.numberToString = function (n) {
    if (Number.isInteger(n)) {
        return n.toString();
    } else if (n) {
        var r = (+n).toFixed(5);
        if (r.match(/./)) {
            r = r.replace(/.?0+$/, '');
        }
        return r;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.resolveLoopIndex = function (index, length) {
    if (index < 0) {
        index = length + index;
    }
    if (index >= length) {
        index = index - length;
    }
    if (index < length) {
        return index;
    } else {
        return null;
    }
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.degToRad = function (deg) {
    return deg * (Math.PI / 180);
};

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.setClassAndWaitForTransition = function (node, newClass) {
    return new Promise(function (resolve) {
        function handler(event) {
            if (event.target == node && event.propertyName == 'visibility') {
                node.removeEventListener('transitionend', handler);
                resolve();
            }
        }
        node.addEventListener('transitionend', handler);
        node.setAttribute('class', newClass);
    });
};

RadialMenu.prototype.destroy = function() {
    document.removeEventListener('wheel', this.wheelBind);
    document.removeEventListener('keydown', this.keyDownBind);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
RadialMenu.nextTick = function (fn) {
    setTimeout(fn, 10);
};

Draw a 2D canvas as 3D

I have this working as is but is in a “birds eye view” looking down.

I was thinking about adding a z field for each Body element to simulate the depth of it being closer or further away from the “view point”.

That got me thinking of how to simulate a “view point” to draw the perspective of how close or far the planets are during their orbit.

Would it be possible with what I have now to “transform” this into a 3D perspective. I have seen people create rotating bodies like planets with a 2D canvas, but how could that translate into several bodies rotating and revolving?

var canvas, width, height, ctx;
var bodies = [];

canvas = document.getElementById("space-time");

document.addEventListener("DOMContentLoaded", function(event) {
    init();
});

function init(){
    width = window.innerWidth;
    height = window.innerHeight;
    canvas.width = width;
    canvas.height = height;
    ctx = canvas.getContext('2d');
    

    createBodies();

    setInterval(function(){
        updateSystem();
        updateBodies(0.01);
        ctx.clearRect(0, 0, width, height);
        drawBodies();
        
    }, 10);
}

function createBodies(){
    bodies.push(new Body((this.width / 2), (this.height / 2) - 525, 125, 0, 20, 30, "#ec4899", true));
    bodies.push(new Body((this.width / 2), (this.height / 2) + 450, 200, 0, 5, 20, "#7e22ce", true));
    bodies.push(new Body((this.width / 2), (this.height / 2) - 300, 250, 0, 2, 15, "#1d4ed8", true));
    bodies.push(new Body((this.width / 2), (this.height / 2) + 200, 300, 0, 1, 10, "#22c55e", true));
    bodies.push(new Body((this.width / 2), (this.height / 2) - 125, 400, 0, 1, 5, "#b91c1c", true));
    bodies.push(new Body(this.width / 2, this.height / 2, 0, 0, 1000000, 50, "#f97316", false)); //sun
}

function drawBodies(){
    for(var i = 0; i < bodies.length; i++){
        bodies[i].draw(ctx);
    }
}

function updateBodies(dt){
    for(var i = 0; i < bodies.length; i++){
        bodies[i].update(dt);
    }
}

function updateSystem(){
    var G = 10;
    for(var i = 0; i < bodies.length; i++){
        for(var j = 0; j < bodies.length; j++){
            if(i === j) continue;
            var b1 = bodies[i];
            var b2 = bodies[j];

            var dist = Math.sqrt((b1.x - b2.x) * (b1.x - b2.x) + (b1.y - b2.y) * (b1.y - b2.y));
            var force = G * (b1.m * b2.m) / dist / dist;
            var nx = (b2.x - b1.x) / dist;
            var ny = (b2.y - b1.y) / dist;

            b1.ax += nx * force / b1.m;
            b1.ay += ny * force / b1.m;

            b2.ax -= nx * force / b2.m;
            b2.ay -= ny * force / b2.m;
        }
    }
}

function Body(x, y, v, angle, mass, radius, color, hasTail){
    this.x = x;
    this.y = y;
    this.v = v;
    this.a = angle;
    this.r = radius;
    this.c = color;
    this.t = hasTail;
    this.vx = v * Math.cos(angle);
    this.vy = v * Math.sin(angle);
    this.m = mass;
    this.radius = radius;
    this.color = color;
    this.ax = 0;
    this.ay = 0;

    if(hasTail){
        this.tail = new Tail(30);
    }

    this.update = function(dt){
        this.vx += this.ax * dt;
        this.vy += this.ay * dt;
        this.x += this.vx * dt;
        this.y += this.vy * dt;
        this.ax = 0;
        this.ay = 0;

        if(this.tail){
            this.tail.addPoint({x: this.x, y: this.y});
        }
    }

    this.draw = function(ctx){
        ctx.strokeStyle = this.color;
        ctx.fillStyle = this.color;
        ctx.shadowColor = this.color;
        ctx.shadowBlur = 5;
        if(this.tail){
            this.tail.draw(ctx);
        }
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, 6.28);
        ctx.fill();
    }
}

function Tail(maxLength){
    this.points = [];
    this.maxLength = maxLength;
    this.addPoint = point => {
        this.points = [point].concat(this.points).slice(0, this.maxLength);
    }
    this.draw = function(ctx){
        for(var i = 1; i < this.points.length; i++){
            ctx.beginPath();
            if(i < maxLength - 20){
                ctx.globalAlpha = (this.maxLength - i) / 20;
            }
            ctx.moveTo(this.points[i - 1].x, this.points[i - 1].y);
            ctx.lineTo(this.points[i].x, this.points[i].y);
            ctx.stroke();
        }
        ctx.globalAplha = 1;
    }
}
#space-time {
  background-color: black;
  width: 100%;
 }
<canvas id="space-time"canvas></canvas>

Return a variable in CYPRESS

I have a class in cypress in which I would like to have a method to decide in the written test later which variable to use (example GoingBefore (yes), then go for yes = cy.get (‘… “)

How can I spell this correctly?

class MyClass {

    GoingBefore(yes, no){
    this.yes=cy.get('.mx-radio').eq(1);
    this.no=cy.get('.mx-radio').eq(2);
    return this }
    }
export default MyClass

and after in test

import MyClass from "./PageObject/MyClass"

it("Test", function(){
        const Test = new MyClass();
        Test.GoingBefore(yes);
    })

Fill tag using text in array

I’m new to JavaScript, so I’m not sure what keywords to search to find my specific answer. Thanks in advance for your help.

I need to dynamically create li tags with a hrefs in the nav. These will scroll to 4 different sections in main.
I have created each li and a href.
I now need to get the text from each h2 to the a element in each li
I have started by creating an array from the h2 elements, but now realized that I could use the outerHTML from the previous array.
How can I get the h2 text, or access the outerHTML property from the sectionIds array?

//creates a element and assigns to  listItemHref
const listItemHref = document.createElement('a');
//assigns #nav_list or ul to variable navList
const navList = document.querySelector('#nav_list');
//creates array from id's in section elements
const sectionIds = Array.from(document.getElementsByTagName('section'));
//creates array of h2
const sectionH2 = Array.from(document.getElementsByTagName('h2'));  

for (section of sectionIds){
    //creates a <li> element for each section name
    const listItem = document.createElement('li');
    //creates an <a> element for each section name
    const listItemHref = document.createElement('a');
    //sets the "href" for each <a> element
    listItemHref.setAttribute("href", "#" + section.id);
    listItem.setAttribute("class", "line_item");
    listItem.appendChild(listItemHref);
    navList.appendChild(listItem);
}

//code to take h2 text and insert into a tag text
for (heading of sectionH2){
    // ? not sure 
}
<header class="page_header" id="home">
  <h1>Resume</h1>
    <!--each li is created using JavaScript-->
  <nav class="nav_menu">
      <ul id="nav_list">
    <li class="line_item">
      <a href="#education"></a></li>
    <li class="line_item">
      <a href="#my_projects"></a></li>
    <li class="line_item">
      <a href="#about"></a></li>
    <li class="line_item">      
      <a href="#contact"></a></li>
    </ul>
    </nav>
</header>
<main>
  <section id="education">
    <h2>My Education</h2>
  </section>
  <section id="my_projects">
    <h2>My Projects</h2>
  </section>
  <section id="about">
    <h2>About Me</h2>
  </section>
  <section id="contact">
    <h2>Contact Me</h2>
  </section>
</main>

Building a tree from a flat array

I am given an array, links:

  const links = [
      {parent: "flare", children: "analytics"} ,
      {parent: "analytics", children: "cluster"} ,
      {parent: "flare", children: "scale"} ,
      {parent: "analytics", children: "graph"} ,
  ];  

I want to make it into tree, like so:

const tree = {
 "name": "flare",
 "children": [
  {
   "name": "analytics",
   "children": [
    {
     "name": "cluster",
    },
    {
     "name": "graph",
    }
   ]
  }
 ]
};

Here is my attempt:

function buildTree(links) {

    const map = { }

    const findNodeInChildren = (name, obj) => {
      if (obj[name]) {
        return obj
      } else if (!obj.children) {
        return null
      }

      for (let i = 0; i < obj.children.length; i++) {
        const found = findNodeInChildren(name, obj.children[i])
        if (found) return found
      }

      return null
    }
    
    links.forEach(link => {
      const foundNode = findNodeInChildren(link.parent, map)
      
      if (!foundNode) {
        const newNode = {
          name: link.parent,
          children: []
        }
        map[newNode.name] = newNode
      } else {
          foundNode[link.parent].children.push({
          name: link.children,
          children: []
        })
      }
    })

   return map
}

  const links = [
      {parent: "flare", children: "analytics"} ,
      {parent: "analytics", children: "cluster"} ,
      {parent: "flare", children: "scale"} ,
      {parent: "analytics", children: "graph"} ,
  ];  
  
  const tree = buildTree(links)
  const json = JSON.stringify(tree)
  console.log(json)

Here’s the prettified JSON – it’s not working as intended:

{
  "flare": {
    "name": "flare",
    "children": [
      {
        "name": "scale",
        "children": []
      }
    ]
  },
  "analytics": {
    "name": "analytics",
    "children": [
      {
        "name": "graph",
        "children": []
      }
    ]
  }
}

What is going wrong?

I have a button that I want to grow and shrink over and over while the user is mousing over that button

The Codepen linked below is where I currently am stuck.

function startHover(e) {
  btn.classList.add("btnPlaying")
}

function removeHover(e) {
  btn.classList.remove("btnPlaying");
}

const btn = document.querySelector('.btn')
btn.addEventListener("mouseenter", startHover);
btn.addEventListener('transitionend', removeHover);
.btn {
  margin-top: 10rem;
  padding: 20px 100px;
  background-color: rgb(255, 204, 3);
  border-radius: 10px;
  border-style: none;
  box-shadow: 0px 0px 10px black;
  color: blue;
  border: 4px solid rgb(53, 106, 188);
  transition: all 1.07s ease;
}

.btnPlaying {
  transform: scale(1.1);
}
<button class="btn">Play!</button>

https://codepen.io/TerrellsCode/pen/zYEyORB

The button grows and shrinks like intended but only does it one time. Look for any pointers on how to make this grow/shrink animation loop infinitely as long as user is hovering over button. Thank You

How can I display “-” as long as string length in textbox?

I have this list:

let teams = [
“real madrid”,
“barcelona”,
“milan”,
“inter”,
“juventus”,
“manchester united”,
“manchester city”,
“liverpool”,
“arsenal”,
“chelsea”,
“bayern munich”,
];

I wrote code for choose random item from this list but I want display “-” as long as item length.

For example: choose real madrid … I want this – – – – – – – – – – in the text box.

Uploading multiple photos at once in firebase storage and storing their URLs in a firestore document (firebase version 9)

I am building a form where the user needs to input information to create a city document. The user also needs to upload multiple photos of the city. When submitting the form, a new city document is created in firestore, then each photo is uploaded to the associated firebase storage, and finally a new field called photosURLs with all the photos URLs is added to the city document.

Here is my code:

async function addDocument() {
    const docRef = await addDoc(collection(db, "cities"), {
        name: "Tokyo",
        country: "Japan"
      });
    return docRef
}

async function UploadMultiplePhotos(docRef) {
    var photos = []
    for (var i = 0; i < files.value.length; i++) { // files.values contains all the files objects
        const file = files.value[i];
        refStorageFunction(
            storage,
            "cities/" +
            docRef.id +
            "/" +
            file.name
          );
        uploadBytes(storageRef, file).then((snapshot) => {
            getDownloadURL(snapshot.ref).then((downloadURL) => {
                photos.push(downloadURL)
            });
        });
    }
    return Promise.resolve(photos)
}

async function updateDocument(docRef, photos) {
    await updateDoc(docRef, { photosURLs: photos });
}

function createCity() {
    addDocument().then((docRef) => {
        UploadMultiplePhotos(docRef).then((photos) => {
            updateDocument(docRef, photos).then(() => {
                router.push($CITY_PATH)
            })
        })
    })
}

My issue is that the resulting photosURLs field in the city document is empty. It seems my function UploadMultiplePhotos does not wait for the photos array to be completely populated.

How to access Ms Access database from browser in this day and age?

I have an Excel workbook full of macros and Ms Access database to store data. The idea is to have a portable software with minimum dependency of host computer. Developing that is major pain in 2022 AD.

I was planning on rewriting it for web UI to drop one dependency, but it would still need to use Ms Access (security and backwards compatibility).

Is there anyway to access the Access with javascript? I find references to now-buried ActiveX components and ODBC settings on Windows, but is that all there is? No workarounds that would enable modern UI for Access on a local computer?

What alternatives do I have should I consider different database engine? The requirements are simple: it needs to run when opening “mysheets.html” from local hard drive to a browser, have no dependencies to configuration of host computer, needs to be able to travel on usb-stick and have password protection for data (any is better than none). It’s acceptable to make it interop with local exe-files (that can travel with), if possbile.

I would rather not use Node for this, but looks like it’s one option (trading a dependency to another). Nothing that needs to be built and does not make source code “available” in the application does not work, the source code cannot be lost under any circumstances (high mobility).

Using Underscore.js help to iterate over it

So I managed to get my array grouped by Entity name using Underscore.js with _.groupBy. It gives me the next output:

enter image description here

The output its correct, the problem is that I cant *ngfor over it on my html because is obviously not an array. Do anybody know how to iterate over this and for example show on my html part a “mat-expansion-panel-header” with the output blue text?