Files
galaxy/static/scripts/galaxy.workflows.js
T
John Chilton 69ddb38557 Refactor interaction between workflows JS and editor JS for adding nodes.
Add abstraction into editor for use by galaxy.workflow.js to hide some editor details from workflow code and reduce duplication between add_node_for_tool and add_node_for_module.
2014-04-10 13:31:47 -05:00

610 lines
23 KiB
JavaScript

$( function() {
if ( window.lt_ie_7 ) {
show_modal(
"Browser not supported",
"Sorry, the workflow editor is not supported for IE6 and below."
);
return;
}
// Init searching.
$("#tool-search-query").click( function (){
$(this).focus();
$(this).select();
})
.keyup( function () {
// Remove italics.
$(this).css("font-style", "normal");
// Don't update if same value as last time
if ( this.value.length < 3 ) {
reset_tool_search(false);
} else if ( this.value != this.lastValue ) {
// Add class to denote that searching is active.
$(this).addClass("search_active");
// input.addClass(config.loadingClass);
// Add '*' to facilitate partial matching.
var q = this.value + '*';
// Stop previous ajax-request
if (this.timer) {
clearTimeout(this.timer);
}
// Start a new ajax-request in X ms
$("#search-spinner").show();
this.timer = setTimeout(function () {
$.get(tool_search_url, { query: q }, function (data) {
// input.removeClass(config.loadingClass);
// Show live-search if results and search-term aren't empty
$("#search-no-results").hide();
// Hide all tool sections.
$(".toolSectionWrapper").hide();
// This hides all tools but not workflows link (which is in a .toolTitle div).
$(".toolSectionWrapper").find(".toolTitle").hide();
if ( data.length != 0 ) {
// Map tool ids to element ids and join them.
var s = $.map( data, function( n, i ) { return "link-" + n; } );
// First pass to show matching tools and their parents.
$(s).each( function(index,id) {
// Add class to denote match.
$("[id='"+id+"']").parent().addClass("search_match");
$("[id='"+id+"']").parent().show().parent().parent().show().parent().show();
});
// Hide labels that have no visible children.
$(".toolPanelLabel").each( function() {
var this_label = $(this);
var next = this_label.next();
var no_visible_tools = true;
// Look through tools following label and, if none are visible, hide label.
while (next.length !== 0 && next.hasClass("toolTitle")) {
if (next.is(":visible")) {
no_visible_tools = false;
break;
} else {
next = next.next();
}
}
if (no_visible_tools) {
this_label.hide();
}
});
} else {
$("#search-no-results").show();
}
$("#search-spinner").hide();
}, "json" );
}, 200 );
}
this.lastValue = this.value;
});
// Canvas overview management
canvas_manager = new CanvasManager( $("#canvas-viewport"), $("#overview") );
// Initialize workflow state
reset();
// Load the datatype info
$.ajax( {
url: get_datatypes_url,
dataType: "json",
cache: false,
success: function( data ) {
populate_datatype_info( data );
// Load workflow definition
$.ajax( {
url: load_workflow_url,
data: { id: workflow_id, "_": "true" },
dataType: 'json',
cache: false,
success: function( data ) {
reset();
workflow.from_simple( data );
workflow.has_changes = false;
workflow.fit_canvas_to_nodes();
scroll_to_nodes();
canvas_manager.draw_overview();
// Determine if any parameters were 'upgraded' and provide message
upgrade_message = "";
$.each( data.upgrade_messages, function( k, v ) {
upgrade_message += ( "<li>Step " + ( parseInt(k, 10) + 1 ) + ": " + workflow.nodes[k].name + "<ul>");
$.each( v, function( i, vv ) {
upgrade_message += "<li>" + vv +"</li>";
});
upgrade_message += "</ul></li>";
});
if ( upgrade_message ) {
show_modal( "Workflow loaded with changes",
"Problems were encountered loading this workflow (possibly a result of tool upgrades). Please review the following parameters and then save.<ul>" + upgrade_message + "</ul>",
{ "Continue" : hide_modal } );
} else {
hide_modal();
}
show_workflow_parameters();
},
beforeSubmit: function( data ) {
show_message( "Loading workflow", "progress" );
}
});
}
});
// For autosave purposes
$(document).ajaxStart( function() {
active_ajax_call = true;
$(document).bind( "ajaxStop.global", function() {
active_ajax_call = false;
});
});
$(document).ajaxError( function ( e, x ) {
// console.log( e, x );
var message = x.responseText || x.statusText || "Could not connect to server";
show_modal( "Server error", message, { "Ignore error" : hide_modal } );
return false;
});
make_popupmenu( $("#workflow-options-button"), {
"Save" : save_current_workflow,
"Run": function() {
window.location = run_workflow_url;
},
//"Create New" : create_new_workflow_dialog,
"Edit Attributes" : edit_workflow_attributes,
//"Edit Workflow Outputs": edit_workflow_outputs,
"Auto Re-layout": layout_editor,
//"Load a Workflow" : load_workflow,
"Close": close_editor
});
function edit_workflow_outputs(){
workflow.clear_active_node();
$('.right-content').hide();
var new_content = "";
for (var node_key in workflow.nodes){
var node = workflow.nodes[node_key];
if(node.type == 'tool'){
new_content += "<div class='toolForm' style='margin-bottom:5px;'><div class='toolFormTitle'>Step " + node.id + " - " + node.name + "</div>";
for (var ot_key in node.output_terminals){
var output = node.output_terminals[ot_key];
// if (node.workflow_outputs[node.id + "|" + output.name]){
if ($.inArray(output.name, node.workflow_outputs) != -1){
new_content += "<p>"+output.name +"<input type='checkbox' name='"+ node.id + "|" + output.name +"' checked /></p>";
}
else{
new_content += "<p>"+output.name +"<input type='checkbox' name='"+ node.id + "|" + output.name +"' /></p>";
}
}
new_content += "</div>";
}
}
$("#output-fill-area").html(new_content);
$("#output-fill-area input").bind('click', function(){
var node_id = this.name.split('|')[0];
var output_name = this.name.split('|')[1];
if (this.checked){
if($.inArray(output_name, workflow.nodes[node_id].workflow_outputs) == -1){
workflow.nodes[node_id].workflow_outputs.push(output_name);
}//else it's already in the array. Shouldn't happen, but forget it.
}else{
while ($.inArray(output_name, workflow.nodes[node_id].workflow_outputs) != -1){
var ia = $.inArray(output_name, workflow.nodes[node_id].workflow_outputs);
workflow.nodes[node_id].workflow_outputs = workflow.nodes[node_id].workflow_outputs.slice(0,ia).concat( workflow.nodes[node_id].workflow_outputs.slice(ia+1) );
}
}
workflow.has_changes = true;
});
$('#workflow-output-area').show();
}
function layout_editor() {
workflow.layout();
workflow.fit_canvas_to_nodes();
scroll_to_nodes();
canvas_manager.draw_overview();
}
function edit_workflow_attributes() {
workflow.clear_active_node();
$('.right-content').hide();
$('#edit-attributes').show();
}
// On load, set the size to the pref stored in local storage if it exists
overview_size = $.jStorage.get("overview-size");
if (overview_size !== undefined) {
$("#overview-border").css( {
width: overview_size,
height: overview_size
});
}
// Show viewport on load unless pref says it's off
if ($.jStorage.get("overview-off")) {
hide_overview();
} else {
show_overview();
}
// Stores the size of the overview into local storage when it's resized
$("#overview-border").bind( "dragend", function( e, d ) {
var op = $(this).offsetParent();
var opo = op.offset();
var new_size = Math.max( op.width() - ( d.offsetX - opo.left ),
op.height() - ( d.offsetY - opo.top ) );
$.jStorage.set("overview-size", new_size + "px");
});
function show_overview() {
$.jStorage.set("overview-off", false);
$("#overview-border").css("right", "0px");
$("#close-viewport").css("background-position", "0px 0px");
}
function hide_overview() {
$.jStorage.set("overview-off", true);
$("#overview-border").css("right", "20000px");
$("#close-viewport").css("background-position", "12px 0px");
}
// Lets the overview be toggled visible and invisible, adjusting the arrows accordingly
$("#close-viewport").click( function() {
if ( $("#overview-border").css("right") === "0px" ) {
hide_overview();
} else {
show_overview();
}
});
// Unload handler
window.onbeforeunload = function() {
if ( workflow && workflow.has_changes ) {
return "There are unsaved changes to your workflow which will be lost.";
}
};
// Tool menu
$( "div.toolSectionBody" ).hide();
$( "div.toolSectionTitle > span" ).wrap( "<a href='#'></a>" );
var last_expanded = null;
$( "div.toolSectionTitle" ).each( function() {
var body = $(this).next( "div.toolSectionBody" );
$(this).click( function() {
if ( body.is( ":hidden" ) ) {
if ( last_expanded ) last_expanded.slideUp( "fast" );
last_expanded = body;
body.slideDown( "fast" );
}
else {
body.slideUp( "fast" );
last_expanded = null;
}
});
});
// Rename async.
async_save_text("workflow-name", "workflow-name", rename_async_url, "new_name");
// Tag async. Simply have the workflow edit element generate a click on the tag element to activate tagging.
$('#workflow-tag').click( function() {
$('.tag-area').click();
return false;
});
// Annotate async.
async_save_text("workflow-annotation", "workflow-annotation", annotate_async_url, "new_annotation", 25, true, 4);
});
// Global state for the whole workflow
function reset() {
if ( workflow ) {
workflow.remove_all();
}
workflow = new Workflow( $("#canvas-container") );
}
function scroll_to_nodes() {
var cv = $("#canvas-viewport");
var cc = $("#canvas-container");
var top, left;
if ( cc.width() < cv.width() ) {
left = ( cv.width() - cc.width() ) / 2;
} else {
left = 0;
}
if ( cc.height() < cv.height() ) {
top = ( cv.height() - cc.height() ) / 2;
} else {
top = 0;
}
cc.css( { left: left, top: top } );
}
// Add a new step to the workflow by tool id
function add_node_for_tool( id, title ) {
node = add_node( 'tool', title, id );
$.ajax( {
url: get_new_module_info_url,
data: { type: "tool", tool_id: id, "_": "true" },
global: false,
dataType: "json",
success: function( data ) {
node.init_field_data( data );
},
error: function( x, e ) {
var m = "error loading field data";
if ( x.status === 0 ) {
m += ", server unavailable";
}
node.error( m );
}
});
}
function add_node_for_module( type, title ) {
node = add_node( type, title );
$.ajax( {
url: get_new_module_info_url,
data: { type: type, "_": "true" },
dataType: "json",
success: function( data ) {
node.init_field_data( data );
},
error: function( x, e ) {
var m = "error loading field data"
if ( x.status == 0 ) {
m += ", server unavailable"
}
node.error( m );
}
});
}
// This function preloads how to display known pja's.
function display_pja(pja, node) {
// DBTODO SANITIZE INPUTS.
$("#pja_container").append( get_pja_form(pja) );
$("#pja_container>.toolForm:last>.toolFormTitle>.buttons").click(function (){
action_to_rem = $(this).closest(".toolForm", ".action_tag").children(".action_tag:first").text();
$(this).closest(".toolForm").remove();
delete workflow.active_node.post_job_actions[action_to_rem];
workflow.active_form_has_changes = true;
});
}
function display_pja_list(){
return pja_list;
}
function display_file_list(node){
addlist = "<select id='node_data_list' name='node_data_list'>";
for (var out_terminal in node.output_terminals){
addlist += "<option value='" + out_terminal + "'>"+ out_terminal +"</option>";
}
addlist += "</select>";
return addlist;
}
function new_pja(action_type, target, node){
if (node.post_job_actions === undefined){
//New tool node, set up dict.
node.post_job_actions = {};
}
if (node.post_job_actions[action_type+target] === undefined){
var new_pja = {};
new_pja.action_type = action_type;
new_pja.output_name = target;
node.post_job_actions[action_type+target] = null;
node.post_job_actions[action_type+target] = new_pja;
display_pja(new_pja, node);
workflow.active_form_has_changes = true;
return true;
} else {
return false;
}
}
function show_workflow_parameters(){
var parameter_re = /\$\{.+?\}/g;
var workflow_parameters = [];
var wf_parm_container = $("#workflow-parameters-container");
var wf_parm_box = $("#workflow-parameters-box");
var new_parameter_content = "";
var matches = [];
$.each(workflow.nodes, function (k, node){
var form_matches = node.form_html.match(parameter_re);
if (form_matches){
matches = matches.concat(form_matches);
}
if (node.post_job_actions){
$.each(node.post_job_actions, function(k, pja){
if (pja.action_arguments){
$.each(pja.action_arguments, function(k, action_argument){
var arg_matches = action_argument.match(parameter_re);
if (arg_matches){
matches = matches.concat(arg_matches);
}
});
}
});
if (matches){
$.each(matches, function(k, element){
if ($.inArray(element, workflow_parameters) === -1){
workflow_parameters.push(element);
}
});
}
}
});
if (workflow_parameters && workflow_parameters.length !== 0){
$.each(workflow_parameters, function(k, element){
new_parameter_content += "<div>" + element.substring(2, element.length -1) + "</div>";
});
wf_parm_container.html(new_parameter_content);
wf_parm_box.show();
}else{
wf_parm_container.html(new_parameter_content);
wf_parm_box.hide();
}
}
function show_form_for_tool( text, node ) {
$('.right-content').hide();
$("#right-content").show().html( text );
// Add metadata form to tool.
if (node) {
$("#right-content").find(".toolForm:first").after( "<p><div class='metadataForm'> \
<div class='metadataFormTitle'>Edit Step Attributes</div> \
<div class='form-row'> \
<label>Annotation / Notes:</label> \
<div style='margin-right: 10px;'> \
<textarea name='annotation' rows='3' style='width: 100%'>" + node.annotation + "</textarea> \
<div class='toolParamHelp'>Add an annotation or notes to this step; annotations are available when a workflow is viewed.</div> \
</div> \
</div> \
</div>" );
}
// Add step actions.
if (node && node.type=='tool'){
pjastr = "<p><div class='metadataForm'><div class='metadataFormTitle'>Edit Step Actions</div><div class='form-row'> \
" + display_pja_list() + " <br/> "+ display_file_list(node) + " <div class='action-button' style='border:1px solid black;display:inline;' id='add_pja'>Create</div>\
</div><div class='form-row'>\
<div style='margin-right: 10px;'><span id='pja_container'></span>";
pjastr += "<div class='toolParamHelp'>Add actions to this step; actions are applied when this workflow step completes.</div></div></div></div>";
$("#right-content").find(".toolForm").after( pjastr );
for (var key in node.post_job_actions){
if (key != "undefined"){ //To make sure we haven't just deleted it.
display_pja(node.post_job_actions[key], node);
}
}
$("#add_pja").click(function (){
new_pja($("#new_pja_list").val(),$("#node_data_list").val(), node);
});
}
$("#right-content").find( "form" ).ajaxForm( {
type: 'POST',
dataType: 'json',
success: function( data ) {
workflow.active_form_has_changes = false;
node.update_field_data( data );
show_workflow_parameters();
},
beforeSubmit: function( data ) {
data.push( { name: 'tool_state', value: node.tool_state } );
data.push( { name: '_', value: "true" } );
}
}).each( function() {
var form = this;
$(this).find( "select[refresh_on_change='true']").change( function() {
$(form).submit();
});
$(this).find( ".popupmenu" ).each( function() {
var id = $(this).parents( "div.form-row" ).attr( 'id' );
var b = $('<a class="popup-arrow" id="popup-arrow-for-' + id + '">&#9660;</a>');
var options = {};
$(this).find( "button" ).each( function() {
var name = $(this).attr( 'name' );
var value = $(this).attr( 'value' );
options[ $(this).text() ] = function() {
$(form).append( "<input type='hidden' name='"+name+"' value='"+value+"' />" ).submit();
};
});
b.insertAfter( this );
$(this).remove();
make_popupmenu( b, options );
});
// Implements auto-saving based on whether the inputs change. We consider
// "changed" to be when a field is accessed and not necessarily modified
// because of an issue where "onchange" is not triggered when activating
// another node, or saving the workflow.
$(this).find("input,textarea,select").each( function() {
$(this).bind("focus click", function() {
workflow.active_form_has_changes = true;
});
});
});
}
var close_editor = function() {
workflow.check_changes_in_active_form();
if ( workflow && workflow.has_changes ) {
do_close = function() {
window.onbeforeunload = undefined;
window.document.location = workflow_index_url;
};
show_modal( "Close workflow editor",
"There are unsaved changes to your workflow which will be lost.",
{
"Cancel" : hide_modal,
"Save Changes" : function() {
save_current_workflow( null, do_close );
}
}, {
"Don't Save": do_close
} );
} else {
window.document.location = workflow_index_url;
}
};
var save_current_workflow = function ( eventObj, success_callback ) {
show_message( "Saving workflow", "progress" );
workflow.check_changes_in_active_form();
if (!workflow.has_changes) {
hide_modal();
if ( success_callback ) {
success_callback();
}
return;
}
workflow.rectify_workflow_outputs();
var savefn = function(callback) {
$.ajax( {
url: save_workflow_url,
type: "POST",
data: {
id: workflow_id,
workflow_data: function() { return JSON.stringify( workflow.to_simple() ); },
"_": "true"
},
dataType: 'json',
success: function( data ) {
var body = $("<div></div>").text( data.message );
if ( data.errors ) {
body.addClass( "warningmark" );
var errlist = $( "<ul/>" );
$.each( data.errors, function( i, v ) {
$("<li></li>").text( v ).appendTo( errlist );
});
body.append( errlist );
} else {
body.addClass( "donemark" );
}
workflow.name = data.name;
workflow.has_changes = false;
workflow.stored = true;
show_workflow_parameters();
if ( data.errors ) {
show_modal( "Saving workflow", body, { "Ok" : hide_modal } );
} else {
if (callback) {
callback();
}
hide_modal();
}
}
});
};
// We bind to ajaxStop because of auto-saving, since the form submission ajax
// call needs to be completed so that the new data is saved
if (active_ajax_call) {
$(document).bind('ajaxStop.save_workflow', function() {
$(document).unbind('ajaxStop.save_workflow');
savefn();
$(document).unbind('ajaxStop.save_workflow'); // IE7 needs it here
active_ajax_call = false;
});
} else {
savefn(success_callback);
}
};