src/js/ContextMenu.js
function ContextMenu (items, options) {
  this.dom = {};

  var me = this;
  var dom = this.dom;
  this.anchor = undefined;
  this.items = items;
  this.eventListeners = {};
  this.selection = undefined; // holds the selection before the menu was opened
  this.onClose = options ? options.close : undefined;

  // create root element
  var root = document.createElement('div');
  root.className = 'jsoneditor-contextmenu-root';
  dom.root = root;

  // create a container element
  var menu = document.createElement('div');
  menu.className = 'jsoneditor-contextmenu';
  dom.menu = menu;
  root.appendChild(menu);

  // create a list to hold the menu items
  var list = document.createElement('ul');
  list.className = 'jsoneditor-menu';
  menu.appendChild(list);
  dom.list = list;
  dom.items = []; // list with all buttons

  // create a (non-visible) button to set the focus to the menu
  var focusButton = document.createElement('button');
  focusButton.type = 'button';
  dom.focusButton = focusButton;
  var li = document.createElement('li');
  li.style.overflow = 'hidden';
  li.style.height = '0';
  li.appendChild(focusButton);
  list.appendChild(li);

  function createMenuItems (list, domItems, items) {
    items.forEach(function (item) {
      if (item.type == 'separator') {
        // create a separator
        var separator = document.createElement('div');
        separator.className = 'jsoneditor-separator';
        li = document.createElement('li');
        li.appendChild(separator);
        list.appendChild(li);
      }
      else {
        var domItem = {};

        // create a menu item
        var li = document.createElement('li');
        list.appendChild(li);

        // create a button in the menu item
        var button = document.createElement('button');
        button.type = 'button';
        button.className = item.className;
        domItem.button = button;
        if (item.title) {
          button.title = item.title;
        }
        if (item.click) {
          button.onclick = function (event) {
            event.preventDefault();
            me.hide();
            item.click();
          };
        }
        li.appendChild(button);

        // create the contents of the button
        if (item.submenu) {
          // add the icon to the button
          var divIcon = document.createElement('div');
          divIcon.className = 'jsoneditor-icon';
          button.appendChild(divIcon);
          var divText = document.createElement('div');
          divText.className = 'jsoneditor-text' +
              (item.click ? '' : ' jsoneditor-right-margin');
          divText.appendChild(document.createTextNode(item.text));
          button.appendChild(divText);

          var buttonSubmenu;
          if (item.click) {
            // submenu and a button with a click handler
            button.className += ' jsoneditor-default';

            var buttonExpand = document.createElement('button');
            buttonExpand.type = 'button';
            domItem.buttonExpand = buttonExpand;
            buttonExpand.className = 'jsoneditor-expand';
            buttonExpand.innerHTML = '<div class="jsoneditor-expand"></div>';
            li.appendChild(buttonExpand);
            if (item.submenuTitle) {
              buttonExpand.title = item.submenuTitle;
            }

            buttonSubmenu = buttonExpand;
          }
          else {
            // submenu and a button without a click handler
            var divExpand = document.createElement('div');
            divExpand.className = 'jsoneditor-expand';
            button.appendChild(divExpand);

            buttonSubmenu = button;
          }

          // attach a handler to expand/collapse the submenu
          buttonSubmenu.onclick = function (event) {
            event.preventDefault();
            me._onExpandItem(domItem);
            buttonSubmenu.focus();
          };

          // create the submenu
          var domSubItems = [];
          domItem.subItems = domSubItems;
          var ul = document.createElement('ul');
          domItem.ul = ul;
          ul.className = 'jsoneditor-menu';
          ul.style.height = '0';
          li.appendChild(ul);
          createMenuItems(ul, domSubItems, item.submenu);
        }
        else {
          // no submenu, just a button with clickhandler
          button.innerHTML = '<div class="jsoneditor-icon"></div>' +
              '<div class="jsoneditor-text">' + translate(item.text) + '</div>';
        }

        domItems.push(domItem);
      }
    });
  }
  createMenuItems(list, this.dom.items, items);

  // TODO: when the editor is small, show the submenu on the right instead of inline?

  // calculate the max height of the menu with one submenu expanded
  this.maxHeight = 0; // height in pixels
  items.forEach(function (item) {
    var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24;
    me.maxHeight = Math.max(me.maxHeight, height);
  });
}
function ContextMenu (items, options) {
  this.dom = {};

  var me = this;
  var dom = this.dom;
  this.anchor = undefined;
  this.items = items;
  this.eventListeners = {};
  this.selection = undefined; // holds the selection before the menu was opened
  this.onClose = options ? options.close : undefined;

  // create root element
  var root = document.createElement('div');
  root.className = 'jsoneditor-contextmenu-root';
  dom.root = root;

  // create a container element
  var menu = document.createElement('div');
  menu.className = 'jsoneditor-contextmenu';
  dom.menu = menu;
  root.appendChild(menu);

  // create a list to hold the menu items
  var list = document.createElement('ul');
  list.className = 'jsoneditor-menu';
  menu.appendChild(list);
  dom.list = list;
  dom.items = []; // list with all buttons

  // create a (non-visible) button to set the focus to the menu
  var focusButton = document.createElement('button');
  focusButton.type = 'button';
  dom.focusButton = focusButton;
  var li = document.createElement('li');
  li.style.overflow = 'hidden';
  li.style.height = '0';
  li.appendChild(focusButton);
  list.appendChild(li);

  function createMenuItems (list, domItems, items) {
    items.forEach(function (item) {
      if (item.type == 'separator') {
        // create a separator
        var separator = document.createElement('div');
        separator.className = 'jsoneditor-separator';
        li = document.createElement('li');
        li.appendChild(separator);
        list.appendChild(li);
      }
      else {
        var domItem = {};

        // create a menu item
        var li = document.createElement('li');
        list.appendChild(li);

        // create a button in the menu item
        var button = document.createElement('button');
        button.type = 'button';
        button.className = item.className;
        domItem.button = button;
        if (item.title) {
          button.title = item.title;
        }
        if (item.click) {
          button.onclick = function (event) {
            event.preventDefault();
            me.hide();
            item.click();
          };
        }
        li.appendChild(button);

        // create the contents of the button
        if (item.submenu) {
          // add the icon to the button
          var divIcon = document.createElement('div');
          divIcon.className = 'jsoneditor-icon';
          button.appendChild(divIcon);
          var divText = document.createElement('div');
          divText.className = 'jsoneditor-text' +
              (item.click ? '' : ' jsoneditor-right-margin');
          divText.appendChild(document.createTextNode(item.text));
          button.appendChild(divText);

          var buttonSubmenu;
          if (item.click) {
            // submenu and a button with a click handler
            button.className += ' jsoneditor-default';

            var buttonExpand = document.createElement('button');
            buttonExpand.type = 'button';
            domItem.buttonExpand = buttonExpand;
            buttonExpand.className = 'jsoneditor-expand';
            buttonExpand.innerHTML = '<div class="jsoneditor-expand"></div>';
            li.appendChild(buttonExpand);
            if (item.submenuTitle) {
              buttonExpand.title = item.submenuTitle;
            }

            buttonSubmenu = buttonExpand;
          }
          else {
            // submenu and a button without a click handler
            var divExpand = document.createElement('div');
            divExpand.className = 'jsoneditor-expand';
            button.appendChild(divExpand);

            buttonSubmenu = button;
          }

          // attach a handler to expand/collapse the submenu
          buttonSubmenu.onclick = function (event) {
            event.preventDefault();
            me._onExpandItem(domItem);
            buttonSubmenu.focus();
          };

          // create the submenu
          var domSubItems = [];
          domItem.subItems = domSubItems;
          var ul = document.createElement('ul');
          domItem.ul = ul;
          ul.className = 'jsoneditor-menu';
          ul.style.height = '0';
          li.appendChild(ul);
          createMenuItems(ul, domSubItems, item.submenu);
        }
        else {
          // no submenu, just a button with clickhandler
          button.innerHTML = '<div class="jsoneditor-icon"></div>' +
              '<div class="jsoneditor-text">' + translate(item.text) + '</div>';
        }

        domItems.push(domItem);
      }
    });
  }
  createMenuItems(list, this.dom.items, items);

  // TODO: when the editor is small, show the submenu on the right instead of inline?

  // calculate the max height of the menu with one submenu expanded
  this.maxHeight = 0; // height in pixels
  items.forEach(function (item) {
    var height = (items.length + (item.submenu ? item.submenu.length : 0)) * 24;
    me.maxHeight = Math.max(me.maxHeight, height);
  });
}
A context menu
@param {Object[]} items    Array containing the menu structure
TODO: describe structure
@param {Object} [options]  Object with options. Available options:
{function} close    Callback called when the
context menu is being closed.
@constructor