How to Reload to same Page while sending a parameter?

I need to reload the page whenever different buttons are pressed, while sending a String to the same page so that on created() it takes that String and sends an HTTP Get into my Database.

Currently I have the following:

export default {
    data(){
        return{
            events: [],
            formData:{
                sportType: 'Ténis'
            }
        }
    },

    created(){
        //Do something here to get the value sent from the reloading
        axios.get(`http://localhost:8001/evento`, {headers: {sportType: this.formData.sportType}})
            .then((response)=>{
                this.events = response.events
            },(error) =>{
                console.log(error);
        });
    },
    pickSport(item){
                
    }

The function pickSport() is called whenever the buttons are pressed and each sends a value to this function that is a String. The idea now is to be able to reload the page when this function is called, while sending this item to the reloaded page, so I can update the value of sportType. I tried:

        pickDesporto(item){
            this.$router.push({
                path: '/betting',
                params: item
            });
        }

But with no success, since it keeps giving me a NavigationDuplicated error. How can I solve this?

How Do I change the color of webgl points?

This is the code I used to try to change the color of WebGL points:

I want to change the color of WebGL points when a user clicks the body element.
And FYI the shaders are compiling correctly.

The numbers in the color_obj object seem to be changing when I click on the screen. However, the WebGL colors don’t change. Can someone help me with this?

const canvas = document.getElementById("canvas");
const gl = canvas.getContext("webgl");

gl.clearColor(0.3, 0.6, 0.7, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT || gl.DEPTH_BUFFER_BIT);

if (!gl) {
    throw new Error("WebGL not supported");
}

console.log("This is working");

const points = [
    1.0, 1.0, 0.0,
    1.0, -1.0, 0.0,
    -1.0, -1.0, 0.0,
    1.0, 1.0, 0.0,
    -1.0, -1.0, 0.0,
    -1.0, 1.0, 0.0
];

let color_obj = {
    color_1: 0.4,
    color_2: 0.7,
    color_3: 0.8,
    color_4: 0.0,
    color_5: 0.5,
}

let colors = [
    color_obj.color_1, color_obj.color_2, color_obj.color_3,
    color_obj.color_3, color_obj.color_1, color_obj.color_3,
    color_obj.color_4, color_obj.color_4, color_obj.color_4,
    color_obj.color_1, color_obj.color_2, color_obj.color_3,
    color_obj.color_4, color_obj.color_4, color_obj.color_4,
    color_obj.color_5, color_obj.color_5, color_obj.color_5
];

const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(points), gl.STATIC_DRAW);

const buffer_2 = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer_2);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW)

const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, `
precision mediump float;

attribute vec3 pos;
attribute vec3 rgb;
varying vec3 rgbColor;

void main() {
    rgbColor = rgb;
    gl_Position = vec4(pos, 1);
}
`);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, `
precision mediump float;

varying vec3 rgbColor;

void main() {
    gl_FragColor = vec4(rgbColor, 1);
}
`);
gl.compileShader(fragmentShader);

const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

const positionRef = gl.getAttribLocation(program, `pos`);
gl.enableVertexAttribArray(positionRef);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(positionRef, 3, gl.FLOAT, false, 0, 0);

const colorRef = gl.getAttribLocation(program, `rgb`);
gl.enableVertexAttribArray(colorRef);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer_2);
gl.vertexAttribPointer(colorRef, 3, gl.FLOAT, false, 0, 0);

gl.useProgram(program);

document.body.addEventListener("mouseup", () => {
    console.log("Body Clicked");
    color_obj.color_1 += 0.1;
    color_obj.color_2 += 0.1;
    color_obj.color_3 += 0.1;
    color_obj.color_4 += 0.1;
    color_obj.color_5 += 0.1;

    console.log(color_obj.color_1);
    console.log(color_obj.color_2);
    console.log(color_obj.color_3);
    console.log(color_obj.color_4);
    console.log(color_obj.color_5);
});

