569b08ba96fa847fbb1a96e5f8b0fb491f877117
braney
  Thu Sep 28 13:36:21 2017 -0700
add dirty flag and a right-click menu on the available tracks pane

diff --git src/hg/js/hgCollection.js src/hg/js/hgCollection.js
index 086117d..a9b55c7 100644
--- src/hg/js/hgCollection.js
+++ src/hg/js/hgCollection.js
@@ -1,33 +1,58 @@
 // hgCollection.js - Interactive features for GTEX Body Map version of GTEx Gene track UI page
 
 // Copyright (C) 2017 The Regents of the University of California
 
 var hgCollection = (function() {
     var names = []; // a list of names that have been used
     var selectedNode = "collectionList"; // keep track of id of selected row
     var selectedTree = "collectionList"; // keep track of id of selected row
     var $tracks;  // the #tracks object
     var trees = [];
+    var isDirty = false;
+
+    function currentTrackItems(node) {
+        // populate the menu for the currentCollection tree
+        var items = {
+            addItem: { // The "delete" menu item
+                label: "Add",
+                action: function () {
+                    var nodeIds = $("#tracks").jstree( "get_selected");
+                    isDirty = true;
+                    var nodes = [];
+                    for(ii=0; ii < nodeIds.length;ii++)
+                        nodes.push($("#tracks").jstree('get_node', nodeIds[ii]));
+                    var parentId = $(selectedTree).jstree("get_node", "ul > li:first").id;
+                    $(selectedTree).jstree("copy_node", nodes, parentId,'last');
+                }
+            }
+        };
+
+        if ($(node).attr('children').length > 0)
+            delete items.addItem;
+
+        return items;
+    }
 
     function currentCollectionItems(node) {
         // populate the menu for the currentCollection tree
         var items = {
             deleteItem: { // The "delete" menu item
                 label: "Delete",
                 action: function () {
                     var nodes = $(selectedTree).jstree( "get_selected");
+                    isDirty = true;
                     $(selectedTree).jstree( "delete_node", nodes);
                 }
             }
         };
 
         // can't delete root
         if ($(node).attr('parent') === '#')
             delete items.deleteItem;
 
         return items;
         }
 
     function selectElements (selectableContainer, elementsToSelect) {
         // add unselecting class to all elements in the styleboard canvas except the ones to select
         $(".ui-selected", selectableContainer).not(elementsToSelect).removeClass("ui-selected").addClass("ui-unselecting");
@@ -77,42 +102,44 @@
                 return false;
         }
 
         return true;
     }
 
     function newCalcTrack() {
         // create a new view under a collection
         var ourCalcName = getUniqueName("calc");
         var newName = "Calc Track";
         var newDescription = "Description of Calculated Track";
         var parent = $(selectedTree).find("li").first();
 
         var newId = $(selectedTree).jstree("create_node", parent, newName + " (" + newDescription + ")");
         var newNode = $(selectedTree).jstree("get_node", newId);
+        isDirty = true;
         newNode.li_attr.class = "folder";
         newNode.li_attr.name = ourCalcName;
         newNode.li_attr.shortlabel = newName;
         newNode.li_attr.longlabel = newDescription;
         newNode.li_attr.visibility = "full";
         newNode.li_attr.color = "#0";
         newNode.li_attr.viewfunc = "add all";
         newNode.li_attr.viewtype = "view";
         $(selectedTree).jstree("set_icon", newNode, '../images/folderC.png');
     }
 
     function newCollection() {
+        isDirty = true;
         // called when the "New Collection" button is pressed
         var ourCollectionName = getUniqueName("coll");
         var ourTreeName = getUniqueName("tree");
         var newName = "A New Collection";
         var newDescription = "Description of New Collection";
         var attributes = "shortLabel='" +  newName + "' ";
         attributes += "longLabel='" +  newDescription + "' ";
         attributes += "color='" + "#0" + "' ";
         attributes += "viewType='" + "track" + "' ";
         attributes += "visibility='" + "full" + "' ";
         attributes += "name='" +  ourCollectionName + "' ";
         attributes += "class='" +  "folder" + "' ";
 
         $('#collectionList').append("<li " + attributes +  "id='"+ourCollectionName+"'>A New Collection</li>");
         $('#currentCollection').append("<div id='"+ourTreeName+"'><ul><li data-jstree='{\"icon\":\"../images/folderC.png\"}' " + attributes+ ">A New Collection</li><ul></div>");
@@ -167,67 +194,73 @@
         });
         json = json.slice(0, -1);
         json += ']';
         console.log(json);
         var requestData = 'jsonp=' + json;
         $.ajax({
             data:  requestData ,
             async: false,
             dataType: "JSON",
             type: "PUT",
             url: "hgCollection?cmd=saveCollection",
             trueSuccess: updatePage,
             success: catchErrorOrDispatch,
             error: errorHandler,
         });
