make relationship app use SVG IDs; make (unused) form for reading info
[scpubgit/stemmatology.git] / stemmaweb / root / js / relationship.js
index ed1b150..32869ee 100644 (file)
@@ -1,5 +1,11 @@
+var MARGIN=30;
+var svg_root = null;
+var svg_root_element = null;
+var start_element_height = 0;
+var reltypes = {};
+
 function getTextPath() {
-    var currpath = window.location.pathname
+    var currpath = window.location.pathname;
     // Get rid of trailing slash
     if( currpath.lastIndexOf('/') == currpath.length - 1 ) { 
        currpath = currpath.slice( 0, currpath.length - 1) 
@@ -25,6 +31,11 @@ function getRelationshipURL() {
        return path_parts[0] + '/' + path_parts[1] + '/relationships';
 }
 
+// Make an XML ID into a valid selector
+function jq(myid) { 
+       return '#' + myid.replace(/(:|\.)/g,'\\$1');
+}
+
 function svgEnlargementLoaded() {
        //Give some visual evidence that we are working
        $('#loading_overlay').show();
@@ -37,11 +48,55 @@ function svgEnlargementLoaded() {
                { 'top': lo_height / 2 - $("#loading_message").height() / 2,
                  'left': lo_width / 2 - $("#loading_message").width() / 2 });
     //Set viewbox widht and height to widht and height of $('#svgenlargement svg').
+    $('#update_workspace_button').data('locked', false);
+    $('#update_workspace_button').css('background-position', '0px 44px');
     //This is essential to make sure zooming and panning works properly.
     $('#svgenlargement ellipse').attr( {stroke:'green', fill:'#b3f36d'} );
     var graph_svg = $('#svgenlargement svg');
     var svg_g = $('#svgenlargement svg g')[0];
+    if (!svg_g) return;
     svg_root = graph_svg.svg().svg('get').root();
+
+    // Find the real root and ignore any text nodes
+    for (i = 0; i < svg_root.childNodes.length; ++i) {
+        if (svg_root.childNodes[i].nodeName != '#text') {
+               svg_root_element = svg_root.childNodes[i];
+               break;
+          }
+    }
+
+    // This might be a little application specific and should be pushed to the backend
+    // but... This collapes corrector hands when they agree with the original
+    // e.g., P46-*,P46-C becomes simply P46
+    $(svg_root).find('text').each(function () {
+               var t = $(this).text();
+               var ws = t.split(',');
+               for (i = 0; i < ws.length; ++i) {
+                       if (ws[i].indexOf('-*')> -1) {
+                               var collapse = false;
+                               var main = $.trim(ws[i].substring(0,ws[i].indexOf('-')));
+                               for (j = 0; j < ws.length; ++j) {
+                                       if (i != j && ws[j].indexOf('-')> -1) {
+                                               var pair = $.trim(ws[j].substring(0,ws[j].indexOf('-')));
+                                               if (main == pair) {
+                                                       collapse = true;
+                                                       ws[j] = '';
+                                               }
+                                       }
+                               }
+                               if (collapse) ws[i] = main;
+                       }
+               }
+               t = '';
+               for (i = 0; i < ws.length; ++i) {
+                       if (ws[i].length) {
+                               if (t.length>0) t+=', ';
+                               t+=ws[i];
+                       }
+               }
+               $(this).text(t);
+       });
+
     svg_root.viewBox.baseVal.width = graph_svg.attr( 'width' );
     svg_root.viewBox.baseVal.height = graph_svg.attr( 'height' );
     //Now set scale and translate so svg height is about 150px and vertically centered in viewbox.
@@ -55,7 +110,7 @@ function svgEnlargementLoaded() {
     var transform = 'rotate(0) scale(' + scale + ') translate(4 ' + translate + ')';
     svg_g.setAttribute('transform', transform);
     //used to calculate min and max zoom level:
-    start_element_height = $("#svgenlargement .node title:contains('START#')").siblings('ellipse')[0].getBBox().height;
+    start_element_height = $('#__START__ ellipse')[0].getBBox().height;
     add_relations( function() { $('#loading_overlay').hide(); });
 }
 
@@ -89,9 +144,7 @@ function add_relations( callback_fn ) {
 }
 
 function get_ellipse( node_id ) {
-  return $('#svgenlargement .node').children('title').filter( function(index) {
-    return $(this).text() == node_id;
-  }).siblings('ellipse');
+       return $( jq( node_id ) + ' ellipse');
 }
 
 function get_node_obj( node_id ) {
@@ -102,12 +155,6 @@ function get_node_obj( node_id ) {
     return node_ellipse.data( 'node_obj' );
 }
 
-function get_edge( edge_id ) {
-  return $('#svgenlargement .edge').filter( function(index) {
-    return $(this).children( 'title' ).text() == $('<div/>').html(edge_id).text() ;
-  });
-}
-
 function node_obj(ellipse) {
   this.ellipse = ellipse;
   var self = this;
@@ -119,7 +166,7 @@ function node_obj(ellipse) {
   this.node_elements = node_elements_for(self.ellipse);
 
   this.get_id = function() {
-    return self.ellipse.siblings('title').text()
+    return $(self.ellipse).parent().attr('id')
   }
   
   this.set_draggable = function( draggable ) {
@@ -158,9 +205,9 @@ function node_obj(ellipse) {
 
   this.mouseup_listener = function(evt) {    
     if( $('ellipse[fill="#ffccff"]').size() > 0 ) {
-        var source_node_id = self.ellipse.siblings('title').text();
+        var source_node_id = $(self.ellipse).parent().attr('id');
         var source_node_text = self.ellipse.siblings('text').text();
-        var target_node_id = $('ellipse[fill="#ffccff"]').siblings("title").text();
+        var target_node_id = $('ellipse[fill="#ffccff"]').parent().attr('id');
         var target_node_text = $('ellipse[fill="#ffccff"]').siblings("text").text();
         $('#source_node_id').val( source_node_id );
         $('#source_node_text').val( source_node_text );
@@ -280,7 +327,7 @@ function node_elements_for( ellipse ) {
 
 function get_edge_elements_for( ellipse ) {
   edge_elements = new Array();
-  node_id = ellipse.siblings('title').text();
+  node_id = ellipse.parent().attr('id');
   edge_in_pattern = new RegExp( node_id + '$' );
   edge_out_pattern = new RegExp( '^' + node_id );
   $.each( $('#svgenlargement .edge,#svgenlargement .relation').children('title'), function(index) {
@@ -309,12 +356,8 @@ function relation_factory() {
     this.relation_colors = [ "#5CCCCC", "#67E667", "#F9FE72", "#6B90D4", "#FF7673", "#E467B3", "#AA67D5", "#8370D8", "#FFC173" ];
 
     this.create_temporary = function( source_node_id, target_node_id ) {
-        var relation = $('#svgenlargement .relation').filter( function(index) {
-            var relation_id = $(this).children('title').text();
-            if( ( relation_id == ( source_node_id + '->' + target_node_id ) ) || ( relation_id == ( target_node_id + '->' + source_node_id ) ) ) {
-                return true;
-            } 
-        } );
+       var relation_id = get_relation_id( source_node_id, target_node_id );
+        var relation = $( jq( relation_id ) );
         if( relation.size() == 0 ) { 
             draw_relation( source_node_id, target_node_id, self.temp_color );
         } else {
@@ -342,16 +385,16 @@ function relation_factory() {
         return relation;
     }
     this.toggle_active = function( relation_id ) {
-        var relation = $("#svgenlargement .relation:has(title:contains('" + relation_id + "'))");
+        var relation = $( jq( relation_id ) );
         var relation_path = relation.children('path');
         if( !relation.data( 'active' ) ) {
             relation_path.css( {'cursor':'pointer'} );
             relation_path.mouseenter( function(event) { 
                 outerTimer = setTimeout( function() { 
                     timer = setTimeout( function() { 
-                        var title = relation.children('title').text();
-                        var source_node_id = title.substring( 0, title.indexOf( "->" ) );
-                        var target_node_id = title.substring( (title.indexOf( "->" ) + 2) );
+                        var related_nodes = get_related_nodes( relation_id );
+                        var source_node_id = related_nodes[0];
+                        var target_node_id = related_nodes[1];
                         $('#delete_source_node_id').val( source_node_id );
                         $('#delete_target_node_id').val( target_node_id );
                         self.showinfo(relation); 
@@ -384,7 +427,7 @@ function relation_factory() {
         var p = svg_root.createSVGPoint();
         p.x = xs + ((xe-xs)*1.1);
         p.y = ye - ((ye-ys)/2);
-        var ctm = svg_root.children[0].getScreenCTM();
+        var ctm = svg_root_element.getScreenCTM();
         var nx = p.matrixTransform(ctm).x;
         var ny = p.matrixTransform(ctm).y;
         var dialog_aria = $ ("div[aria-labelledby='ui-dialog-title-delete-form']");
@@ -392,14 +435,28 @@ function relation_factory() {
         dialog_aria.offset({ left: nx, top: ny });
     }
     this.remove = function( relation_id ) {
-        var relation = $("#svgenlargement .relation:has(title:contains('" + relation_id + "'))");
+        var relation = $( jq( relation_id ) );
         relation.remove();
     }
 }
 
+// Utility function to create/return the ID of a relation link between
+// a source and target.
+function get_relation_id( source_id, target_id ) {
+       var idlist = [ source_id, target_id ];
+       idlist.sort();
+       return 'relation-' + idlist[0] + '-...-' + idlist[1];
+}
+
+function get_related_nodes( relation_id ) {
+       var srctotarg = relation_id.substr( 9 );
+       return srctotarg.split('-...-');
+}
+
 function draw_relation( source_id, target_id, relation_color ) {
     var source_ellipse = get_ellipse( source_id );
     var target_ellipse = get_ellipse( target_id );
+    var relation_id = get_relation_id( source_id, target_id );
     var svg = $('#svgenlargement').children('svg').svg().svg('get');
     var path = svg.createPath(); 
     var sx = parseInt( source_ellipse.attr('cx') );
@@ -407,7 +464,8 @@ function draw_relation( source_id, target_id, relation_color ) {
     var sy = parseInt( source_ellipse.attr('cy') );
     var ex = parseInt( target_ellipse.attr('cx') );
     var ey = parseInt( target_ellipse.attr('cy') );
-    var relation = svg.group( $("#svgenlargement svg g"), {'class':'relation'} );
+    var relation = svg.group( $("#svgenlargement svg g"), 
+       { 'class':'relation', 'id':relation_id } );
     svg.title( relation, source_id + '->' + target_id );
     svg.path( relation, path.move( sx, sy ).curveC( sx + (2*rx), sy, ex + (2*rx), ey, ex, ey ), {fill: 'none', stroke: relation_color, strokeWidth: 4});
     var relation_element = $('#svgenlargement .relation').filter( ':last' );
@@ -428,11 +486,13 @@ $(document).ready(function () {
         .data('x', event.clientX)
         .data('y', event.clientY)
         .data('scrollLeft', this.scrollLeft)
-        stateTf = svg_root.children[0].getCTM().inverse();
+        stateTf = svg_root_element.getCTM().inverse();
         var p = svg_root.createSVGPoint();
         p.x = event.clientX;
         p.y = event.clientY;
         stateOrigin = p.matrixTransform(stateTf);
+        event.returnValue = false;
+        event.preventDefault();
         return false;
   }).mouseup(function (event) {
         $(this).data('down', false);
@@ -445,22 +505,28 @@ $(document).ready(function () {
         p = p.matrixTransform(stateTf);
         var matrix = stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y);
         var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
-        svg_root.children[0].setAttribute("transform", s);
+        svg_root_element.setAttribute("transform", s);
     }
+    event.returnValue = false;
+    event.preventDefault();
   }).mousewheel(function (event, delta) {
     event.returnValue = false;
     event.preventDefault();
     if ( $('#update_workspace_button').data('locked') == false ) {
+        if (!delta || delta == null || delta == 0) delta = event.originalEvent.wheelDelta;
+        if (!delta || delta == null || delta == 0) delta = -1 * event.originalEvent.detail;
         if( delta < -9 ) { delta = -9 }; 
         var z = 1 + delta/10;
-        var g = svg_root.children[0];
-        if( (z<1 && (g.getScreenCTM().a * start_element_height) > 4.0) || (z>1 && (g.getScreenCTM().a * start_element_height) < 100) ) {
+        z = delta > 0 ? 1 : -1;
+        var g = svg_root_element;
+        if (g && ((z<1 && (g.getScreenCTM().a * start_element_height) > 4.0) || (z>=1 && (g.getScreenCTM().a * start_element_height) < 100))) {
             var root = svg_root;
             var p = root.createSVGPoint();
-            p.x = event.clientX;
-            p.y = event.clientY;
+            p.x = event.originalEvent.clientX;
+            p.y = event.originalEvent.clientY;
             p = p.matrixTransform(g.getCTM().inverse());
-            var k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);
+            var scaleLevel = 1+(z/20);
+            var k = root.createSVGMatrix().translate(p.x, p.y).scale(scaleLevel).translate(-p.x, -p.y);
             var matrix = g.getCTM().multiply(k);
             var s = "matrix(" + matrix.a + "," + matrix.b + "," + matrix.c + "," + matrix.d + "," + matrix.e + "," + matrix.f + ")";
             g.setAttribute("transform", s);
@@ -480,16 +546,20 @@ $(document).ready(function () {
     buttons: {
       "Ok": function() {
         $('#status').empty();
-        form_values = $('#collapse_node_form').serialize()
+        form_values = $('#collapse_node_form').serialize();
         ncpath = getRelationshipURL();
         $(':button :contains("Ok")').attr("disabled", true);
         var jqjson = $.post( ncpath, form_values, function(data) {
             $.each( data, function(item, source_target) { 
-                var relation = relation_manager.create( source_target[0], source_target[1], $('#rel_type').attr('selectedIndex') );
-                relation.data( 'type', $('#rel_type :selected').text()  );
-                relation.data( 'scope', $('#scope :selected').text()  );
-                relation.data( 'note', $('#note').val()  );
-                relation_manager.toggle_active( relation.children('title').text() );
+               var source_found = get_ellipse( source_target[0] );
+               var target_found = get_ellipse( source_target[1] );
+               if( source_found.size() && target_found.size() ) {
+                    var relation = relation_manager.create( source_target[0], source_target[1], $('#rel_type')[0].selectedIndex-1 );
+                                       relation.data( 'type', $('#rel_type :selected').text()  );
+                                       relation.data( 'scope', $('#scope :selected').text()  );
+                                       relation.data( 'note', $('#note').val()  );
+                                       relation_manager.toggle_active( relation.attr('id') );
+                               }
             });
             $( "#dialog-form" ).dialog( "close" );
         }, 'json' );
@@ -505,12 +575,12 @@ $(document).ready(function () {
         var jqjson = $.getJSON( basepath + '/definitions', function(data) {
             var types = data.types.sort();
             $.each( types, function(index, value) {   
-                 $('#rel_type').append( $('<option>').attr( "value", value ).text(value) ); 
+                 $('#rel_type').append( $('<option />').attr( "value", value ).text(value) ); 
                  $('#keymaplist').append( $('<li>').css( "border-color", relation_manager.relation_colors[index] ).text(value) ); 
             });
             var scopes = data.scopes;
             $.each( scopes, function(index, value) {   
-                 $('#scope').append( $('<option>').attr( "value", value ).text(value) ); 
+                 $('#scope').append( $('<option />').attr( "value", value ).text(value) ); 
             });
         });        
     },
@@ -548,7 +618,7 @@ $(document).ready(function () {
           ncpath = getRelationshipURL()
           var jqjson = $.ajax({ url: ncpath, data: form_values, success: function(data) {
               $.each( data, function(item, source_target) { 
-                  relation_manager.remove( source_target[0] + '->' + source_target[1] );
+                  relation_manager.remove( get_relation_id( source_target[0], source_target[1] ) );
               });
               $( "#delete-form" ).dialog( "close" );
           }, dataType: 'json', type: 'DELETE' });
@@ -572,9 +642,39 @@ $(document).ready(function () {
     }
   });
 
+  // function for reading form dialog should go here; for now hide the element
+  $('#reading-form').dialog({
+       autoOpen: false,
+       height: 400,
+       width: 200,
+       modal: true,
+       buttons: {
+               Cancel: function() {
+                       $( this ).dialog( "close" );
+               },
+               Update: function() {
+                       form_values = $('#reading_data_form').serialize();
+                       ncpath = getReadingURL();
+                       var reading_element = get_node_obj( $('#reading_data_id').val() );
+                       $(':button :contains("Update")').attr("disabled", true);
+                       var jqjson = $.post( ncpath, form_values, function(data) {
+                               $.each( data, function(key, value) { 
+                                       reading_element.data( key, value );
+                               });
+                               $( "#reading-form" ).dialog( "close" );
+                       });
+               }
+       },
+       create: function() {},
+       open: function() {},
+       close: function() {
+               $("#dialog_overlay").hide();
+       }
+  });
+
   $('#update_workspace_button').click( function() {
      var svg_enlargement = $('#svgenlargement').svg().svg('get').root();
-     mouse_scale = svg_root.children[0].getScreenCTM().a;
+     mouse_scale = svg_root_element.getScreenCTM().a;
      if( $(this).data('locked') == true ) {
          $('#svgenlargement ellipse' ).each( function( index ) {
              if( $(this).data( 'node_obj' ) != null ) {
@@ -590,7 +690,7 @@ $(document).ready(function () {
      } else {
          var left = $('#enlargement').offset().left;
          var right = left + $('#enlargement').width();
-         var tf = svg_root.children[0].getScreenCTM().inverse(); 
+         var tf = svg_root_element.getScreenCTM().inverse(); 
          var p = svg_root.createSVGPoint();
          p.x=left;
          p.y=100;
@@ -628,11 +728,57 @@ $(document).ready(function () {
       $('#svgenlargement .relation').find( "title:contains('" + node_id +  "')" ).each( function(index) {
           matchid = new RegExp( "^" + node_id );
           if( $(this).text().match( matchid ) != null ) {
-              relation_manager.toggle_active( $(this).text() );
+                 var relation_id = $(this).parent().attr('id');
+              relation_manager.toggle_active( relation_id );
           };
       });
   }
 
+  expandFillPageClients();
+  $(window).resize(function() {
+    expandFillPageClients();
+  });
+
 });
 
 
+function expandFillPageClients() {
+       $('.fillPage').each(function () {
+               $(this).height($(window).height() - $(this).offset().top - MARGIN);
+       });
+}
+
+function loadSVG(svgData) {
+       var svgElement = $('#svgenlargement');
+
+       $(svgElement).svg('destroy');
+
+       $(svgElement).svg({
+               loadURL: svgData,
+               onLoad : svgEnlargementLoaded
+       });
+}
+
+
+/*     OS Gadget stuff
+
+function svg_select_callback(topic, data, subscriberData) {
+       svgData = data;
+       loadSVG(svgData);
+}
+
+function loaded() {
+       var prefs = new gadgets.Prefs();
+       var preferredHeight = parseInt(prefs.getString('height'));
+       if (gadgets.util.hasFeature('dynamic-height')) gadgets.window.adjustHeight(preferredHeight);
+       expandFillPageClients();
+}
+
+if (gadgets.util.hasFeature('pubsub-2')) {
+       gadgets.HubSettings.onConnect = function(hum, suc, err) {
+               subId = gadgets.Hub.subscribe("interedition.svg.selected", svg_select_callback);
+               loaded();
+       };
+}
+else gadgets.util.registerOnLoadHandler(loaded);
+*/