Fixed recursively loading re-rooted stemma
[scpubgit/stemmaweb.git] / root / js / componentload.js
CommitLineData
3f9d7ae5 1// Global state variables
2var selectedTextID;
3var selectedTextInfo;
6aabefa3 4var selectedTextEditable;
3f9d7ae5 5var selectedStemmaID = -1;
6var stemmata = [];
7
8// Load the names of the appropriate traditions into the directory div.
9function refreshDirectory () {
10 var lmesg = $('#loading_message').clone();
11 $('#directory').empty().append( lmesg.contents() );
12 $('#directory').load( _get_url(["directory"]),
13 function(response, status, xhr) {
14 if (status == "error") {
15 var msg = "An error occurred: ";
16 $("#directory").html(msg + xhr.status + " " + xhr.statusText);
17 } else {
18 if( textOnLoad != "" ) {
19 // Call the click callback for the relevant text, if it is
20 // in the page.
21 $('#'+textOnLoad).click();
22 textOnLoad = "";
23 }
24 }
25 }
26 );
27}
28
29// Load a tradition with its information and stemmata into the tradition
30// view pane. Calls load_textinfo.
98a45925 31function loadTradition( textid, textname, editable ) {
32 selectedTextID = textid;
6aabefa3 33 selectedTextEditable = editable;
98a45925 34 // First insert the placeholder image and register an error handler
04469f3e 35 $('#textinfo_load_status').empty();
5f0eda3f 36 $('#stemma_graph').empty();
98a45925 37 $('#textinfo_waitbox').show();
75354c3a 38 $('#textinfo_container').hide().ajaxError(
39 function(event, jqXHR, ajaxSettings, thrownError) {
40 if( ajaxSettings.url.indexOf( 'textinfo' ) > -1 && ajaxSettings.type == 'GET' ) {
41 $('#textinfo_waitbox').hide();
42 $('#textinfo_container').show();
43 display_error( jqXHR, $("#textinfo_load_status") );
98a45925 44 }
75354c3a 45 });
46
47 // Hide the functionality that is irrelevant
48 if( editable ) {
ce1c5863 49 $('#open_stemma_add').show();
ce1c5863 50 $('#open_textinfo_edit').show();
cbd23059 51 $('#relatebutton_label').text('View collation and edit relationships');
75354c3a 52 } else {
ce1c5863 53 $('#open_stemma_add').hide();
db234220 54 $('#open_stemweb_ui').hide();
c2b80bba 55 $('#query_stemweb_ui').hide();
ce1c5863 56 $('#open_textinfo_edit').hide();
cbd23059 57 $('#relatebutton_label').text('View collation and relationships');
75354c3a 58 }
59
62723740 60 // Then get and load the actual content.
98a45925 61 // TODO: scale #stemma_graph both horizontally and vertically
f6a8db89 62 // TODO: load svgs from SVG.Jquery (to make scaling react in Safari)
3f9d7ae5 63 $.getJSON( _get_url([ "textinfo", textid ]), function (textdata) {
98a45925 64 // Add the scalar data
75354c3a 65 selectedTextInfo = textdata;
66 load_textinfo();
63378fe0 67 // Add the stemma(ta)
98a45925 68 stemmata = textdata.stemmata;
69 if( stemmata.length ) {
70 selectedStemmaID = 0;
65a0c9c6 71 } else {
57e3a008 72 selectedStemmaID = -1;
65a0c9c6 73 }
6aabefa3 74 load_stemma( selectedStemmaID );
98a45925 75 // Set up the relationship mapper button
3f9d7ae5 76 $('#run_relater').attr( 'action', _get_url([ "relation", textid ]) );
38627d20 77 // Set up the download button
78 $('#dl_tradition').attr( 'href', _get_url([ "download", textid ]) );
79 $('#dl_tradition').attr( 'download', selectedTextInfo.name + '.xml' );
98a45925 80 });
81}
82
3f9d7ae5 83// Load the metadata about a tradition into the appropriate div.
75354c3a 84function load_textinfo() {
85 $('#textinfo_waitbox').hide();
86 $('#textinfo_load_status').empty();
87 $('#textinfo_container').show();
24e64429 88 // The tradition name should appear here and should be identical in the
89 // corresponding directory span. In case the name was just changed...
75354c3a 90 $('.texttitle').empty().append( selectedTextInfo.name );
24e64429 91 $('#' + selectedTextID).empty().append( selectedTextInfo.name );
75354c3a 92 // Witnesses
93 $('#witness_num').empty().append( selectedTextInfo.witnesses.size );
94 $('#witness_list').empty().append( selectedTextInfo.witnesses.join( ', ' ) );
95 // Who the owner is
96 $('#owner_id').empty().append('no one');
97 if( selectedTextInfo.owner ) {
897a22fc 98 var owneremail = selectedTextInfo.owner;
99 var chop = owneremail.indexOf( '@' );
100 if( chop > -1 ) {
101 owneremail = owneremail.substr( 0, chop + 1 ) + '...';
102 }
103 $('#owner_id').empty().append( owneremail );
75354c3a 104 }
105 // Whether or not it is public
106 $('#not_public').empty();
107 if( selectedTextInfo['public'] == false ) {
108 $('#not_public').append('NOT ');
109 }
110 // What language setting it has, if any
111 $('#marked_language').empty().append('no language set');
112 if( selectedTextInfo.language && selectedTextInfo.language != 'Default' ) {
113 $('#marked_language').empty().append( selectedTextInfo.language );
114 }
115}
116
3f9d7ae5 117// Enable / disable the appropriate buttons for paging through the stemma.
6aabefa3 118function show_stemmapager () {
bf81fb57 119 $('.pager_left_button').unbind('click').addClass( 'greyed_out' );
120 $('.pager_right_button').unbind('click').addClass( 'greyed_out' );
65a0c9c6 121 if( selectedStemmaID > 0 ) {
122 $('.pager_left_button').click( function () {
6aabefa3 123 load_stemma( selectedStemmaID - 1, selectedTextEditable );
bf81fb57 124 }).removeClass( 'greyed_out' );
65a0c9c6 125 }
126 if( selectedStemmaID + 1 < stemmata.length ) {
127 $('.pager_right_button').click( function () {
6aabefa3 128 load_stemma( selectedStemmaID + 1, selectedTextEditable );
bf81fb57 129 }).removeClass( 'greyed_out' );
65a0c9c6 130 }
131}
132
3f9d7ae5 133// Load a given stemma SVG into the stemmagraph box.
6aabefa3 134function load_stemma( idx ) {
65a0c9c6 135 // Load the stemma at idx
136 selectedStemmaID = idx;
6aabefa3 137 show_stemmapager( selectedTextEditable );
63378fe0 138 $('#open_stemma_edit').hide();
139 $('#run_stexaminer').hide();
140 $('#stemma_identifier').empty();
c2b80bba 141 // Add the relevant Stemweb functionality
6aabefa3 142 if( selectedTextEditable ) {
509e94bc 143 switch_stemweb_ui();
c2b80bba 144 }
98a45925 145 if( idx > -1 ) {
63378fe0 146 // Load the stemma and its properties
ec2f89ff 147 var stemmadata = stemmata[idx];
6aabefa3 148 if( selectedTextEditable ) {
63378fe0 149 $('#open_stemma_edit').show();
150 }
151 if( stemmadata.directed ) {
152 // Stexaminer submit action
153 var stexpath = _get_url([ "stexaminer", selectedTextID, idx ]);
154 $('#run_stexaminer').attr( 'action', stexpath );
155 $('#run_stexaminer').show();
156 }
157 loadSVG( stemmadata.svg );
158 $('#stemma_identifier').text( stemmadata.name );
40803b80 159 setTimeout( 'start_element_height = $("#stemma_graph .node")[0].getBBox().height;', 500 );
98a45925 160 }
5ba6c2b4 161}
75354c3a 162
509e94bc 163function switch_stemweb_ui() {
164 if( selectedTextInfo.stemweb_jobid == 0 ) {
165 // We want to run Stemweb.
166 $('#open_stemweb_ui').show();
509e94bc 167 $('#query_stemweb_ui').hide();
e6d34d3e 168 if( ! $('#stemweb-ui-dialog').dialog('isOpen') ) {
169 $('#call_stemweb').show()
170 $('#stemweb_run_button').show();
171 }
509e94bc 172 } else {
173 $('#query_stemweb_ui').show();
174 $('#open_stemweb_ui').hide();
175 $('#call_stemweb').hide();
176 $('#stemweb_run_button').hide();
177 }
178}
179
c2b80bba 180function query_stemweb_progress() {
181 var requrl = _get_url([ "stemweb", "query", selectedTextInfo.stemweb_jobid ]);
509e94bc 182 $('#stemweb-ui-dialog').dialog('open');
183 $('#stemweb_run_status').empty().append(
184 _make_message( 'notification', 'Querying Stemweb for calculation progress...') );
c2b80bba 185 $.getJSON( requrl, function (data) {
3cb9d9c0 186 process_stemweb_result( data );
187 });
188}
189
190function process_stemweb_result(data) {
191 // Look for a status message, either success, running, or notfound.
192 if( data.status === 'success' ) {
193 // Add the new stemmata to the textinfo and tell the user.
194 selectedTextInfo.stemweb_jobid = 0;
195 if( data.stemmata.length > 0 ) {
196 stemmata = stemmata.concat( data.stemmata );
197 if( selectedStemmaID == -1 ) {
198 // We have a stemma for the first time; load the first one.
199 load_stemma( 0, true );
c2b80bba 200 } else {
3cb9d9c0 201 // Move to the index of the first added stemma.
202 var newIdx = stemmata.length - data.stemmata.length;
203 load_stemma( newIdx, true );
509e94bc 204 }
e4fd6366 205 // Hide the call dialog no matter how we got here
206 $('#call_stemweb').hide()
207 $('#stemweb_run_button').hide();
509e94bc 208 $('#stemweb_run_status').empty().append(
209 _make_message( 'notification', 'You have one or more new stemmata!' ) );
3cb9d9c0 210 } else {
509e94bc 211 $('#stemweb_run_status').empty().append(
212 _make_message( 'warning', 'Stemweb run finished with no stemmata...huh?!' ) );
c2b80bba 213 }
3cb9d9c0 214 } else if( data.status === 'running' ) {
215 // Just tell the user.
509e94bc 216 $('#stemweb_run_status').empty().append(
e4fd6366 217 _make_message( 'warning', 'Your Stemweb query is still running!' ) );
3cb9d9c0 218 } else if( data.status === 'notfound' ) {
219 // Ask the user to refresh, for now.
509e94bc 220 $('#stemweb_run_status').empty().append(
221 _make_message( 'warning', 'Your Stemweb query probably finished and reported back. Please reload to check.' ) );
222 } else if( data.status === 'failed' ) {
223 selectedTextInfo.stemweb_jobid = 0;
224 failureMsg = 'Your stemweb query failed';
225 if( data.message ) {
226 failureMsg = failureMsg + ' with the following message: ' + data.message
e6d34d3e 227 } else {
228 failureMsg = failureMsg + ' without telling us why.'
509e94bc 229 }
230 $('#stemweb_run_status').empty().append(
e6d34d3e 231 _make_message( 'error', failureMsg ) );
3cb9d9c0 232 }
509e94bc 233}
234
235function _make_message( type, msg ) {
236 theMessage = $('<span>').attr( 'class', type );
237 theMessage.append( msg );
238 return theMessage;
c2b80bba 239}
240
bd3ccd15 241// Load the SVG we are given
242function loadSVG(svgData) {
243 var svgElement = $('#stemma_graph');
244
245 $(svgElement).svg('destroy');
246
247 $(svgElement).svg({
248 loadURL: svgData,
249 onLoad : function () {
250 var theSVG = svgElement.find('svg');
251 var svgoffset = theSVG.offset();
bd3ccd15 252 var browseroffset = 1;
23f8bfc2 253 // Firefox needs a different offset, stupidly enough
bd3ccd15 254 if( navigator.userAgent.indexOf('Firefox') > -1 ) {
23f8bfc2 255 browseroffset = 3; // works for tall images
256 // ...but if the SVG is wider than it is tall, Firefox treats
257 // the top as being the top of the graph, loaded into the middle
258 // of the canvas, but then the margin at the top of the canvas
259 // extends upward. So we have to find the actual top of the canvas
260 // and correct for *that* instead.
261 var vbdim = svgElement.svg().svg('get').root().viewBox.baseVal;
262 if( vbdim.height < vbdim.width ) {
263 var vbscale = svgElement.width() / vbdim.width;
264 var vbrealheight = vbdim.height * vbscale;
265 browseroffset = 3 + ( svgElement.height() - vbrealheight ) / 2;
266 }
bd3ccd15 267 }
268 var topoffset = theSVG.position().top - svgElement.position().top - browseroffset;
bd3ccd15 269 theSVG.offset({ top: svgoffset.top - topoffset, left: svgoffset.left });
b63f3a77 270 set_stemma_interactive( theSVG );
bd3ccd15 271 }
272 });
273}
274
b63f3a77 275function set_stemma_interactive( svg_element ) {
6aabefa3 276 if( selectedTextEditable ) {
54c050ab 277 // unbind is needed as this set_stemma_interactive is called each time
278 // the stemma is re-rooted, and each time jquery adds an
279 // onclick handler to the root_tree_dialog_button_ok
280 // that all re-root the stemma, that all add an onclick, etc..
281 $( "#root_tree_dialog_button_ok" ).unbind();
6aabefa3 282 $( "#root_tree_dialog_button_ok" ).click( function() {
283 var requrl = _get_url([ "stemmaroot", selectedTextID, selectedStemmaID ]);
9f4b205a 284 var targetnode = $('#root_tree_dialog').data( 'selectedNode' );
6aabefa3 285 $.post( requrl, { root: targetnode }, function (data) {
286 // Reload the new stemma
287 stemmata[selectedStemmaID] = data;
288 load_stemma( selectedStemmaID );
289 // Put away the dialog
9f4b205a 290 $('#root_tree_dialog').data( 'selectedNode', null ).hide();
6aabefa3 291 } );
292 } ).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
293 if( ajaxSettings.url.indexOf( 'stemmaroot' ) > -1
294 && ajaxSettings.type == 'POST' ) {
295 display_error( jqXHR, $("#stemma_load_status") );
296 }
297 } );
298 // TODO Clear error at some appropriate point
299 $.each( $( 'ellipse', svg_element ), function(index) {
300 var ellipse = $(this);
301 var g = ellipse.parent( 'g' );
302 g.click( function(evt) {
303 if( typeof root_tree_dialog_timeout !== 'undefined' ) { clearTimeout( root_tree_dialog_timeout ) };
304 g.unbind( 'mouseleave' );
305 var dialog = $( '#root_tree_dialog' );
9f4b205a 306 // Note which node triggered the dialog
307 dialog.data( 'selectedNode', g.attr('id') );
308 // Position the dialog
6aabefa3 309 dialog.hide();
310 dialog.css( 'top', evt.pageY + 3 );
311 dialog.css( 'left', evt.pageX + 3 );
312 dialog.show();
313 root_tree_dialog_timeout = setTimeout( function() {
9f4b205a 314 $( '#root_tree_dialog' ).data( 'selectedNode', null ).hide();
6aabefa3 315 ellipse.removeClass( 'stemma_node_highlight' );
316 g.mouseleave( function() { ellipse.removeClass( 'stemma_node_highlight' ) } );
317 }, 3000 );
318 } );
319 g.mouseenter( function() {
320 $( 'ellipse.stemma_node_highlight' ).removeClass( 'stemma_node_highlight' );
321 ellipse.addClass( 'stemma_node_highlight' )
322 } );
323 g.mouseleave( function() { ellipse.removeClass( 'stemma_node_highlight' ) } );
324 } );
325 }
b63f3a77 326}
39b8b37b 327
3f9d7ae5 328// General-purpose error-handling function.
329// TODO make sure this gets used throughout, where appropriate.
75354c3a 330function display_error( jqXHR, el ) {
ce1c5863 331 var errmsg;
332 if( jqXHR.responseText == "" ) {
333 errmsg = "perhaps the server went down?"
75354c3a 334 } else {
ce1c5863 335 var errobj;
336 try {
337 errobj = jQuery.parseJSON( jqXHR.responseText );
338 errmsg = errobj.error;
339 } catch ( parse_err ) {
340 errmsg = "something went wrong on the server."
341 }
75354c3a 342 }
ce1c5863 343 var msghtml = $('<span>').attr('class', 'error').text( "An error occurred: " + errmsg );
75354c3a 344 $(el).empty().append( msghtml ).show();
3f9d7ae5 345}
346
e0b90236 347// Event to enable the upload button when a file has been selected
348function file_selected( e ) {
349 if( e.files.length == 1 ) {
350 $('#upload_button').button('enable');
ab0d1218 351 $('#new_file_name_container').html( '<span id="new_file_name">' + e.files[0].name + '</span>' );
e0b90236 352 } else {
353 $('#upload_button').button('disable');
ab0d1218 354 $('#new_file_name_container').html( '(Use \'pick file\' to select a tradition file to upload.)' );
e0b90236 355 }
356}
357
2ece58b3 358// Implement our own AJAX method that uses the features of XMLHttpRequest2
359// but try to let it have a similar interface to jquery.post
360// The data var needs to be a FormData() object.
361// The callback will be given a single argument, which is the response data
362// of the given type.
363
364function post_xhr2( url, data, cb, type ) {
365 if( !type ) {
366 type = 'json';
367 }
368 var xhr = new XMLHttpRequest();
369 // Set the expected response type
370 if( type === 'data' ) {
371 xhr.responseType = 'blob';
372 } else if( type === 'xml' ) {
373 xhr.responseType = 'document';
374 }
375 // Post the form
376 // Gin up an AJAX settings object
377 $.ajaxSetup({ url: url, type: 'POST' });
378 xhr.open( 'POST', url, true );
379 // Handle the results
380 xhr.onload = function( e ) {
381 // Get the response and parse it
382 // Call the callback with the response, whatever it was
383 var xhrs = e.target;
384 if( xhrs.status > 199 && xhrs.status < 300 ) { // Success
385 var resp;
386 if( type === 'json' ) {
387 resp = $.parseJSON( xhrs.responseText );
388 } else if ( type === 'xml' ) {
389 resp = xhrs.responseXML;
390 } else if ( type === 'text' ) {
391 resp = xhrs.responseText;
392 } else {
393 resp = xhrs.response;
394 }
395 cb( resp );
396 } else {
397 // Trigger the ajaxError...
398 _trigger_ajaxerror( e );
399 }
400 };
401 xhr.onerror = _trigger_ajaxerror;
402 xhr.onabort = _trigger_ajaxerror;
403 xhr.send( data );
404}
405
406function _trigger_ajaxerror( e ) {
407 var xhr = e.target;
408 var thrown = xhr.statusText || 'Request error';
409 jQuery.event.trigger( 'ajaxError', [ xhr, $.ajaxSettings, thrown ]);
410}
411
e0b90236 412function upload_new () {
413 // Serialize the upload form, get the file and attach it to the request,
414 // POST the lot and handle the response.
415 var newfile = $('#new_file').get(0).files[0];
416 var reader = new FileReader();
417 reader.onload = function( evt ) {
2ece58b3 418 var data = new FormData();
419 $.each( $('#new_tradition').serializeArray(), function( i, o ) {
420 data.append( o.name, o.value );
421 });
422 data.append( 'file', newfile );
e0b90236 423 var upload_url = _get_url([ 'newtradition' ]);
2ece58b3 424 post_xhr2( upload_url, data, function( ret ) {
e0b90236 425 if( ret.id ) {
426 $('#upload-collation-dialog').dialog('close');
427 refreshDirectory();
428 loadTradition( ret.id, ret.name, 1 );
429 } else if( ret.error ) {
430 $('#upload_status').empty().append(
431 $('<span>').attr('class', 'error').append( ret.error ) );
432 }
2ece58b3 433 }, 'json' );
e0b90236 434 };
435 reader.onerror = function( evt ) {
436 var err_resp = 'File read error';
437 if( e.name == 'NotFoundError' ) {
438 err_resp = 'File not found';
439 } else if ( e.name == 'NotReadableError' ) {
440 err_resp == 'File unreadable - is it yours?';
441 } else if ( e.name == 'EncodingError' ) {
442 err_resp == 'File cannot be encoded - is it too long?';
443 } else if ( e.name == 'SecurityError' ) {
444 err_resp == 'File read security error';
445 }
446 // Fake a jqXHR object that we can pass to our generic error handler.
447 var jqxhr = { responseText: '{error:"' + err_resp + '"}' };
448 display_error( jqxhr, $('#upload_status') );
449 $('#upload_button').button('disable');
450 }
451
452 reader.readAsBinaryString( newfile );
3f9d7ae5 453}
454
455// Utility function to neatly construct an application URL
456function _get_url( els ) {
457 return basepath + els.join('/');
458}
459
6aabefa3 460// TODO Attach unified ajaxError handler to document
3f9d7ae5 461$(document).ready( function() {
50778a5d 462 // See if we have the browser functionality we need
463 // TODO Also think of a test for SVG readiness
7c25980f 464 if( !!window.FileReader && !!window.File ) {
50778a5d 465 $('#compatibility_check').empty();
466 }
b63f3a77 467
468 // hide dialog not yet in use
469 $('#root_tree_dialog').hide();
50778a5d 470
3f9d7ae5 471 // call out to load the directory div
472 $('#textinfo_container').hide();
473 $('#textinfo_waitbox').hide();
474 refreshDirectory();
475
476 // Set up the textinfo edit dialog
477 $('#textinfo-edit-dialog').dialog({
478 autoOpen: false,
479 height: 200,
480 width: 300,
481 modal: true,
482 buttons: {
483 Save: function (evt) {
484 $("#edit_textinfo_status").empty();
932277e1 485 var mybuttons = $(evt.target).closest('button').parent().find('button');
486 mybuttons.button( 'disable' );
3f9d7ae5 487 var requrl = _get_url([ "textinfo", selectedTextID ]);
488 var reqparam = $('#edit_textinfo').serialize();
489 $.post( requrl, reqparam, function (data) {
490 // Reload the selected text fields
491 selectedTextInfo = data;
492 load_textinfo();
493 // Reenable the button and close the form
932277e1 494 mybuttons.button("enable");
3f9d7ae5 495 $('#textinfo-edit-dialog').dialog('close');
496 }, 'json' );
497 },
498 Cancel: function() {
499 $('#textinfo-edit-dialog').dialog('close');
500 }
501 },
502 open: function() {
503 $("#edit_textinfo_status").empty();
504 // Populate the form fields with the current values
505 // edit_(name, language, public, owner)
506 $.each([ 'name', 'language', 'owner' ], function( idx, k ) {
507 var fname = '#edit_' + k;
508 // Special case: language Default is basically language null
509 if( k == 'language' && selectedTextInfo[k] == 'Default' ) {
510 $(fname).val( "" );
511 } else {
512 $(fname).val( selectedTextInfo[k] );
513 }
514 });
515 if( selectedTextInfo['public'] == true ) {
516 $('#edit_public').attr('checked','true');
517 } else {
518 $('#edit_public').removeAttr('checked');
519 }
520 },
521 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
522 $(event.target).parent().find('.ui-button').button("enable");
523 if( ajaxSettings.url.indexOf( 'textinfo' ) > -1
524 && ajaxSettings.type == 'POST' ) {
525 display_error( jqXHR, $("#edit_textinfo_status") );
526 }
527 });
528
529
530 // Set up the stemma editor dialog
531 $('#stemma-edit-dialog').dialog({
532 autoOpen: false,
533 height: 700,
534 width: 600,
535 modal: true,
536 buttons: {
537 Save: function (evt) {
538 $("#edit_stemma_status").empty();
932277e1 539 var mybuttons = $(evt.target).closest('button').parent().find('button');
540 mybuttons.button( 'disable' );
3f9d7ae5 541 var stemmaseq = $('#stemmaseq').val();
542 var requrl = _get_url([ "stemma", selectedTextID, stemmaseq ]);
543 var reqparam = { 'dot': $('#dot_field').val() };
544 // TODO We need to stash the literal SVG string in stemmata
545 // somehow. Implement accept header on server side to decide
546 // whether to send application/json or application/xml?
547 $.post( requrl, reqparam, function (data) {
548 // We received a stemma SVG string in return.
549 // Update the current stemma sequence number
550 selectedStemmaID = data.stemmaid;
be536c89 551 delete data.stemmaid;
552 // Stash the answer in the appropriate spot in our stemma array
553 stemmata[selectedStemmaID] = data;
3f9d7ae5 554 // Display the new stemma
63378fe0 555 load_stemma( selectedStemmaID, true );
3f9d7ae5 556 // Reenable the button and close the form
932277e1 557 mybuttons.button("enable");
3f9d7ae5 558 $('#stemma-edit-dialog').dialog('close');
559 }, 'json' );
560 },
561 Cancel: function() {
562 $('#stemma-edit-dialog').dialog('close');
563 }
564 },
565 open: function(evt) {
566 $("#edit_stemma_status").empty();
567 var stemmaseq = $('#stemmaseq').val();
568 if( stemmaseq == 'n' ) {
569 // If we are creating a new stemma, populate the textarea with a
570 // bare digraph.
571 $(evt.target).dialog('option', 'title', 'Add a new stemma')
db234220 572 $('#dot_field').val( "digraph \"NAME STEMMA HERE\" {\n\n}" );
3f9d7ae5 573 } else {
574 // If we are editing a stemma, grab its stemmadot and populate the
575 // textarea with that.
576 $(evt.target).dialog('option', 'title', 'Edit selected stemma')
577 $('#dot_field').val( 'Loading, please wait...' );
578 var doturl = _get_url([ "stemmadot", selectedTextID, stemmaseq ]);
579 $.getJSON( doturl, function (data) {
580 // Re-insert the line breaks
581 var dotstring = data.dot.replace(/\|n/gm, "\n");
582 $('#dot_field').val( dotstring );
583 });
584 }
585 },
586 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
587 $(event.target).parent().find('.ui-button').button("enable");
588 if( ajaxSettings.url.indexOf( 'stemma' ) > -1
589 && ajaxSettings.type == 'POST' ) {
590 display_error( jqXHR, $("#edit_stemma_status") );
591 }
592 });
db234220 593
594 $('#stemweb-ui-dialog').dialog({
595 autoOpen: false,
e239d45f 596 height: 'auto',
509e94bc 597 width: 520,
db234220 598 modal: true,
599 buttons: {
509e94bc 600 Run: {
601 id: 'stemweb_run_button',
602 text: 'Run',
603 click: function (evt) {
db234220 604 $("#stemweb_run_status").empty();
932277e1 605 var mybuttons = $(evt.target).closest('button').parent().find('button');
606 mybuttons.button( 'disable' );
70744367 607 var requrl = _get_url([ "stemweb", "request" ]);
608 var reqparam = $('#call_stemweb').serialize();
db234220 609 // TODO We need to stash the literal SVG string in stemmata
610 // somehow. Implement accept header on server side to decide
611 // whether to send application/json or application/xml?
70744367 612 $.getJSON( requrl, reqparam, function (data) {
932277e1 613 mybuttons.button("enable");
3cb9d9c0 614 if( 'jobid' in data ) {
615 // There is a pending job.
616 selectedTextInfo.stemweb_jobid = data.jobid;
e4fd6366 617 $('#stemweb_run_status').empty().append(
618 _make_message( 'notification', "Your request has been submitted to Stemweb.\nThe resulting tree will appear in due course." ) );
3cb9d9c0 619 // Reload the current stemma to rejigger the buttons
e4fd6366 620 switch_stemweb_ui();
3cb9d9c0 621 } else {
622 // We appear to have an answer; process it.
623 process_stemweb_result( data );
624 }
db234220 625 }, 'json' );
509e94bc 626 },
db234220 627 },
509e94bc 628 Close: {
629 id: 'stemweb_close_button',
630 text: 'Close',
631 click: function() {
e6d34d3e 632 $('#stemweb-ui-dialog').dialog('close');
633 switch_stemweb_ui();
509e94bc 634 },
635 },
db234220 636 },
637 create: function(evt) {
638 // Call out to Stemweb to get the algorithm options, with which we
639 // populate the form.
640 var algorithmTypes = {};
641 var algorithmArgs = {};
66458003 642 var requrl = _get_url([ "stemweb", "available" ]);
643 $.getJSON( requrl, function( data ) {
644 $.each( data, function( i, o ) {
645 if( o.model === 'algorithms.algorithm' ) {
646 // it's an algorithm.
647 algorithmTypes[ o.pk ] = o.fields;
648 } else if( o.model == 'algorithms.algorithmarg' && o.fields.external ) {
649 // it's an option for an algorithm that we should display.
650 algorithmArgs[ o.pk ] = o.fields;
70744367 651 }
652 });
66458003 653 // TODO if it is an empty object, disable Stemweb entirely.
654 if( !jQuery.isEmptyObject( algorithmTypes ) ) {
655 $.each( algorithmTypes, function( pk, fields ) {
656 var algopt = $('<option>').attr( 'value', pk ).append( fields.name );
657 $('#stemweb_algorithm').append( algopt );
658 });
659 // Set up the relevant options for whichever algorithm is chosen.
660 // "key" -> form name, option ID "stemweb_$key_opt"
661 // "name" -> form label
e239d45f 662 $('#stemweb_algorithm_help').click( function() {
663 $('#stemweb_algorithm_desc_text').toggle( 'blind' );
664 });
66458003 665 $('#stemweb_algorithm').change( function() {
666 var pk = $(this).val();
d9d6b62b 667 // Display a link to the popup description, and fill in
668 // the description itself, if we have one.
669 if( 'desc' in algorithmTypes[pk] ) {
670 $('#stemweb_algorithm_desc_text').empty().append( algorithmTypes[pk].desc );
671 $('#stemweb_algorithm_desc').show();
672 } else {
673 $('#stemweb_algorithm_desc').hide();
674 }
66458003 675 $('#stemweb_runtime_options').empty();
676 $.each( algorithmTypes[pk].args, function( i, apk ) {
677 var argInfo = algorithmArgs[apk];
678 if( argInfo ) {
679 // Make the element ID
680 var optId = 'stemweb_' + argInfo.key + '_opt';
681 // Make the label
682 var optLabel = $('<label>').attr( 'for', optId )
683 .append( argInfo.name + ": " );
684 var optCtrl;
685 var argType = argInfo.value;
686 if( argType === 'positive_integer' ) {
687 // Make it an input field of smallish size.
688 optCtrl = $('<input>').attr( 'size', 4 );
689 } else if ( argType === 'boolean' ) {
690 // Make it a checkbox.
691 optCtrl = $('<checkbox>');
692 }
693 // Add the name and element ID
694 optCtrl.attr( 'name', argInfo.key ).attr( 'id', optId );
695 // Append the label and the option itself to the form.
696 $('#stemweb_runtime_options').append( optLabel )
697 .append( optCtrl ).append( $('<br>') );
698 }
699 });
700 });
701 $('#stemweb_algorithm').change();
702 }
70744367 703 });
704 // Prime the initial options
db234220 705 },
706 open: function(evt) {
70744367 707 $('#stemweb_run_status').empty();
b8f3a8c8 708 $('#stemweb_tradition').attr('value', selectedTextID );
509e94bc 709 if( selectedTextInfo.stemweb_jobid == 0 ) {
710 $('#stemweb_merge_reltypes').empty();
711 $.each( selectedTextInfo.reltypes, function( i, r ) {
712 var relation_opt = $('<option>').attr( 'value', r ).append( r );
713 $('#stemweb_merge_reltypes').append( relation_opt );
714 });
715 $('#stemweb_merge_reltypes').multiselect({
716 header: false,
717 selectedList: 3
718 });
719 }
db234220 720 },
721 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
722 $(event.target).parent().find('.ui-button').button("enable");
509e94bc 723 if( ajaxSettings.url.indexOf( 'stemweb/' ) > -1 ) {
db234220 724 display_error( jqXHR, $("#stemweb_run_status") );
725 }
726 });
3f9d7ae5 727
6cf17f04 728 // Set up the download dialog
729 $('#download-dialog').dialog({
730 autoOpen: false,
731 height: 150,
732 width: 300,
733 modal: true,
734 buttons: {
735 Download: function (evt) {
736 var dlurl = _get_url([ "download", $('#download_tradition').val(), $('#download_format').val() ]);
737 window.location = dlurl;
6cf17f04 738 },
8e26de0f 739 Done: function() {
6cf17f04 740 $('#download-dialog').dialog('close');
741 }
742 },
743 open: function() {
744 $('#download_tradition').attr('value', selectedTextID );
745 },
746 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
747 $(event.target).parent().find('.ui-button').button("enable");
748 if( ajaxSettings.url.indexOf( 'download' ) > -1
749 && ajaxSettings.type == 'POST' ) {
750 display_error( jqXHR, $("#download_status") );
751 }
752 });
753
3f9d7ae5 754 $('#upload-collation-dialog').dialog({
755 autoOpen: false,
e0b90236 756 height: 360,
3f9d7ae5 757 width: 480,
758 modal: true,
759 buttons: {
3f9d7ae5 760 upload: {
761 text: 'Upload',
762 id: 'upload_button',
763 click: function() {
764 $('#upload_status').empty();
e0b90236 765 $('#upload_button').button("disable");
766 upload_new();
3f9d7ae5 767 }
768 },
ab0d1218 769 pick_file: {
770 text: 'Pick File',
771 id: 'pick_file_button',
772 click: function() {
773 $('#new_file').click();
774 }
775 },
3f9d7ae5 776 Cancel: function() {
777 $('#upload-collation-dialog').dialog('close');
778 }
779 },
e0b90236 780 open: function() {
781 // Set the upload button to its correct state based on
782 // whether a file is loaded
783 file_selected( $('#new_file').get(0) );
2ece58b3 784 $('#upload_status').empty();
3f9d7ae5 785 }
e0b90236 786 }).ajaxError( function(event, jqXHR, ajaxSettings, thrownError) {
787 // Reset button state
788 file_selected( $('#new_file').get(0) );
789 // Display error message if applicable
790 if( ajaxSettings.url.indexOf( 'newtradition' ) > -1
791 && ajaxSettings.type == 'POST' ) {
792 display_error( jqXHR, $("#upload_status") );
793 }
794 });;
3f9d7ae5 795
796 $('#stemma_graph').mousedown( function(evt) {
797 evt.stopPropagation();
798 $('#stemma_graph').data( 'mousedown_xy', [evt.clientX, evt.clientY] );
799 $('body').mousemove( function(evt) {
800 mouse_scale = 1; // for now, was: mouse_scale = svg_root_element.getScreenCTM().a;
801 dx = (evt.clientX - $('#stemma_graph').data( 'mousedown_xy' )[0]) / mouse_scale;
802 dy = (evt.clientY - $('#stemma_graph').data( 'mousedown_xy' )[1]) / mouse_scale;
803 $('#stemma_graph').data( 'mousedown_xy', [evt.clientX, evt.clientY] );
804 var svg_root = $('#stemma_graph svg').svg().svg('get').root();
805 var g = $('g.graph', svg_root).get(0);
806 current_translate = g.getAttribute( 'transform' ).split(/translate\(/)[1].split(')',1)[0].split(' ');
807 new_transform = g.getAttribute( 'transform' ).replace( /translate\([^\)]*\)/, 'translate(' + (parseFloat(current_translate[0]) + dx) + ' ' + (parseFloat(current_translate[1]) + dy) + ')' );
808 g.setAttribute( 'transform', new_transform );
809 evt.returnValue = false;
810 evt.preventDefault();
811 return false;
812 });
813 $('body').mouseup( function(evt) {
814 $('body').unbind('mousemove');
815 $('body').unbind('mouseup');
816 });
817 });
818
819 $('#stemma_graph').mousewheel(function (event, delta) {
820 event.returnValue = false;
821 event.preventDefault();
822 if (!delta || delta == null || delta == 0) delta = event.originalEvent.wheelDelta;
823 if (!delta || delta == null || delta == 0) delta = -1 * event.originalEvent.detail;
824 if( delta < -9 ) { delta = -9 };
825 var z = 1 + delta/10;
826 z = delta > 0 ? 1 : -1;
827 var svg_root = $('#stemma_graph svg').svg().svg('get').root();
828 var g = $('g.graph', svg_root).get(0);
829 if (g && ((z<1 && (g.getScreenCTM().a * start_element_height) > 4.0) || (z>=1 && (g.getScreenCTM().a * start_element_height) < 1000))) {
830 var scaleLevel = z/10;
831 current_scale = parseFloat( g.getAttribute( 'transform' ).split(/scale\(/)[1].split(')',1)[0].split(' ')[0] );
832 new_transform = g.getAttribute( 'transform' ).replace( /scale\([^\)]*\)/, 'scale(' + (current_scale + scaleLevel) + ')' );
833 g.setAttribute( 'transform', new_transform );
834 }
835 });
836
837});