X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=root%2Fjs%2Frelationship.js;h=1e75e205f82274f4ce2638d18a236de7eed66e2e;hb=f069bb5cd56e0b4e2467bd63d2197c24190ca0da;hp=9782e82a543118a5d6ea0d574a41212cca22e236;hpb=4c5efc084c095e443c7d2efc9624160e855cecb2;p=scpubgit%2Fstemmaweb.git diff --git a/root/js/relationship.js b/root/js/relationship.js index 9782e82..1e75e20 100644 --- a/root/js/relationship.js +++ b/root/js/relationship.js @@ -5,6 +5,23 @@ var start_element_height = 0; var reltypes = {}; var readingdata = {}; +jQuery.removeFromArray = function(value, arr) { + return jQuery.grep(arr, function(elem, index) { + return elem !== value; + }); +}; + +function arrayUnique(array) { + var a = array.concat(); + for(var i=0; i').attr( - 'value', wit ).text( wit ) ); - }); - } // Now do the morphological properties. morphology_form( reading_info['lexemes'] ); // and then open the dialog. @@ -173,11 +182,6 @@ function svgEnlargementLoaded() { $('#update_workspace_button').data('locked', false); $('#update_workspace_button').css('background-position', '0px 44px'); } - var rdgpath = getTextURL( 'readings' ); - $.getJSON( rdgpath, function( data ) { - readingdata = data; - $('#svgenlargement ellipse').each( function( i, el ) { color_inactive( el ) }); - }); $('#svgenlargement ellipse').parent().dblclick( node_dblclick_listener ); var graph_svg = $('#svgenlargement svg'); var svg_g = $('#svgenlargement svg g')[0]; @@ -208,7 +212,15 @@ function svgEnlargementLoaded() { svg_g.setAttribute('transform', transform); //used to calculate min and max zoom level: start_element_height = $('#__START__').children('ellipse')[0].getBBox().height; - add_relations( function() { $('#loading_overlay').hide(); }); + //some use of call backs to ensure succesive execution + add_relations( function() { + var rdgpath = getTextURL( 'readings' ); + $.getJSON( rdgpath, function( data ) { + readingdata = data; + $('#svgenlargement ellipse').each( function( i, el ) { color_inactive( el ) }); + $('#loading_overlay').hide(); + }); + }); //initialize marquee marquee = new Marquee(); @@ -233,19 +245,20 @@ function add_relations( callback_fn ) { $.getJSON( textrelpath, function(data) { $.each(data, function( index, rel_info ) { var type_index = $.inArray(rel_info.type, rel_types); - var source_found = get_ellipse( rel_info.source ); - var target_found = get_ellipse( rel_info.target ); + var source_found = get_ellipse( rel_info.source_id ); + var target_found = get_ellipse( rel_info.target_id ); if( type_index != -1 && source_found.size() && target_found.size() ) { - var relation = relation_manager.create( rel_info.source, rel_info.target, type_index ); - relation.data( 'type', rel_info.type ); - relation.data( 'scope', rel_info.scope ); - relation.data( 'note', rel_info.note ); + var relation = relation_manager.create( rel_info.source_id, rel_info.target_id, type_index ); + // Save the relationship data too. + $.each( rel_info, function( k, v ) { + relation.data( k, v ); + }); if( editable ) { - var node_obj = get_node_obj(rel_info.source); - node_obj.set_draggable( false ); + var node_obj = get_node_obj(rel_info.source_id); + node_obj.set_selectable( false ); node_obj.ellipse.data( 'node_obj', null ); - node_obj = get_node_obj(rel_info.target); - node_obj.set_draggable( false ); + node_obj = get_node_obj(rel_info.target_id); + node_obj.set_selectable( false ); node_obj.ellipse.data( 'node_obj', null ); } } @@ -275,21 +288,46 @@ function node_obj(ellipse) { this.dx = 0; this.dy = 0; this.node_elements = node_elements_for(self.ellipse); - + this.get_id = function() { return $(self.ellipse).parent().attr('id') } + this.set_selectable = function( clickable ) { + if( clickable && editable ) { + $(self.ellipse).attr( {stroke:'black', fill:'#fff'} ); + $(self.ellipse).parent().hover( this.enter_node, this.leave_node ); + $(self.ellipse).parent().mousedown( function(evt) { evt.stopPropagation() } ); + $(self.ellipse).parent().click( function(evt) { + evt.stopPropagation(); + if( $('ellipse[fill="#9999ff"]').size() > 0 ) { + $('ellipse[fill="#9999ff"]').each( function() { + $(this).data( 'node_obj' ).set_draggable( false ); + } ); + } + self.set_draggable( true ) + }); + } else { + $(self.ellipse).attr( {stroke:'black', fill:'#fff'} ); + self.ellipse.siblings('text').attr('class', ''); + $(self.ellipse).parent().unbind(); + $('body').unbind('mousemove'); + $('body').unbind('mouseup'); + } + } + this.set_draggable = function( draggable ) { if( draggable && editable ) { - $(self.ellipse).attr( {stroke:'black', fill:'#fff'} ); + $(self.ellipse).attr( {stroke:'black', fill:'#9999ff'} ); $(self.ellipse).parent().mousedown( this.mousedown_listener ); - $(self.ellipse).parent().hover( this.enter_node, this.leave_node ); + $(self.ellipse).parent().unbind( 'mouseenter' ).unbind( 'mouseleave' ); self.ellipse.siblings('text').attr('class', 'noselect draggable'); } else { + $(self.ellipse).attr( {stroke:'black', fill:'#fff'} ); self.ellipse.siblings('text').attr('class', ''); - $(self.ellipse).parent().unbind( 'mouseenter' ).unbind( 'mouseleave' ).unbind( 'mousedown' ); - color_inactive( self.ellipse ); + $(self.ellipse).parent().unbind( 'mousedown '); + $(self.ellipse).parent().mousedown( function(evt) { evt.stopPropagation() } ); + $(self.ellipse).parent().hover( this.enter_node, this.leave_node ); } } @@ -300,7 +338,7 @@ function node_obj(ellipse) { $('body').mousemove( self.mousemove_listener ); $('body').mouseup( self.mouseup_listener ); $(self.ellipse).parent().unbind('mouseenter').unbind('mouseleave') - self.ellipse.attr( 'fill', '#ff66ff' ); + self.ellipse.attr( 'fill', '#6b6bb2' ); first_node_g_element = $("#svgenlargement g .node" ).filter( ":first" ); if( first_node_g_element.attr('id') !== self.get_g().attr('id') ) { self.get_g().insertBefore( first_node_g_element ) }; } @@ -322,14 +360,15 @@ function node_obj(ellipse) { var target_node_text = $('ellipse[fill="#ffccff"]').siblings("text").text(); $('#source_node_id').val( source_node_id ); $('#source_node_text').val( source_node_text ); + $('.rel_rdg_a').text( "'" + source_node_text + "'" ); $('#target_node_id').val( target_node_id ); $('#target_node_text').val( target_node_text ); + $('.rel_rdg_b').text( "'" + target_node_text + "'" ); $('#dialog-form').dialog( 'open' ); }; $('body').unbind('mousemove'); $('body').unbind('mouseup'); - self.ellipse.attr( 'fill', '#fff' ); - $(self.ellipse).parent().hover( self.enter_node, self.leave_node ); + self.ellipse.attr( 'fill', '#9999ff' ); self.reset_elements(); } @@ -361,32 +400,55 @@ function node_obj(ellipse) { }); } + this.reposition = function( dx, dy ) { + $.each( self.node_elements, function(index, value) { + value.reposition( dx, dy ); + } ); + } + this.move_elements = function() { $.each( self.node_elements, function(index, value) { - value.move(self.dx,self.dy); - }); + value.move( self.dx, self.dy ); + } ); } this.reset_elements = function() { $.each( self.node_elements, function(index, value) { value.reset(); - }); + } ); } this.update_elements = function() { self.node_elements = node_elements_for(self.ellipse); } - self.set_draggable( true ); + this.get_witnesses = function() { + return readingdata[self.get_id()].witnesses + } + + self.set_selectable( true ); } function svgshape( shape_element ) { this.shape = shape_element; + this.reposx = 0; + this.reposy = 0; + this.repositioned = this.shape.parent().data( 'repositioned' ); + if( this.repositioned != null ) { + this.reposx = this.repositioned[0]; + this.reposy = this.repositioned[1]; + } + this.reposition = function (dx, dy) { + this.move( dx, dy ); + this.reposx = this.reposx + dx; + this.reposy = this.reposy + dy; + this.shape.parent().data( 'repositioned', [this.reposx,this.reposy] ); + } this.move = function(dx,dy) { - this.shape.attr( "transform", "translate(" + dx + " " + dy + ")" ); + this.shape.attr( "transform", "translate( " + (this.reposx + dx) + " " + (this.reposy + dy) + " )" ); } this.reset = function() { - this.shape.attr( "transform", "translate( 0, 0 )" ); + this.shape.attr( "transform", "translate( " + this.reposx + " " + this.reposy + " )" ); } this.grey_out = function(filter) { if( this.shape.parent(filter).size() != 0 ) { @@ -405,14 +467,24 @@ function svgpath( path_element, svg_element ) { this.path = path_element; this.x = this.path.x; this.y = this.path.y; + + this.reposition = function (dx, dy) { + this.x = this.x + dx; + this.y = this.y + dy; + this.path.x = this.x; + this.path.y = this.y; + } + this.move = function(dx,dy) { this.path.x = this.x + dx; this.path.y = this.y + dy; } + this.reset = function() { this.path.x = this.x; this.path.y = this.y; } + this.grey_out = function(filter) { if( this.svg_element.parent(filter).size() != 0 ) { this.svg_element.attr('stroke', '#e5e5e5'); @@ -440,7 +512,7 @@ function get_edge_elements_for( ellipse ) { edge_elements = new Array(); node_id = ellipse.parent().attr('id'); edge_in_pattern = new RegExp( node_id + '$' ); - edge_out_pattern = new RegExp( '^' + node_id ); + edge_out_pattern = new RegExp( '^' + node_id + '-' ); $.each( $('#svgenlargement .edge,#svgenlargement .relation').children('title'), function(index) { title = $(this).text(); if( edge_in_pattern.test(title) ) { @@ -527,6 +599,19 @@ function relation_factory() { this.showinfo = function(relation) { $('#delete_relation_type').text( relation.data('type') ); $('#delete_relation_scope').text( relation.data('scope') ); + $('#delete_relation_attributes').empty(); + if( relation.data( 'a_derivable_from_b' ) ) { + $('#delete_relation_attributes').append( + "'" + relation.data('source_text') + "' derivable from '" + relation.data('target_text') + "'
"); + } + if( relation.data( 'b_derivable_from_a' ) ) { + $('#delete_relation_attributes').append( + "'" + relation.data('target_text') + "' derivable from '" + relation.data('source_text') + "'
"); + } + if( relation.data( 'non_independent' ) ) { + $('#delete_relation_attributes').append( + "Variance unlikely to arise coincidentally
"); + } if( relation.data( 'note' ) ) { $('#delete_relation_note').text('note: ' + relation.data( 'note' ) ); } @@ -578,8 +663,7 @@ 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', 'id':relation_id } ); + 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' ); @@ -587,39 +671,222 @@ function draw_relation( source_id, target_id, relation_color ) { return relation_element; } +function detach_node( readings ) { + // separate out the deleted relationships, discard for now + if( 'DELETED' in readings ) { + // Remove each of the deleted relationship links. + $.each( readings['DELETED'], function( idx, pair ) { + var relation_id = get_relation_id( pair[0], pair[1] ); + var relation = $( jq( relation_id ) ); + if( relation.size() == 0 ) { + relation_id = get_relation_id( pair[1], pair[0] ); + } + relation_manager.remove( relation_id ); + }); + delete readings['DELETED']; + } + // add new node(s) + $.extend( readingdata, readings ); + // remove from existing readings the witnesses for the new nodes/readings + $.each( readings, function( node_id, reading ) { + $.each( reading.witnesses, function( index, witness ) { + var witnesses = readingdata[ reading.orig_rdg ].witnesses; + readingdata[ reading.orig_rdg ].witnesses = $.removeFromArray( witness, witnesses ); + } ); + } ); + + detached_edges = []; + + // here we detach witnesses from the existing edges accoring to what's being relayed by readings + $.each( readings, function( node_id, reading ) { + var edges = edges_of( get_ellipse( reading.orig_rdg ) ); + incoming_remaining = []; + outgoing_remaining = []; + $.each( reading.witnesses, function( index, witness ) { + incoming_remaining.push( witness ); + outgoing_remaining.push( witness ); + } ); + $.each( edges, function( index, edge ) { + detached_edge = edge.detach_witnesses( reading.witnesses ); + if( detached_edge != null ) { + detached_edges.push( detached_edge ); + $.each( detached_edge.witnesses, function( index, witness ) { + if( detached_edge.is_incoming == true ) { + incoming_remaining = $.removeFromArray( witness, incoming_remaining ); + } else { + outgoing_remaining = $.removeFromArray( witness, outgoing_remaining ); + } + } ); + } + } ); + + // After detaching we still need to check if for *all* readings + // an edge was detached. It may be that a witness was not + // explicitly named on an edge but was part of a 'majority' edge + // in which case we need to duplicate and name that edge after those + // remaining witnesses. + if( outgoing_remaining.length > 0 ) { + $.each( edges, function( index, edge ) { + if( edge.get_label() == 'majority' && !edge.is_incoming ) { + detached_edges.push( edge.clone_for( outgoing_remaining ) ); + } + } ); + } + if( incoming_remaining.length > 0 ) { + $.each( edges, function( index, edge ) { + if( edge.get_label() == 'majority' && edge.is_incoming ) { + detached_edges.push( edge.clone_for( incoming_remaining ) ); + } + } ); + } + + // Finally multiple selected nodes may share edges + var copy_array = []; + $.each( detached_edges, function( index, edge ) { + var do_copy = true; + $.each( copy_array, function( index, copy_edge ) { + if( copy_edge.g_elem.attr( 'id' ) == edge.g_elem.attr( 'id' ) ) { do_copy = false } + } ); + if( do_copy == true ) { + copy_array.push( edge ); + } + } ); + detached_edges = copy_array; + + // Lots of unabstracted knowledge down here :/ + // Clone original node/reading, rename/id it.. + duplicate_node = get_ellipse( reading.orig_rdg ).parent().clone(); + duplicate_node.attr( 'id', node_id ); + duplicate_node.children( 'title' ).text( node_id ); + + // This needs somehow to move to node or even to shapes! #repositioned + duplicate_node_data = get_ellipse( reading.orig_rdg ).parent().data( 'repositioned' ); + if( duplicate_node_data != null ) { + duplicate_node.children( 'ellipse' ).parent().data( 'repositioned', duplicate_node_data ); + } + + // Add the node and all new edges into the graph + var graph_root = $('#svgenlargement svg g.graph'); + graph_root.append( duplicate_node ); + $.each( detached_edges, function( index, edge ) { + id_suffix = node_id.slice( node_id.indexOf( '_' ) ); + edge.g_elem.attr( 'id', ( edge.g_elem.attr( 'id' ) + id_suffix ) ); + edge_title = edge.g_elem.children( 'title' ).text(); + edge_weight = 0.8 + ( 0.2 * edge.witnesses.length ); + edge_title = edge_title.replace( reading.orig_rdg, node_id ); + edge.g_elem.children( 'title' ).text( edge_title ); + edge.g_elem.children( 'path').attr( 'stroke-width', edge_weight ); + // Reg unabstracted knowledge: isn't it more elegant to make + // it edge.append_to( graph_root )? + graph_root.append( edge.g_elem ); + } ); + + // Make the detached node a real node_obj + var ellipse_elem = get_ellipse( node_id ); + var new_node = new node_obj( ellipse_elem ); + ellipse_elem.data( 'node_obj', new_node ); + + // Move the node somewhat up for 'dramatic effect' :-p + new_node.reposition( 0, -70 ); + + } ); + +} + +function merge_nodes( source_node_id, target_node_id, consequences ) { + if( consequences.status != null && consequences.status == 'ok' ) { + merge_node( source_node_id, target_node_id ); + if( consequences.checkalign != null ) { + $.each( consequences.checkalign, function( index, node_ids ) { + var temp_relation = draw_relation( node_ids[0], node_ids[1], "#89a02c" ); + var sy = parseInt( temp_relation.children('path').attr('d').split('C')[0].split(',')[1] ); + var ey = parseInt( temp_relation.children('path').attr('d').split(' ')[2].split(',')[1] ); + var yC = ey + (( sy - ey )/2); + // TODO: compute xC to be always the same distance to the amplitude of the curve + var xC = parseInt( temp_relation.children('path').attr('d').split(' ')[1].split(',')[0] ); + var svg = $('#svgenlargement').children('svg').svg('get'); + parent_g = svg.group( $('#svgenlargement svg g') ); + var ids_text = node_ids[0] + '-' + node_ids[1]; + var merge_id = 'merge-' + ids_text; + svg.image( parent_g, xC, (yC-8), 16, 16, merge_button_yes, { id: merge_id } ); + svg.image( parent_g, (xC+20), (yC-8), 16, 16, merge_button_no, { id: 'no' + merge_id } ); + $( '#' + merge_id ).hover( function(){ $(this).addClass( 'draggable' ) }, function(){ $(this).removeClass( 'draggable' ) } ); + $( '#no' + merge_id ).hover( function(){ $(this).addClass( 'draggable' ) }, function(){ $(this).removeClass( 'draggable' ) } ); + $( '#' + merge_id ).click( function( evt ){ + merge_node( node_ids[0], node_ids[1] ); + temp_relation.remove(); + $( '#' + merge_id ).parent().remove(); + //notify backend + var ncpath = getTextURL( 'merge' ); + var form_values = "source_id=" + node_ids[0] + "&target_id=" + node_ids[1] + "&single=true"; + $.post( ncpath, form_values ); + } ); + $( '#no' + merge_id ).click( function( evt ) { + temp_relation.remove(); + $( '#' + merge_id ).parent().remove(); + } ); + } ); + } + } +} + +function merge_node( source_node_id, target_node_id ) { + $.each( edges_of( get_ellipse( source_node_id ) ), function( index, edge ) { + if( edge.is_incoming == true ) { + edge.attach_endpoint( target_node_id ); + } else { + edge.attach_startpoint( target_node_id ); + } + } ); + $( jq( source_node_id ) ).remove(); +} + function Marquee() { var self = this; + this.x = 0; + this.y = 0; + this.dx = 0; + this.dy = 0; this.enlargementOffset = $('#svgenlargement').offset(); this.svg_rect = $('#svgenlargement svg').svg('get'); this.show = function( event ) { - // TODO: uncolor possible selected - // TODO: unless SHIFT? + self.x = event.clientX; + self.y = event.clientY; p = svg_root.createSVGPoint(); p.x = event.clientX - self.enlargementOffset.left; p.y = event.clientY - self.enlargementOffset.top; - // NB: I think next line is officially needed, it's only - // coincidentally that viewport and svg scale 1 to 1 initially - // and therefor we don't need a transform? - // p.matrixTransform( svg_root_element.getCTM().inverse() ); self.svg_rect.rect( p.x, p.y, 0, 0, { fill: 'black', 'fill-opacity': '0.1', stroke: 'black', 'stroke-dasharray': '4,2', strokeWidth: '0.02em', id: 'marquee' } ); }; this.expand = function( event ) { + self.dx = (event.clientX - self.x); + self.dy = (event.clientY - self.y); var rect = $('#marquee'); - if( rect.length != 0 ) { - var pX = (event.clientX - self.enlargementOffset.left) - rect.attr("x");; - var pY = (event.clientY - self.enlargementOffset.top) - rect.attr("y");; - rect.attr("width", pX); - rect.attr("height", pY); + if( rect.length != 0 ) { + var rect_w = Math.abs( self.dx ); + var rect_h = Math.abs( self.dy ); + var rect_x = self.x - self.enlargementOffset.left; + var rect_y = self.y - self.enlargementOffset.top; + if( self.dx < 0 ) { rect_x = rect_x - rect_w } + if( self.dy < 0 ) { rect_y = rect_y - rect_h } + rect.attr("x", rect_x).attr("y", rect_y).attr("width", rect_w).attr("height", rect_h); } }; - this.hide = function() { + this.select = function() { var rect = $('#marquee'); if( rect.length != 0 ) { + //unselect any possible selected first + //TODO: unless SHIFT? + if( $('ellipse[fill="#9999ff"]').size() > 0 ) { + $('ellipse[fill="#9999ff"]').each( function() { + $(this).data( 'node_obj' ).set_draggable( false ); + } ); + } + //compute dimension of marquee var left = $('#marquee').offset().left; var top = $('#marquee').offset().top; var right = left + parseInt( $('#marquee').attr( 'width' ) ); @@ -634,21 +901,74 @@ function Marquee() { p.y=bottom; var cx_max = p.matrixTransform(tf).x; var cy_max = p.matrixTransform(tf).y; + //select any node with its center inside the marquee + var readings = []; + //also merge witness sets from nodes + var witnesses = []; $('#svgenlargement ellipse').each( function( index ) { var cx = parseInt( $(this).attr('cx') ); var cy = parseInt( $(this).attr('cy') ); + + // This needs somehow to move to node or even to shapes! #repositioned + // We should ask something more aling the lines of: nodes.each { |item| node.selected? } + var org_translate = $(this).parent().data( 'repositioned' ); + if( org_translate != null ) { + cx = cx + org_translate[0]; + cy = cy + org_translate[1]; + } + if( cx > cx_min && cx < cx_max) { if( cy > cy_min && cy < cy_max) { // we actually heve no real 'selected' state for nodes, except coloring - $(this).attr( 'fill', '#ffccff' ); + $(this).attr( 'fill', '#9999ff' ); + // Take note of the selected reading(s) and applicable witness(es) + // so we can populate the multipleselect-form + readings.push( $(this).parent().attr('id') ); + var this_witnesses = $(this).data( 'node_obj' ).get_witnesses(); + witnesses = arrayUnique( witnesses.concat( this_witnesses ) ); } } }); - // select here + if( $('ellipse[fill="#9999ff"]').size() > 0 ) { + //add intersection of witnesses sets to the multi select form and open it + $('#detach_collated_form').empty(); + $.each( readings, function( index, value ) { + $('#detach_collated_form').append( $('').attr( + "type", "hidden").attr("name", "readings[]").attr( + "value", value ) ); + }); + $.each( witnesses, function( index, value ) { + $('#detach_collated_form').append( + '' + value + '
' ); + }); + $('#multiple_selected_readings').attr('value', readings.join(',') ); + $('#multipleselect-form').dialog( 'open' ); + } self.svg_rect.remove( $('#marquee') ); } }; + this.unselect = function() { + $('ellipse[fill="#9999ff"]').attr( 'fill', '#fff' ); + } + +} + +function readings_equivalent( source, target ) { + var sourcetext = readingdata[source].text; + var targettext = readingdata[target].text; + if( sourcetext === targettext ) { + return true; + } + // Lowercase and strip punctuation from both and compare again + var nonwc = XRegExp('[^\\p{L}\\s]|_'); + var stlc = XRegExp.replace( sourcetext.toLocaleLowerCase(), nonwc, "", 'all' ); + var ttlc = XRegExp.replace( targettext.toLocaleLowerCase(), nonwc, "", 'all' ); + if( stlc === ttlc ) { + return true; + } + return false; } @@ -658,7 +978,8 @@ $(document).ready(function () { relation_manager = new relation_factory(); $('#update_workspace_button').data('locked', false); - + + // Set up the mouse events on the SVG enlargement $('#enlargement').mousedown(function (event) { $(this) .data('down', true) @@ -678,7 +999,7 @@ $(document).ready(function () { event.preventDefault(); return false; }).mouseup(function (event) { - marquee.hide(); + marquee.select(); $(this).data('down', false); }).mousemove(function (event) { if( timer != null ) { clearTimeout(timer); } @@ -723,37 +1044,51 @@ $(document).ready(function () { }); + // Set up the relationship creation dialog. This also functions as the reading + // merge dialog where appropriate. + if( editable ) { - $( "#dialog-form" ).dialog({ + $( '#dialog-form' ).dialog( { autoOpen: false, height: 270, - width: 290, + width: 330, modal: true, buttons: { - "Ok": function( evt ) { - $(evt.target).button("disable"); - $('#status').empty(); - form_values = $('#collapse_node_form').serialize(); + 'Merge readings': function( evt ) { + $( evt.target ).button( 'disable' ); + $( '#status' ).empty(); + form_values = $( '#collapse_node_form' ).serialize(); + ncpath = getTextURL( 'merge' ); + var jqjson = $.post( ncpath, form_values, function( data ) { + merge_nodes( $( '#source_node_id' ).val(), $( '#target_node_id' ).val(), data ); + $(evt.target).button( 'enable' ); + $( '#dialog-form' ).dialog( 'close' ); + } ); + }, + OK: function( evt ) { + $( evt.target ).button( 'disable' ); + $( '#status' ).empty(); + form_values = $( '#collapse_node_form' ).serialize(); ncpath = getTextURL( 'relationships' ); - var jqjson = $.post( ncpath, form_values, function(data) { - $.each( data, function(item, source_target) { + var jqjson = $.post( ncpath, form_values, function( data ) { + $.each( data, function( item, source_target ) { var source_found = get_ellipse( source_target[0] ); var target_found = get_ellipse( source_target[1] ); - var relation_found = $.inArray( source_target[2], $('#keymap').data('relations') ); + var relation_found = $.inArray( source_target[2], $( '#keymap' ).data( 'relations' ) ); if( source_found.size() && target_found.size() && relation_found > -1 ) { var relation = relation_manager.create( source_target[0], source_target[1], relation_found ); - relation.data( 'type', source_target[2] ); - relation.data( 'scope', $('#scope :selected').text() ); - relation.data( 'note', $('#note').val() ); relation_manager.toggle_active( relation.attr('id') ); + $.each( $('#collapse_node_form').serializeArray(), function( i, k ) { + relation.data( k.name, k.value ); + }); } - $(evt.target).button("enable"); + $(evt.target).button( 'enable' ); }); - $( "#dialog-form" ).dialog( "close" ); + $( '#dialog-form' ).dialog( 'close' ); }, 'json' ); }, Cancel: function() { - $( this ).dialog( "close" ); + $( this ).dialog( 'close' ); } }, create: function(event, ui) { @@ -775,7 +1110,15 @@ $(document).ready(function () { }); }, open: function() { - relation_manager.create_temporary( $('#source_node_id').val(), $('#target_node_id').val() ); + relation_manager.create_temporary( + $('#source_node_id').val(), $('#target_node_id').val() ); + var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ) + if( readings_equivalent( $('#source_node_id').val(), + $('#target_node_id').val() ) ) { + buttonset.find( "button:contains('Merge readings')" ).show(); + } else { + buttonset.find( "button:contains('Merge readings')" ).hide(); + } $(".ui-widget-overlay").css("background", "none"); $("#dialog_overlay").show(); $("#dialog_overlay").height( $("#enlargement_container").height() ); @@ -808,20 +1151,25 @@ $(document).ready(function () { } ); } - var deletion_buttonset = { - cancel: function() { $( this ).dialog( "close" ); }, - global: function () { delete_relation( true ); }, - delete: function() { delete_relation( false ); } - }; + // Set up the relationship info display and deletion dialog. $( "#delete-form" ).dialog({ autoOpen: false, height: 135, - width: 250, + width: 300, modal: false, + buttons: { + OK: function() { $( this ).dialog( "close" ); }, + "Delete all": function () { delete_relation( true ); }, + Delete: function() { delete_relation( false ); } + }, create: function(event, ui) { // TODO What is this logic doing? + // This scales the buttons in the dialog and makes it look proper + // Not sure how essential it is, does anything break if it's not here? var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' ); - buttonset.find( "button:contains('Cancel')" ).css( 'float', 'right' ); + buttonset.find( "button:contains('OK')" ).css( 'float', 'right' ); + // A: This makes sure that the pop up delete relation dialogue for a hovered over + // relation auto closes if the user doesn't engage (mouseover) with it. var dialog_aria = $("div[aria-labelledby='ui-dialog-title-delete-form']"); dialog_aria.mouseenter( function() { if( mouseWait != null ) { clearTimeout(mouseWait) }; @@ -831,27 +1179,82 @@ $(document).ready(function () { }) }, open: function() { + // Show the appropriate buttons... + var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ) + // If the user can't edit, show only the OK button if( !editable ) { - $( this ).dialog( "option", "buttons", - [{ text: "OK", click: deletion_buttonset['cancel'] }] ); + buttonset.find( "button:contains('Delete')" ).hide(); + // If the relationship scope is local, show only OK and Delete } else if( $('#delete_relation_scope').text() === 'local' ) { $( this ).dialog( "option", "width", 160 ); - $( this ).dialog( "option", "buttons", - [{ text: "Delete", click: deletion_buttonset['delete'] }, - { text: "Cancel", click: deletion_buttonset['cancel'] }] ); + buttonset.find( "button:contains('Delete')" ).show(); + buttonset.find( "button:contains('Delete all')" ).hide(); + // Otherwise, show all three } else { $( this ).dialog( "option", "width", 200 ); - $( this ).dialog( "option", "buttons", - [{ text: "Delete", click: deletion_buttonset['delete'] }, - { text: "Delete all", click: deletion_buttonset['global'] }, - { text: "Cancel", click: deletion_buttonset['cancel'] }] ); + buttonset.find( "button:contains('Delete')" ).show(); } - mouseWait = setTimeout( function() { $("#delete-form").dialog( "close" ) }, 2000 ); }, close: function() {} }); + $( "#multipleselect-form" ).dialog({ + autoOpen: false, + height: 150, + width: 250, + modal: true, + buttons: { + Cancel: function() { $( this ).dialog( "close" ); }, + Detach: function ( evt ) { + var self = $(this); + $( evt.target ).button( "disable" ); + var form_values = $('#detach_collated_form').serialize(); + ncpath = getTextURL( 'duplicate' ); + var jqjson = $.post( ncpath, form_values, function(data) { + detach_node( data ); + $(evt.target).button("enable"); + self.dialog( "close" ); + } ); + } + }, + create: function(event, ui) { + var buttonset = $(this).parent().find( '.ui-dialog-buttonset' ).css( 'width', '100%' ); + buttonset.find( "button:contains('Cancel')" ).css( 'float', 'right' ); + }, + open: function() { + $( this ).dialog( "option", "width", 200 ); + $(".ui-widget-overlay").css("background", "none"); + $('#multipleselect-form-status').empty(); + $("#dialog_overlay").show(); + $("#dialog_overlay").height( $("#enlargement_container").height() ); + $("#dialog_overlay").width( $("#enlargement_container").innerWidth() ); + $("#dialog_overlay").offset( $("#enlargement_container").offset() ); + }, + close: function() { + marquee.unselect(); + $("#dialog_overlay").hide(); + } + }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) { + if( ajaxSettings.url == getTextURL('duplicate') + && ajaxSettings.type == 'POST' && jqXHR.status == 403 ) { + var error; + if( jqXHR.responseText.indexOf('do not have permission to modify') > -1 ) { + error = 'You are not authorized to modify this tradition. (Try logging in again?)'; + } else { + try { + var errobj = jQuery.parseJSON( jqXHR.responseText ); + error = errobj.error + '
The relationship cannot be made.

'; + } catch(e) { + error = jqXHR.responseText; + } + } + $('#multipleselect-form-status').append( '

Error: ' + error ); + } + $(event.target).parent().find('.ui-button').button("enable"); + }); + + // Helpers for relationship deletion function delete_relation( scopewide ) { @@ -986,7 +1389,8 @@ $(document).ready(function () { $('#svgenlargement ellipse' ).each( function( index ) { if( $(this).data( 'node_obj' ) != null ) { $(this).data( 'node_obj' ).ungreyout_edges(); - $(this).data( 'node_obj' ).set_draggable( false ); + $(this).data( 'node_obj' ).set_selectable( false ); + color_inactive( $(this) ); var node_id = $(this).data( 'node_obj' ).get_id(); toggle_relation_active( node_id ); $(this).data( 'node_obj', null ); @@ -1010,7 +1414,7 @@ $(document).ready(function () { if( $(this).data( 'node_obj' ) == null ) { $(this).data( 'node_obj', new node_obj( $(this) ) ); } else { - $(this).data( 'node_obj' ).set_draggable( true ); + $(this).data( 'node_obj' ).set_selectable( true ); } $(this).data( 'node_obj' ).greyout_edges(); var node_id = $(this).data( 'node_obj' ).get_id();