function animate() {
    requestAnimationFrame(animate);

    gl.drawArrays(gl.TRIANGLES, 0, 6);
}

requestAnimationFrame(animate);
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>WebGL</title>
    <style>
        canvas {
            position:fixed;
            width:100%;
            height:100%;
        }

        html, body {
            margin:0 !important;
            padding:0 !important;
            overflow:hidden;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
    <script src="app.js"></script>
</body>

</html>

How to pass the key used from JSON response objects in a map as a parameter for onclick() function in React?

I have tried to create a list of ‘League’ objects from the server, the user can click to create a league successfully, but to join a league the user will need to click the icon and the parameter will need to be sent to the joinLeague() function as the league.leagueId is required to join the league by the API:

LeagueList component:

 constructor(props) {
    super(props);
    this.state = {
        leagues: []
    };

    this.createLeague = this.createLeague.bind(this);

    this.joinLeague = this.joinLeague.bind(this);
}

 joinLeague() {
    const payload = {
        "username": localStorage.getItem("username"),
        "leagueId":
    };
    fetch(JOIN_LEAGUE_URL, {
        method: 'POST',
        headers: {
            "Accept": "application/json",
            "Content-Type": "application/json",
            "Authorization": localStorage.getItem("token")
        },
        body: JSON.stringify(payload)
    })
        .then(response => response.json())
        .then();
}

   render() {
        const {leagues} = this.state;

    const leagueList = leagues.map(league => {
        return <tr key={league.leagueId}>
            <td style={{whiteSpace: 'nowrap'}}>{league.leagueCreatorUsername}</td>
            <td>{league.playerCount}/10</td>
            <td>{league.matchesPerPlayer}</td>
            <td>{league.numberOfGamesPlayed}</td>
            <td>{league.totalGamesToBePlayed}</td>
            <i className="fas fa-plus" onClick={this.joinLeague}></i>
        </tr>
    });

}

In the joinLeague() function above you can see I am trying to create the payload but I need the leagueId from the as a parameter.

How can I accomplish this? I have tried {this.joinLeague.bind(league)} but it didn’t work, thanks.

React and Next postMessage communication CORS problem

I have two apps – CRA running on port 3000, and Next running on 3005.

In Next app I have simple message event listener:

  useEffect(() => {
    const handleMessage = (event: MessageEvent<{}>) =>
      console.log('Message event: ', event);

    window.addEventListener('message', handleMessage);
    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, []);

And I’ve set up headers in next.config.js:

const securityHeaders = [
  { key: 'Access-Control-Allow-Credentials', value: 'true' },
  { key: 'Access-Control-Allow-Origin', value: '*' },
  {
    key: 'Access-Control-Allow-Methods',
    value: 'GET,OPTIONS,PATCH,DELETE,POST,PUT',
  },
  {
    key: 'Access-Control-Allow-Headers',
    value:
      'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version',
  },
];

const nextConfig = {
  async headers() {
    return [
      {
        // Apply these headers to all routes in your application.
        source: '/:path*',
        headers: securityHeaders,
      },
    ];
  },
};

module.exports = nextConfig;

In React app I’m calling postMessage through iframe tag like this:

export const Frame = () => {
  const frameRef = useRef<HTMLIFrameElement>(null);

  const handleFrameLoad = () => {
    frameRef?.current?.contentWindow?.postMessage('TEST');
  };

  return (
    <iframe
      src="http://localhost:3005"
      ref={frameRef}
      onLoad={handleFrameLoad}
      sandbox="allow-same-origin allow-scripts"
    />
  );
};

And I’m still receiving error below in CRA’s console.

Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://localhost:3000') does not match the recipient window's origin ('http://localhost:3005').

Is there anything else that I can do to allow postMessage communication between two different local ports in NextJS?

await hitting timeout in jest unit test?

import { fs } from 'memfs';

describe('copyFolder()', () => {
  it('should work', async () => {
    await fs.promises.mkdir('/tmp/destination');
    console.log(fs.existsSync('/tmp/destination'));
  });
});

I’ve verified that fs.promises.mkdir('/tmp/destination') will return a promise and hence by having an await in front of it should be able to resolve it but seems like this is not the case for jest? it will just hit timeout instead?

Anyone has any clue about this?

Can you make a map based off address firebase?

I am trying to make a program that creates a google map based off the address given by user input. As i can not manually embed a link for each page, how can i make it so that when a user answers a form with the address I can take that address and make a google map. Do i have to convert the address to latitude and longitude and then do that?

Show wallet address after connecting Metamask with Web3.js

I got this code off github which allows you to connect to MetaMask using web3.js and also to make payment. I then modified it to

  1. Display a Connect Button when user is not logged in by checking if the content of an element is empty and if it is not empty, the connect button is hidden.
  2. Retrieve the connected wallet address which is in the element that hides the button.

I want the connected wallet to show up and hide the Connect button as soon as MetaMask is connected but it does not do that until i manually reload the page

Below is my code

  <head>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
   <script src="https://unpkg.com/@metamask/legacy-web3@latest/dist/metamask.web3.min.js"></script>
  </head>
  <body>
    <div>
        
      <div id="selected-account"></div>
      <button class="pay-button">Pay</button>
      <div id="status"></div>
      <div id="accTabs"></div>
    </div>
    <script type="text/javascript">
      async function initWeb3() {
        if (window.ethereum) {
        window.web3 = new Web3(ethereum);
        
        try {
            
          await ethereum.enable();
    window.location.reload();
          } catch (err) {
            $("#status").html("User denied account access", err);
          }
        } else if (window.web3) {
            
          return (window.web3 = new Web3(web3.currentProvider));
          
        } else {
          return $("#status").html("No Metamask (or other Web3 Provider) installed");
        }
      }
      
      selectedAccount = ethereum.selectedAddress;
  document.querySelector("#selected-account").textContent = selectedAccount;

      $(".pay-button").click(async () => {
        await initWeb3();
        // paymentAddress is where funds will be send to
        const paymentAddress = "0x192c96bfee59158441f26101b2db1af3b07feb40";
        const amountEth = "1";



        web3.eth.sendTransaction(
          {
            to: paymentAddress, 
          value: web3.toWei(amountEth, 'ether')
          },
          (err, transactionId) => {
            if (err) {
              console.log("Payment failed", err);
              $("#status").html("Payment failed");
            } else {
              console.log("Payment successful", transactionId);
              $("#status").html("Payment successful");
            }
          }
        );
      });
    </script>
    
    <script>
  if ($('#selected-account').text() == '') {
document.getElementById("accTabs").innerHTML = '<button onclick="initWeb3()">Connect Ethereum</button>';
} else {


}
     
</script>
  </body>
</html> 

Your help will be appreciated!

Thanks for your assistance.

DOM update disabled after form submit in some browsers?

I have a simple <form action='/a/url' method='POST' onSubmit='setInterval(my_func, 1000)'.

The POST triggers a ~40 second operation, so my_func is updating an HTML progress bar with the estimated time remaining, to keep it simple.

When I visit the site in production, the progress bar works fine on:

  • my laptop’s Firefox
  • my laptop’s Chrome

But it won’t increment on:

  • my phone’s Firefox
  • my phone’s Safari
  • my laptop’s Safari

In Safari, for example, I can confirm that a console.log is firing 1x per second, so the JS is running. I thought it might be an issue with progress bar compatibility, so I just used a_div.insertAdjacentHTML('afterend', '<h3>foo</h3>'). Again, this works in Firefox/Chrome, but not in the others.

In all browsers, the form submits and the user is eventually brought to the next page.

Do some browsers not allow updating elements after submitting the form?

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?

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>

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