ui_ui.js

/**
 * $3Dmol.UI - UI creates panels in the viewer to assist control of the viewport
 * @constructor 
 * @param {$3Dmol.StateManager} stateManager StateManager is required to have interaction between glviewer and the ui. 
 * @param {Object} config Loads the user defined parameters to generate the ui
 * @param {Object} parentElement Refers the parent division used to hold the canvas for 3Dmol.js 
 */
$3Dmol.UI = (function () {

  /*
       The dictionaries are for dropdown menus and validation of the viewer
   */


  // prop : It is used to add the option for property in context menu in the 3dmol ui
  // the code for prop can be found under /ui/ui.js -> UI -> ContextMenu -> setProperties -> submit.ui.on 
  // gui : It is used to generate forms for different features in the 3dmol ui
  // the code for gui can be found under /ui/form.js -> Form (Form defination)
  // floatType : separates integer from float since these are used in 
  // input validation of the 3dmol ui
  var validAtomSpecs = {
    "resn": { type: "string", valid: true, prop: true, gui: true }, // Parent residue name
    "x": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom's x coordinate
    "y": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom's y coordinate
    "z": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom's z coordinate
    "color": { type: "color", gui: false }, // Atom's color, as hex code
    "surfaceColor": { type: "color", gui: false }, // Hex code for color to be used for surface patch over this atom
    "elem": { type: "element", gui: true, prop: true }, // Element abbreviation (e.g. 'H', 'Ca', etc)
    "hetflag": { type: "boolean", valid: false, gui: true }, // Set to true if atom is a heteroatom
    "chain": { type: "string", gui: true, prop: true }, // Chain this atom belongs to, if specified in input file (e.g 'A' for chain A)
    "resi": { type: "array_range", gui: true }, // Residue number 
    "icode": { type: "number", valid: false, step: 0.1 },
    "rescode": { type: "number", valid: false, step: 0.1, prop: true },
    "serial": { type: "number", valid: false, step: 0.1 }, // Atom's serial id numbermodels
    "atom": { type: "string", valid: false, gui: true, prop: true }, // Atom name; may be more specific than 'elem' (e.g 'CA' for alpha carbon)
    "bonds": { type: "array", valid: false }, // Array of atom ids this atom is bonded to
    "ss": { type: "string", valid: false }, // Secondary structure identifier (for cartoon render; e.g. 'h' for helix)
    "singleBonds": { type: "boolean", valid: false }, // true if this atom forms only single bonds or no bonds at all
    "bondOrder": { type: "array", valid: false }, // Array of this atom's bond orders, corresponding to bonds identfied by 'bonds'
    "properties": { type: "properties", valid: false }, // Optional mapping of additional properties
    "b": { type: "number", floatType: true, valid: false, step: 0.1, prop: true }, // Atom b factor data
    "pdbline": { type: "string", valid: false }, // If applicable, this atom's record entry from the input PDB file (used to output new PDB from models)
    "clickable": { type: "boolean", valid: false, gui: false }, // Set this flag to true to enable click selection handling for this atom
    "contextMenuEnabled": { type: "boolean", valid: false, gui: false }, // Set this flag to true to enable click selection handling for this atom
    "callback": { type: "function", valid: false }, // Callback click handler function to be executed on this atom and its parent viewer
    "invert": { type: "boolean", valid: false }, // for selection, inverts the meaning of the selection
    //unsure about this
    "reflectivity": { type: "number", floatType: true, gui: false, step: 0.1 }, //for describing the reflectivity of a model
    "altLoc": { type: "invalid", valid: false }, //alternative location, e.g. in PDB
    "sym": { type: 'number', gui: false }, //which symmetry
  };

  //type is irrelivent here becuase htey are are invalid
  var validExtras = {  // valid atom specs are ok too
    "model": { type: "string", valid: false }, // a single model or list of models from which atoms should be selected
    "bonds": { type: "number", valid: false, gui: true }, // overloaded to select number of bonds, e.g. {bonds: 0} will select all nonbonded atoms
    "predicate": { type: "string", valid: false }, // user supplied function that gets passed an {AtomSpec} and should return true if the atom should be selected
    "invert": { type: "boolean", valid: false, gui: true }, // if set, inverts the meaning of the selection
    "byres": { type: "boolean", valid: false, gui: true }, // if set, expands the selection to include all atoms of any residue that has any atom selected
    "expand": { type: "number", valid: false, gui: false }, // expands the selection to include all atoms within a given distance from the selection
    "within": { type: "string", valid: false }, // intersects the selection with the set of atoms within a given distance from another selection
    "and": { type: "string", valid: false }, // and boolean logic
    "or": { type: "string", valid: false }, // or boolean logic
    "not": { type: "string", valid: false }, // not boolean logic
  };

  var validAtomSelectionSpecs = $3Dmol.extend({}, validAtomSpecs);
  validAtomSelectionSpecs = $3Dmol.extend(validAtomSelectionSpecs, validExtras);

  var validLineSpec = {
    "hidden": { type: "boolean", gui: true },
    "linewidth": { type: "number", floatType: true, gui: true, step: 0.1, default: 1.0 },
    "colorscheme": { type: "colorscheme", gui: true },
    "color": { type: "color", gui: true },
    "opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
  };

  var validCrossSpec = {
    "hidden": { type: "boolean", gui: true },
    "linewidth": { type: "number", floatType: true, gui: false, step: 0.1, default: 1.0, min: 0 },//deprecated
    "colorscheme": { type: "colorscheme", gui: true },
    "color": { type: "color", gui: true },
    "radius": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0.1 },
    "scale": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0 },
    "opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
  };

  var validStickSpec = {
    "hidden": { type: "boolean", gui: true },
    "colorscheme": { type: "colorscheme", gui: true },
    "color": { type: "color", gui: true },
    "radius": { type: "number", floatType: true, gui: true, step: 0.1, default: 0.25, min: 0.1 },
    "singleBonds": { type: "boolean", gui: true },
    "opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
  };

  var validSphereSpec = {
    "hidden": { type: "boolean", gui: false }, // needed in the new gui it has separate function to hide the spheres
    "singleBonds": { type: "boolean", gui: true },
    "colorscheme": { type: "colorscheme", gui: true },
    "color": { type: "color", gui: true },
    "radius": { type: "number", floatType: true, gui: true, step: 0.1, default: 1.5, min: 0 },
    "scale": { type: "number", floatType: true, gui: true, step: 0.1, default: 1.0, min: 0.1 },
    "opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
  };

  var validCartoonSpec = {
    "style": { validItems: ["trace", "oval", "rectangle", "parabola", "edged"], gui: true },
    "color": { type: "color", gui: true, spectrum: true },
    "arrows": { type: "boolean", gui: true },
    "ribbon": { type: "boolean", gui: true },
    "hidden": { type: "boolean", gui: true },
    "tubes": { type: "boolean", gui: true },
    "thickness": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0 },
    "width": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0 },
    "opacity": { type: "number", floatType: true, gui: true, step: 0.1, default: 1, min: 0, max: 1 },
  };

  var validAtomStyleSpecs = {
    "line": { validItems: validLineSpec, type: "form", gui: true }, // draw bonds as lines
    "cross": { validItems: validCrossSpec, type: "form", gui: true }, // draw atoms as crossed lines (aka stars)
    "stick": { validItems: validStickSpec, type: "form", gui: true }, // draw bonds as capped cylinders
    "sphere": { validItems: validSphereSpec, type: "form", gui: true }, // draw atoms as spheres
    "cartoon": { validItems: validCartoonSpec, type: "form", gui: true }, // draw cartoon representation of secondary structure
    "colorfunc": { validItems: null, type: "js", valid: false },
    "clicksphere": { validItems: validSphereSpec, type: "form" } //invisible style for click handling
  };

  var validSurfaceSpecs = {
    "opacity": { type: "number", floatType: true, gui: true, step: 0.01, default: 1, min: 0, max: 1 },
    "colorscheme": { type: "colorscheme", gui: true },
    "color": { type: "color", gui: true },
    "voldata": { type: "number", floatType: true, gui: false },
    "volscheme": { type: "number", floatType: true, gui: false },
    "map": { type: "number", gui: false }
  };


  function UI(stateManager, config, parentElement) {
    config = config || {}

    // Extract the viewer and then render it
    var icons = new $3Dmol.UI.Icons();
    var _editingForm = null;
    var mainParent = $(parentElement[0]);
    // Generates the necessary UI elements
    var HEIGHT = config.height;
    this.tools = generateUI(config);

    /**
     * Creates all the jquery object of different UI features
     */
    function generateUI() {
      var modelToolBar = new ModelToolbar();
      mainParent.append(modelToolBar.ui);
      setLocation(mainParent, modelToolBar.ui, 'left', 'top');
      // modelToolBar.updateInputLength();

      var contextMenu = new ContextMenu();
      mainParent.append(contextMenu.ui);
      setPosition(contextMenu.ui, 100, 100)

      var surfaceMenu = new SurfaceMenu();
      mainParent.append(surfaceMenu.ui);
      setLocation(mainParent, surfaceMenu.ui, 'right', 'top', 0, modelToolBar.ui.height() + 5);


      var selectionBox = new SelectionBox(icons.select);
      mainParent.append(selectionBox.ui);
      setLocation(mainParent, selectionBox.ui, 'left', 'top', 0, modelToolBar.ui.height() + 5);

      // Fixing Context Menu Behaviour
      selectionBox.ui.on('mousedown', () => {
        stateManager.exitContextMenu();
      });

      surfaceMenu.ui.on('mousedown', () => {
        stateManager.exitContextMenu();
      });

      return {
        modelToolBar: modelToolBar,
        selectionBox: selectionBox,
        contextMenu: contextMenu,
        surfaceMenu: surfaceMenu
      }
    }

    /**
     * Resize the panel with respect to the new viewport
     * 
     * @function $3Dmol.UI#resize
     */
    this.resize = function () {
      var selectionBox = this.tools.selectionBox;
      var surfaceMenu = this.tools.surfaceMenu;
      var modelToolBar = this.tools.modelToolBar;
      var HEIGHT = mainParent.height();

      setLocation(mainParent, modelToolBar.ui, 'left', 'top');
      // modelToolBar.updateInputLength();
      setLocation(mainParent, selectionBox.ui, 'left', 'top', 0, modelToolBar.ui.height() + 5);
      selectionBox.updateScrollBox(HEIGHT);
      setLocation(mainParent, surfaceMenu.ui, 'right', 'top', 0, modelToolBar.ui.height() + 5);
      surfaceMenu.updateScrollBox(HEIGHT);
    }

    /*
     * ModelToolbar is part of $3Dmol.UI to edit or change the model loaded into the viewer
     * 
     * @function ModelToolbar
     */
    function ModelToolbar() {
      var boundingBox = this.ui = $('<div></div>');

      boundingBox.css({
        'position': 'relative',
        'min-width': '150px'
      });


      var modelButton = new button(icons.molecule, 20, { tooltip: 'Toggle Model Selection Bar' });
      boundingBox.append(modelButton.ui);

      modelButton.ui.css({
        'display': 'inline-block',
        'top': '3px',
      });

      var control = {
        urlType: {
          active: true,
          value: null,
          key: 'Model type'
        },

        url: {
          active: true,
          value: null,
          key: 'Url'
        },
      };

      var surroundingBox = $('<div></div>');

      surroundingBox.css({
        'display': 'inline-block',
        'background': '#e4e4e4',
        'padding': '2px',
        'border-radius': '3px',
        // 'width' : '90%'
      });

      boundingBox.append(surroundingBox);

      var currentModelBox = $('<div></div>');
      currentModelBox.css({

      });

      var currentModel = $('<div></div>');
      currentModel.css({
        'display': 'inline-block',
        'font-family': 'Arial',
        'font-size': '12px',
        'font-weight': 'bold',
        // 'padding' : '3px'
      });

      currentModelBox.append(currentModel);

      var changeButton = new button(icons.change, 16, { tooltip: 'Change Model', backgroundColor: 'white', bfr: 0.5 });
      changeButton.ui.css({
        'display': 'inline-block',
        'margin-left': '4px',
      });
      currentModelBox.append(changeButton.ui);

      currentModelBox.hide();
      surroundingBox.append(currentModelBox);

      var formBox = $('<div></div>');
      surroundingBox.append(formBox);

      var dbs = 'pdb,mmtf,cid'.split(',');
      var list = this.list = new $3Dmol.UI.Form.ListInput(control.urlType, dbs);
      list.showAlertBox = false;

      list.ui.css({
        'display': 'inline-block',
      })

      formBox.append(list.ui);

      var input = this.url = new $3Dmol.UI.Form.Input(control.url);
      formBox.append(input.ui);

      input.ui.css({
        'display': 'inline-block',
        'width': '125px'
      });

      // input.setWidth(125);

      var submitButton = new button(icons.tick, 16, { bfr: 0.5, backgroundColor: 'lightgreen', tooltip: 'Add Model' });
      submitButton.ui.css({
        'margin': '0px'
      })
      formBox.append(submitButton.ui);

      this.updateInputLength = function () {
        // var width = parentElement.width()*0.3;
        // boundingBox.width(width);
        // input.setWidth(width - 12);
      }

      modelButton.ui.on('click', () => {
        surroundingBox.toggle();
      });

      submitButton.ui.on('click', function () {
        var validateDb = list.validate();
        var validateId = input.validate();

        if (validateId && validateDb) {
          stateManager.addModel(control);
        }

      });

      /*
       * Sets the title in the ui with specified value
       * 
       * @function ModelToolbar#setModel 
       * @param {String} heading Name of the molecule that is to be displayed on the title
       */
      this.setModel = function (heading) {
        currentModel.text(heading);
        currentModelBox.show();
        formBox.hide();
      }

      changeButton.ui.on('click', function () {
        currentModelBox.hide();
        formBox.show();
        input.setValue('');
      });

      boundingBox.on('keypress', function (e) {
        if (e.key == 'Enter' || e.key == 'Return') {
          submitButton.ui.trigger('click')
        }
      });
    }


    /*
     * Selection box creates the UI panel to manipulate selections and style that are drawn 
     * on the viewport
     * 
     * @function SelectionBox  
     * @param {$3Dmol.UI.Icons} icon takes the svg code for the icon that is to be used to display
     * selection box
     * @return {Object}  Jquery element of div
     */
    function SelectionBox(icon, side = 'left') {
      var selectionBox = this.ui = $('<div></div>');
      _editingForm = false;
      var selectionObjects = [];

      var selections = $('<div></div>');
      var scrollBox = $('<div></div>');

      selections.css('opacity', '0.9');

      var showArea = $('<div></div>');
      var addArea = $('<div></div>');
      var plusButton = new button(icons.plus, 20, { tooltip: 'Add New Selection' });
      plusButton.ui.css('margin', '0px');

      var hideButton = new button(icon, 20, { tooltip: 'Toggle Selection Menu' });
      this.selectionObjects = [];

      // Content
      selectionBox.append(hideButton.ui);
      selectionBox.append(showArea);
      selectionBox.css('position', 'absolute');

      scrollBox.append(selections);
      showArea.append(scrollBox);
      addArea.append(plusButton.ui);

      var alertBox = new AlertBox();
      showArea.append(alertBox.ui);
      showArea.append(addArea);
      alertBox.ui.css('width', 162);

      // CSS
      if (side == 'left') {
        selectionBox.css('text-align', 'left');
      }
      else if (side == 'right') {
        selectionBox.css('text-align', 'right');
      }
      else {
        // Add alert box code
        selectionBox.css('text-align', 'right');
      }

      showArea.css('box-sizing', 'border-box');
      showArea.css('padding', '3px');
      // showArea.css('width', '162px');

      scrollBox.css('max-height', HEIGHT * 0.8);
      scrollBox.css('overflow-y', 'auto');
      scrollBox.css('overflow-x', 'visible');

      selections.css('box-sizing', 'content-box');

      this.updateScrollBox = function (height) {
        scrollBox.css('max-height', height * 0.8);
      }

      // Action
      var hidden = true;
      showArea.hide();

      hideButton.ui.click(toggleHide);

      function toggleHide() {
        if (hidden) {
          showArea.show(100);
        }
        else {
          showArea.hide(100);
        }
        hidden = !hidden;
      }

      /*
       * Card for manipulation of a selection form and related styles
       * 
       * @function Selection
       */
      function Selection() {
        var boundingBox = this.ui = $('<div></div>');
        var sid = this.id = null;
        selectionObjects.push(this);
        boundingBox.css({
          'background': '#e8e8e8',
          'padding': '4px 4px 2px 4px',
          'border-radius': '6px',
          'margin-bottom': '3px',
          'position': 'relative',
          'width': '156px'
        });

        var header = $('<div></div>');
        boundingBox.append(header);
        var heading = $('<div></div>');
        var controls = $('<div></div>');

        header.append(heading, controls);
        heading.css({
          'font-family': 'Arial',
          'font-weight': 'bold',
          'font-size': '12px',
          'display': 'inline-block',
          'width': '60px'
        });

        controls.css({
          'display': 'inline-block'
        });

        header.hide();
        controls.editMode = false;

        var removeButton = new button(icons.minus, 16, { bfr: 0.5, backgroundColor: '#f06f6f', tooltip: 'Remove Selection' });
        var editButton = new button(icons.pencil, 16, { tooltip: 'Edit Selection' });
        var visibleButton = new button(icons.visible, 16, { tooltip: 'Show / Hide Selection' });

        controls.append(removeButton.ui)
        controls.append(editButton.ui);
        controls.append(visibleButton.ui);

        var parameters = $('<div></div>');
        boundingBox.append(parameters);

        var styleHolder = $('<div></div>');

        removeButton.ui.on('click', function () {
          stateManager.removeSelection(sid);
          boundingBox.detach();
          //delete this;
        });

        editButton.ui.on('click', function () {
          parameters.toggle();
        });

        var hidden = false;
        visibleButton.ui.on('click', () => {
          stateManager.toggleHide(sid);
          if (hidden) {
            hidden = false;
            visibleButton.setSVG(icons.visible);
          }
          else {
            hidden = true;
            visibleButton.setSVG(icons.invisible);
          }
        });

        var styleBox = new StyleBox();

        styleHolder.append(styleBox.ui);
        styleBox.ui.css({
          'position': 'static',
          // 'left' : '0',
          'width': 'px',
          'border-radius': '4px'
        });

        styleBox.ui.hide();

        var allControl = this.allSelector = {
          key: 'Select All Atom',
          value: null,
          active: true
        }

        var allCheckBox = new $3Dmol.UI.Form.Checkbox(allControl);
        parameters.append(allCheckBox.ui);


        var selectionFormControl = this.selectionValue = {
          key: 'Selection Spec',
          value: null,
          active: true
        }

        var selectionSpecForm = new $3Dmol.UI.Form(validAtomSelectionSpecs, selectionFormControl);
        parameters.append(selectionSpecForm.ui);

        var submitControls = $('<div></div>');
        var submit = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
        var cancel = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
        submitControls.append(submit.ui, cancel.ui);


        var alertBox = new AlertBox();
        parameters.append(alertBox.ui);

        parameters.append(submitControls);
        boundingBox.append(styleHolder);

        allCheckBox.update = function () {
          selectionSpecForm.ui.toggle();
        }

        function finalizeSelection(id) {
          header.show();
          controls.editMode = true;
          sid = this.id = id;
          heading.text('Sel#' + id);
          boundingBox.attr('data-id', id);
          parameters.hide();
          styleBox.setSid(id);
          styleBox.ui.show();
        }

        function checkAndAddSelection(sid = null) {
          var validate = selectionSpecForm.validate();
          if (validate) {
            selectionSpecForm.getValue();
            var checkAtoms = stateManager.checkAtoms(selectionFormControl.value);

            if (Object.keys(selectionFormControl.value).length == 0) {
              alertBox.error('Please enter some input');
            }
            else {
              if (checkAtoms) {
                var id = stateManager.addSelection(selectionFormControl.value, sid);
                finalizeSelection(id);
                if (sid == null) _editingForm = false;
              }
              else {
                alertBox.error('No atom selected');
              }
            }
          }
          else {
            alertBox.error('Invalid Input');
          }
        }

        function removeSelf() {

          // delete selectionToRemove;
        }

        submit.ui.on('click', () => {
          if (controls.editMode == false) {
            if (allControl.value) {
              let id = stateManager.addSelection({});
              finalizeSelection(id);
              _editingForm = false;
            }
            else {
              checkAndAddSelection();
            }

          }
          else {
            if (allControl.value) {
              let id = sid;
              stateManager.addSelection({}, id);
              finalizeSelection(id);
            }
            else {
              let id = sid;
              checkAndAddSelection(id);
            }
          }
        });

        var self = this;

        cancel.ui.on('click', () => {
          if (controls.editMode) {
            parameters.hide();
          }
          else {
            boundingBox.detach();
            removeSelf(self);
            _editingForm = false;
          }
        });


        boundingBox.on('keyup', (e) => {
          if (e.key == 'Enter') {
            submit.ui.trigger('click');
          }
        });

        /*
         * @function Selection#setProperty
         * @param {string} id Id of the selection created in StateManager 
         * @param {Object} specs Defination of the selection that will be used to set default 
         * values in the form
         */
        this.setProperty = function (id, specs) {
          // check for all selection
          if (Object.keys(specs).length == 0) {
            allCheckBox.setValue(true)
          } else {
            selectionSpecForm.setValue(specs);
          }

          // finalize the selection 
          finalizeSelection(id);
        }

        /*
         * Adds style to the given selection 
         * 
         * @function Selection#addStyle 
         * @param {String} selId Id of the selection to inititate the StyleBox
         * @param {String} styleId Id of the style that is created through StateManager
         * @param {AtomStyleSpecs} styleSpecs 
         */
        this.addStyle = function (selId, styleId, styleSpecs) {
          styleBox.addStyle(selId, styleId, styleSpecs);
        }
      }

      plusButton.ui.on('click', () => {
        if (!_editingForm) {
          var newSelection = new Selection();
          selections.append(newSelection.ui);
          _editingForm = true;
        } else {
          alertBox.warning('Please complete the previous form');
        }

      });


      /*
       * Remove all the selection card from the ui
       */
      this.empty = function () {
        selections.empty();
        _editingForm = false;
      }

      /*
       * Adds or create new selection card
       * 
       * @function SelectionBox#editSelection
       * @param {String} id Id created in StateManager and passed down to this function during call
       * @param {AtomSelectionSpec} selSpec Selection spec that is used to generate the selection form
       * @param {String} styleId Id of style created in StateManager
       * @param {AtomStyleSpecs} styleSpec Style spec if specified add the selection to the current selection 
       */
      this.editSelection = function (id, selSpec, styleId, styleSpec) {
        // if selection does not exist create new 

        // This thing works but I am not sure how!

        // Search selection with id 
        var selectionUI = selections.children('[data-id=' + id + ']');

        if (selectionUI.length == 0) {
          var selection = new Selection();
          selection.setProperty(id, selSpec);
          selections.append(selection.ui);

          if (styleId != null) {
            selection.addStyle(id, styleId, styleSpec);
          }
        }

      }
    }


    /*
     * Creates StyleBox for listing out different styles inside the selection
     * 
     * @function StyleBox 
     * @param {String} selId Id of the selection for which the style box is created 
     * @param {String} side Alignment of text inside the box
     */
    function StyleBox(selId, side = 'left') {
      var styleBox = this.ui = $('<div></div>');
      _editingForm = false;
      var sid = this.sid = selId; // selection id

      this.setSid = function (id) {
        sid = this.sid = id;
      }

      var styles = $('<div></div>');
      var scrollBox = $('<div></div>');

      styles.css('opacity', '0.9');

      var showArea = $('<div></div>');
      var addArea = $('<div></div>');
      addArea.css('text-align', 'center');
      var plusButton = new button(icons.plus, 20, { tooltip: 'Add New Style' });
      plusButton.ui.css('margin', '0px');

      this.selectionObjects = [];

      // Content
      styleBox.append(showArea);
      styleBox.css('position', 'absolute');

      scrollBox.append(styles);
      showArea.append(scrollBox);

      var alertBox = new AlertBox();
      showArea.append(alertBox.ui);

      addArea.append(plusButton.ui);
      showArea.append(addArea);

      // CSS
      if (side == 'left') {
        styleBox.css('text-align', 'left');
      }
      else if (side == 'right') {
        styleBox.css('text-align', 'right');
      }
      else {
        // Add alert box code
        styleBox.css('text-align', 'right');
      }

      showArea.css('box-sizing', 'border-box');
      showArea.css('padding', '3px');
      // showArea.css('width', '162px');
      showArea.css('background-color', '#a4a4a4')
      showArea.css('border-radius', '4px');

      // scrollBox.css('max-height', HEIGHT*0.8);
      scrollBox.css('overflow', 'hidden');

      // styles.css('max-height', HEIGHT*0.8);
      // styles.css('overflow', 'auto');
      styles.css('box-sizing', 'content-box');


      /*
       * Style card to define the value of the style 
       * 
       * @param {string} sid Id of the selction for which the style box is created
       * and this stye will be added under that selection
       */
      function Style(sid) {
        var boundingBox = this.ui = $('<div></div>');
        var stid = this.id = null; // style id 
        boundingBox.css({
          'background': '#e8e8e8',
          'padding': '4px 4px 2px 4px',
          'border-radius': '6px',
          'margin-bottom': '3px',
          'position': 'relative'
        });

        var header = $('<div></div>');
        boundingBox.append(header);
        var heading = $('<div></div>');
        var controls = $('<div></div>');

        header.append(heading, controls);
        heading.css({
          'font-family': 'Arial',
          'font-weight': 'bold',
          'font-size': '12px',
          'display': 'inline-block',
          'width': '60px'
        });

        controls.css({
          'display': 'inline-block'
        });

        header.hide();
        controls.editMode = false;

        var removeButton = new button(icons.minus, 16, { bfr: 0.5, backgroundColor: '#f06f6f', tooltip: 'Remove Style' });
        var editButton = new button(icons.pencil, 16, { tooltip: 'Edit Style' });
        var visibleButton = new button(icons.visible, 16, { tooltip: 'Show / Hide Style' });

        controls.append(removeButton.ui)
        controls.append(editButton.ui);
        controls.append(visibleButton.ui);

        var parameters = $('<div></div>');
        boundingBox.append(parameters);

        removeButton.ui.on('click', { parent: this, stid: stid }, function () {
          stateManager.removeStyle(sid, stid);
          boundingBox.detach();
          //delete this;
        });

        editButton.ui.on('click', function () {
          parameters.toggle();
        });

        var hidden = false;
        visibleButton.ui.on('click', () => {
          stateManager.toggleHideStyle(sid, stid);
          if (hidden) {
            hidden = false;
            visibleButton.setSVG(icons.visible);
          }
          else {
            hidden = true;
            visibleButton.setSVG(icons.invisible);
          }
        });

        var styleFormControl = this.selectionValue = {
          key: 'Style Spec',
          value: null,
          active: true
        }

        var styleSpecForm = new $3Dmol.UI.Form(validAtomStyleSpecs, styleFormControl);
        parameters.append(styleSpecForm.ui);

        var submitControls = $('<div></div>');
        var submit = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
        var cancel = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
        submitControls.append(submit.ui, cancel.ui);


        var alertBox = new AlertBox();
        parameters.append(alertBox.ui);

        parameters.append(submitControls);

        function finalizeStyle(id) {
          header.show();
          controls.editMode = true;
          stid = id;
          heading.text('Sty#' + id);
          parameters.hide();
        }

        function checkAndAddStyle(stid = null) {
          var validate = styleSpecForm.validate();
          if (validate) {
            styleSpecForm.getValue();

            if (Object.keys(styleFormControl.value).length == 0) {

              alertBox.error('Please enter some value');
            }
            else {
              var id = stateManager.addStyle(styleFormControl.value, sid, stid);
              finalizeStyle(id);
              if (stid == null) _editingForm = false;
            }

          }
          else {
            alertBox.error('Invalid Input');
          }
        }

        submit.ui.on('click', () => {
          if (controls.editMode == false) {
            checkAndAddStyle();
          }
          else {
            var id = stid
            styleSpecForm.getValue();

            if (Object.keys(styleFormControl.value).length == 0) {
              alertBox.error('Please enter some value');
            }
            else {
              checkAndAddStyle(id);
            }

          }
        });

        cancel.ui.on('click', () => {
          if (controls.editMode) {
            parameters.hide();
          }
          else {
            boundingBox.detach();
            //delete this;
          }
        });

        boundingBox.on('keyup', (e) => {
          if (e.key == 'Enter') {
            submit.ui.trigger('click');
          }
        });

        /**
         * @function Style#updateStyle 
         * @param {String} styleId Id of the style created by StateManager 
         * @param {AtomStyleSpecs} styleSpec Specs for defining the style and setting default values
         */
        this.updateStyle = function (styleId, styleSpec) {
          styleSpecForm.setValue(styleSpec);

          finalizeStyle(styleId);
        }

      }

      plusButton.ui.on('click', () => {
        if (!_editingForm) {
          var newStyle = new Style(sid);
          styles.append(newStyle.ui);
          _editingForm = true;
        }
        else {
          alertBox.warning('Please complete editing the current form');
        }
      });

      /**
       * @function StyleBox#addStyle
       * @param {String} selectionId Id of the selection for which styles will be created   
       * @param {String} styleId Id of the style part of the selection 
       * @param {AtomStyleSpecs} styleSpecs Style specs that will be used to create 
       * style for the specified selection and set default values in the Style card
       */
      this.addStyle = function (selectionId, styleId, styleSpecs) {
        var style = new Style(selectionId);
        styles.append(style.ui);
        style.updateStyle(styleId, styleSpecs);
      }
    }


    /*
     * Add alert messages to different panels 
     * 
     * @function AlertBox
     * @param {Object} config Configuraiton for alert box display
     */
    function AlertBox(config) {
      var boundingBox = this.ui = $('<div></div>');
      config = config || {}
      var delay = config.delay || 5000;
      var autohide = (config.autohide == undefined) ? true : config.autohide;

      boundingBox.css({
        'font-family': 'Arial',
        'font-size': '14px',
        'padding': '3px',
        'border-radius': '4px',
        'margin-top': '2px',
        'margin-bottm': '2px',
        'font-weight': 'bold',
        'text-align': 'center',
      });

      boundingBox.hide();

      function hide() {
        if (autohide) {
          setTimeout(() => {
            boundingBox.hide();
          }, delay);
        }
      }

      /**
       * Generate Internal alert message  
       * @param {String} msg Error Message 
       */
      this.error = function (msg) {
        boundingBox.css({
          'background': 'lightcoral',
          'color': 'darkred',
          'border': '1px solid darkred'
        });

        boundingBox.text(msg);
        boundingBox.show();

        hide();
      }

      /**
       * Generates Internal warning message
       * @param {String} msg Warming message 
       */
      this.warning = function (msg) {
        boundingBox.css({
          'background': '#fff3cd',
          'color': '#856409',
          'border': '1px solid #856409'
        });

        boundingBox.text(msg);
        boundingBox.show();

        hide();
      }

      /**
       * Generates Internal Info message 
       * @param {String} msg Info message
       */
      this.message = function (msg) {
        boundingBox.css({
          'background': 'lightgreen',
          'color': 'green',
          'border': '1px solid green'
        });

        boundingBox.text(msg);
        boundingBox.show();

        hide();
      }
    }

    /*
     * Creates the panel for manipulation of labels on the viewport
     * 
     * @function ContextMenu
     */
    function ContextMenu() {
      var boundingBox = this.ui = $('<div></div>');

      boundingBox.css('position', 'absolute');
      // boundingBox.css('border', '1px solid black');
      boundingBox.css('border-radius', '3px');
      boundingBox.css('background', '#f1f1f1');
      boundingBox.css('z-index', 99);
      var contentBox = $('<div></div>');
      contentBox.css('position', 'relative');
      boundingBox.css('opacity', '0.85');

      boundingBox.append(contentBox);
      contentBox.css({
        'background': '#f1f1f1',
        'border-radius': '4px',
        'padding': '4px',
        'width': '140px'
      });
      // Context Box
      // Remove Label Button 

      var labelMenuStyle = {
        'background': '#d3e2ee',
        'padding': '2px',
        'font-family': 'Arial',
        'font-weight': 'bold',
        'font-size': '12px',
        'border-radius': '2px',
        // 'margin-top':'3px'
      }

      var removeLabelMenu = $('<div></div>');
      removeLabelMenu.text('Remove Label');
      removeLabelMenu.css(labelMenuStyle);
      removeLabelMenu.css('margin-bottom', '3px');

      contentBox.append(removeLabelMenu);
      removeLabelMenu.hide();

      // Label Property List 
      var propertyKeys = Object.keys(validAtomSpecs);
      var propertyList = [];
      var propertyObjectList = [];

      propertyKeys.forEach((prop) => {
        var propObj = validAtomSpecs;
        if (propObj[prop].prop === true) {
          propertyList.push(prop);
        }
      });

      // Property Menu 
      var propertyMenu = $('<div></div>');
      contentBox.append(propertyMenu);

      /*
       * Property object used in property menu 
       * 
       * @function Property 
       * @param {String} key Name of the atom property
       * @param {*} value Value of the property 
       */
      function Property(key, value) {
        this.row = $('<tr></tr>');
        var propLabelValue = this.control = {
          key: '',
          value: null,
          active: true,
          name: key,
        }

        this.key = key;
        this.value = value;

        var checkbox = new $3Dmol.UI.Form.Checkbox(propLabelValue);
        var checkboxHolder = $('<td></td>');
        checkboxHolder.append(checkbox.ui);
        var keyHolder = $('<td></td>');
        var separatorHolder = $('<td></td>').text(':');
        var valueHolder = $('<td></td>');

        this.row.append(checkboxHolder, keyHolder, separatorHolder, valueHolder);

        keyHolder.text(key);

        if (typeof (value) == "number") {
          valueHolder.text(value.toFixed(2));
        } else {
          valueHolder.text(value.replace(/\^/g, ''));
        }

        console.log('Type of value', typeof (value), value);
      }

      /*
       * @param {AtomSpec} atom Value of different property of the atom, if the atom has prop : true
       * then that option is made visible in the context menu
       */
      function setProperties(atom) {
        propertyMenu.empty();
        propertyObjectList = [];

        var propertyTable = $('<table></table>');

        propertyList.forEach((prop) => {
          var propObj = new Property(prop, atom[prop]);
          propertyTable.append(propObj.row);
          propertyObjectList.push(propObj);
        });

        propertyMenu.append(propertyTable);

        var labelStyleHolder = $('<div><div>');

        var labelStyle = $('<div><div>');
        labelStyle.text('Style');
        labelStyle.css({
          'display': 'inline-block',
          'font-family': 'Arial',
          'font-size': '14px',
          'margin-right': '6px',
          'margin-left': '6px'
        });

        var stylesForLabel = new $3Dmol.UI.Form.ListInput(labelStyle, Object.keys($3Dmol.labelStyles));
        stylesForLabel.ui.css({
          'display': 'inline-block'
        });

        stylesForLabel.setValue('milk');

        labelStyleHolder.append(labelStyle, stylesForLabel.ui);
        propertyMenu.append(labelStyleHolder);

        var submit = new button(icons.tick, 18, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
        var cancel = new button(icons.cross, 18, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });

        var controlButtons = $('<div></div>');
        controlButtons.append(submit.ui, cancel.ui);
        // controlButtons.css('text-align', 'center');

        var alertBox = new AlertBox();
        propertyMenu.append(alertBox.ui);

        propertyMenu.append(controlButtons);


        submit.ui.on('click', () => {
          var props = processPropertyList();
          var labelStyleValidation = stylesForLabel.validate();

          if (props != null) {
            if (labelStyleValidation) {
              stateManager.addAtomLabel(props, atom, stylesForLabel.getValue().value);
              stateManager.exitContextMenu(false);
            }
            else {
              alertBox.error('Select style for label');
            }
          }
          else {
            alertBox.error('No value selected for label');
          }
        });

        cancel.ui.on('click', () => {
          stateManager.exitContextMenu();
        });
      }

      // Previous Labels 
      var labelHolder = $('<div></div>');
      contentBox.append(labelHolder);

      // Add Menu 
      var addMenu = $('<div></div>');
      contentBox.append(addMenu);
      addMenu.css('width', '100%');

      var addLabelMenu = $('<div></div>');
      addMenu.append(addLabelMenu);


      addLabelMenu.text('Add Label');
      addLabelMenu.css(labelMenuStyle);
      addLabelMenu.css('margin-bottom', '3px');
      addLabelMenu.hide();

      // Edit Menu
      var editMenu = $('<div></div>');
      contentBox.append(editMenu);

      contentBox.css({
        'position': 'relative',
      });

      editMenu.css({
        'background': '#dfdfdf',
        'border-radius': '3px',
        'font-family': 'Arial',
        'font-weight': 'bold',
        'font-size': '12px',
        // 'position': 'absolute',
        // 'left' : '105%',
        // 'top' : '0',,
        'box-sizing': 'border-box',
        'width': '100%',

      });
      editMenu.hide();

      var alertBox = new AlertBox({ autohide: false });
      contentBox.append(alertBox.ui);

      // Add Label Inputs 

      /*
       * Generate input elements that are used as form values in the context menu under addLabelForm
       * @returns {Object} that holds different input elements
       */
      function generateAddLabelForm() {
        var addLabelForm = $('<div></div>');

        var addLabelValue = {
          text: {
            key: 'Label Text',
            value: null,
            active: true,
          },

          style: {
            key: 'Style',
            value: null,
            active: true,
          },

          sel: {
            key: 'Selection',
            value: null,
            active: true,
          }
        }
        var formModifierControl = $('<div></div>');
        var removeButton = new button(icons.minus, 16);
        var tick = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
        var cross = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
        formModifierControl.append(removeButton.ui, tick.ui, cross.ui);
        removeButton.ui.hide();
        addLabelForm.append(formModifierControl);

        var addLabelTextBox = $('<div></div>');
        var lt = $('<div></div>').text('Label Text');
        var addLabelTextInput = new $3Dmol.UI.Form.Input(addLabelValue.text);
        addLabelTextBox.append(lt, addLabelTextInput.ui);
        var width = 126//editMenu.innerWidth()*0.8;
        addLabelTextInput.setWidth(width);
        addLabelForm.append(addLabelTextBox);

        var addLabelStyleBox = $('<div></div>');
        var ls = $('<div></div>').text('Label Style');
        var addLabelStyleInput = new $3Dmol.UI.Form.ListInput(addLabelValue.style, Object.keys($3Dmol.labelStyles));
        addLabelStyleInput.setValue('milk');
        addLabelStyleBox.append(ls, addLabelStyleInput.ui);
        addLabelForm.append(addLabelStyleBox);

        var selectionList = stateManager.getSelectionList();

        var addLabelSelectionBox = $('<div></div>');
        var lsl = $('<div></div>').text('Label Selection');
        var addLabelSelectionInput = new $3Dmol.UI.Form.ListInput(addLabelValue.sel, selectionList);
        addLabelSelectionBox.append(lsl, addLabelSelectionInput.ui);
        addLabelForm.append(addLabelSelectionBox);

        // CSS 
        addLabelForm.css({
          'padding': '2px',

        });

        tick.ui.on('click', () => {
          var validate = true;

          if (!addLabelStyleInput.validate())
            validate = false;

          if (!addLabelTextInput.validate())
            validate = false;

          if (!addLabelSelectionInput.validate())
            validate = false;

          if (validate) {
            stateManager.addLabel(addLabelValue);
          }
        });

        cross.ui.on('click', () => {
          stateManager.exitContextMenu();
        });

        removeButton.ui.on('click', () => {
          stateManager.removeLabel()
        });

        addLabelForm.on('keyup', (e) => {
          if (e.key == 'Enter') {
            tick.ui.trigger('click');
          }
        });

        return {
          boundingBox: addLabelForm,
          text: addLabelTextInput,
          style: addLabelStyleInput,
          selection: addLabelSelectionInput,
          editMode: function () {
            removeButton.ui.show();
          }
        }
      }


      function processPropertyList() {
        var propsForLabel = {};

        propertyObjectList.forEach((propObj) => {
          if (propObj.control.value === true) {
            propsForLabel[propObj.key] = propObj.value;
          }
        });

        if (Object.keys(propsForLabel).length != 0) {
          return propsForLabel
        }
        else {
          return null;
        }
      }

      // Context Menu UI Funciton 
      boundingBox.hide();
      this.hidden = true;
      this.atom = null;

      removeLabelMenu.on('click', { atom: this.atom }, function () {
        stateManager.removeAtomLabel(removeLabelMenu.atom);
      });


      /**
       * Shows the context menu 
       * 
       * @function ContextMenu#show 
       * 
       * @param {Number} x x coordinate of the mouse
       * @param {Number} y y coordinate of the mouse in the viewport in pixels
       * @param {AtomSpec} atom Value of the atoms that is selected 
       * @param {Boolean} atomExist if atom label is previously added it is set true else false
       */
      this.show = function (x, y, atom, atomExist) {

        if (atomExist) {
          removeLabelMenu.show();
          removeLabelMenu.atom = atom;
        }
        else {
          removeLabelMenu.hide();
          removeLabelMenu.atom = null;
        }

        alertBox.ui.hide();
        addLabelMenu.hide();

        if (stateManager.getSelectionList().length == 0) {
          alertBox.message('Please create selections before adding label');
        } else {
          addLabelMenu.show();
        }

        unsetForm();
        setPosition(boundingBox, x, y);
        boundingBox.show();
        this.hidden = false;

        if (atom) {
          setProperties(atom);
          this.atom = atom;
        }
        else {
          propertyMenu.empty();
        }
      }

      /**
       * Hides the context menu and if needed process the propertyMenu
       * 
       * @function ContextMenu#hide
       * @param {Boolean} processContextMenu If true then submission of the property to add label is executed
       */

      this.hide = function (processContextMenu) {
        if (processContextMenu) {
          var propsForLabel = processPropertyList();
          if (propsForLabel != null) {
            stateManager.addAtomLabel(propsForLabel, this.atom);
          }
        }

        boundingBox.hide();
        this.hidden = true;
        unsetForm();
      }

      addLabelMenu.on('click', function () {
        var addLabelMenuForm = generateAddLabelForm();
        setForm(addLabelMenuForm);
      });

      function setForm(form) {
        editMenu.children().detach();
        editMenu.append(form.boundingBox);
        editMenu.show();
      }

      function unsetForm() {
        editMenu.children().detach();
        editMenu.hide();
      }
    }

    /*
     * Creates UI panel for surface manipulations
     * 
     * @function SurfaceMenu 
     */
    function SurfaceMenu() {
      var boundingBox = this.ui = $('<div></div>');
      var _editingForm = false;
      // Selection Layout

      boundingBox.css({
        'position': 'absolute',
        'width': '140px',
        'text-align': 'right'
      });

      var surfaceButton = new button(icons.surface, 20, { tooltip: 'Toggle Surface Menu' });

      boundingBox.append(surfaceButton.ui);


      var displayBox = $('<div></div>');
      boundingBox.append(displayBox);

      // Overflow fix 
      boundingBox.css({
        'overflow': 'visible',
      });

      var newSurfaceSpace = $('<div></div>');
      newSurfaceSpace.css({
        'max-height': HEIGHT * 0.8,
        'overflow-y': 'auto',
        'overflow-x': 'hidden'
      });

      this.updateScrollBox = function (height) {
        newSurfaceSpace.css('max-height', height * 0.8);
      }
      // newSurfaceSpace.append(controlButton);
      // controlButton.hide();

      displayBox.append(newSurfaceSpace);

      var alertBox = new AlertBox();
      displayBox.append(alertBox.ui);

      var addArea = $('<div></div>');
      var addButton = new button(icons.plus, 20, { tooltip: 'Add New Surface' });
      addArea.append(addButton.ui);
      displayBox.append(addArea);
      displayBox.hide();

      var surfaces = this.surfaces = [];

      /*
       * Creates cards for manipulation of surface
       * 
       * @function Surface 
       */
      function Surface() {
        var control = {
          surfaceType: {
            key: 'Surface Type',
            value: null
          },
          surfaceStyle: {
            key: 'Surface Style',
            value: null
          },
          surfaceFor: {
            key: 'Selection Atoms',
            value: null
          },
          surfaceOf: {
            key: 'Surface Generating Atoms',
            value: null,
          },
        };

        var surfaceBox = this.ui = $('<div></div>');
        surfaceBox.css({
          'margin-top': '3px',
          'padding': '6px',
          'border-radius': '3px',
          'background-color': '#e8e8e8',
          // 'position':'relative',
          'width': '100%',
          'box-sizing': 'border-box',
          // 'left': "-100%",
          'opacity': 0.9,
          'text-align': 'left'
        });

        var heading = this.heading = $('<div></div>');
        var header = $('<div></div>');

        header.css({
          'text-align': 'right'
        })

        // Control Buttons
        var toolButtons = $('<div></div>');

        var editButton = new button(icons.pencil, 16, { tooltip: 'Edit Surface' });
        var removeButton = new button(icons.minus, 16, { bfr: 0.5, backgroundColor: '#f06f6f' });

        toolButtons.append(removeButton.ui);
        toolButtons.append(editButton.ui);

        toolButtons.editButton = editButton;
        toolButtons.removeButton = removeButton;
        toolButtons.editMode = false;

        var defaultTextStyle = {
          'font-weight': 'bold',
          'font-family': 'Arial',
          'font-size': '12px'
        }

        heading.css('display', 'inline-block');
        heading.css(defaultTextStyle);

        toolButtons.css('display', 'inline-block');
        header.hide();

        header.append(heading, toolButtons);
        surfaceBox.append(header);

        // toolButtons.hide();
        var surfacePropertyBox = $('<div></div>');
        surfaceBox.append(surfacePropertyBox);

        // Surface Type
        var surfaceType = $('<div></div>');

        var labelSurfaceType = $('<div></div>');
        labelSurfaceType.text('Surface Type');
        labelSurfaceType.css(defaultTextStyle);

        var listSurfaceType = new $3Dmol.UI.Form.ListInput(control.surfaceType, Object.keys($3Dmol.SurfaceType));


        surfaceType.append(labelSurfaceType, listSurfaceType.ui);
        surfacePropertyBox.append(surfaceType);

        listSurfaceType.setValue(Object.keys($3Dmol.SurfaceType)[0]);
        // Surface Style
        var surfaceStyle = $('<div></div>');

        var labelSurfaceStyle = $('<div></div>');
        // labelSurfaceStyle.text('Surface Style');

        var formSurfaceStyle = new $3Dmol.UI.Form(validSurfaceSpecs, control.surfaceStyle);

        surfaceStyle.append(labelSurfaceStyle, formSurfaceStyle.ui);
        surfacePropertyBox.append(surfaceStyle);

        // Surface Of
        var surfaceOf = $('<div></div>');

        var labelSurfaceOf = $('<div></div>');
        labelSurfaceOf.text('Surface Atoms');
        labelSurfaceOf.css(defaultTextStyle);

        var surfaceGeneratorAtomType = ['self', 'all'];
        var surfaceGeneratorDesc = {
          'self': 'Atoms in the selections will be used to generate the surface',
          'all': 'All the atoms will be used to generate the surface'
        }

        var listSurfaceOf = new $3Dmol.UI.Form.ListInput(control.surfaceOf, surfaceGeneratorAtomType);

        var hintbox = $('<div></div>');
        hintbox.css({
          'background-color': '#e4e4e4',
          'border': '1px solid grey',
          'color': 'grey',
          'padding': '2px',
          'border-radius': '3px',
          'font-family': 'Arial',
          'font-size': '12px',
          'font-weight': 'bold',
          'margin-top': '3px'
        });

        hintbox.hide();

        listSurfaceOf.update = function (control) {
          if (control.value == 'self') {
            hintbox.show();
            hintbox.text(surfaceGeneratorDesc['self']);
          }
          else if (control.value == 'all') {
            hintbox.show();
            hintbox.text(surfaceGeneratorDesc['all']);
          }
          else {
            hintbox.hide();
          }
        }

        listSurfaceOf.setValue('all');

        surfaceOf.append(labelSurfaceOf, listSurfaceOf.ui, hintbox);
        surfacePropertyBox.append(surfaceOf);

        // Surface For
        var selectionListElement = ['all'].concat(stateManager.getSelectionList());
        var surfaceFor = $('<div></div>');

        var labelSurfaceFor = $('<div></div>');
        labelSurfaceFor.text('Show Atoms');
        labelSurfaceFor.css(defaultTextStyle);

        var listSurfaceFor = new $3Dmol.UI.Form.ListInput(control.surfaceFor, selectionListElement);
        listSurfaceFor.setValue('all');

        surfaceFor.append(labelSurfaceFor, listSurfaceFor.ui);
        surfacePropertyBox.append(surfaceFor);

        var alertBox = new AlertBox();
        surfacePropertyBox.append(alertBox.ui);

        // Control Button
        var controlButton = $('<div></div>');
        var submit = new button(icons.tick, 16, { backgroundColor: 'lightgreen', tooltip: 'Submit' });
        var cancel = new button(icons.cross, 16, { backgroundColor: 'lightcoral', tooltip: 'Cancel' });
        controlButton.append(submit.ui);
        controlButton.append(cancel.ui);
        surfacePropertyBox.append(controlButton);

        // Functionality 
        removeButton.ui.on('click', { surfaceBox: surfaceBox }, function (e) {
          var id = e.data.surfaceBox.data('surf-id');
          surfaceBox.remove();
          stateManager.removeSurface(id);
        });

        editButton.ui.on('click', function () {
          surfacePropertyBox.toggle();

          // After creation of the surface box all the changes will be edit to the surfaces so on first submit toolButtons.editMode == true;
        });

        // Form Validation 

        var validateInput = this.validateInput = function () {
          var validated = true;

          if (!listSurfaceFor.validate()) {
            validated = false;
          }

          if (!listSurfaceOf.validate()) {
            validated = false;
          }

          if (!listSurfaceType.validate()) {
            validated = false;
          }

          if (!formSurfaceStyle.validate()) {
            validated = false;
          }

          return validated;
        }

        // edit this code to add on edit selection option to work
        // boundingBox.on('mouseenter', function(){
        //   selections = stateManager.getSelectionList();
        //   selectionListElement = selections.map( (m)=>{
        //     return m.id;
        //   });
        //   listSurfaceFor.updateList(selectionListElement);
        //   listSurfaceOf.updateList(selectionListElement);
        // });

        function finalize(id) {
          // element properties
          surfaceBox.data('surf-id', id);
          heading.text('surf#' + id);

          header.show();
          toolButtons.editMode = true;
          surfacePropertyBox.hide();
        }

        // Submit 
        submit.ui.on('click', {}, function () {
          listSurfaceFor.getValue();
          listSurfaceOf.getValue();
          listSurfaceType.getValue();
          formSurfaceStyle.getValue();

          if (validateInput()) {
            if (toolButtons.editMode === false) {
              var id = stateManager.addSurface(control);
              control.id = id;

              finalize(id);

              surfaces.push(this);
              _editingForm = false;
            }
            else {
              formSurfaceStyle.getValue();
              control.id = surfaceBox.data('surf-id');
              console.log('Edit surface called')
              stateManager.editSurface(control); // -> add updateSurface funciton to surfaceMenu
              surfacePropertyBox.hide();
            }
          }
          else {
            alertBox.error('Invalid Input');
          }
        });

        // Cancel Edit
        cancel.ui.on('click', {}, function () {
          if (toolButtons.editMode == false) {
            surfaceBox.detach();
            surfaceBox.remove();
            _editingForm = false;
          }
          else {
            surfacePropertyBox.hide();
            toolButtons.editMode = false;
          }
        });

        surfaceBox.on('keyup', (e) => {
          if (e.key == 'Enter') {
            submit.ui.trigger('click');
          }
        });

        /**
         * Finalizes the surface card with value specified in the surfaceSpec
         * 
         * @function Surface#editSurface 
         * @param {String} id Id of the surface generated by StateManager
         * @param {Object} surfaceSpec Different value of the surface menu
         */
        this.editSurface = function (id, surfaceSpec) {
          finalize(id);
          listSurfaceType.setValue(surfaceSpec.surfaceType.value);
          formSurfaceStyle.setValue(surfaceSpec.surfaceStyle.value);
          listSurfaceOf.setValue(surfaceSpec.surfaceOf.value);
          listSurfaceFor.setValue(surfaceSpec.surfaceFor.value);

          listSurfaceFor.getValue();
          listSurfaceOf.getValue();
          listSurfaceType.getValue();
          formSurfaceStyle.getValue();

        }

      }

      // Functionality

      // Surface addition

      addButton.ui.on('click', { surfaces: this }, function () {

        if (!_editingForm) {
          var newSurface = new Surface();
          newSurfaceSpace.append(newSurface.ui);
          _editingForm = true;
        } else {
          alertBox.warning('Please complete the previous form first');
        }


      });

      surfaceButton.ui.on('click', () => {
        displayBox.toggle();
      });

      /**
       * Clear all the surface cards 
       * @function SurfaceMenu#empty
       */

      this.empty = function () {
        newSurfaceSpace.empty();
        _editingForm = false;
      }

      /**
       * Add Surface in the Surface Menu 
       * 
       * @function SurfaceMenu#addSurface
       * @param {String} id Id of the surface generated in the StateManager
       * @param {Object} surfaceSpec Values of different property required for setting values in surface menu
       */
      this.addSurface = function (id, surfaceSpec) {
        var newSurface = new Surface();
        newSurfaceSpace.append(newSurface.ui);

        newSurface.editSurface(id, surfaceSpec);
      }
    }

    /*
     * Sets the css position property left and top for the element
     * 
     * @function setPosition
     * 
     * @param {object} jquery html element
     * @param {number} left : css left property
     * @param {number} top : css top peroperty
     */
    function setPosition(ele, left, top) {
      ele.css('left', left);
      ele.css('top', top);
    }


    /*
      * Sets the location of the element relative to the parseInt
      * as per position types
      * @function setLocation
      * 
      * @param  {Object} parent jquery object
      * @param  {Object} child  jquery object
      * @param  {String} x_type 'left|right'
      * @param  {String} y_type 'top|bottom'
      * @param  {Number} x_offset Offset x values in pixels
      * @param  {Number} y_offset Offset y values in pixels 
      */
    function setLocation(parent, child, x_type = 'left', y_type = 'top', x_offset = 0, y_offset = 0) {

      // p_ stands for parent
      child.css('z-index', 99);


      var p_width = getWidth(parent);
      var p_height = getHeight(parent);

      // c_ stand for child
      var c_width = child.outerWidth(); // includes padding and margin
      var c_height = child.outerHeight(); // includes padding and margin

      var padding = parseInt(parent.css('padding').replace('px', ''));
      padding = (padding) ? padding : 0;

      // Setting position
      var c_position = {
        left: 0,
        top: 0
      };

      if (x_type == 'left') {
        c_position.left = padding + x_offset;
      }
      else if (x_type == 'center') {
        c_position.left = p_width / 2 - c_width / 2 + x_offset;
      }
      else if (x_type == 'right') {
        c_position.left = p_width - c_width - padding + x_offset;
      }
      else {
        c_position.left = x_offset + padding;
      }

      if (y_type == 'top') {
        c_position.top = y_offset + padding;
      }
      else if (y_type == 'center') {
        c_position.top = p_height / 2 - c_height / 2 + y_offset;
      }
      else if (y_type == 'bottom') {
        c_position.top = p_height - c_height - y_offset - padding;
      }
      else {
        c_position.top = y_offset + padding;
      }

      setPosition(child, c_position.left, c_position.top);
    }

    // Copied from glviewer.js
    function getRect(container) {
      let div = container[0];
      let rect = div.getBoundingClientRect();
      if (rect.width == 0 && rect.height == 0 && div.style.display === 'none') {
        let oldpos = div.style.position;
        let oldvis = div.style.visibility;
        div.style.display = 'block';
        div.style.visibility = 'hidden';
        div.style.position = 'absolute';
        rect = div.getBoundingClientRect();
        div.style.display = 'none';
        div.style.visibility = oldvis;
        div.style.position = oldpos;
      }
      return rect;
    }


    function getHeight(container) {
      return getRect(container).height;
    }

    function getWidth(container) {
      return getRect(container).width;
    }

    /*
      * button - generates button with the given markup as contents
      * @param {String} svg SVG markup string that contains the content of the button
      * @param {Number} height Height of the content
      * @param {Object} config Various properties to define the button 
      * 
      */
    function button(svg, height, config) {
      config = config || {};
      var borderRadius = config.bfr * height || (height / 4); // body radius factor
      var bgColor = config.backgroundColor || 'rgb(177, 194, 203)';
      var color = config.color || 'black';
      var hoverable = config.hoverable || 'true';
      var tooltipText = config.tooltip || null;

      // Button instance
      var button = this.ui = $('<div></div>');
      var innerButton = $('<div></div>');
      button.append(innerButton);

      // CSS
      button.css('box-sizing', 'border-box');
      button.css('display', 'inline-block');
      button.css('margin', '3px');
      button.css('height', height);
      button.css('width', height);
      button.css('border-radius', borderRadius + 'px');

      //  button.css('padding', '3px');
      button.css('color', color);
      button.css('background', bgColor);

      innerButton.css('display', 'flex');
      innerButton.css('justify-content', 'center');
      innerButton.css('align-items', 'center');
      innerButton.css('padding', '2px');

      // content
      this.setSVG = function (svg) {
        innerButton.empty();
        var formatted_content = $(svg);
        innerButton.append(formatted_content);

      }

      this.setSVG(svg);

      // Hover

      // Setting up tool tip
      button.css({
        'position': 'relative'
      });


      // setting up tool tip
      if (tooltipText != null) {
        button.attr('title', tooltipText);
      }

      if (hoverable == 'true') {
        button.on('mouseenter',
          () => {
            button.css('box-shadow', '0px 0px 3px black');

          }).on('mouseleave',
            () => {
              button.css('box-shadow', 'none');

            }
          );

        // click
        button.on('mousedown', () => {
          button.css('box-shadow', '0px 0px 1px black');
        });

        button.on('mouseup', () => {
          button.css('box-shadow', '0px 0px 3px black');
        });

        button.on('mousemove', () => {
          // mouseX = e.clientX;
          // mouseY = e.clientY;
        });

      }
    }
  }

  return UI;
})();