+        isDirty = false;
     }
 
     function rebuildLabel() {
         // rebuild the label for tree item
         var newText = selectedNode.li_attr.shortlabel + "   (" + selectedNode.li_attr.longlabel + ")";
         $(selectedTree).jstree('rename_node', selectedNode, newText);
     }
 
     function descriptionChange() {
         // change the description (longLabel) for a track
         selectedNode.li_attr.longlabel = $("#customDescription").val();
+        isDirty = true;
         rebuildLabel();
     }
 
     function viewFuncChange() {
         // change the view function for a track
+        isDirty = true;
         selectedNode.li_attr.viewfunc = $("#viewFunc").val();
         }
 
     function nameChange() {
         // change the name (shortLabel)  of a track
+        isDirty = true;
         selectedNode.li_attr.shortlabel = $("#customName").val();
         rebuildLabel();
         if (selectedNode.parent === '#') {
             $("#collectionList .ui-selected").text($("#customName").val());
             $('#collectedTracksTitle').text($("#customName").val());
         }
     }
 
     function colorChange() {
         // change the color for a track
+        isDirty = true;
         var color = $("#customColorPicker").spectrum("get"); $('#customColorInput').val(color);
         selectedNode.li_attr.color = $("#customColorInput").val();
     }
 
     function visChange() {
         // change the visibility of a track
+        isDirty = true;
         selectedNode.li_attr.visibility = $("#customVis").val();
     }
 
     function isDraggable(nodes) {
         // only children can be dragged
         var ii;
         for (ii=0; ii < nodes.length; ii++)
             if (nodes[ii].children.length !== 0)
                 return false;
         return true;
     }
 
     function collectionListRightClick (event) {
         // popup the right menu in the collection list
         $(".collectionList-menu").finish().toggle(100).css({
@@ -235,30 +268,40 @@
             left: event.pageX + "px"
         });
         return false;
     }
 
     function recordNames(tree) {
         // keep an accounting of track names that have been used
         var v = $(tree).jstree(true).get_json('#', {'flat': true});
         for (i = 0; i < v.length; i++) {
             var z = v[i];
             names[z.li_attr.name] = 1;
         }
     }
 
     function init() {
+        window.addEventListener("beforeunload", function (e) {
+            if (!isDirty)
+                return undefined;
+
+            var confirmationMessage = 'Do you want to leave this page without saving?';
+
+            (e || window.event).returnValue = confirmationMessage; //Gecko + IE
+            return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
+        });
+
         // called at initialization time
         $("#viewFunc").change(viewFuncChange);
         $("#customName").change(nameChange);
         $("#customDescription").change(descriptionChange);
         $("#customVis").change(visChange);
         //$("#customColorInput").change(colorChange);
         $("#saveCollections").click ( function() {saveCollections(trees);} );
         $("#discardChanges").click ( function () { window.location.reload(); });
 
         $("#newCollection").click ( newCollection );
         $("#newCalcTrack").click ( newCalcTrack );
         $('#collectionList').selectable({selected : selectCollection});
         
         $( "#collectionList" ).contextmenu(collectionListRightClick);
 
@@ -304,31 +347,32 @@
 
             $(newTree).jstree({
                'plugins' : ['dnd', 'conditionalselect', 'contextmenu'],
                'contextmenu': { "items" : currentCollectionItems},
                'dnd': {
                 "check_callback" : checkCallback,
                 }
             });
             recordNames(newTree);
             trees[this.id] = $(newTree);
            $(newTree).on("select_node.jstree", selectTreeNode);
         });
 
         treeDiv=$('#tracks');
         treeDiv.jstree({
-               'plugins' : ['dnd', 'conditionalselect'],
+               'plugins' : ['dnd', 'conditionalselect', 'contextmenu'],
+               'contextmenu': { "items" : currentTrackItems},
                'dnd': {
                 "check_callback" : checkCallback,
                'always_copy' : true,
                 is_draggable: isDraggable,
                },
                'core' :  {
                 "check_callback" : checkCallback
             }
         });
         $(treeDiv).on("select_node.jstree", function (evt, data)  {
             $(evt.target).jstree("toggle_node", data.node);
         });
 
         var firstElement = $("#collectionList li").first();
         selectElements($("#collectionList"), firstElement) ;